標籤:

大多tcp應用採用長度+數據的格式傳輸數據,如何防止惡意虛報長度?

為了效率,讀到長度後開一塊長度大小的內存後把數據寫入該內存塊,肯定比讀一點,擴展一下內存塊,寫一下,再讀一塊,再擴展一下,再寫一下,速度快。但是這樣如何防止虛報長度的這種惡意攻擊?雖然服務端可以檢測一段時間沒有數據斷開它


你可以規定一條消息最多1MB,如果超了就斷開連接。這就跟很多論壇規定附件不大於10MB一個道理。而且你也不需要提前分配內存空間,如果用 vector 當 buffer,只要一邊 read 一邊 push_back 即可,省事又安全。


不太覺得在TCP層面進行這樣類型的資源耗盡攻擊是一個好主意。

一般來說,攻擊者應該用比防禦者小很多的資源達到最大的效果才是「划算」的攻擊,題主說的這種攻擊方法不僅要逆向解析協議,還會暴露真實IP,同時攻擊方資源消耗不小,防禦起來也不太難。

相比來說,如果用TCP半連接或者更高的應用層協議來攻擊可能效果都會好很多,兩者攻擊者都幾乎不需要消耗資源,前者還沒有任何痕迹,後者攻擊效果很好也非常難以防範,畢竟太多程序員不熟悉業務邏輯優化。

如果狹隘地針對本題來討論,我會建議開發者估算一個平均數據長度和一般最大數據長度,然後採用「預測」+「擴展」模式來設計內存分配,配合超時切斷和定期資源回收,應該能滿足大多數情況。以及,如果不需要太高的接受速度,為什麼不開輕量線程直接把Socket跟I/O直接對接?來一個用戶就啟動一個輕量線程然後直接扔到I/O訪問,實測單機跑上幾萬連接也不會有性能問題。


1、中低級協議不應存在「無限大包」,太容易出錯又太難恢復。超大包在這層就應該被拒絕掉。

2、高層協議可以存在大包,但它必定是由中低層協議分段拼來的。

3、哪怕底層協議,要求「連續的XX位元組空間」也是無理的。正確做法是分成合適大小的chunk,比如4K;無論收發包,都以chunk為單位進行。

這是因為,CPU的頁面大小常常就是4K;超過4K的連續內存是必然超過頁面範圍的,而且CPU也很可能並不會給你大於4K的連續內存空間——所謂的「連續」,只是演算法虛擬出來的假象而已。

換句話說,大小合適的chunk並不會帶來可察覺的性能消耗;甚至,如果量身定做了合適的內存池,反而能提高執行效率(我曾經測試過,單論內存分配/回收速率的話,定製的內存池可以比直接用malloc/new快40多倍)。


再大的包,我也是最多4k-16 為單位逐個的收,,

--16是為了避開N-way association cache的陷阱。


這都是哪跟哪兒…

先把提問者的問題解析一下:在採用TLV格式設計的TCP協議中如何防止Client偽造協議數據包中的長度。

首先TLV (Type Length Value)的消息package格式再TCP協議設計中應用非常廣泛,可以說幾乎所以稍微複雜點的TCP層協議都採用了這種消息包格式。即使使用了protobuf來封裝協議消息,在傳輸過程中也會在上層採用一層至少LV格式來封裝序列化的protobuf消息。

[int32_t]Type(4bytes) | [int32_t]Length(4bytes) | [bytes array]Value() |

當Client發送數據的時候會先構造一個協議消息package,舉個列子,Client段定義一個消息的Type

T_GREET 0x01 // 表示用於問候的消息類型,此消息跟隨一個問候語句

T_BYBE 0X02 // 表示用於再見的消息類型,此消息跟隨一個結束語句

當Client需要向Server發送一個問候語為Hello的問候消息的時候會構造這樣的協議消息包

以Windows為例在本機內存這個消息的內存序列為

| 0x01 0x00 0x00 0x00 | 0x05 0x00 0x00 0x00 | 0x48 0x65 0x6C 0x6C 0x6F |

經過host to network 轉換後變為

| 0x00 0x00 0x00 0x01 | 0x00 0x00 0x00 0x05 | 0x48 0x65 0x6C 0x6C 0x6F |

然後發送到網路上

Server端如何從socket讀取這個消息包呢

  1. Server會先讀取4 bytes的數據,然後經過network to host 轉換成int32_t,這樣就得到了這個消息包的Type
  2. 繼續讀取4 bytes,然後經過network to host 轉換成int32_t,這樣得到的是length
  3. 根據上一步讀出的length,繼續讀取length長度的bytes,作為這個協議包的數據,然後對這個數據做進一步複雜的deserialization,比如把這個buffer作為一個protobuf的源數據來反序列化出來一個protobuf消息

如果Server真的按照這個傻瓜的做法來讀取數據,OK 那麼問題來了。

如果我用一堆肉雞發送數據包給serve 並且設置length為0xFFFFFFFE,可想而知Server的壓力多大,很可能垮掉了。

問題在哪?

因為Server在第2步中採用了來自client端的數據來做內存分配操作。這違反了一個協議設計中的基本原則:一切來自Client端的數據都是不可信的,同理一切來自Server端的數據也是不可信的。

這個如何改進?

很簡單,因為協議是Server和Client雙方共同設計的,所以大家都知道每一個Type對應的消息格式,也知道每個Type對應的消息的數據長度的上下限,即使不知道也要共同確定一個。

比如我們的例子中,如果協議中規定,TYPE_GREET消息的payload最長不能超過128 bytes,這樣一來,Server就可以在讀取到length後進行校驗,如果長度超過協議規定,就丟棄這個協議消息,甚至斷開連接。

某些古老的程序員會在這裡犯一個低級但是超嚴重的錯誤。

先分配固定內存比如128 bytes,然而卻不校驗Client消息中發送的數據長度,如果Client發送了65536 bytes的數據,而Server仍然按照65536這個長度來讀取數據到這個128 bytes的buffer裡面,那這就是妥妥的遠程緩衝區溢出漏洞了(Heap Overflow),著名的SSL Heartbleed是比較近的典型例子。

然後我們回到題主的問題,如何防範在TLV中偽造長度?

這不是單單寫一個代碼就能解決的問題,而是在整個協議設計,網路編程的立體空間中的每個層面做一些設計去綜合解決。


首先題主要考慮,協議設計好了,你為什麼要不按照協議去設計,比如我提供了一個服務,而且發布了協議,你是一個Client開發者你想使用這個服務,你就要按照我的協議設計去開發你的Client,你發送一個corrupted的協議數據包,OK,我會discard。什麼,你不是為了要使用這個服務,你就是要攻擊我的Server,行啊,那我就繼續丟棄你的數據包。這是第一層防範,也是最根本的保證Server正常運轉基礎。


我發現異常,那我就把你的IP直接ban掉了。你覺得雖然我丟棄了你的數據包,但是我就是想要消耗你的資源,就是要讓你的Server忙於丟棄我的數據而沒有機會為正常用戶提供服務,所以你去找老黑買了個DDOS的攻擊服務。嗯,這下我是沒辦法Ban你的IP了,不過你可能沒算過,你買一次DDOS攻擊服務的錢,已經超過了我在這段時間內為正常用戶提供服務所賺取的利潤的好幾倍,不知道你的動機是啥。而且我還可以輕易的買個硬體防火牆來阻擋你的DDOS攻擊。


而實際上,如果通不過鑒權,你的client都沒機會跟別人的業務伺服器建立TCP鏈接的。

退一步,你通過了鑒權,然後你想抓包修改重放,然而你又要面臨破解協議加密演算法的問題。

即使你牛逼,破解了演算法,重放了協議消息,然而Server就只讀取128 bytes數據,你發個65536過去,Server很可能給你kick off了。


等一下,TCP 不是你緩衝區裡面有多少東西就分配多大的內存然後讀一次緩存的么……

針對題目來說的話,只要你規定伺服器和客戶端對於超過指定大小的數據一定分塊傳送,然後你伺服器收到了拼接一下就好了。


可以採用另外一個方式:一個線程讀取數據,一個線程發送數據,數據包大小可定製,通過queue通訊


看回答里說,如果超出約定最大消息體,則斷開連接。那如果Client的TLV的lenght寫128,但卻發了不到128,要怎麼處理?讀超時了斷連的話,會有誤判吧


也可以數據長度100位元組,數據payload故意慢速發送,每秒發送1個位元組,耗盡並發連接數


最大長度限制,太長的數據分塊,使用特定分隔標記,可以加id,後面組裝。人家偽造長度沒用的,超過設定的最大長度就是無效的數據了。


我們伺服器以前就被攻擊過, 也不會如何, 就是 內存佔用爆表而已。

就算你「虛報」了長度, 後面的內容你也需要 慢慢傳進來的, 不會馬上崩潰。


推薦閱讀:

動態ip會阻礙網警查到使用人嗎?
如何深入學習 TCP/IP 協議,以及網路層、傳輸層、應用層各協議?
TCP四次分手中,主動關閉方最後為什麼要等待2MSL之後才關閉連接?
tcp首部只有埠號沒有ip地址,那麼網路層怎麼知道目的ip地址的呢?
關於CSMA/CD的問題?

TAG:TCPIP |