每一個可以努力的日子,都是一份厚禮。
VPS 屏蔽掃描網站的 IP
前段時間博客經常性地無法訪問,網站宕機。SSH 上去看進程,發現大量 php-fpm 佔用系統資源,查看服務器的 Nginx 日誌,就知道發生了什麼事情。個別 IP “友情”為我的站點掃描漏洞,瞬時並發連接很大。我知道大家也沒什麼惡意,只是用黑客工具比較興奮,拿 www.lovelucy.info 練練手而已嘛。但是博主很窮,小站搭建在一個配置並不高的免費 VPS 上,折騰不起,壓力很大,結果一不小心讓各位搞成 DoS 拒絕服務攻擊了,真是慚愧。
有趣的是,除掉一些窮舉後台密碼的,掃描者一股腦地發請求,大部分卻是在找 asp 的漏洞。可是這樣好浪費時間啊,塵埃落定的博客實在是用的 wordpress 程序,是 php 平台啊……
GET /mirserver.rar 404
GET /save.asp 404
GET /wwwroot.rar 404
GET /upfile_flash.asp 404
GET /web.rar 404
GET /mirserver3.rar 404
GET /www.rar 404
GET /eWebEditor/admin_login.asp 404
GET /mirserver.zip 404
GET /wwwroot.zip 404
GET /mirserver4.rar 404
GET /newsadmin/ubb/admin_login.asp 404
GET /CmsEditor/admin_login.asp 404
GET /admin/webeditor/admin_login.asp 404
…
言歸正傳,掃描漏洞的人目的大多是想做黑鏈 SEO,給黑掉的站點加上隱藏鏈接,提高目標網站在搜索引擎中的排名。這背後已經形成產業鏈了,可惜這種手段收到的效果已經越來越差,Google 早就不給隱藏鏈接權重了,現在連百度都能檢測出惡意外鏈,這樣一來還有什麼意義呢?
對於站長來講,要避免掃描給站點帶來的影響,最好是對有關 IP 進行屏蔽。在發現網站宕掉,手工用 iptables 封了幾個 IP 後,網站立刻就恢復正常了。
iptables -I INPUT -s 124.115.0.199 -j DROP |
但是過了幾天又有別的人來掃描,各種 IP 層出不窮,一個個地去封收效甚微。怎麼辦?
一、使用 Nginx 的 limit_conn 模塊
Ngnix 服務器的 limit_conn 模塊可以限制單個 IP 的並發連接數,剛了解到它的時候感覺這碉堡了,簡直是應用層防火牆了。修改配置文件 nginx.conf
http { ... limit_zone ten $binary_remote_addr 10m; limit_conn ten 10; ... } |
這樣限制使用 10M 內存來管理 session,同時限制每個 IP 可以同時發起最多 10 個請求。
二、使用 iptables 做連接數控制
iptables 也可以做到限制同一 IP 的瞬間連接數,如果使用 iptables 做並發控制,可以在網絡層就把惡意數據包丟棄,理論上講,效率比 Nginx (應用層)的方式更高一些。
iptables -I INPUT -p tcp --dport 80 -d SERVER_IP -m state --state NEW -m recent --name httpuser --set iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j LOG --log-prefix 'HTTP attack: ' iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j DROP |
SERVER_IP 為被攻擊的服務器 IP。
- 第一行的意思是:-I,將本規則插入到 INPUT 鏈的最上頭。即,所有目標端口是80、目標 IP 是我們機器的IP,的 TCP 連接,在連接建立時,我們就將其列入 httpuser 清單中。
- 第二行的意思是:-A,將本規則附在 INPUT 鏈的最尾端。只要是 60 秒內,同一個來源連續產生 9 個連接請求,我們就對此進行 Log 記錄。記錄行會以 HTTP attack 開頭。 –update 表示規則匹配時會更新 httpuser 列表清單。
- 第三行的意思是:-A,將本規則附在 INPUT 鏈的最尾端。和第二行同樣的比對條件,但是本次的動作則是將此連接直接丟棄。
所以,這三行規則表示,我們允許一個客戶端 IP,每一分鐘內可以向服務器發出 8 個連接請求。
三、使用 Nginx 過濾惡意請求
上面兩個方法最大的缺陷在於對數值的拿捏十分困難:配置太松,起不到屏蔽掃描的作用;配置太嚴格,又可能把正常訪問也拒絕了,特別是某些搜索引擎過來抓頁面的時候。網絡環境差異,這個值該設多少,是沒有一個標準答案的。
於是,有外國友人寫了一套 Nginx 規則,僅僅對惡意請求進行屏蔽。何為惡意呢?
server { [...] ## Block SQL injections set $block_sql_injections 0; if ($query_string ~ "union.*select.*\(") { set $block_sql_injections 1; } if ($query_string ~ "union.*all.*select.*") { set $block_sql_injections 1; } if ($query_string ~ "concat.*\(") { set $block_sql_injections 1; } if ($block_sql_injections = 1) { return 403; } ## Block file injections set $block_file_injections 0; if ($query_string ~ "[a-zA-Z0-9_]=http://") { set $block_file_injections 1; } if ($query_string ~ "[a-zA-Z0-9_]=(\.\.//?)+") { set $block_file_injections 1; } if ($query_string ~ "[a-zA-Z0-9_]=/([a-z0-9_.]//?)+") { set $block_file_injections 1; } if ($block_file_injections = 1) { return 403; } ## Block common exploits set $block_common_exploits 0; if ($query_string ~ "(<|%3C).*script.*(>|%3E)") { set $block_common_exploits 1; } if ($query_string ~ "GLOBALS(=|\[|\%[0-9A-Z]{0,2})") { set $block_common_exploits 1; } if ($query_string ~ "_REQUEST(=|\[|\%[0-9A-Z]{0,2})") { set $block_common_exploits 1; } if ($query_string ~ "proc/self/environ") { set $block_common_exploits 1; } if ($query_string ~ "mosConfig_[a-zA-Z_]{1,21}(=|\%3D)") { set $block_common_exploits 1; } if ($query_string ~ "base64_(en|de)code\(.*\)") { set $block_common_exploits 1; } if ($block_common_exploits = 1) { return 403; } ## Block spam set $block_spam 0; if ($query_string ~ "\b(ultram|unicauca|valium|viagra|vicodin|xanax|ypxaieo)\b") { set $block_spam 1; } if ($query_string ~ "\b(erections|hoodia|huronriveracres|impotence|levitra|libido)\b") { set $block_spam 1; } if ($query_string ~ "\b(ambien|blue\spill|cialis|cocaine|ejaculation|erectile)\b") { set $block_spam 1; } if ($query_string ~ "\b(lipitor|phentermin|pro[sz]ac|sandyauer|tramadol|troyhamby)\b") { set $block_spam 1; } if ($block_spam = 1) { return 403; } ## Block user agents set $block_user_agents 0; # Don't disable wget if you need it to run cron jobs! #if ($http_user_agent ~ "Wget") { # set $block_user_agents 1; #} # Disable Akeeba Remote Control 2.5 and earlier if ($http_user_agent ~ "Indy Library") { set $block_user_agents 1; } # Common bandwidth hoggers and hacking tools. if ($http_user_agent ~ "libwww-perl") { set $block_user_agents 1; } if ($http_user_agent ~ "GetRight") { set $block_user_agents 1; } if ($http_user_agent ~ "GetWeb!") { set $block_user_agents 1; } if ($http_user_agent ~ "Go!Zilla") { set $block_user_agents 1; } if ($http_user_agent ~ "Download Demon") { set $block_user_agents 1; } if ($http_user_agent ~ "Go-Ahead-Got-It") { set $block_user_agents 1; } if ($http_user_agent ~ "TurnitinBot") { set $block_user_agents 1; } if ($http_user_agent ~ "GrabNet") { set $block_user_agents 1; } if ($block_user_agents = 1) { return 403; } [...] } |
我們來看上面這個配置文件,它將所有包含類似 union.*select.*\(
URL 參數的請求都拒絕,這樣來阻止 SQL 注入攻擊。又拒絕所有 URL 參數後面有 =http://
的請求,來防止文件包含漏洞。類似地,屏蔽掉所有有 <|%3C).*script.*(>|%3E
的請求,阻止跨站腳本。還有就是屏蔽一些預定義的 User Agent,拒絕惡意抓站。
回過頭來看,這個配置其實不太符合我國國情,比如屏蔽的那些瀏覽器 UA,並不是掃描中文網站常見的 UA。另外,要在配置文件中定義和枚舉所有的惡意行為,是很困難的一件事情。
四、使用腳本定時檢測日誌
所有對服務器的訪問請求都會在 Nginx 日誌中記錄,這其中也包括那些造成出錯的請求。我們能否通過分析日誌來確認惡意的訪問請求呢?受第二種方法的啟發,我們可以監控服務器日誌,將請求出錯的訪問者 IP 放入一個 List 特別觀察,在一段時間內如果沒有太多的出錯,我們就將其從列表中移除,否則,錯誤太多達到警戒值就調用 iptable 將其禁封。
通過一個腳本就可以完成這項工作。這個腳本與方法二的區別在於,方法二僅僅記錄並發連接,只要並發過高,就可以觸髮禁封,這不夠科學。這裡我們通過對日誌的監控,禁封的只會是給我們造成出錯和麻煩的 IP(例如頻繁的 404 錯誤,明顯是掃描),搜索引擎過來就不會有問題了。
腳本代碼
#!/usr/bin/perl use strict; use warnings; ## 本腳本將會監控 Web 服務器的 log 記錄,(例如 Apache 或者 Nginx) ## 並統計同一個 IP 所引發的 HTTP 錯誤數目。該數值達到用戶配置的數量, ## 則使用防火牆對該 IP 進行屏蔽,拒絕其訪問。 ## log 文件路徑 my $log = "/var/log/nginx/access.log"; ## 一個 IP 觸發了多少次錯誤,我們就將其屏蔽? my $errors_block = 10; ## 過期時間,超過多少秒沒有再見到該 IP 則將其從觀察列表中移除? my $expire_time = 7200; ## 將 IP 從觀察列表中移除時,清理多少個錯誤日誌行數? my $cleanup_time = 10; ## 調試模式 on=1 off=0 my $debug_mode = 1; ## 聲明一些內部變量 my ( $ip, $errors, $time, $newtime, $newerrors ); my $trigger_count=1; my %abusive_ips = (); ## 打開日誌文件。使用系統的 tail 命令,有效輪詢 open(LOG,"tail --follow=$log |") || die "Failed!\n"; ## For Linux (Ubuntu) systems # open(LOG,"tail -f $log |") || die "Failed!\n"; ## For OpenBSD, FreeBSD or Linux systems while(<LOG>) { ## 定義錯誤代碼。這裡使用了正則表達式匹配,你可以自行添加一些, ## 例如無端訪問 .vbs 後綴文件請求,列入屏蔽條件 if ($_ =~ m/( 401 | 402 | 403 | 404 | 405 | 406 | 407 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 444 | 500 | 501 | 502 | 503 | 504 | 505 )/) { ## 自定義: 白名單 IP。 不論這些 IP 做了什麼,均不屏蔽。 ## Google 爬蟲 IP 段 66.249/16 就是一個好例子。 ## 為了方便程序員開發測試,內部子網 192.168/16 也不屏蔽。 if ($_ !~ m/(^66\.249\.\d{1-3}\.d{1-3}|^192\.168\.\d{1-3}\.d{1-3})/) { ## 從日誌行中解析出 IP $time = time(); $ip = (split ' ')[0]; ## 若 IP 之前從未出現過,我們需要初始化,以避免出現警告消息 $abusive_ips{ $ip }{ 'errors' } = 0 if not defined $abusive_ips{ $ip }{ 'errors' }; ## 給這個 IP 增加出錯計數,更新時間戳 $abusive_ips{ $ip }{ 'errors' } = $abusive_ips{ $ip }->{ 'errors' } + 1; $abusive_ips{ $ip }{ 'time' } = $time; ## DEBUG: 輸出詳細信息 if ( $debug_mode == 1 ) { $newerrors = $abusive_ips{ $ip }->{ 'errors' }; $newtime = $abusive_ips{ $ip }->{ 'time' }; print "unix_time: $newtime, errors: $newerrors, ip: $ip, cleanup_time: $trigger_count\n"; } ## 如果該 IP 已經觸發 $errors_block 出錯數量,調用 system() 函數屏蔽之 if ($abusive_ips{ $ip }->{ 'errors' } >= $errors_block ) { ## DEBUG: 輸出詳細信息 if ( $debug_mode == 1 ) { print "ABUSIVE IP! unix_time: $newtime, errors: $newerrors, ip: $ip, cleanup_time: $trigger_count\n"; } ## 自定義: 這裡是屏蔽 IP 的 system() 系統調用 ## 你可以對這個 IP 添加更多的執行操作。例如,我們使用 logger 打印記錄到 /var/log/messages ## 注釋掉的是 OpenBSD 系統 Pf 防火牆 system("logger '$ip blocked by calomel abuse detection'; iptables -I INPUT -s $ip -j DROP"); # system("logger '$ip blocked by calomel abuse detection'; pfctl -t BLOCKTEMP -T add $ip"); ## 當 IP 已經被屏蔽,它就沒必要繼續留在觀察列表中了 delete($abusive_ips{ $ip }); } ## 為後面的清理函數增加觸發計數 $trigger_count++; ## 清理函數:當觸發計數達到 $cleanup_time 我們將所有已經過期的條目從 $abusive_ips 列表中刪除 if ($trigger_count >= $cleanup_time) { my $time_current = time(); ## DEBUG: 輸出詳細信息 if ( $debug_mode == 1 ) { print " Clean up... pre-size of hash: " . keys( %abusive_ips ) . ".\n"; } ## 清理我們已經很久沒再見到的 IP while (($ip, $time) = each(%abusive_ips)){ ## DEBUG: 輸出詳細信息 if ( $debug_mode == 1 ) { my $total_time = $time_current - $abusive_ips{ $ip }->{ 'time' }; print " ip: $ip, seconds_last_seen: $total_time, errors: $newerrors\n"; } ## 如果 IP 未出現的時間已經超過我們設定的過期時間,則將其從列表中移除 if ( ($time_current - $abusive_ips{ $ip }->{ 'time' } ) >= $expire_time) { delete($abusive_ips{ $ip }); } } ## DEBUG: 輸出詳細信息 if ( $debug_mode == 1 ) { print " Clean up.... post-size of hash: " . keys( %abusive_ips ) . ".\n"; } ## 重置清理觸發計數 $trigger_count = 1; } } } } #### EOF #### |
保存以上內容到 web_server_abuse_detection.pl,增加可執行權限
chmod +x web_server_abuse_detection.pl |
變量解釋:
my $log 是要監控的日誌路徑。日誌文件格式是標準的 Apache “common” 或 “combined”。這個腳本可以處理 Apache, Nginx, Lighttpd 甚至 thttpd 的日誌,它將會在日誌中尋找第一個字符串,即遠程連接過來的 IP 地址。例如,這個是 Google 爬蟲訪問我網站的一條日誌 “66.249.72.6 www.lovelucy.info – …”
my $errors_block 是一個客戶端所能觸發的最大錯誤數量,超過此值 IP 將被屏蔽。 Web 服務器的錯誤代碼 400-417(如果你用 Nginx 則還包括 444),以及 500-505 都是觸發條件。我們默認設置 errors_block 為 10,即如果一個 IP 在 7200 秒 ($expire_time) 以內觸發了 10 個錯誤 ($errors_block),它將被屏蔽。
my $expire_time 是一個 IP 從觀察列表中移除的過期時間。默認我們設 7200 秒(2 小時)。請注意,一個 IP 必須在 7200 秒以內沒有觸發任何錯誤,我們才會將它從列表中移除。這意味着一個惡意用戶用很慢的速率掃描,我們仍可能會將其屏蔽。例如,一個 IP 每一小時訪問一次來檢測漏洞,第一個小時它就被列入觀察列表,在第二個小時出錯計數被增加,同時“最後一次見到這個IP” 的時間戳也被更新。普通的入侵檢測系統 (IDS) 可能會漏報這樣的行為,但這個腳本會在一個較長的時間(2小時)持續跟蹤監測一個 IP。在這個 IP 達到觸發 10 次錯誤時,也就是10小時後,我們仍會將其屏蔽。
my $cleanup_time 是觸發清理觀察列表的出錯行數量。清理工作是一個循環,很耗費 CPU 所以沒必要每一個錯誤日誌行都去執行。請保證你的清理計數值足夠低,從而讓舊 IP 能以合適的速率從列表中移除。但是太低又會耗費 CPU,一個恰當的值應該是你的服務器5分鐘內所產生的錯誤日誌行數。
my $debug_mode 調試模式,會打印出一些有用的信息。
自定義白名單:注釋中已經說過,Google 機器人 IP段 66.249/16 就應該放到白名單里,因為任何別人的網站鏈接到我們的一個錯誤的 URL,都會導致搜索引擎抓取失敗。開發人員的 IP 也應該放入白名單,因為程序測試也會經常產生失敗錯誤。
自定義系統調用:檢測到惡意 IP 後,我們通過系統調用屏蔽之。默認的調用包括 logger 打印消息到 /var/log/messages,並執行 iptables 屏蔽命令。我們也可以添加更多操作,例如觸發一個 Nagios 監控警告,給運維人員發 Email,等等。
運行:
設置 my $debug_mode = 0;
腳本即會靜默運行。要讓它在後台運行,不佔用終端,則在命令後加一個 & 符號
./web_server_abuse_detection.pl & |
總結
月光博客寫過一篇《防止CC攻擊的方法》,他說現在 CC 攻擊的技術含量低,利用工具和一些 IP 代理,搞個幾百個肉雞,一個初、中級的電腦水平的用戶就能夠實施攻擊。門檻還真低啊。
做人還是要低調一點。
參考鏈接:
Web Server Abuse Detection
iptables 限制同一 IP 連接數
Nginx: How To Block Exploits, SQL Injections, File Injections, Spam, User Agents, Etc.
這篇文章由lovelucy於2012-08-17 18:35發表在信息安全。你可以訂閱RSS 2.0 也可以發表評論或引用到你的網站。除特殊說明外文章均為本人原創,並遵從署名-非商業性使用-相同方式共享創作協議,轉載或使用請註明作者和來源,尊重知識分享。 |
Google Chrome 32.0.1700.76 Windows 7 大約10年前
請問”使用 iptables 做連接數控制”提供的那些代碼具體放在那個文件具體那個地方?
同時請問一下寫入以下代碼是否正確:
iptables -I INPUT -p tcp –dport 80 -d 192.168.1.1 -m state –state NEW -m recent –name httpuser –set
iptables -A INPUT -m recent –update –name httpuser –seconds 60 –hitcount 21 -j LOG –log-prefix ‘HTTP attack: ‘
iptables -A INPUT -m recent –update –name httpuser –seconds 60 –hitcount 21 -j DROP
想每60秒只許發出20個請求,同時IP地址那裡用一個內容的來表示,具本到時會換上服務器的真實IP,以上這些代碼是否正確?
待博客的回復,謝謝。
Google Chrome 33.0.1750.154 Windows 7 大約10年前
iptables 不是代碼,是直接在命令行運行的命令。
你上面的指令理論上是沒問題的,只是我說過
“對數值的拿捏十分困難:配置太松,起不到屏蔽掃描的作用;配置太嚴格,又可能把正常訪問也拒絕了,特別是某些搜索引擎過來抓頁面的時候。網絡環境差異,這個值該設多少,是沒有一個標準答案的。”
Google Chrome 33.0.1750.154 Windows 7 大約10年前
後來我理解了,按教程設置了相應的iptables設置並且重啟,但是事後用百度站長工具刷掃漏洞後馬上出現主機內存佔滿,i/o數提高到5K左右,MYSQL停止運行,後來重啟後才恢復正常。
對於這種情況有沒有什麼辦法解決?針對於DOS、CC功擊,有什麼好的解決方案?
Google Chrome 33.0.1750.154 Windows 7 大約10年前
mod_security
fail2ban
試下這兩個
Google Chrome 21.0.1180.79 Windows 7 大約12年前
樓主的免費VPS是哪家的呢? 😛 也想搞一個來做做實驗,求分享 : )
Google Chrome 21.0.1180.79 Windows 7 大約12年前
Amazon EC2雲計算,第一年免費。我選用的EC2日本節點,速度還行。
Google Chrome 21.0.1180.79 Windows 7 大約12年前
大謝樓主 😛