2018-08-30:网站攻击优化

近段时间网站受到攻击,访问网站经常返回502,也是没得闲下来,跟他们斗智斗勇慢慢知道了很多防刷防攻击的配置。
由于我网站架构是nginx+php+wordpress构成的,对于此优化仅仅限于linux、nginx和php配置的优化。下面给大家一一讲解。

分类命令:

1
2
3
awk '{print $1,$4}' /usr/local/nginx/logs/$1 |grep 2014:`date|awk '{print $4}'|awk -F ':' '{print $1}'`|awk '{print $1}'|sort |uniq -c |sort -rn|head -$2 > /usr/local/uniqIpAccessCnt/`date +%Y-%m-%d-%H`.txt
cat host.access.log|awk '{print $1}'|sort|uniq -c|sort -n

一、php的配置

1.1 配置请求的时间

首先设置下请求时间,不过看情况来设置,如果受到攻击应该配置短些。下面例子为配置300s,如果位置仅仅是小部分人使用或者是统计分析业务,可以设置长些到300s。其他的如果是面对公众的,还是设置短些好。

php-fpm的配置:

1
2
max_execution_time = 300
request_terminate_timeout = 300

1.2 启用opcache

启用opcache后,php部分源码会缓存到内存里面,加速读取,建议开启。

二、nginx配置

2.1 nginx查看并发连接数

这个操作难度稍大,要在编译nginx时加上--with-http_stub_status_module,然后配置Nginx.conf,在server点里面加入如下内容:

1
2
3
4
5
location /status {
stub_status on;
access_log /usr/local/nginx/logs/status.log;
auth_basic "NginxStatus";
}

然后可以通过浏览器访问 http://localhost/status 查看,如下图

各个参数的解读如下:

  • Active connections:当前 Nginx 正处理的活动连接数。
  • server accepts handledrequests:总共处理了8 个连接 , 成功创建 8 次握手,总共处理了500个请求。
  • Reading:nginx 读取到客户端的 Header 信息数。
  • Writing:nginx 返回给客户端的 Header 信息数。
  • Waiting:开启 keep-alive 的情况下,这个值等于 active - (reading + writing),意思就是 Nginx 已经处理完正在等候下一次请求指令的驻留连接

2.2 [核心]nginx防刷模块 limit

1
2
3
4
5
6
7
8
9
10
11
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=8r/s;
server {
limit_req zone=one burst=1 nodelay;
location /search/ { # 限制server请求
limit_req zone=one burst=5;
}
}
}
  • limit_req_zone size 配置说明:设置10M 就可以存储 16 万的 IP 统计信息,这可是非常吊了,可小站的话可以设置陈1m这样,即1s内只处理16k的请求,多的就返回503错误。
  • limit_req_zone rate配置说明:普通浏览器的同时并发数量,按照 Dropbox 技术博客里所谈到的,目前主流浏览器限制 AJAX 对同一个子域名的并发连接数是6个。IE 6,IE 7 是两个。

rate=10r/m 的意思是每个地址每分钟只能请求10次,也就是说根据令牌桶原理burst=1 一共有1块令牌,并且每分钟只新增10块令牌,
1块令牌发完后多出来的那些请求就会返回503

加上 nodelay之后超过 burst大小的请求就会直接返回503

如果没有该字段会造成大量的tcp连接请求等待。

参考资料

http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

2.3 nginx日志分析刷量IP

每小时记录一次独立IP的访问次数到文件里:

1
2
3
4
5
6
#!/bin/bash
if [ $# -ne 2 ];then
echo "Missing Operand..."
exit 1
fi
awk '{print $1,$4}' /usr/local/nginx/logs/$1 |grep 2014:`date|awk '{print $4}'|awk -F ':' '{print $1}'`|awk '{print $1}'|sort |uniq -c |sort -rn|head -$2 > /usr/local/uniqIpAccessCnt/`date +%Y-%m-%d-%H`.txt

再写python监控报警:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/python
# -*- coding: cp936 -*-
import os
import time
import datetime
import string
import email
import smtplib
import mimetypes
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
def SendMail():
server = 'Web-1.2.3.4'
mail_to = ["san.zhang@abc.com", "si.li@abc.com"]
mail_host = "mail.abc.com"
mail_user = "webmaster@abc.com"
mail_pass = "passwd"
mail_from = "webmaster@abc.com"
mail_postfix = "abc.com"
mail_subject = "Malicious Access - " + server +" !!!"
mail_body = mailbody
msg = MIMEMultipart()
body = MIMEText(mail_body,_subtype='plain',_charset='gb2312')
body = MIMEText(mail_body,_subtype='plain',_charset='utf-8')
msg.attach(body)
msg['Subject'] = mail_subject
msg['From'] = mail_from
msg['To'] = ";".join(mail_to)
try:
s = smtplib.SMTP()
s.connect(mail_host)
s.login(mail_user,mail_pass)
s.sendmail(mail_from, mail_to, msg.as_string())
s.close()
return True
except Exception, e:
print str(e)
return False
def handlefile():
path=r"/usr/local/uniqIpAccessCnt/"
files=[(os.path.getmtime(path+x),path+x) for x in os.listdir(path)]
files.sort()
fname = files[-1][1]
print fname
f = open(fname, 'r')
line = f.readline()
cnt = (line.split('.')[0]).split(' ')[-2]
print cnt
if int(cnt) > 10000:
global mailbody
mailbody = 'Access record as follows in the last hour:\n\n\n Count IP\n\n'
lines = f.readlines()
f.close()
mailbody = mailbody + line
for x in lines:
mailbody = mailbody + x
print mailbody
SendMail()
def main():
handlefile()
main()

2.4 切割nginx日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
#此脚本用于自动分割Nginx的日志,包括access.log和error.log
#每天00:00执行此脚本 将前一天的access.log重命名为access-xxxx-xx-xx.log格式,并重新打开日志文件
#Nginx日志文件所在目录
LOG_PATH=/opt/nginx/logs
#获取昨天的日期
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
#获取pid文件路径
PID=/var/run/nginx/nginx.pid
#分割日志
mv ${LOG_PATH}access.log ${LOG_PATH}access-${YESTERDAY}.log
mv ${LOG_PATH}error.log ${LOG_PATH}error-${YESTERDAY}.log
#向Nginx主进程发送USR1信号,重新打开日志文件
kill -USR1 `cat ${PID}`
chmod +x /opt/nginx/cut_nginx_log.sh
1
2
3
crontab -e
# 输入以下内容并保存
00 00 * * * /bin/bash /opt/nginx/sbin/cut_nginx_log.sh

三、linux配置

3.1 linux下使用命令统计服务器并发数

1
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

显示结果:

TIME_WAIT 17
ESTABLISHED 3254
LAST_ACK 236
FIN_WAIT_1 648
FIN_WAIT_2 581
CLOSING 7
CLOSE_WAIT 4916

解析:

  • CLOSED //无连接是活动的或正在进行
  • LISTEN //服务器在等待进入呼叫
  • SYN_RECV //一个连接请求已经到达,等待确认
  • SYN_SENT //应用已经开始,打开一个连接
  • ESTABLISHED //正常数据传输状态/当前并发连接数
  • FIN_WAIT1 //应用说它已经完成
  • FIN_WAIT2 //另一边已同意释放
  • ITMED_WAIT //等待所有分组死掉
  • CLOSING //两边同时尝试关闭
  • TIME_WAIT //另一边已初始化一个释放
  • LAST_ACK //等待所有分组死掉