TCP零窗口攻击?
摘自:https://blog.csdn.net/fanst_/article/details/88185471
初識零窗口
??13年時曾經遇到過一個問題(原諒我現在才寫這篇文章…):提供下載服務的生產環境上(SUSE Linux,使用Tomcat BIO的Connector提供服務),有大量的狀態為ESTABLISHED的連接存在。本來作為下載服務器,有大量連接存在是很正常的事情,但是由于數量遠高于平時,引起維護人員的關注并只會到研發這邊,我們通過觀察這些鏈接的客戶端IP和端口,發現大部分連接一直沒有斷開的跡象,tomcat創建的大量線程處于阻塞狀態(BIO),服務器打開連接句柄過多,都可能會影響新的下載服務請求。情況很像是遇到了DDoS,當然也不排除是下載客戶端存在BUG導致。
??通過抓包發現,這些鏈接無一例外都處于一個無限循環中:每隔一段時間,服務端發送長度為0的KEEP-ALIVE,客戶端回復ZeroWindow,如此循環。
TCP滑動窗口
??滑動窗口的概念大家可以自行搜索,這里就不贅述了,大致的概念是發送和接受雙方都各有一個發送窗口和一個接受窗口,其主要目的就是為了流量控制,使雙方的發送、接收速度盡可能匹配。
零窗口(ZeroWindow)
??當發送方的發送速度大于接收方的處理速度,接收方的緩沖塞滿后,就會告訴發送方當前窗口size=0,請停止發送,發送方此時停止發送數據。
堅持定時器(TCP Persist Timer)
??零窗口出現后,如何繼續數據的傳輸呢。我們假設:接收方窗口更新為非0后,發送ACK給發送方,更新window的size,是一種可行的方式。但是如果這個ACK丟掉了(TCP不會給ACK回復ACK),那么發送方和接收方就會處于一種尷尬的局面,一個等著發,一個等著收,直到超時。
??此時引入了堅持定時器的概念,由發送方主動創建,持續不斷地詢問接收方窗口是否更新,這個詢問被稱為窗口探測(window probes)。
??終于到了問題的關鍵,這個定時器沒有時間限制,意味著這個TCP連接會永遠保持下去。
是不是DDos攻擊
google了很久,沒有查到各大操作系統對于堅持定時器的時間設置,提到零窗口攻擊的信息也很少,截取其中一個明確提到零窗口攻擊的頁面,來自一個硬件負載均衡的廠商的產品介紹,貌似硬件防火墻或者負載均衡設備應該有相關的防護設置:
原鏈接
問題重現
??這個現象還是很容易重現的,當時使用IE瀏覽器(6還是7來著),直接下載一個文件,在彈出確認提示框后不要點擊保存或者另存為,放著不動,用wireshark等工具查看,就會發現此時客戶端服務端就進入了零窗口探測的狀態,且會一直持續下去。
??13年時chrome、firefox瀏覽器都不能重現,因為他們比較聰明,會自動開始下載,不給你確認的機會…現在19年的IE11也不重現了,確認框還是彈的,但是在我們不確認的情況下,后臺已經下載完畢了。
??這也難不倒我們,用Socket寫個客戶端,模擬建鏈后不接受數據的情況,服務端就用tomcat,隨便在webapps下建個目錄放一個zip文件用來下載。代碼如下(完整代碼點擊這里):
public class Client
{
public static void main(String[] args)
throws Exception
{
Socket client = new Socket("10.253.178.218", 8080);
client.setSoTimeout(10000);
StringBuilder sb = new StringBuilder();
// HTTP協議中的換行符為CRLF,即
,每一行請求消息頭都需要以其結尾
sb.append("GET /fst/aaa.zip HTTP/1.1
");
sb.append("Host: 10.253.178.218
");
sb.append("Connection: keep-alive
");
sb.append("Accept: */*
");
sb.append("User-Agent: JavaSocket
");
// 消息頭結束需要單獨一行CRLF
sb.append("
");
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream();
os.write(sb.toString().getBytes("UTF-8"));
os.flush();
......
// HTTP協議中的換行符為CRLF,即
,讀取到單獨的一行
意味著消息頭讀取完畢。
while (!"
".equals(line))
{
line = readLine(is);
// 模擬緩沖滿的情況
Thread.sleep(1000000000000000000L);
if (line.startsWith("Content-Length"))
{
contentLength = Long.parseLong(line.split(":")[1].trim());
}
System.out.print(line);
}
......
}
解決辦法
當時的辦法
??修改Tomcat源碼,將socket對象暴露給業務代碼,業務代碼將所有socket和已經發送的字節數緩存起來(每次發送數據后即更新對應的字節數),每隔一段時間掃描一遍,將超過閾值時間后發送字節仍然沒有變化的socket,強制調用socket.close()將其關閉。
socket.setSoLinger(true, 0); // socket是阻塞IO,數據不發送完默認是關不掉的,需要設置soLinger
socket.close();
新的問題
??寫這篇文章時,試了一下NIO的Connector會如何,測試結果也不盡人意,堅持定時器仍然生效,過一會兒連接狀態遷移為FIN_WAIT1(抓包里沒看到發FIN,奇怪),連接句柄仍然被占用。
恐怖的是,./shutdown.sh關了tomcat,句柄仍不能釋放,堅持定時器仍然在持續不斷地探測…客戶端已經利用TCP的機制,綁架了服務端操作系統層面的句柄資源?
??作者對網絡和系統層面了解不多,歡迎大家提出更優雅的解決方法,或者提出指正。
參考
[1]:http://www.pcvr.nl/tcpip/tcp_pers.htm
總結
- 上一篇: 工业以太网 简介
- 下一篇: NanaZip 3.0 发布:同步 7-