部分漏洞讓使用者能輸入「在 HTML 與 HTTP 回應中具有特殊意義的編碼字元」。一般情況下應用程式會 sanitize 這些字元,但若忘記或處理不當,伺服器、Proxy 或瀏覽器就會把這些特殊字元當作程式碼解譯,改變原始的 HTTP 訊息。
什麼是 CRLF#
兩個關鍵字元:
%0D=\r(carriage return,回車)%0A=\n(line feed,換行)
兩者合稱 CRLF(Carriage Return Line Feed)。它們是 HTTP 訊息中標頭分段的依據——每一個 HTTP 標頭以 CRLF 結尾,整個標頭區塊與 body 之間以兩個 CRLF 分隔。
CRLF 注入(CRLF Injection)發生在應用程式未正確處理使用者輸入的
%0D%0A。攻擊者一旦能塞入 CRLF,就能在 HTTP 訊息中插入新的標頭甚至新的回應。
兩大攻擊形式#
HTTP Request Smuggling#
攻擊者把第二個請求夾帶在第一個合法請求的尾巴。前端伺服器(Proxy/Firewall)解析成一個請求,後端應用伺服器卻看成兩個——兩端不一致時可導致:
- Cache poisoning:把惡意頁面寫入快取
- Firewall evasion:繞過前端安全檢查
- Request hijacking:竊取
httponlyCookie 或 HTTP 認證資訊(不需與受害者互動)
HTTP Response Splitting#
本章重點。攻擊者注入 CRLF 把單一回應切成兩個,第二段由攻擊者控制。利用方式有兩種:
- 完整插入第二個回應:注入 CRLF 終止原回應,再寫入新標頭與 body
- 僅修改既有回應:插入額外標頭(如
Location)導向惡意網站,或串接 XSS(第 7 章)
案例 1:v.shopify.com Response Splitting#
- 難度:中
- URL:
v.shopify.com/last_shop?<YOURSITE>.myshopify.com- 回報來源:https://hackerone.com/reports/106427/ ↗
- 回報日期:2015-12-22
- 獎金:$500
漏洞描述#
Shopify 用 last_shop 參數記錄使用者最近登入的商店並寫入 Cookie——但沒有 sanitize 輸入。研究者 krankopwnz 注入:
%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2019%0d%0a%0d%0a<html>deface</html>解碼後:
Content-Length: 0
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 19
<html>deface</html>瀏覽器收到後解讀為兩個 HTTP 回應:
- 第一個回應
Content-Length: 0→ 沒有 body - CRLF 之後接著一個全新的
HTTP/1.1 200 OK,body 是<html>deface</html>
一旦能完整偽造第二個回應,可串聯出 XSS 等更嚴重的後果。
Takeaways#
留意「使用者輸入直接被放進回應標頭」的場景,特別是 Cookie 設定。看到時試試
%0D%0A(IE 可改%0A%20),確認對 CRLF 的處理。最理想的觸發點是 GET 請求——使用者互動越少越值錢。
案例 2:Twitter HTTP Response Splitting#
- 難度:高
- URL:
https://twitter.com/i/safety/report_story/- 回報來源:https://hackerone.com/reports/52042/ ↗
- 回報日期:2015-03-15
- 獎金:$3,500
漏洞描述#
研究者 FileDescriptor 觀察到 Twitter 在處理 reported_tweet_id 參數時會把它寫入 Cookie,但對 \r、\n 已有過濾:
- 收到
\n→ 換成空格 - 收到
\r→ 直接回 HTTP 400
但他想到 Firefox 一個 bug:Cookie 解碼會把 ASCII 範圍外的 Unicode 字元剝除多位元組,讓殘餘的位元組可能變成惡意字元。FileDescriptor 把這個概念套到 Twitter:
- 找到 Unicode 字元
嘊,UTF-8 編碼為%E5%98%8A - 三個位元組對 Twitter 來說都不是黑名單字元,順利通過 sanitize
- Twitter 解碼成 Unicode
560A,再剝除非法的56,留下0A
同理 嘍(%E5%98%8D)解碼後留下 0D。FileDescriptor 送入:
%E5%98%8A%E5%98%8DSet-Cookie:%20test解碼後變成 \r\nSet-Cookie: test——成功在回應中切出新標頭。
升級為 XSS#
他進一步打造完整 PoC:
https://twitter.com/login?redirect_after_login=https://twitter.com:21/%E5%98%8A%E5%98%8Dcontent-type:text/html%E5%98%8A%E5%98%8Dlocation:%E5%98%8A%E5%98%8D%E5%98%8A%E5%98%8D%E5%98%BCsvg/onload=alert%28innerHTML%29%E5%98%BE各 3 位元組序列解碼後:
| 多位元組 | 解碼後 | 用途 |
|---|---|---|
%E5%98%8A | %0A | Line Feed |
%E5%98%8D | %0D | Carriage Return |
%E5%98%BC | %3C | < |
%E5%98%BE | %3E | > |
最終回應切出第二段:
content-type: text/html
location:
<svg/onload=alert(innerHTML)><svg onload> 觸發 JavaScript,跳出包含 DOM 內容(含 session/認證 Cookie)的 alert——等同帳號被接管。
Takeaways#
看到網站把
%0D%0A當作黑名單擋掉時,思考它是怎麼擋的。雙重編碼、UTF-8 多位元組字元都可能是繞過點。送入會被「字元剝除」後仍保有 CR/LF 殘餘的多位元組字元,是這類繞過的經典招式。
章末總結#
- CRLF 注入的本質:注入
%0D%0A改變 HTTP 訊息的標頭分段 - 兩大形式:Request Smuggling(夾帶第二個請求)與 Response Splitting(切出第二個回應)
- 高價值目標:使用者輸入被放進回應標頭(如
Set-Cookie)的點,最值得嘗試 - 黑名單防禦不是萬靈丹——多位元組字元、雙重編碼都可能繞過
- 找到 CRLF 後盡量串接 XSS 等高衝擊漏洞,能大幅提升獎金級距