最近發生主機被大量 request 攻擊的事件,而且慘的是這台主機放的是論壇服務
假設攻擊的點是論壇首頁,每次 request 都會去 query DB,而且有一堆 join
其中還有些是 POST 的指令會讓 db update
就這樣一直瘋狂又 select 又 update 導致 DB lock,cpu 飆高掛掉
如果論壇是自己寫的,還可以在 DB 跟 application 之間加上 redis 之類的快取
但偏偏這個論壇系統是別人的,沒有辦法動
先簡單講一下架構,為了分散流量
前面有一台 AWS ELB 做 load balancing,後面有兩台機器
所有 request 都會先到 ELB,再自動到後面兩台其中一台
被攻擊之後怎麼辦呢?第一個想到的就是從 aws 提供的服務:WAF 來擋
https://aws.amazon.com/tw/waf/
可是發現 WAF 跟原本想的不一樣,他沒有辦法設定像是:「擋掉 10 秒鐘內發超過 100 個 request 的 IP」這種規則
只能繼續在網路上找解法,找到從 nginx 來擋的解法:
nginx防止DDOS攻击配置
通过Nginx和Nginx Plus阻止DDoS攻击
Module ngx_http_limit_req_module
http {
//觸發條件,限制 ip 每秒 10 個 request
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
location ~ \.php$ {
//執行的動作
limit_req zone=one burst=5 nodelay;
}
}
}
總之就是利用 limit_req_zone
,這個 nginx 提供的東西
宣告一個 10mb 的 zone 叫做 one
來儲存狀態,這裡的 10r/s
指的就是一秒 10 個 request
接著在你想擋的地方加上:limit_req zone=one burst=5 nodelay;
,就可以擋掉了
nginx 會把處理 request 的數量調整成「最多 1 秒 10 個」,如果那個 ip 在同一時間有超過 5 個 request 還沒處理的話,就會傳回 503 service temporarily unavailable
,這邊的 5 就是 burst
設定的值
傳回去的 status code 也可以自己指定,例如說:limit_req_status 505;
儘管這個解法看起來很棒,但不知道為什麼,加了之後好像沒有用似的
伺服器的警報還是一直在響,DB 還是持續飆高
在請教過其他同事之後,得知 iptabls 也可以擋,而且還是直接從 tcp 層擋
找到下面兩篇資料:
-A INPUT -p tcp –dports 80 -j WEB_SRV_DOS
-A WEB_SRV_DOS -p tcp –syn –dports 80 -m recent –rcheck –second 30 –hitcount 200 -j LOG –log-prefix "[Possible DOS Attack]"
-A WEB_SRV_DOS -p tcp –syn –dports 80 -m recent –rcheck –second 30 –hitcount 200 -j REJECT
-A WEB_SRV_DOS -p tcp –syn –dports 80 -m recent –set
-A WEB_SRV_DOS -p tcp –dports 80 -j ACCEPT
-A INPUT -p tcp --dport 80 -m recent --rcheck --seconds 1 --hitcount 5 --name HTTP_LOG --rsource -j DROP
-A INPUT -p tcp --dport 80 -m recent --set --name HTTP_LOG --rsource
-A INPUT -p tcp --dport 80 -j ACCEPT
兩個的原理都一樣,是透過-m recent –rcheck –second 30 –hitcount 200
這段敘述,描述說你要擋住幾秒內發送幾次的 request,把這個連線 reject 或是 drop。
直接從 iptables 去擋聽起來是個更好的解法,這樣 request 連 nginx 都不會進去就被擋住了
可是天不從人願,用了之後發現還是不行!怎麼會這樣呢
心灰意冷之下,同事又推薦一個好東西叫做:fail2ban
查了一下之後發現用法非常簡單,而且原理很好懂
決定用別台機器來測測看,測試成功之後再套用到正式環境的機器
用 Fail2Ban 防範暴力破解 (SSH、vsftp、dovecot、sendmail)
fail2ban教學
Ubuntu 中使用 fail2ban 針對大量 access 做判斷及阻擋
綜合其中幾篇的敘述,可以得出以下流程
1.修改vim /etc/fail2ban/jail.local
2.寫入
[http-get-dos]
enabled = true
port = http
filter = http-get-dos
logpath = /var/log/nginx/access.log # 要判斷的log
maxretry = 100 # 最多幾次
findtime = 5 # 時間區間
bantime = 600 # 要 ban 多久
action = iptables[name=HTTP, port=http, protocol=tcp]
上面的規則就是:5 秒內嘗試 100 次失敗之後就 ban 600 秒
3.新增/etc/fail2ban/filter.d/http-get-dos.conf
這邊的檔名就是對應到剛剛 jail.local
設定的名稱
[Definition]
failregex = ^<HOST>- - .*\"(GET|POST).*
ignoreregex =
這裡的failregex
要根據你的 log 去寫
像是 nginx 的 access log 長這樣:
106.184.3.122 - - [21/Jul/2016:11:38:29 +0000] "GET / HTTP/1.1" 200 396 "-" "Go-http-client/1.1"
你就寫一個可以抓到<HOST>
,也就是 ip 的 regular expression
都設定好之後,重開一下應該就有效了
就會發現一直發 request 之後自己順利的被 ban 掉
可以用 iptables --list
看一下自己是不是真的有被 ban
fail2ban 的原理應該就是去看你指定的 log 檔跟規則,用這個檔案去判斷是不是超出設定的規則,超出的話就把 ip 抓出來,加規則到 iptables 裡面去擋掉,時間到了之後再把規則移除掉
試到這邊,終於成功了!可是既然原理也是 iptables,為什麼剛剛不行呢?
還記得一開始我提過伺服器架構嗎?前面一台 ELB,後面兩台 web server
因為 ELB 是 AWS 提供的服務,所以客製化程度很低,甚至連 ssh 進去都不行
因此上面嘗試的方案都是個別套用到那兩台 web server
這時候問題就來了
咦?那這樣子 web server 的 request 的來源,不就都是 ELB 的 ip 了嗎?
沒錯,你突破盲點了!之前沒有用就是卡在這邊
你用 iptables 來檔,因為來源都是 ELB 的 ip,所以會擋掉的只有 ELB,而不是真正的攻擊來源
所以會造成 ELB 被擋掉,整個服務就因為一個攻擊者被搞的超級慢
所以在這個網路環境底下,iptables 是行不通的!
那 nginx 呢?還記得我們的規則嗎?
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
$binary_remote_addr
抓到的也都會是 ELB 的 ip
這時候靈機一動突然想到,那能不能根據X-Forwarded-For
這個 header 來設定呢?就會是真的 IP 了
找到這一篇:nginx rate limiting with X-Forwarded-For header
把$binary_remote_addr
換成$http_x_forwarded_for
搞定!大功告成
經歷一番千辛萬苦,最後終於在 nginx 把攻擊流量擋掉
用 JMeter 測試之後也發現確實成功了,多的 request 會直接回傳 503
真是可喜可賀
評論