使用HAProxy防范简单的DDos攻击
第一部分: 系统级防护1.TCP syn flood 攻击
syn flood攻击是通过发送大量SYN包到一台服务器,使其饱和或者至少造成其上行带宽饱和。
如果攻击规模很大,已经撑满了你的所有Internet带宽,那么唯一的方法就是请求你的ISP给与协助。
我们本地的HAProxy上可以做一点简单防护,聊胜于无。
修改/etc/sysctl.conf,加入如下内容:
# Protection SYN flood
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.tcp_max_syn_backlog = 1024
第二部分: HAProxy的防护功能
HAProxy 1.5版本中多了一些有趣的功能,我们可以拿来防范一些小规模的攻击行为。
主要是stick-table表中可以存储额外的附加信息了。
形式如下:
stick-table type {ip | integer | string | binary } size *
每个主机只能有一个stick-table,表中能存储的额外信息类型如下:
- server_id : 用户请求被分配到的服务器的ID。形式为整数。
这个值可以被"stick match", "stick store","stick on"规则使用。
It is automatically enabled when referenced.
- gpc0 : 第一个通用计数器,形式为一个正32位整数。
可以用于任何东西。通常会用于给一些特定的entry打tag。
- conn_cnt : Connection计数器,形式为一个正32位整数。
记录了匹配当前entry的,从一个客户端处接收到的连接的绝对数量。
这个数量并不意味着被accepted的连接数量,单纯就是收到的数量。
- conn_cur : 当前连接数,形式为一个正32位整数。
当一新连接匹配指定的entry的时候,这个数值增加。
当连接结束的时候,这个数值减少。
通过这个值可以了解一个entry上任意时间点的准确的连接数。
- conn_rate() : connection的连接频率 (takes 12 bytes).
这个值统计指定时间范围内(毫秒为单位)进来的connection的频率。
这个数值可以通过ACL匹配一些规则。
- sess_cnt : Session计数器,形式为一个正32位整数。
记录了匹配当前entry的,从一个客户端处接收到的session的绝对数量。
一个session指的是一个已经被layer 4规则接受的connection。
- sess_rate() : session的连接频率 (takes 12 bytes).
这个值统计指定时间范围内(毫秒为单位)进来的session的频率。
这个数值可以通过ACL匹配一些规则。
- http_req_cnt : HTTP请求计数器,形式为一个正32位整数。
记录了匹配当前entry的,从一个客户端接受到的HTTP请求的绝对数量。
无论这个请求是合法还是非法。
Note that this is different from sessions when keep-alive is used on the client side.
- http_req_rate() : HTTP的请求频率 (takes 12 bytes).
这个值统计指定时间范围内(毫秒为单位)进来的HTTP请求的频率。
无论这个请求是合法还是非法。
Note that this is different from sessions when keep-alive is used on the client side.
- http_err_cnt : HTTP错误计数器,形式为一个正32位整数。
记录了匹配这个entry的HTTP错误的绝对数量,包含:
无效的、被截断的请求
被拒绝的或封堵的请求
认证失败
4xx错误
- http_err_rate() : HTTP的请求错误频率 (takes 12 bytes).
这个值统计指定时间范围内(毫秒为单位)匹配的entry产生的HTTP错误的频率。
- bytes_in_cnt : 一个匹配entry的客户端发往服务器的字节数,形式为一个正64位整数。
Headers也包含在统计中,通常用于图片或者video服务器限制上传文件。
- bytes_in_rate() : 收到字节频率计数器(takes 12 bytes).
这个值统计指定时间范围内(毫秒为单位)收到的字节数的频率。
通常用于防止用户上传太快上传太多内容。
Warning: with large uploads, it is possible that the amount of uploaded data will be counted
once upon termination, thus causing spikes in the average transfer speed
instead of having a smooth one. This may partially be smoothed with
"option contstats" though this is not perfect yet. Use of byte_in_cnt is
recommended for better fairness.
- bytes_out_cnt : 服务器发往客户端的字节数,形式为一个正64位整数。
Headers也包含在统计中,通常用于防止机器人爬站。
- bytes_out_rate() : 发送字节频率计数器(takes 12 bytes).
这个值统计指定时间范围内(毫秒为单位)服务器发送给客户端的字节数的频率。
通常用于防止用户下载太快太多内容。
Warning: with large transfers, it is possible that the amount of transferred data will be
counted once upon termination, thus causing spikes in the average
transfer speed instead of having a smooth one. This may partiallybe
smoothed with "option contstats" though this is not perfect yet.Use of
byte_out_cnt is recommended for better fairness.
2.慢查询攻击
Slowloris类型的攻击,客户端用非常非常慢的速度发送请求到服务器上。
通常是一个包头接一个包头或者更夸张的一个字符接一个字符,而每个包之间等待非常长的时间。
这样服务器端就不得不等待所有请求全部接收完毕才能返回响应。
这个攻击的目的是阻止正常用户访问我们提供的服务,所有的服务器资源都被用来等待处理慢查询了。
应对这种攻击,方法是在HAProxy中加入选项: "timeout http-request"
可以将这个值设置成5秒钟,应该已经足够长了。
这个参数告诉HAProxy最多等待5秒钟让客户端发送完整的HTTP请求,如果超过5秒,则HAProxy会切断连接并返回错误。
01 # On Aloha, the global section is already setup for you
02 # and the haproxy stats socket is available at /var/run/haproxy.stats
03 global
04 stats socket ./haproxy.stats level admin
05
06 defaults
07 option http-server-close
08 mode http
09 timeout http-request 5s
10 timeout connect 5s
11 timeout server 10s
12 timeout client 30s
13
14 listen stats
15 bind 0.0.0.0:8880
16 stats enable
17 stats hide-version
18 stats uri /
19 stats realm HAProxy\ Statistics
20 stats auth admin:admin
21
22 frontend ft_web
23 bind 0.0.0.0:8080
24
25 # Spalreadylit static and dynamic traffic since these requests have different impacts on the servers
26 use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
27
28 default_backend bk_web
29
30 # Dynamic part of the application
31 backend bk_web
32 balance roundrobin
33 cookie MYSRV insert indirect nocache
34 server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
35 server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
36
37 # Static objects
38 backend bk_web_static
39 balance roundrobin
40 server srv1 192.168.1.2:80 check maxconn 1000
41 server srv2 192.168.1.3:80 check maxconn 1000
为了测试这个选项的效果,可以使用telnet连接到HAProxy的端口,然后等待5秒钟,输出类似下边:
telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
HTTP/1.0 408 Request Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html
408 Request Time-out
Your browser didn't send a complete request in time.
Connection closed by foreign host.
3.Unfair users, AKA abusers
何谓一个"Unfair"用户?就是指这个用户(或者脚本)有不同于正常用户的行为:
打开太多连接
建立新连接的频率太快
http请求的频率太快
使用太多的带宽
客户端不遵守RFC协议(IE for SMTP)
正常的浏览器行为?
在保护我们站点不受怪异的行为伤害之前,我们需要了解正常的浏览器行为是什么样子的。
首先,用户会打开Chrome, Firefox, Internet Explorer, Opera之间的一种,然后输入URL。
浏览器首先去请求DNS解析IP地址,然后它会建立一个和服务器的连接,下载首页,分析其中的内容。
然后根据页面的HTML代码的连接去下载不同的对象:javascript, css, 图片等等。
为了下载对象,对于每个域名,浏览器会打开6或7个TCP连接。
等到所有对象下载完毕,浏览器会将这些对象叠加渲染出整个页面。
4.限制每个用户的连接数
根据之前的说明,每个用户浏览器会与webserver之间非常快的打开6或7个TCP连接,那么我们可以认为如果
一个用户打开了超过10个TCP连接就是不正常的行为。
以下示例作了每用户连接数的限制,重点在于25-32行。
01 # On Aloha, the global section is already setup for you
02 # and the haproxy stats socket is available at /var/run/haproxy.stats
03 global
04 stats socket ./haproxy.stats level admin
05
06 defaults
07 option http-server-close
08 mode http
09 timeout http-request 5s
10 timeout connect 5s
11 timeout server 10s
12 timeout client 30s
13
14 listen stats
15 bind 0.0.0.0:8880
16 stats enable
17 stats hide-version
18 stats uri /
19 stats realm HAProxy\ Statistics
20 stats auth admin:admin
21
22 frontend ft_web
23 bind 0.0.0.0:8080
24
25 # Table definition
26 stick-table type ip size 100k expire 30s store conn_cur
27
28 # Allow clean known IPs to bypass the filter
29 tcp-request connection accept if { src -f /etc/haproxy/whitelist.lst }
30 # Shut the new connection as long as the client has already 10 opened
31 tcp-request connection reject if { src_conn_cur ge 10 }
32 tcp-request connection track-sc1 src
33
34 # Split static and dynamic traffic since these requests have different impacts on the servers
35 use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
36
37 default_backend bk_web
38
39 # Dynamic part of the application
40 backend bk_web
41 balance roundrobin
42 cookie MYSRV insert indirect nocache
43 server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
44 server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
45
46 # Static objects
47 backend bk_web_static
48 balance roundrobin
49 server srv1 192.168.1.2:80 check maxconn 1000
50 server srv2 192.168.1.3:80 check maxconn 1000
注意:
1.如果HAProxy用于代理多个域名,则需要增加conn_cur的数量,因为每个域名会有5-7个连接。
2.如果多个用户在同一NAT设备后边,则这个限制会对他们产生不利影响,原因显而易见。
为了测试限制的效果,我们做如下测试:
使用Apache bench打开10个连接:
ab -n 50000000 -c 10 http://127.0.0.1:8080/
观察haproxy的stats输出:
echo "show table ft_web" | socat unix:./haproxy.stats -
# table: ft_web, type: ip, size:102400, used:1
0x7afa34: key=127.0.0.1 use=10 exp=29994 conn_cur=10
我们打开telnet尝试建立新连接:
telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Connection closed by foreign host.
发现已经被HAProxy拒绝了。
5.限制每用户建立连接的频率
我们可以认为3秒钟之内建立超过20个连接的用户就是非正常访问。
以下示例作了每用户建立连接的频率,重点在于25-32行。
01 # On Aloha, the global section is already setup for you
02 # and the haproxy stats socket is available at /var/run/haproxy.stats
03 global
04 stats socket ./haproxy.stats level admin
05
06 defaults
07 option http-server-close
08 mode http
09 timeout http-request 5s
10 timeout connect 5s
11 timeout server 10s
12 timeout client 30s
13
14 listen stats
15 bind 0.0.0.0:8880
16 stats enable
17 stats hide-version
18 stats uri /
19 stats realm HAProxy\ Statistics
20 stats auth admin:admin
21
22 frontend ft_web
23 bind 0.0.0.0:8080
24
25 # Table definition
26 stick-table type ip size 100k expire 30s store conn_rate(3s)
27
28 # Allow clean known IPs to bypass the filter
29 tcp-request connection accept if { src -f /etc/haproxy/whitelist.lst }
30 # Shut the new connection as long as the client has already 10 opened
31 tcp-request connection reject if { src_conn_rate ge 10 }
32 tcp-request connection track-sc1 src
33
34 # Split static and dynamic traffic since these requests have different impacts on the servers
35 use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
36
37 default_backend bk_web
38
39 # Dynamic part of the application
40 backend bk_web
41 balance roundrobin
42 cookie MYSRV insert indirect nocache
43 server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
44 server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
45
46 # Static objects
47 backend bk_web_static
48 balance roundrobin
49 server srv1 192.168.1.2:80 check maxconn 1000
50 server srv2 192.168.1.3:80 check maxconn 1000
注意:如果多个用户在同一NAT设备后边,则这个限制也会对他们产生不利影响,原因同样显而易见。
为了测试限制的效果,我们做如下测试:
ab -n 10 -c 1 -r http://127.0.0.1:8080/
echo "show table ft_web" | socat unix:./haproxy.stats -
# table: ft_web, type: ip, size:102400, used:1
0x11faa3c: key=127.0.0.1 use=0 exp=28395 conn_rate(3000)=10
telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Connection closed by foreign host.
6.限制HTTP请求的频率
01 # On Aloha, the global section is already setup for you
02 # and the haproxy stats socket is available at /var/run/haproxy.stats
03 global
04 stats socket ./haproxy.stats level admin
05
06 defaults
07 option http-server-close
08 mode http
09 timeout http-request 5s
10 timeout connect 5s
11 timeout server 10s
12 timeout client 30s
13
14 listen stats
15 bind 0.0.0.0:8880
16 stats enable
17 stats hide-version
18 stats uri /
19 stats realm HAProxy\ Statistics
20 stats auth admin:admin
21
22 frontend ft_web
23 bind 0.0.0.0:8080
24
25 # Use General Purpose Couter (gpc) 0 in SC1 as a global abuse counter
26 # Monitors the number of request sent by an IP over a period of 10 seconds
27 stick-table type ip size 1m expire 10s store gpc0,http_req_rate(10s)
28 tcp-request connection track-sc1 src
29 tcp-request connection reject if { src_get_gpc0 gt 0 }
30
31 # Split static and dynamic traffic since these requests have different impacts on the servers
32 use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
33
34 default_backend bk_web
35
36 # Dynamic part of the application
37 backend bk_web
38 balance roundrobin
39 cookie MYSRV insert indirect nocache
40
41 # If the source IP sent 10 or more http request over the defined period,
42 # flag the IP as abuser on the frontend
43 acl abuse src_http_req_rate(ft_web) ge 10
44 acl flag_abuser src_inc_gpc0(ft_web)
45 tcp-request content reject if abuse flag_abuser
46
47 server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
48 server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
49
50 # Static objects
51 backend bk_web_static
52 balance roundrobin
53 server srv1 192.168.1.2:80 check maxconn 1000
54 server srv2 192.168.1.3:80 check maxconn 1000
测试方法同上边。
7.检测漏洞扫描
如果有人尝试对我们的站点进行漏洞扫描,那么通过HAProxy可以追踪到不同的错误。
HAProxy可以监控每个用户产生错误的频率,并且根据这个频率决定进一步的操作。
01 # On Aloha, the global section is already setup for you
02 # and the haproxy stats socket is available at /var/run/haproxy.stats
03 global
04 stats socket ./haproxy.stats level admin
05
06 defaults
07 option http-server-close
08 mode http
09 timeout http-request 5s
10 timeout connect 5s
11 timeout server 10s
12 timeout client 30s
13
14 listen stats
15 bind 0.0.0.0:8880
16 stats enable
17 stats hide-version
18 stats uri /
19 stats realm HAProxy\ Statistics
20 stats auth admin:admin
21
22 frontend ft_web
23 bind 0.0.0.0:8080
24
25 # Use General Purpose Couter 0 in SC1 as a global abuse counter
26 # Monitors the number of errors generated by an IP over a period of 10 seconds
27 stick-table type ip size 1m expire 10s store gpc0,http_err_rate(10s)
28 tcp-request connection track-sc1 src
29 tcp-request connection reject if { src_get_gpc0 gt 0 }
30
31 # Split static and dynamic traffic since these requests have different impacts on the servers
32 use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
33
34 default_backend bk_web
35
36 # Dynamic part of the application
37 backend bk_web
38 balance roundrobin
39 cookie MYSRV insert indirect nocache
40
41 # If the source IP generated 10 or more http request over the defined period,
42 # flag the IP as abuser on the frontend
43 acl abuse src_http_err_rate(ft_web) ge 10
44 acl flag_abuser src_inc_gpc0(ft_web)
45 tcp-request content reject if abuse flag_abuser
46
47 server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
48 server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
49
50 # Static objects
51 backend bk_web_static
52 balance roundrobin
53 server srv1 192.168.1.2:80 check maxconn 1000
54 server srv2 192.168.1.3:80 check maxconn 1000
我们通过如下方法测试:
ab -n 10 http://127.0.0.1:8080/dlskfjlkdsjlkfdsj
echo "show table ft_web" | socat unix:./haproxy.stats -
# table: ft_web, type: ip, size:1048576, used:1
0x8a9770: key=127.0.0.1 use=0 exp=5866 gpc0=1 http_err_rate(10000)=11.
再次执行上边的ab命令,会得到如下错误:
apr_socket_recv: Connection reset by peer (104)
说明HAProxy已经block这个IP。
------------------------
PS: 关于gpc0这个计数器的用法,我说说我的理解,这个地方还没搞太透,有点疑惑,如有达人明白,欢迎赐教解惑。
在haproxy 1.5的官方文档中,有如下一段示例:
frontend http
# Use General Purpose Couter 0 in SC1 as a global abuse counter
# protecting all our sites
stick-table type ip size 1m expire 5m store gpc0
tcp-request connection track-sc1 src
tcp-request connection reject if { sc1_get_gpc0 gt 0 }
...
use_backend http_dynamic if { path_end .php }
backend http_dynamic
# if a source makes too fast requests to this dynamic site (tracked
# by SC2), block it globally in the frontend.
stick-table type ip size 1m expire 5m store http_req_rate(10s)
acl click_too_fast sc2_http_req_rate gt 10
acl mark_as_abuser sc1_inc_gpc0
tcp-request content track-sc2 src
tcp-request content reject if click_too_fast mark_as_abuser
关于上边这段,我是这么理解的:
1.backend部分
定义一个表,类型IP地址,大小1M,过期时间5分钟,保存10秒之内的http请求速率。
通过sc2_http_req_rate取出当前速率,如果大于10,则acl click_too_fast生效。
因为acl click_too_fast已经检验生效,则判定该用户为abuser,给gpc0计数器加1。
拒绝符合acl click_too_fast和符合aclmark_as_abuser 的用户。
2.frontend部分
定义一个表,类型IP地址,大小1M,过期时间5分钟,保存通用计数器gpc0
跟踪所有connection的源地址。(此处作用比较疑惑,求赐教。)
在layer 4拒绝被backend标记为abuser的IP(acl mark_as_abuser sc1_inc_gpc0)
另有一段示例如下:
# block if 5 consecutive requests continue to come faster than 10 sess
# per second, and reset the counter as soon as the traffic slows down.
acl abuse src_http_req_rate gt 10
acl killsrc_inc_gpc0 gt 5
acl savesrc_clr_gpc0
tcp-request connection accept if !abuse save
tcp-requestconnection reject if abuse kill
页:
[1]