CRLF注入引发的HTTP流斩首:一次请求头注入的深度利用报告"

admin 2026-10-18 04:59:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

CRLF注入引发的HTTP流斩首:一次请求头注入的深度利用报告

幻泉之洲

2026年8月15日 10:15
北京

在小说阅读器读本章

去阅读

HTTP Header Injection 被严重低估了。本文不讲那些老掉牙的跳转和XSS,直接亮底牌——用一记CRLF注入打穿HTTP流,变成可以自我复制的蠕虫攻击。从原理到实战,从CDN内网的响应队列投毒到浏览器里的IP锁定反序列化,全部配齐。如果你手里捏着个header注入不知道怎么玩,这里全是武器。

一个帖子的刺激

差不多一年前,我在Bluesky上刷到一个帖子[1],里面提了一种攻击手法。我之前听说过,但从来没见过活的。这人说这种漏洞\"不算罕见\",我有点不服。毕竟我和Tobia Righi(TurtleSec的研究员,这次论文的合著者)翻遍了也没碰上一个。

更要命的是,同一时期还有两篇论文也是讲这个的,而且都进了当年的Top 10 Web Hacking Techniques榜单[2]。

James Kettle的 Making HTTP header injection critical via response queue poisoning[3]用一个案例证明了通过请求拆分可以实现HTTP请求走私。Sergey Bobrov的 HTTP Request Splitting Vulnerabilities Exploitation[4] 则展示了由于Nginx配置错误导致请求拆分有多普遍,但对反序列化只是一笔带过。

我就想了——如果把James的反序列化技术,硬套到所有看起来被header注入影响的场景上,会发生什么?结果第一次试手,就发现路子比想象中野得多。

Nginx的那条老毛病

Nginx有多普及不用我说。当 proxy_pass 指令里用了 $uri 变量,Nginx会先对请求路径做规范化[5],把%0d%0a这种URL编码字符原样还原。这就等于把换行符直接喂给了上游服务器。

简单说:你可以往上游请求里随便插新行,完全控制请求的结构。

下面这个配置就是典型的作死写法:

nginx.conf

http {
  upstream backend {
    server backend.internal.com:8000;
  }
  server {
    location / {
      proxy\_pass http://backend$uri;
    }
  }
}

比如你发个这样的请求,就能在中间塞一个非法的 Content-Length 头,后端还老老实实给你回个400。

GET /%20HTTP/1.1%0d%0aContent-Length:%20X%0d%0aX:%20x HTTP/1.1
Host: example.com

后端实际看到的是:

GET / HTTP/1.1
Content-Length: X
X: x HTTP/1.1
Host: example.com

检测这个东西很直白。往请求里塞个头或者一段垃圾语法,看返回码就行。比如版本号写成 13.37,期待505;或者塞个 Transfer-Encoding: x,等501。

请求拆分:从理论到响应队列投毒

请求拆分以前更多被当成一种超强的CSRF变种。但James说清楚了,把请求精确切成两个,加一点自动化,就能搞出响应队列投毒(Response Queue Poisoning,RQP)。

这个技术的妙处在于,它不违反任何RFC。两个连续的CRLF就是请求之间的天然边界。后端收到两个完整的请求,就不知道该把响应发给谁了,于是开始随机分发——你的响应给了别人,别人的给你。

攻击者这边呢?一边收割别人的敏感数据,一边对所有其他人造成拒绝服务。一鱼两吃。

我们没花几天,照着James论文里的方法扫,就碰上了一个能触发RQP的域名。但收到的响应不对劲——不是目标应用的内容,而是一堆风马牛不相及的应用,技术栈各不相同。再一看,这些响应全来自不同域名,但都托管在同一家CDN上。

这时候我们才明白:反序列化发生在CDN自己的基础设施内部,离边缘节点非常近。

我们报告给厂商,对方不信,让补证据。那就补呗。我们找了个可以持久存储的端点,用前缀攻击把其他用户的请求存到我们自己账户的昵称字段里。每条捕获的请求都带一个Host头,指向它原来的路由目标。证据够了。

影响面有多大呢?我们同时洗出了成千上万应用的会话cookie和认证令牌。对方终于没话说了。

注入点不止路径

有时候你的注入不会落在路径里,而是跑到某个自定义头里。比如某家电信巨头的场景:

GET /%0d%0aHost:%20x HTTP/1.1
Host: tele.com

后端处理完的头是这个样子:

X-Original-Url: /
Host: x

找到注入点之后,利用起来就很简单了。直接跳出原请求,塞进第二个完整请求,RQP照旧。我们跑了500个连接,20分钟后开始从他们内部基础设施偷访问令牌。奖金2万美金,挺厚道。

直到最近,我们让Claude帮忙写扫描器,才在一个支付提供商的session cookie里找到了类似的案例。注入点不在路径,却在cookie值里:

Cookie: sess=abc
Transfer-Encoding: notchunked
X: x

后端重写后变成了:

POST /graphql/v1/abc
Transfer-Encoding: notchunked
X: x HTTP/1.1
Host: payment.com

触发的反序列化让我们劫持到了多家大公司的信用卡号和个人身份信息。响应头还暴露了这是Kubernetes集群内部的通信——等于说,所有使用这家支付商的公司都成了我们的信息源。给前同事Wictor(感谢你)演示时,他也被这个影响范围惊到了。

AI帮忙找新路子

到这个时候,我们的检测手段开始不够用了。于是又问Claude。它给了一个头:Expect,利用它独有的417状态码来验证注入是否生效。

靠这个我们发现了一家流行的服装店。能注入单个头,但两个CRLF连续出现就会报错、关连接。还是能注,却拆不了请求。

那怎么搞?答案藏在CL.TE反序列化里。

CRLF驱动的CL.TE反序列化攻击

构造一个请求,带正确的Content-Length,再用注入塞进一个Transfer-Encoding头,就能触发经典的CL.TE反序列化。利用超时技巧可以判断上游是否处理了这个注入头。

我们往卖衣服的站发了一个带恶意profile更新的请求。成功了。

然后灾难来了。我们在响应里无意识地反射了自己的session cookie,结果所有遭受反序列化影响的活用户全部被登入我的账户。每次刷新页面,购物车里就多了几样别人放的东西,几千人在抢同一个车。

补救方案:把用户的邮箱全换成我们的,然后账号接管。程序方原谅了这个乌龙,赏了2200美金。

接着是一家手机厂商的账户子域名,出现了叠包异常的嵌套响应。最初以为是请求隧道(没法影响其他用户),后来发现加大连接数,可以挤出跨用户影响。一个盲XSS payload最终从世界各地收集到pingback——从手机端传回来的,壮观得很。

Tobia则用他之前研究里的二维码小工具做了一次账号接管,在任意活用户身上。厂商关了报告说是重复,几个月后我们确认漏洞还在,发邮件过去又开了,拿了500美金。

缓存投毒和AI产的HEAD构件

最后一个CL.TE案例在一家社媒的CDN域名上。我们可以用任何平台上已有的合法响应来投毒首页。对方说影响不够。

靶域上没有任何可用的反射点。我们想到了用HEAD技巧(Web Security Academy有详细解释:https://portswigger.net/web-security/request-smuggling/advanced/request-tunnelling#non-blind-request-tunnelling-using-head)。问题是找一个响应长度刚刚好的东西,找了几个月。最后直接问Claude,要一个在两个指定Content-Length值之间的响应,它给了414 URI Too Long。

稳了。构造一个超长路径的HEAD请求往前抛,后面跟个XSS payload,靶域首页开始随机返回我们的脚本。

让浏览器来发动攻击

James在Browser-Powered Desync Attacks[6]里已经说清楚了,好几类反序列化是fetch兼容的——完全能从浏览器里打出来。

我们验证了一下,CRLF驱动的反序列化也完全符合这个特性。绝大多数场景不需要任何魔法,直接fetch请求或者一次浏览器导航就够了。POST场景下带body也是同理。

这就引出了一个可怕的东西:反序列化蠕虫。攻击者用请求走私触发受害者的浏览器XSS,然后浏览器再用fetch发动同样的反序列化,把攻击扩散出去,自我复制。

▲ CRLF反序列化蠕虫的传播模型——从一台浏览器感染另一台

我们前面那些案例,回溯看全都可以浏览器兼容。如果在triage上卡住了,提醒一下厂商蠕虫扩散的潜力,可能能让报告走得更远。

请求隧道和它被绕过的方式

请求隧道看起来和走私很像,但keep-alive连接不跨用户复用,所以跨用户利用行不通。通常还是个盲信道——你看不到你的前缀对响应产生的影响。

有时候用HEAD动词可以让前端意外地\"多读\"后端数据,从而泄露。但我们找到了一个更稳的方法:Nginx在遇到未预期的100-continue时,会因为没有Content-Length头而持续读数据直到连接关闭。加一个 Expect: 100-continue,就可以迫使它返回走私的响应。

我们用这个绕过了一家著名车企前端的访问控制,直接读到了内部配置文件。从 /robots.txt 漫游到 /config,前后端权限逻辑的缝隙被完美利用。

IP锁定和连接锁定的反序列化:全搬到浏览器里

某些反序列化限制极多。连接锁定的只允许同一连接复用,IP锁定的只影响同公网IP的人。Bug Bounty Program通常不认这个影响面。

对付连接锁定,我们在一家流媒体服务上用了0.CL的技巧(参考HTTP/1.1 Must Die:https://portswigger.net/research/http1-must-die#0.cl-desync-attacks)。通过 window.open() 加页面导航,让浏览器在同一个连接上发两个请求,配合HEAD技巧搞出XSS。PII数据到手,5000美金。

IP锁定更绝。让受害者自己发动攻击,受害者的浏览器通过VPN或其他共享IP环境,把反序列化执行在公网IP上,同IP的其他用户也跟着遭殃。

我们在一家软件提供商上用HTTP/2加注入的Expect头构造出了IP锁定反序列化。这次用HEAD+Range作为构件。Range头允许我们精准控制响应的Content-Length,想多长就多长,完全绕过了响应尺寸的限制。

浏览器端利用时遇到一个问题:先前 window.open() 的魔法不够快,RQP需要竞态。Tobia花了一下午,用iframe快速创建和销毁来维持竞速。用户需要在攻击页面停留大约10秒,我们做了个\"加载你的资料\"的假界面。配合CORS配置错误,从目标iframe里偷走了认证令牌和PII。3255美金。

最后还有一个绝活:窃取HTTPOnly Cookie。在一个账户域上,XSS本身没用,因为所有敏感操作都有二次验证,且cookie设置了HttpOnly。但Tobia发现会话cookie在某些响应中会被刷新。我们用HEAD找XSS,然后堆叠一个带 Set-Cookie: Session=victim; HttpOnly 的响应,把这个头挤进响应body里。XSS读DOM、送出token。浏览器端通过点击打开新窗口,持续刷新直到XSS命中。

响应头的把戏:Cookies和XSS

问到了响应头注入。这个Nginx配置同样容易踩坑:

location / {
  return 302 https://example.com$uri;
}

通过注入,我们能往302的响应里加 Set-Cookie,把攻击者的会话粘到受害者浏览器上。之后受害者的所有敏感操作都在攻击者的会话里。在TikTok的一个子域上,我们让受害者的新私密视频被上传到我们的账户。审核流程极其快速,4500美金。

要是能突破响应头,直接写响应体呢?通常不行,因为重定向响应一跳就跑了。但找到了一个巧办法:Cloudflare的 CDN-Cache-Control: private=\"Location\" 指令可以剥离Location头。重定向被终止,body里的XSS触发。WAF拦了?Tobia直接打了一个特殊字符集在Content-Type上,绕过WAF,完美落地反射型XSS。

逆向反序列化:一个被遗忘的老兵

翻2004年Amit Klein的论文 Divide and Conquer[7],响应拆分最初的梦想就是这个:注入一个短Content-Length和第二个响应,制造反序列化。现在这个叫逆向反序列化。

问题是浏览器和大多数前端会轻微\"多读\"响应,发现比Content-Length多出来一点数据就丢掉并关连接。这个\"叠包\"问题堵住了这条路。我们至今没能绕过。

怎么防

对Nginx来说,防住绝大多数header注入很简单:别在 proxy_pass 和 return 指令里用 $uri 和 $document_uri 变量。也别用正则捕获来创建新变量,除非你的表达式排除了换行符之类的东西。

如果你用的是OpenResty或Tengine(都基于Nginx),也去查同样的配置问题。长期可靠方案来自HTTP/1.1 Must Die[8]:把上游升级到HTTP/2。

我们开源了所有工具:一个Burp Suite扩展和一组nuclei模板,外加练习用的实验环境。

工具地址:
https://github.com/t0xodile/crlf-powered-desync-scanner
https://github.com/turtlesec-software/crlf-desyncs

▲ 开源工具集的架构展示,包括Burp扩展和nuclei模板

最后说两句

这篇文章不是水文。header注入不是低危,把它推到极致的反序列化蠕虫就是铁证。CRLF驱动的反序列化能在其他类型反序列化失效时达成影响面,而且只要Nginx还在,这东西就跑不掉。

我们公开了工具包,你们去挖吧。很多反序列化已经躺在那太久了。

给做研究的人一句话:安全行业的记性很短。哪怕是最近几年Top 10 Web Hacking Techniques里的论文,你也可以拿来组合、变异、继续深挖。试验,就行。


参考资料

[1] https://bsky.app/profile/aroly.bsky.social/post/3lq7obuqcrk2m

[2] https://portswigger.net/research/top-10-web-hacking-techniques

[3] https://portswigger.net/research/making-http-header-injection-critical-via-response-queue-poisoning

[4] https://www.youtube.com/watch?v=gWQyWdZbdoY

[5] https://nginx.org/en/docs/http/ngx\_http\_core\_module.html#location

[6] https://portswigger.net/research/browser-powered-desync-attacks

[7] https://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20HTTP%20Response%20Splitting%20-%20Divide%20and%20Conquer.pdf

[8] https://portswigger.net/research/http1-must-die#defending-against-http-desync-attacks

[9] https://portswigger.net/research/crlf-powered-desync-attacks


CRLF注入引发的HTTP流斩首:一次请求头注入的深度利用报告" 网络安全文章

CRLF注入引发的HTTP流斩首:一次请求头注入的深度利用报告"

本文深度剖析CRLF注入攻击,指出其可演变为自我复制的蠕虫攻击。核心发现包括利用Nginx配置中uri变量漏洞实现请求拆分与响应队列投毒,进而触发反序列化攻击,影响范围覆盖CDN内部基础设施。文章提供了多种利用场景,如缓存投毒、浏览器端攻击及IP锁定反序列化,并给出了防御建议:避免在proxy\\_pass中使用uri变量,并考虑将上游升级至HTTP/2。
利用路径遍历进入Cron并绕过DNS限制回调,将盲文件上传提升为RCE" 网络安全文章

利用路径遍历进入Cron并绕过DNS限制回调,将盲文件上传提升为RCE"

本文详细介绍了在漏洞赏金目标中发现的一个无需认证的文件上传点,存在目录路径注入漏洞。通过利用路径遍历写入真实文件系统,并借助Cron自动处理/etc/cron.d目录下文件(包括强制.png扩展名)的特性,将盲文件上传提升为远程代码执行。关键发现包括:后端以root权限运行、可探测文件系统存在性、以及通过crontab实现命令执行。建议安全测试中关注类似技术链。
原创—建言献策之建议国家早点取消退休金养老制度" 网络安全文章

原创—建言献策之建议国家早点取消退休金养老制度"

本文认为现行养老保险制度不科学,随着人口长寿与老龄化,高额退休金会压榨八十后九十后的财富并影响生育率。作者建议取消退休金制度,改为从出生到死亡的全民发工资模式,主张以此大水漫灌式发放刺激经济和提升生育率,并引用历史人物决策呼吁管理层打破顾虑推行改革。
评论:0   参与:  0