部分漏洞讓使用者能輸入「在 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:竊取 httponly Cookie 或 HTTP 認證資訊(不需與受害者互動)

HTTP Response Splitting#

本章重點。攻擊者注入 CRLF 把單一回應切成兩個,第二段由攻擊者控制。利用方式有兩種:

  1. 完整插入第二個回應:注入 CRLF 終止原回應,再寫入新標頭與 body
  2. 僅修改既有回應:插入額外標頭(如 Location)導向惡意網站,或串接 XSS(第 7 章)

案例 1:v.shopify.com Response Splitting#

漏洞描述#

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#

漏洞描述#

研究者 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%0ALine Feed
%E5%98%8D%0DCarriage 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 等高衝擊漏洞,能大幅提升獎金級距