强网杯Web题目ThinkShop渗透笔记
环境搭建与初步分析
本题需要使用Docker环境部署,这也是CTF比赛中考察参赛者环境搭建能力的常规方式。首先导入Docker镜像:
docker load < thinkshop.tar
docker run -tid --name thinkshop -p 36000:80 -e FLAG=flag{test_flag} thinkshop
sudo docker start thinkshop
sudo docker exec -it thinkshop /bin/bash
服务启动后访问 http://127.0.0.1:36000/,页面自动跳转到后台管理界面。
目录结构分析
在Web根目录进行侦察,查看各层目录结构:
/var/www/html
├── index.php ← 入口文件(重定向作用)
├── public/
│ └── index.php ← 核心入口(ThinkPHP框架启动点)
├── application/
│ ├── index/ ← 主模块
│ │ ├── controller/ ← 控制器层
│ │ ├── model/ ← 模型层
│ │ └── view/ ← 视图层
└── thinkphp/ ← 框架核心
根据ThinkPHP的路由规则,URL结构 /index.php/index/index/index 分别对应:模块名/控制器名/方法名
| URL段 | 对应路径 |
|---|---|
| index | application/index/ |
| index | controller/Index.php |
| index | Index类中的index()方法 |
业务逻辑审计
查看商品模型文件 Goods.php:
public function getGoodsList()
{
$select = new Select();
return $select->select('goods');
}
public function getGoodsById($id)
{
$selector = new Select();
$result = $selector->select('goods', 'id', $id);
if (!empty($result[0]['data']) && substr($result[0]['data'], 0, 3) !== "YTo")
{
$this->error("数据异常" , 'index/admin/goods_edit');
}
return $result;
}
数据库配置信息:root/root,数据库名为shop,包含admin和goods两个表。admin表中存在预设的管理员账户,密码经过MD5哈希处理。
管理员登录绕过
访问管理员登录页面:
http://127.0.0.1:36000/public/index.php/index/admin/index
登录验证逻辑位于 do_login 方法:
public function do_login()
{
$username = input('post.username');
$password = input('post.password');
if (empty($username) || empty($password)) {
$this->error('用户名或密码不能为空');
}
if(strlen($password) > 100)
{
$this->error('用户名或密码错误');
}
$encryptedPassword = md5($password);
$cacheKey = ["Login" , $username];
$expireTime = 600;
$adminData = Db::table('admin')
->cache(true, $expireTime)
->find($username);
if ($adminData && $adminData['password'] === $encryptedPassword) {
session('admin', $adminData['username']);
$this->success($username.'登录成功', 'index/admin/goods_edit');
} else {
$this->error('用户名或密码错误');
}
}
关键漏洞点:find() 方法传递标量参数时,ThinkPHP框架会将其作为主键值进行查询。这意味着 $username 实际上被当作主键(id字段)使用,而非用户名。
登录时使用数据库中admin表的id值作为用户名,并使用密码"123456"(MD5值:e10adc3949ba59abbe56e057f20f883e)即可成功登录。
反序列化漏洞利用
登录后访问商品编辑页面,查看视图文件 goods_edit.html:
<textarea class="form-control" id="data" name="data" rows="3" required>
{php}use app\index\model\Goods;$viewObj=new Goods();
echo $viewObj->arrayToMarkdown(unserialize(base64_decode($goods['data'])));
↑反序列化触发点
{/php}
</textarea>
可以看到页面直接对base64解码后的数据进行反序列化操作。提交数据的处理流程如下:
数据更新流程
do_edit方法处理表单提交:
public function do_edit()
{
if(!session('?admin'))
{
$this->error('请先登录','index/admin/login');
}
$goodsModel = new Goods();
$postData = input('post.');
$result = $goodsModel->saveGoods($postData);
if ($result) {
$this->success('商品信息更新成功', 'index/index/index');
} else {
$this->error('商品信息更新失败');
}
}
saveGoods方法会对数据进行序列化并base64编码存储:
public function saveGoods($data)
{
$data['data'] = base64_encode(serialize($this->markdownToArray($data['data'])));
return $this->save($data);
}
public function save($data){
$updater = new Update();
return $updater->updatedata($data , 'goods' , $data['id']);
}
Update类中的updatedata方法存在SQL语句拼接:
public function updatedata($data, $tableName, $id)
{
if (!$this->connect()) {
die('Error');
} else {
$sql = "UPDATE $tableName SET ";
foreach ($data as $key => $value) {
$sql .= "`$key` = unhex('" . bin2hex($value) . "'), ";
}
$sql = rtrim($sql, ', ') . " WHERE `id` = " . intval($id);
return mysqli_query($this->connect(), $sql);
}
}
利用思路
通过抓包可以修改data参数的值,结合反序列化点可以构造恶意Payload。需要构造ThinkPHP的反序列化利用链来达到代码执行或读取Flag的目的。
示例请求包:
POST http://target:36000/public/index.php/index/admin/do_edit.html HTTP/1.1
Host: target:36000
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=xxx
id=2&name=product&price=100.00&data=恶意序列化数据
总结
本题涉及以下技术点:
- ThinkPHP框架结构分析
- SQL注入漏洞发现
- 认证机制绕过(主键查询漏洞)
- 反序列化漏洞利用
- ThinkPHP反序列化利用链构造
后续需要深入学习ThinkPHP的反序列化利用链相关知识来完成最终flag获取。