這幾天,DDoS 攻擊事件在台灣鬧得沸沸揚揚。
DoS 攻擊可約略分為頻寬消耗型(找一大群鄉民擠在餐廳門口)及資源消耗型(召喚服務生過來點菜連點兩鐘頭,或一口氣點兩百盤紅燒獅子頭),從網站管理者的角度,對頻寬消耗型攻擊完全無能為力,只能靠 ISP 或網管單位防禦;但對於資源消耗型攻擊,倒是有些因應對策。(例如:把十分鐘還沒點完菜的客人攆出去或把點兩百盤獅子頭的客人打成豬頭)
我知道有一些 ASP.NET 設定就與 DoS 防護有關,例如:每次搞檔案上傳時都要手動調大的 MaxRequestLength參數,選用偏小的 4MB 預設值,用意就在防止惡意程式透過 POST Request 傳送大量資料耗盡頻寬、記憶體或執行緒。而這陣子詢問度很高則是另一項 IIS功能-「動態 IP 限制」(在 IIS 7 時代為額外模組需透過 Web PI 安裝,IIS 8 起改為內建,詳情可參考小朱的文章), 過去曾聽過有此功能,但沒實際玩過,把握這個機會弄個小 Lab 觀察驗證也好。
以 IIS 8 為例,先從 IIS 管理員開啟「IP位址及網域限制」:
點選「編輯動態限制設定…」即可叫出設定畫面:
動態 IP 限制可從兩個方向下手:限制同一 IP 同時開啟連線數或限制同一 IP 在一段期間內發送的最大要求數量, 一旦超過上限,IIS 便直接回傳錯誤,不再耗費頻寬或 CPU、記憶體處理要求。
預設建議值為每個 IP 同時連線數不超過 5 條,每 0.2 秒要求數不得超過 20 個,超過這個數字 IIS 就暫時不處理來自該 IP 的請求,直接傳回 HTTP 錯誤以節省資源。
為印證效果,我設計了以下兩個實驗:
【實驗1】
用 IFrame 同時開啟超過五個網頁,網頁故意超過 1 秒傳回結果,迫使瀏覽器同時開啟多條連線,以突破同時要求數上限。
TestSimuLimit.html (開啟七個 IFrame 載入 SlowTask.aspx)
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8"/>
<style>
iframe {
display: block; margin: 3px; width: 600px; height: 50px;
}
</style>
</head>
<body>
<script>
for (var i = 0; i < 7; i++) {
document.write('<iframe src="SlowTask.aspx?t=' + Math.random() + '"></iframe>');
}
</script>
</body>
</html>
SlowTask.aspx (延遲 1 秒傳回 Guid)
<%@ Page Language="C#" %>
<%
System.Threading.Thread.Sleep(1000);
Response.ContentType = "text/plain";
Response.Write(Guid.NewGuid());
Response.End();
%>
測試結果:前五個 IFrame 顯示正常,後兩個傳回 403.501。註:
501: Dynamic IP Restrictions rejected the request due to too many concurrent requests
502: Dynamic IP Restrictions rejected the request due to too many requests over time
【實驗2】
以 setInterval 方式連續以 $.get() 方式發出 30 個 GET 存取,藉由改變時間間隔調整單位時間內發出要求數,例如:間隔 10ms 等於每 200ms 發出 20 個 AJAX 要求。
TestRPSLimit.html
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8"/>
<style>
li {
display: inline-block; margin: 3px; padding: 3px;
border: 1px solid gray;
}
.warn {
background-color: sandybrown;
}
</style>
</head>
<body>
Interval = <inputtype="text"value="11"size="2"/> ms
<buttonid="btnRun">Test</button>
<ulid="ulResults"></ul>
<scriptsrc="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
var MAX_COUNT = 30;
var ul = $("ul");
var count = 0;
function test() {
ul.empty();
count = MAX_COUNT;
var hnd = setInterval(function () {
$.get("OK.txt?t=" + Math.random()).done(function () {
ul.append("<li>Succ</li>");
}).fail(function () {
ul.append("<li class='warn'>Fail</li>");
});
count--;
if (count <= 0) clearInterval(hnd);
}, parseInt($("input").val()));
}
$("button").click(test);
</script>
</body>
</html>
間隔 11ms,相當於 18.2 Requests / 200ms,未突破上限,所有 $.get() 都成功。
間隔 9ms,約 22.2 Requests / 200 ms,我們觀察到前 20 次成功,接著有三次失敗(由 F12 開發者工具可知為 HTTP 403),滿 200ms 後重新計算,後面 7 次也成功。
間隔 7ms,約 28.6 Requests / 200 ms,前 20 次成功,接著 8 次失敗,滿 200ms 重新計算,後面又成功。
【結論】
透過上述兩個實驗,我們驗證 IIS 的動態 IP 限制確實能降低單一 IP 同時或連續發出大量請求拖垮系統的風險,雖然功能與強韌度無法與專業 DDoS 防禦設備相提並論,但仍具有基本的防護效力(甚至對付「網頁搶票 F5 Combo攻擊」也多少有效果,哈),值得一試。
實務設定時,同時要求數及連續要求數的拿捏是門學問,較嚴謹的做法是觀察平日負載下單一 IP 可能產生的要求數,以判定超過多少需視為異常。設定上限數字的畫面有個「僅啟用記錄模式」選項,此時就能派上用場,在記錄模式下,IIS 遇到超過上限時仍會以 HTTP 200 正常傳回結果,但 IIS Log 的 sc-substatus 會註記 501/502 做為區別,以便觀察誤判比率。有個比較麻煩的問題,是以來源 IP 做為判定依據,來自同一 NAT、防火牆、代理伺服器後方的使用者會共用同一 IP,有可能因誤判造成困擾,此點在是高流量網站更明顯。針對大型網站或重要服務,恐怕還是得靠專業 DoS 防護設備較有保障。