前段时间博客经常性地无法访问,网站宕机。SSH 上去看进程,发现大量php-fpm 占用系统资源,查看服务器的Nginx 日志,就知道发生了什么事情。个别IP “友情”为我的站点扫描漏洞,瞬时并发连接很大。我知道大家也没什么恶意,只是用黑客工具比较兴奋,拿www.lovelucy.info 练练手而已嘛。但是博主很穷,小站搭建在一个配置并不高的免费VPS 上,折腾不起,压力很大,结果一不小心让各位搞成DoS 拒绝服务攻击了,真是惭愧。) `9 c, f6 w: F x7 y! P
6 t% S. q. w8 t; h6 O* U5 v
' n: P, Y! }- J5 z0 @0 G; o有趣的是,除掉一些穷举后台密码的,扫描者一股脑地发请求,大部分却是在找asp 的漏洞。可是这样好浪费时间啊,尘埃落定的博客实在是用的wordpress 程序,是php 平台啊……0 I/ I( O' i1 m2 I3 @1 M
GET /mirserver.rar 404
2 d% }8 H: i/ r9 }, ?6 a" mGET /save.asp 404
& t9 _+ \9 G4 |" r: IGET /wwwroot.rar 404
* J Q }8 \! U6 BGET /upfile_flash.asp 404
0 @) T% R5 q/ c7 u( [0 O8 z' RGET /web.rar 404. {5 A" M" n: {/ X* G4 N1 v
GET /mirserver3.rar 4042 x9 u7 ~9 u% g% n& _* R/ a
GET /www.rar 404! q8 X2 ]6 @) V7 C! s
GET /eWebEditor/admin_login.asp 404
0 L" I# u" h1 C& s6 DGET /mirserver.zip 404
5 N. ?1 C9 |$ HGET /wwwroot.zip 404
5 F, r$ u$ T3 MGET /mirserver4.rar 404
, E; \# ` b4 z p5 wGET /newsadmin/ubb/admin_login.asp 404" C* V! B% g! X, f' a1 g* O
GET /CmsEditor/admin_login.asp 404$ m# U9 r5 U1 d/ t
GET /admin/webeditor/admin_login.asp 404
( L0 M& }, F8 q+ ? L; W…
; a3 _7 E8 D, Q3 X, ]) C言归正传,扫描漏洞的人目的大多是想做黑链SEO,给黑掉的站点加上隐藏链接,提高目标网站在搜索引擎中的排名。这背后已经形成产业链了,可惜这种手段收到的效果已经越来越差,Google 早就不给隐藏链接权重了,现在连百度都能检测出恶意外链,这样一来还有什么意义呢?
9 J" H2 R+ d+ z' H: I对于站长来讲,要避免扫描给站点带来的影响,最好是对有关IP 进行屏蔽。在发现网站宕掉,手工用iptables 封了几个IP 后,网站立刻就恢复正常了。; O$ g, r" x3 |- s' g7 Z$ k
iptables -I INPUT -s 124.115.0.199 -j DROP4 |0 W- i3 u, m( Y# z7 k7 P9 P+ t% b D" K
但是过了几天又有别的人来扫描,各种IP 层出不穷,一个个地去封收效甚微。怎么办?: p$ l0 C) t e) |- S
一、使用Nginx 的limit_conn 模块
Z$ i& ] ?0 i# I! A# H) Z& ENgnix 服务器的limit_conn 模块可以限制单个IP 的并发连接数,刚了解到它的时候感觉这碉堡了,简直是应用层防火墙了。修改配置文件nginx.conf! U7 l' q& C$ X% I! Y" D% T
http {0 e' t6 u# a4 j. z8 b
...4 K/ X) W: c. v+ V( [, ~
limit_zone ten $binary_remote_addr 10m;
# }5 A+ {) e4 O+ s" m. V% t" Qlimit_conn ten 10;8 J2 U- Q/ `- O5 j3 v
...
. H) @& c# s) r/ I& m2 }}
5 p: D! N3 ]6 w这样限制使用10M 内存来管理session,同时限制每个IP 可以同时发起最多10 个请求。
; S4 \( E( {. L. d. u( ^二、使用iptables 做连接数控制9 o% r9 Y& g" a! p* H2 v3 y- ~8 |3 u
iptables 也可以做到限制同一IP 的瞬间连接数,如果使用iptables 做并发控制,可以在网络层就把恶意数据包丢弃,理论上讲,效率比Nginx (应用层)的方式更高一些。: z8 W, M3 o" e f# O
iptables -I INPUT -p tcp --dport 80 -d SERVER_IP -m state --state NEW -m recent --name httpuser --set6 C- t' I1 U( y# E$ ?
" I4 g& ]6 h E% b; a/ q s
iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j LOG --log-prefix 'HTTP attack: '' t' ]+ ~: ^/ {4 i! W# x) o2 ]4 H* h1 {3 O
( G( ?# r% J N$ t& c/ {- `
iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j DROP6 P, T8 t4 ]& x0 E' L3 r0 @ V/ Y
SERVER_IP 为被攻击的服务器IP。
: s+ `/ }% [' Z) f9 r" \2 O1. 第一行的意思是:-I,将本规则插入到INPUT 链的最上头。即,所有目标端口是80、目标IP 是我们机器的IP,的TCP 连接,在连接建立时,我们就将其列入 httpuser 清单中。/ Y6 U) f/ N9 _6 w4 Q
2. 第二行的意思是:-A,将本规则附在INPUT 链的最尾端。只要是60 秒内,同一个来源连续产生9 个连接请求,我们就对此进行Log 记录。记录行会以HTTP attack 开头。 –update 表示规则匹配时会更新httpuser 列表清单。6 s7 E! Q8 {( h8 a5 N
3. 第三行的意思是:-A,将本规则附在INPUT 链的最尾端。和第二行同样的比对条件,但是本次的动作则是将此连接直接丢弃。
- v, j; G$ S1 j/ V& B% i$ G0 a所以,这三行规则表示,我们允许一个客户端IP,每一分钟内可以向服务器发出8 个连接请求。
; l: Q2 K% D0 T, w三、使用Nginx 过滤恶意请求
+ ~# K Q7 c& o, p4 C5 y1 @上面两个方法最大的缺陷在于对数值的拿捏十分困难:配置太松,起不到屏蔽扫描的作用;配置太严格,又可能把正常访问也拒绝了,特别是某些搜索引擎过来抓页面的时候。网络环境差异,这个值该设多少,是没有一个标准答案的。
+ G& D' o$ N9 p' U( A% Z于是,有外国友人写了一套Nginx 规则,仅仅对恶意 请求进行屏蔽。何为恶意呢?
, k% K" X+ C, x( }$ D; l7 v: yserver {+ Z( `( V( r) M# _& ]/ ~* L' V
[...]( Z0 G- i+ v2 U0 ?" L5 h
6 L8 J7 r( g0 R) b## Block SQL injections$ w9 F; T# h% m/ P5 o5 x$ ]
set $block_sql_injections 0;. W5 Z! A7 X( x9 w
if ($query_string ~ "union.*select.*(") {
% l2 Y% k, R1 E0 [7 jset $block_sql_injections 1;
* \8 Z7 H- {7 I. M2 s' O0 S2 O}
& P* u# v; i1 u4 X$ X! Uif ($query_string ~ "union.*all.*select.*") {
$ ?8 a: O6 {, Y5 R0 e- zset $block_sql_injections 1;
: D- A* J8 W4 Z, ~3 E: N}' h& V" T. V$ Q; g9 N
if ($query_string ~ "concat.*(") {
$ Z$ o/ @$ p8 rset $block_sql_injections 1;( L7 n$ K u. O9 s6 _ z
}% g$ }1 h; y" X
if ($block_sql_injections = 1) {: Y7 k/ E9 R! c: ]6 r1 ~
return 403;" {4 U' R8 p( U
}% l' ^1 n3 j3 u
( s4 F# |" a8 @0 @7 M
## Block file injections
$ G9 ~& m- U& g/ R# {% Z/ oset $block_file_injections 0;5 e3 V Y2 ^2 p9 H+ e& _- @
if ($query_string ~ "[a-zA-Z0-9_]=http://") {7 F9 G; ~' r# U0 c
set $block_file_injections 1;
5 a1 k& h! I; E2 s8 K T# J}
. @2 l5 M; }- [" P2 S. l( r- t6 l* Cif ($query_string ~ "[a-zA-Z0-9_]=(..//?)+") {: m8 e" E: j W" j0 l- Z/ h* D
set $block_file_injections 1;
9 c& g1 }) F( k2 W}
w2 x$ K( {5 Z" [1 Eif ($query_string ~ "[a-zA-Z0-9_]=/([a-z0-9_.]//?)+") {
0 S# O1 D+ x; \8 x% x" x" I/ @set $block_file_injections 1;
% s3 D8 C% h' \$ I8 h% S}5 e6 B8 ^ |% c' `6 c& C
if ($block_file_injections = 1) {
) m2 w4 ~9 F6 {& Jreturn 403;
0 t' \! e2 c6 y( x# p" S' a5 I}8 @6 K w$ w( g7 g. H. g8 t; W
' c4 Z# z& }8 ]1 d0 e
## Block common exploits$ f6 w7 K8 [- ? F; R/ {6 D. T
set $block_common_exploits 0;
' B+ w' t3 ^3 [3 m# ?$ n; n8 yif ($query_string ~ "(<|<).*script.*(>|>)") {
: ?8 o0 V( p! `! \* C1 wset $block_common_exploits 1;& I* W! V; G# {! v( x
}2 y& i8 }0 m8 f6 D% j
if ($query_string ~ "GLOBALS(=|[|%[0-9A-Z]{0,2})") {3 r; X- V( C/ E, Y
set $block_common_exploits 1;; m8 Y. p n) k/ @1 p1 V+ Z, y* B
}
8 H3 O( q9 L. O, ]8 Q# @4 X5 Zif ($query_string ~ "_REQUEST(=|[|%[0-9A-Z]{0,2})") {
; L6 W8 w; _4 \4 E( Yset $block_common_exploits 1;, w( X$ b2 J: K$ w- d- j
}/ a- e, H; M! P; F. d
if ($query_string ~ "proc/self/environ") {
( V& Y3 [, V* M0 g9 T+ H: Sset $block_common_exploits 1;, _/ L! ^* D8 @' n8 x
}) y+ K2 k: K5 D$ _, v `+ }
if ($query_string ~ "mosConfig_[a-zA-Z_]{1,21}(=|=)") {$ s/ {, Y* U- u6 A1 D
set $block_common_exploits 1;
4 q+ J. A ?% j3 h! h}& a# s. u/ U% r/ g
if ($query_string ~ "base64_(en|de)code(.*)") {0 x* }" G: v t7 ^' U
set $block_common_exploits 1;
^. j% G: C& J/ S7 p$ O}
( P3 q( m, l& h/ J) `- A: sif ($block_common_exploits = 1) {/ [4 o. g2 ]/ G( c& C) t; n
return 403;
& A4 M+ w& j/ F& E7 q, ?+ E}) k0 W0 @9 V2 U: `
! q8 h7 ?# w* _6 I
## Block spam
+ y# I/ n4 N: T* gset $block_spam 0;
$ R! p& n2 l7 V5 s: y5 aif ($query_string ~ "b(ultram|unicauca|valium|viagra|vicodin|xanax|ypxaieo)b") {
1 x& F1 q$ I/ k$ a8 \( w( G) a$ zset $block_spam 1;
3 u/ D' L- N# H7 b: X6 J0 i}* Q5 w2 R* X% z6 A( z
if ($query_string ~ "b(erections|hoodia|huronriveracres|impotence|levitra|libido)b") {
: S( ^2 Y5 c1 `* Q, G- ~. }set $block_spam 1;
( q0 q9 A; \7 \5 d}$ s# P1 j3 p' b! f8 W# d: W
if ($query_string ~ "b(ambien|bluespill|cialis|cocaine|ejaculation|erectile)b") {- m3 ]0 F. {- o' o5 S
set $block_spam 1;
& d, f9 G( {4 F5 L6 G}
' K$ M3 `4 K( F& B2 p. Z* Dif ($query_string ~ "b(lipitor|phentermin|pro[sz]ac|sandyauer|tramadol|troyhamby)b") {. _/ D) F# |$ P: I, ]4 B
set $block_spam 1;
! ^, N5 u. c5 O5 x t5 q1 W3 ]" ~}# R1 H4 a" _" J7 v2 _! Q3 F$ X1 }- r" d
if ($block_spam = 1) {
* l; q4 | e: n, ereturn 403;; g& Q" A% K+ H, ^7 m; e
}0 T2 {) \9 J: u; `
) z/ f! O I, K* G## Block user agents
3 J) l7 f7 h' @" W7 `3 Q% Nset $block_user_agents 0;* W' k& x2 S+ @) S* ?
$ _" S0 y- x5 |3 d" O4 F+ c# Don't disable wget if you need it to run cron jobs!
" @$ y8 D; R, v/ [: r c; _#if ($http_user_agent ~ "Wget") {
0 f+ h; `* `, H+ r# set $block_user_agents 1;
4 X1 x4 T. i0 e" _3 g0 g#}
0 u& V2 E$ j0 B+ @! U6 f, ]: x* d. U. t4 r. k
# Disable Akeeba Remote Control 2.5 and earlier+ G3 E2 A4 s+ W3 c$ K4 C
if ($http_user_agent ~ "Indy Library") {
5 e9 u8 P& j3 N0 sset $block_user_agents 1;- x6 W3 w" k8 r- k
}/ z( L8 S* w! w& V' h
6 J( ^1 F! O/ o( ~, X# Common bandwidth hoggers and hacking tools.
. ?: }) w$ l# I% dif ($http_user_agent ~ "libwww-perl") {1 w/ J* J0 `% A0 ]% P
set $block_user_agents 1;9 i% I/ Q3 W. F @5 Q
}/ k3 a, j4 l' U& N3 m+ l* q
if ($http_user_agent ~ "GetRight") {
1 t+ Q; h' @- N: |3 ~7 jset $block_user_agents 1;% Z1 R3 p# h/ h8 E
}
6 ~ C% ]4 s) B% \if ($http_user_agent ~ "GetWeb!") {
3 I; {/ `: N' H% S4 f* k% O; sset $block_user_agents 1;
& N$ b2 k. e" a$ j. R2 p}
6 D- G8 F" A) mif ($http_user_agent ~ "Go!Zilla") {
O$ s' ]2 _. s% f/ `set $block_user_agents 1;
: S% a6 j- J2 r% a}
, S/ ]" h' ?% y7 k, cif ($http_user_agent ~ "Download Demon") {
$ ~* `0 _7 n: ]( }/ p vset $block_user_agents 1;
! P5 O0 L) E( K: m& s4 I4 m}- h c0 G$ T) P( k/ u3 }
if ($http_user_agent ~ "Go-Ahead-Got-It") {% A# D! Y) R9 M- n' z
set $block_user_agents 1;% [ L! T* L* M' F: h9 x
}
; ]9 w( r9 C& l% S5 O/ yif ($http_user_agent ~ "TurnitinBot") {7 T9 \* u7 i7 u$ I: d/ x
set $block_user_agents 1;
* G2 L+ g: L6 ~}: q( v6 g+ N x* {% r
if ($http_user_agent ~ "GrabNet") {5 f' v I* I+ C% k: W$ ?" y g: v
set $block_user_agents 1;
0 K/ a$ ?8 i/ n9 I. n, b}
9 k1 K5 F2 D' C- _- q& [5 q/ T7 k2 x' k" Z
if ($block_user_agents = 1) {
* j; s; J% i) \return 403;. W' ]1 A4 a* X! a% ^+ x/ [; O
}
, V" D, c% x# l/ f4 k: }5 A[...]5 I5 O0 N# {7 t
}
& }0 |+ S6 _1 `: a' R k5 u我们来看上面这个配置文件,它将所有包含类似union.*select.*(URL 参数的请求都拒绝,这样来阻止SQL 注入攻击。又拒绝所有URL 参数后面有=http://的请求,来防止文件包含漏洞。类似地,屏蔽掉所有有|>的请求,阻止跨站脚本。还有就是屏蔽一些预定义的User Agent,拒绝恶意抓站。
- X; s M* ?2 W回过头来看,这个配置其实不太符合我国国情,比如屏蔽的那些浏览器UA,并不是扫描中文网站常见的UA。另外,要在配置文件中定义和枚举所有的恶意行为,是很困难的一件事情。
9 N, t* ~- A! [; H* o9 B6 Z四、使用脚本定时检测日志7 D! Y8 b- n9 b- Z& c. O9 _
所有对服务器的访问请求都会在Nginx 日志中记录,这其中也包括那些造成出错的请求。我们能否通过分析日志来确认恶意的访问请求呢?受第二种方法的启发,我们可以监控服务器日志,将请求出错的访问者IP 放入一个List 特别观察,在一段时间内如果没有太多的出错,我们就将其从列表中移除,否则,错误太多达到警戒值就调用iptable 将其禁封。
, Y4 C; n4 `8 l; L1 j3 n( ^5 z' W通过一个脚本就可以完成这项工作。这个脚本与方法二的区别在于,方法二仅仅记录并发连接,只要并发过高,就可以触发禁封,这不够科学。这里我们通过对日志的监控,禁封的只会是给我们造成出错和麻烦的IP(例如频繁的404 错误,明显是扫描),搜索引擎过来就不会有问题了。
4 @4 k2 |7 m8 I脚本代码& E8 H& n$ w! T* K+ H* y5 p
#!/usr/bin/perl8 B6 M3 ?6 o4 B
- g$ {7 X, E6 T6 e6 ]7 z. R. @
use strict;7 g) N* j" G: A+ T
use warnings;
- u* q1 t4 ` \1 h' u @4 c6 ~* x& M6 ]8 }, c3 g$ v& Z+ Y
## 本脚本将会监控Web 服务器的log 记录,(例如Apache 或者Nginx)) K6 U9 {/ u: @' c0 @
## 并统计同一个IP 所引发的HTTP 错误数目。该数值达到用户配置的数量,
. k/ P5 r5 M6 h+ L8 d8 x## 则使用防火墙对该IP 进行屏蔽,拒绝其访问。
! g9 M% m4 ?. J( Y! X3 o" [. u, m
5 m. f) j( O; m1 K6 B## log 文件路径+ h' z0 D4 `* M7 q M) m, s# q
my $log = "/var/log/nginx/access.log";
! U5 f7 m0 p V3 u) ?% p2 Y2 N6 [$ F* i$ t' z( _
## 一个IP 触发了多少次错误,我们就将其屏蔽?3 Q% Y |# @1 ]. J* f) j
my $errors_block = 10;
y4 J4 [5 D5 A1 a; v3 a" I# ^+ J& i
5 s7 j+ {& ]0 a## 过期时间,超过多少秒没有再见到该IP 则将其从观察列表中移除?, n9 N/ g+ x0 J6 d* M
my $expire_time = 7200;
. T, C7 o1 k% k1 Z
3 v- `* p: n: A" H6 n$ v## 将IP 从观察列表中移除时,清理多少个错误日志行数?& |) j7 s. x$ o& V2 o+ E! U
my $cleanup_time = 10;
0 Q+ Z& a. y& d8 p8 ^4 z5 M0 t, R
- a2 n. x( x; t5 Z. E; P## 调试模式on=1 off=07 e/ @; }, u3 k, C
my $debug_mode = 1;- `1 |& H* s( }4 k& f2 f8 ~
2 x) X5 f7 Y5 F$ U
## 声明一些内部变量
b( }' r) G5 e1 `0 i$ ~- |! Umy ( $ip, $errors, $time, $newtime, $newerrors );% X$ p0 m2 \0 e# w5 L. k1 q5 J1 ^/ g
my $trigger_count=1;3 u. v- Q$ c& P' e
my �usive_ips = ();- I4 x0 m/ d2 D8 U
& ?) s1 L0 ~5 x, o; V## 打开日志文件。使用系统的tail 命令,有效轮询
5 M; o, X6 ~2 Nopen(LOG,"tail --follow=$log |") || die "Failed!n"; ## For Linux (Ubuntu) systems
6 I! N/ l& Z" I/ V$ J# open(LOG,"tail -f $log |") || die "Failed!n"; ## For OpenBSD, FreeBSD or Linux systems
7 n0 n- \. z1 ~0 x- z9 \; x& |; s# E/ T, s6 s( n q6 G$ C0 F
while() {
% v& R7 u. p2 {" |& a## 定义错误代码。这里使用了正则表达式匹配,你可以自行添加一些,* l" W; U3 g$ n, L& l' v4 f
## 例如无端访问.vbs 后缀文件请求,列入屏蔽条件
! @! F' v7 W$ y& f% xif ($_ =~ m/( 401 | 402 | 403 | 404 | 405 | 406 | 407 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 444 | 500 | 501 | 502 | 503 | 504 | 505 )/)1 d1 u `- q/ N
{+ @& W! L. h _5 Q- k
3 s& i- a& y0 J## 自定义: 白名单IP。 不论这些IP 做了什么,均不屏蔽。
! |7 Y- D7 J8 f0 V4 x0 P## Google 爬虫IP 段66.249/16 就是一个好例子。+ Y3 v2 [1 e" c, z% B3 ~
## 为了方便程序员开发测试,内部子网192.168/16 也不屏蔽。
% C( d W5 f* L3 [8 ~if ($_ !~ m/(^66.249.d{1-3}.d{1-3}|^192.168.d{1-3}.d{1-3})/)0 n) }" l. }4 k& ~
{" i1 u2 Q) J C% }' ~
1 R+ a( P9 {6 l6 Y/ u% \! V" ~) q; H
## 从日志行中解析出IP
w0 n& I, A; e$time = time();+ S6 D! B+ ]+ m9 C9 k" B
$ip = (split ' ')[0]; y7 ^! r+ u A& x) V+ `7 P. t
' S5 t! U# A% A5 \## 若IP 之前从未出现过,我们需要初始化,以避免出现警告消息0 g( A; M3 P. h9 [# \5 J# L6 x
$abusive_ips{ $ip }{ 'errors' } = 0 if not defined $abusive_ips{ $ip }{ 'errors' };0 J# W3 b: j% w( R
3 B2 j) u/ C# X$ N# F) p3 C7 ?
## 给这个IP 增加出错计数,更新时间戳
6 y9 C2 h$ c& `0 O* D% a/ ?& R5 j$abusive_ips{ $ip }{ 'errors' } = $abusive_ips{ $ip }->{ 'errors' } + 1;
& v0 y+ M1 K! I4 Q$abusive_ips{ $ip }{ 'time' } = $time;1 q& ^ g7 P4 j/ G6 H
) P/ y5 j7 Q0 x4 Y- u! N
## DEBUG: 输出详细信息
: _' c; K Q8 V' |if ( $debug_mode == 1 ) {
& I( Z7 z6 Z" K' g) i6 x$newerrors = $abusive_ips{ $ip }->{ 'errors' };: P* i* j% S( y' f% O
$newtime = $abusive_ips{ $ip }->{ 'time' };
! T$ f q# `+ \& D9 H6 d' q1 \print "unix_time: $newtime, errors: $newerrors, ip: $ip, cleanup_time: $trigger_countn";# X2 h' e3 O" q' g; {( @
}
* F! d" v6 r3 ~& M2 m& [9 w2 b# Y
I8 J9 o* M- I5 Y9 g5 |1 {- n## 如果该IP 已经触发$errors_block 出错数量,调用system() 函数屏蔽之
3 n6 S- m# `! y k2 N- ]* s6 Zif ($abusive_ips{ $ip }->{ 'errors' } >= $errors_block ) {7 h# ?& V3 F/ y! `% v$ V6 H$ N
4 X2 X* e% Q3 F' O## DEBUG: 输出详细信息, q# G' N6 L/ p
if ( $debug_mode == 1 ) {
, e: r& z8 E; U/ z/ `2 Tprint "ABUSIVE IP! unix_time: $newtime, errors: $newerrors, ip: $ip, cleanup_time: $trigger_countn";. V3 p5 a2 l* N0 q) a7 L- E6 g
}
~1 @6 I6 C* ~, h+ y0 L2 S6 S; G$ P0 \
## 自定义: 这里是屏蔽IP 的system() 系统调用
( }# [) Q( Q0 ]& Z# |## 你可以对这个IP 添加更多的执行操作。例如,我们使用logger 打印记录到/var/log/messages* y- X/ t% i0 n2 I% `/ p
## 注释掉的是OpenBSD 系统Pf 防火墙/ P# B. p0 k; Y
system("logger '$ip blocked by calomel abuse detection'; iptables -I INPUT -s $ip -j DROP");( C$ e; n* t) O" H
# system("logger '$ip blocked by calomel abuse detection'; pfctl -t BLOCKTEMP -T add $ip");
8 ]& Z- O* F$ t; l
" u: o Q, S4 C+ \) ]+ B* d## 当IP 已经被屏蔽,它就没必要继续留在观察列表中了/ E3 o- a: p7 h) s& M( s4 ]0 j- [
delete($abusive_ips{ $ip });/ G' T' W5 d& A9 W" }8 T
}
5 ^5 c8 z8 p& A A6 A o6 E
8 E: \. X5 U4 R## 为后面的清理函数增加触发计数
; w8 A! r9 q: M7 X$trigger_count++;
3 y, b! l& }9 s+ d- f& n; v
6 @9 D0 q4 I5 z0 c## 清理函数:当触发计数达到$cleanup_time 我们将所有已经过期的条目从$abusive_ips 列表中删除- K |# l1 `/ S! _
if ($trigger_count >= $cleanup_time) {( i: ^5 a) w* l0 X6 b y- \4 \
my $time_current = time();6 E! @# b7 g6 E$ T8 y5 S& w) w
8 R, \) u: E( b; q, }4 h) ?
## DEBUG: 输出详细信息% D3 l- y6 u. P1 W& b+ p3 w0 e
if ( $debug_mode == 1 ) {
- Q6 e0 W1 v) p' L+ Zprint " Clean up... pre-size of hash: " . keys( �usive_ips ) . ".n"; N. [; y/ b5 s- ^4 L" Y
}
1 L& R* ~* l8 y/ q. J+ m& ^- q& h' M" p: e: m9 W+ k
## 清理我们已经很久没再见到的IP
; T/ u1 I8 x* B& swhile (($ip, $time) = each(�usive_ips)){( ?3 W( r$ j; T5 h
) U$ }, A0 M6 r7 [- ^& \
## DEBUG: 输出详细信息
0 B! G- Q# N9 h7 R; \; c2 ?7 f. lif ( $debug_mode == 1 ) {. r4 @6 C' [# a. q7 D5 R( e r
my $total_time = $time_current - $abusive_ips{ $ip }->{ 'time' };
! L, {/ U. S2 M2 N) k/ `0 K, e, hprint " ip: $ip, seconds_last_seen: $total_time, errors: $newerrorsn";
" p' Z0 U( w4 `. b8 T}
7 F- R% H m, ~& ]) {5 z$ u/ U/ ~5 R) p3 ^* _/ ]4 I
## 如果IP 未出现的时间已经超过我们设定的过期时间,则将其从列表中移除) ~' q2 \/ z; ?, Z' O6 i5 i
if ( ($time_current - $abusive_ips{ $ip }->{ 'time' } ) >= $expire_time) {
" g+ ~4 C. n" u* w+ R! m2 p1 @delete($abusive_ips{ $ip });
% @) U; Q# a* j+ _7 S7 {5 W}" b; H: Q( D" n" X( [5 B
}
2 [+ `3 v- ^& c7 ~& ^' d" {. q" l( C: D) o# a- j
## DEBUG: 输出详细信息, A% e+ i- I5 R- u
if ( $debug_mode == 1 ) {
) v: C' F2 G3 Z+ p" i# }$ [+ b$ Lprint " Clean up.... post-size of hash: " . keys( �usive_ips ) . ".n";3 f0 m, E6 Y9 L( ~. ]; l, j
}
4 X8 S* Z+ o- E8 R4 H* a- X+ w1 E+ l
## 重置清理触发计数. h- l9 ]0 s) y a# W( \! r! q
$trigger_count = 1;9 J$ n+ ]( S; G o u
}
0 a2 {+ U( D2 `4 J' ~}
9 r( j9 D9 K/ Z Y- R2 l}
9 `: G6 y' b% Q0 K% s7 f}+ W x, F( a, X
#### EOF ####
8 U) |- |4 U0 o: M9 b' _0 f保存以上内容到web_server_abuse_detection.pl,增加可执行权限
5 u) ]* R* H% Echmod +x web_server_abuse_detection.pl$ N5 g$ V4 S, o' h- u0 W
变量解释:6 V8 m, D# i- P( }$ E
my $log 是要监控的日志路径。日志文件格式是标准的Apache “common” 或 “combined”。这个脚本可以处理Apache, Nginx, Lighttpd 甚至thttpd 的日志,它将会在日志中寻找第一个字符串,即远程连接过来的IP 地址。例如,这个是Google 爬虫访问我网站的一条日志 “66.249.72.6 www.lovelucy.info – …”' A; S& {5 O" I! i% y
my $errors_block 是一个客户端所能触发的最大错误数量,超过此值IP 将被屏蔽。Web 服务器的错误代码400-417(如果你用Nginx 则还包括444),以及500-505 都是触发条件。我们默认设置errors_block 为10,即如果一个IP 在7200 秒($expire_time) 以内触发了10 个错误($errors_block),它将被屏蔽。
2 M5 i7 D; p K# gmy $expire_time 是一个IP 从观察列表中移除的过期时间。默认我们设7200 秒(2 小时)。请注意,一个IP 必须在7200 秒以内没有触发任何错误,我们才会将它从列表中移除。这意味着一个恶意用户用很慢的速率扫描,我们仍可能会将其屏蔽。例如,一个IP 每一小时访问一次来检测漏洞,第一个小时它就被列入观察列表,在第二个小时出错计数被增加,同时“最后一次见到这个IP” 的时间戳也被更新。普通的入侵检测系统(IDS) 可能会漏报这样的行为,但这个脚本会在一个较长的时间(2小时)持续跟踪监测一个IP。在这个IP 达到触发10 次错误时,也就是10小时后,我们仍会将其屏蔽。
; s- `; Y8 O# I/ E& W& [% c Hmy $cleanup_time 是触发清理观察列表的出错行数量。清理工作是一个循环,很耗费CPU 所以没必要每一个错误日志行都去执行。请保证你的清理计数值足够低,从而让旧IP 能以合适的速率从列表中移除。但是太低又会耗费CPU,一个恰当的值应该是你的服务器5分钟内所产生的错误日志行数。
9 B: u& K2 v# kmy $debug_mode 调试模式,会打印出一些有用的信息。4 ]9 K$ ~8 e' Y
自定义白名单:注释中已经说过,Google 机器人IP段66.249/16 就应该放到白名单里,因为任何别人的网站链接到我们的一个错误的URL,都会导致搜索引擎抓取失败。开发人员的IP 也应该放入白名单,因为程序测试也会经常产生失败错误。
# v8 ]2 x1 o8 o2 u/ z* k自定义系统调用:检测到恶意IP 后,我们通过系统调用屏蔽之。默认的调用包括logger 打印消息到/var/log/messages,并执行iptables 屏蔽命令。我们也可以添加更多操作,例如触发一个Nagios 监控警告,给运维人员发Email,等等。) \' W8 I5 ?( x& P9 z6 m. I% ?
运行:
: ]0 O& Q, V0 U4 h8 Z y设置my $debug_mode = 0;脚本即会静默运行。要让它在后台运行,不占用终端,则在命令后加一个& 符号
$ i! B8 G. E1 D3 }/ F: c) a./web_server_abuse_detection.pl &8 X9 k# H7 S! }% {
总结
: K# I5 D( e% g. |" O月光博客写过一篇《防止CC攻击的方法》,他说现在CC 攻击的技术含量低,利用工具和一些IP 代理,搞个几百个肉鸡,一个初、中级的电脑水平的用户就能够实施攻击。门槛还真低啊。
- W$ B+ B% p; e( x. \( t做人还是要低调一点。/ V. s' o/ d8 u: ]
参考链接: i- R& a- J+ l$ S2 m2 T
Web Server Abuse Detection 2 H6 Z1 T# x$ i% a7 E
iptables 限制同一 IP 连接数
. e. w, A( Z: {7 f- X" WNginx: How To Block Exploits, SQL Injections, File Injections, Spam, User Agents, Etc |