跨平臺通殺看不懂?老師傅帶你讀懂網絡協議漏洞
為什么將協議漏洞單列一類?
一般而言,我們對漏洞有三種傳統分類:Web漏洞、二進制漏洞、邏輯漏洞。
這樣的分類方式是來自于技術類別。Web漏洞就是指在基于Web技術的軟件中由于設計或實現不當導致的漏洞,例如我們常見的XSS、SQLi、XXE等,都是Web技術上的漏洞;二進制漏洞是指軟件在對內存的管理、使用、解析的過程中由于不當的設計或實現導致的漏洞,例如OOB、UAF等;而邏輯漏洞是指程序邏輯的問題導致的漏洞,一般來說不屬于前面兩類的漏洞多數情況下都會被理解為是邏輯漏洞。
而我們單獨提“協議漏洞”這個概念的時候是按業務場景來分類的,其實不止是協議漏洞,在實際中還有很多漏洞都不能按上述的三種類別來準確描述其特點,例如環境變量注入漏洞、各類利用RPC實現提權的漏洞等。就拿RPC提權來說,當你和別人交流時首先介紹這是一個“邏輯漏洞”的話大概率人家會極為疑惑,但是如果你說這是一個“RPC提權漏洞”的話人家就能馬上有個大致概念了,也更符合一般的交流習慣。
而把“協議漏洞”單列一類基本上有如下的原因:
- 大部分都是協議設計階段引入的問題,而不是軟件實現階段;
- 大部分存在跨軟件、跨操作系統,甚至是跨硬件平臺的通殺現象;
- 漏洞利用的目標通常是實現通信劫持,而不是RCE/ACE;
“協議漏洞”在漏洞響應處置比一般軟件漏洞更難,因為常常出現跨軟件通殺,所以一個漏洞的修復就可能需要協調多個廠商共同完成;另外如果是協議設計上的問題,那么下游軟件開發者可能無法拿出一個合理的修復方案,最終需要協議設計者的參與。
下面,我們就用拒絕服務、通信劫持和穿透防火墻的三個協議漏洞相關case來全方位展現協議漏洞的奧秘。
來看幾個拒絕服務的Case
說了那么多“虛”的東西,現在來看點“實”的例子。從拒絕服務開始,首先給大家介紹的是一類叫 CPDoS(Cache Poisoning Denial of Service)的漏洞。這是 Web 緩存投毒的一種:CPDoS通常出現在通過反向代理給Web server提供靜態資源 cache 服務的場景中,也就是 CDN。CDN 的 cache 可以理解成一個key-value 索引,多數情況下這個 key 就是 HTTP 請求中的 URL + Host + 一些關鍵 header,這里不會把所有header都納入key,原因就是有些header會隨著時間或者用戶而變化(例如cookie),如果將其納入key那么會把一份靜態資源 cache 多次,產生浪費。而在這種場景下如果對HTTP請求中key以外的部分后端 web server 和 cache server 有不同的解析方式的話,就有可能出現 CPDoS 問題。

CPDoS case 1
當 Client 向 Server 發送的 HTTP 請求中包含一個長度超過 Server 接受范圍的header,那么Server一般會返回一個400錯誤的響應。這在一般的Web Server場景下沒什么問題,但如果 Client 和 Web Server 中間插了一個Cache Server(CDN節點)的時候就可能出現 Cache Server 和 Web Server 對 header 長度的限制不同的問題。此時如果 Cache Server 對header 長度的限制大于 Web Server 的限制,那么 Client 發過來的 HTTP 請求就能穿越 Cache Server 順利到達 Web Server,然后得到一個大大的400錯誤,并且被 Cache Server 緩存下來。
如果此時 Client 請求的是 /index.html,Host 為 example.org,Cache key為 URL+Host,那剛剛 Client 的請求所產生的400 Cache 就會影響到其他同樣訪問 example.org/index.com 的用戶,由此實現一例 DoS 攻擊。
當然 CPDoS 的攻擊手法還可以有各種變體:

CPDoS case 2
一部分的 Web Server 支持一種叫做 “HTTP Method Override” 的技術,也就是用一個 header 來覆蓋掉 HTTP 請求中的 Method,最常見的就是“X-HTTP-Method-Override” header,當 Client 發送一個 GET 請求并帶上X-HTTP-Method-Override = POST 時,這個請求實際會被 Web Server 或者Web 中間件當作 POST 請求處理。
這種情況下 Client 發出一個 example.org/index.html 的 GET 請求,并override 成 POST,如果 Web Server 那里沒有這個路徑下 POST 請求的handler,那就會產生一個404錯誤,當 Cache Server 緩存下這個404錯誤后,和上文同樣的問題也會影響所有訪問 example.org/index.html 的用戶,于是你又搞成了一次 DoS 攻擊。
......,這里省略了一些細節,更全面的內容可以參考以下鏈接:https://cpdos.org/
從 CPDoS 的例子里面大家可以發現,單獨把 Cache Server 或者 Web Server 拆出來看都沒有問題,但是當他們放在一起使用的時候就會出現問題。對,就是這么奇怪!而且這種問題還會因為 Cache Server 和Web Server 對 HTTP 協議解析細節的各種不同而產生細分變體,再加上多級緩存結構和 Web 應用所形成的協議特點最終讓 CPDoS 在實際環境下變得五花八門。
而這時你應該會意識到:
(1)CPDoS 不是某個具體軟件的問題,而是軟件組合使用+不恰當的 Cache 策略導致的;
(2)修復 CPDoS 大概率不是軟件開發層面的事情,而是運維層面的事情。
看完拒絕服務再來瞧瞧通信劫持
接下來要說的是 HTTP Request Smuggling,這種漏洞在國內的知名度要比上文中的 CPDoS 大一些。HTTP Request Smuggling 的技術原理如下:
首先 HTTP 是基于 TCP 的,最原始的情況下一個 HTTP Session 的生命周期是等于一個 TCP Session 的生命周期的,但是頻繁拆建 TCP 連接是一個比較消耗資源的事情,尤其對于 Client 和 Server 之間需要一批多次發送數據的場景。于是就有了 HTTP 協議的一個著名的 header:“Connectoin: Keep-Alive”,這個 header 的作用是通知對方當前這個 HTTP Session 結束后不要忙關 TCP 連接,后面還要用它!也就是用一個 TCP Session 去承載多個 HTTP Session。而為了進一步優化通信性能,還在此基礎上發展出了 Pipeline 方式,也就是當一個 HTTP 請求發出后不用等待響應就直接發送下一個 HTTP 請求,可以看到這對于提高通信吞吐效率來說非常有效。

其實這里就引入了新的問題:HTTP 是一個以“報文”為基本傳輸單位的協議,但 TCP 是一個以“流”為基本傳輸單位的協議;這兩者并不是天然兼容的,要在一個“流”上面準確的傳輸多個“報文”需要額外的分割策略。所謂“分割策略”最簡單的例子就是分隔符(例如連續兩個”\r”就可以用來分割 HTTP header 和body)。HTTP 上最基本的兩種“分割策略”是:
(1) Content-Length
(2) Transfer-Encoding=chunked
第一種就是在 HTTP header 中使用一個 Content-Length 來指定當前報文的長度,超出這個長度的數據就屬于下一個報文。第二種則是按照一種特殊的編碼方式組織數據,例如這樣:
POST /test.action HTTP/1.1\rHost: example.org\rContent_type: text/plain\rTransfer-Encoding: chunked\r\r\r4\rTest\r5\rPages\r0\r\r
其中的 4\r,Test\r,就是一個 chunk,數字4代表后面跟了4個字節,而 \r 是分隔符。當 Client 發送的 HTTP 請求里面同時包含這兩種分割策略時不同的 Server 可能對這個請求會有不同的理解,例如下面這樣:
GET / HTTP/1.1\rHost:localhost\rContent-length:56\rTransfer-Encoding: chunked\rDummy:Header\r\r0\r\rGET /tmp HTTP/1.1\rHost:localhost\rDummy:Header\r\rGET /tests HTTP/1.1\rHost:localhost\rDummy:Header\r\r
如果 Server 對 Content-length 的解析優先于 Transfer-Encoding 的話,這段數據會被理解為三個 HTTP 請求,但如果反過來的話則只會被理解為兩個 HTTP 請求。和上文中的 CPDoS 類似,在 CDN 的場景下,如果前后兩個Server 對 HTTP 協議的理解不同的話就會出現問題。例如在第三方用戶的請求里面插入一段攻擊者指定的數據:

細節參考鏈接:
https://xz.aliyun.com/t/6878
HTTP Request Smuggling 和 CPDoS 有很多相似之處:
(1)都存在跨軟件的廣泛影響;
(2)都很難通過軟件開發者的漏洞修補去解決問題。
但 HTTP Request Smuggling 帶來的威脅要比 CPDoS 更上一個臺階,它可以用來劫持(篡改)正常用戶的通信內容。
最后來個更絕的:看我如何突破防火墻
除了拒絕服務和通信劫持以外還有一類更特殊的case:“自外向內突破防火墻”,這個就是2020年末的時候被披露的 NAT Split Streaming 漏洞。這個漏洞的技術細節大致如下:
一般我們使用的家用路由器或者防火墻大多是NAT網關,NAT網關的作用是讓多個設備共享同一個IP地址來接入互聯網,一般來說NAT網關需要維護一個內外網的地址映射表來實現地址與端口的轉換。

這張圖里面就是一個單方向 NAT(SNAT)的地址映射,內網發往外網的IP報文的源地址與源端口會被 NAT 網關篡改,變成 NAT 網關自己對外的 IP 和端口,而此時就需要維護一個內網 IP+端口到外網 IP+端口的映射表。當外網的數據回來時就可以按這張映射表來把 IP 包的目的地址和目的端口再改回來。
這種單方向的 NAT 有個特點,就是外網無法主動向內網 IP 發送數據,所以很多防火墻也是這種結構。
剛剛說的 NAT 的技術實際上無法滿足一些特殊協議的工作需求,例如 FTP。

FTP 在主動模式下需要 Client 和 Server 都主動連接對方,只能單向通行的 SNAT 就無法 cover 這種需求了,所以又有了一個叫 ALG(Application Level Gateway)的技術:

說白了 ALG 干的事情就是又去篡改應用層的網絡數據,把 FTP 的 PORT 報文里面的 IP 和端口再篡改一次,并且在 NAT 的映射表里頭把篡改的這個 IP 給他添加進去,例如這個圖里頭把 PORT 報文里頭的192.168.1.2:1084改成 NAT 網關的公網 IP+端口,再把這個公網IP和端口映射回內網的機器上。
剛剛說的是 FTP 協議的 ALG,很多其他協議也都有 ALG 支持。
這里的 NAT Split Streaming 漏洞就是 ALG 的一種缺陷:攻擊者利用 TCP 的分段機制去偽造一個可以觸發 ALG 工作的數據包,然后利用 ALG 把內網的 IP+端口映射到外網,從而實現從外網突破 NAT 直接訪問內網主機。這里攻擊者使用的是 SIP 協議的 REGISTER 包,它大概長這樣:
REGISTER sip:192.168.2.89 SIP/2.0Via: SIP/2.0/UDP 192.168.2.161:10586Max-Forwards: 70From: ;tag=ca04c1391af3429491f2c4dfbe5e1b2e;epid=4f2e395931To: Call-ID: da56b0fab5c54398b16c0d9f9c0ffcf2@192.168.2.161CSeq: 1 REGISTERContact: ;methods="INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER"User-Agent: RTC/1.2.4949 (BOL SIP Phone 1005)Event: registrationAllow-Events: presenceContent-Length: 0
里面的 Contact 字段就是 ALG 會去處理的字段,如果內網主機向外發送這么一個 SIP 包,那么 Contact 字段里面的 IP+端口就會被ALG映射出去。要發起攻擊,可以給內網的用戶發送一個釣魚鏈接,受害者點開后會通過瀏覽器發送一個超長的 POST 數據,而只需要控制這個 POST 數據的長度和內容就能在TCP數據的第二或者第三分段上創造出一個長得和 SIP REGISTER 包一模一樣的數據包,就像這樣:
控制分段長度的技術細節略微復雜,涉及到 TCP 握手中的 MSS 和 IP 協議的 MTU,更多細節參考以下鏈接:https://forum.butian.net/share/88。
這個漏洞的特殊之處在于:
(1)它不是程序實現上的失誤導致的,而是 ALG 這種 NAT 上補充出來的技術在設計上就存在缺陷;
(2)這帶來的一個很棘手的現實狀況:沒有完美的修補方案。要么完全不用 ALG,要么只能通過限制 ALG 的功能來緩解問題;
(3)NAT Split Streaming 也是一個跨軟件甚至跨硬件的漏洞,從各個廠牌的家用路由器到商用防火墻均有中招的例子。
來個總結
上面列舉了三個大 case 來介紹網絡協議漏洞,總的來說就是這是一個大多數沒有 CVE 編號,但實際影響卻又不小的漏洞類別。雖然幾乎不能達到 RCE/ACE 的利用效果,卻由于廣泛的影響和自身的技術特點帶來了響應處置上的獨特挑戰。業界對此類漏洞的關注度目前還不是特別高,但應對這些“特殊”漏洞的挑戰還是值得從業人員們調整一下思路,新的風暴永遠都在出現。