网友提到,wordpress的某些php文件提交一些不太友好的请求,会让php崩掉。
用wp的应该都经常遇到了,我也是。。。
原因没什么好深究的,不止是wp,大概所有暴漏在外的web和非web服务应该都经常遇到这么个情况。
本文仅探讨web环境。
解决的思路无非就那么几种,要么苦修内功,自己把程序方面做扎实,对输入的内容进行严格过滤,这种被动挨揍的方式挺累,而且程序越大,越不可控。并且这方面还要反复协调php、nginx的接受参数范围,没多大意思的。
要么就是在网络层进行限制,比如拒绝所有国外IP,这种其实没多大用,因为扫漏洞的IP各地都有。
要么,就是像本文的思路这样,做一个主动点的防护网。
我自己的web环境是
Debian 11 / 12
nginx 1.22.x / 1.25.x
php 7.4 / 8.3
mariadb 版本忘了
首先,在Debian系统上,绝大多数服务程序是低权限运行的,而且数据库端口只有限定范围可以连接,所以这一块可以忽略,即便退一万步,数据库就算root密码丢出去也没多大事,毕竟我这库里没什么机密。
其他需要的软件,包括syslog-ng、iptables、ipset。
系统的处理流程有点复杂,做起来倒是很简单。
首先,我这里web的日志分为两部分,一部分发往后端的中央服务器进行统计、分析(这一块跟本文无关),另一部分发往本机,进行即时分析处理。
因为整套架构经过多年多次更新,有些与本文有关的关联设置我可能会忘掉,如果遗漏了什么就留言说一下我再补上。
nginx里面http部分增加的:
map $http_x_forwarded_for $clientRealIp {
"" $remote_addr;
~^(?P<firstAddr>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[0-9A-Fa-f]+:[0-9A-Fa-f]+:[0-9A-Fa-f]+:[0-9A-Fa-f]+:[0-9A-Fa-f]+:[0-9A-Fa-f]+:[0-9A-Fa-f]+:[0-9A-Fa-f]+),?.*$ $firstAddr;
}
log_format badrequest '"$status","$clientRealIp"';
这是定义一个日志格式,仅包含http状态码和客户端IP,当然也可以包含更多东西,这个在后面补充说明。
然后在站点的server段增加一行:
access_log syslog:server=127.0.0.1:515,facility=local6,tag=tingtao,severity=info main;
上面两者配合,就可以让nginx把每一次请求的状态码和IP发给本机的日志服务。注意端口我用了非常规的515,这样可以和完整日志的514进行区分,因为后端服务器也有一些直接对外的服务,避免混乱。
然后来看日志部分,我用的是syslog-ng
这是我自用脚本的一部分,实际使用的比这个复杂的多,所以复制进来做一些更改,也许会有错漏,遇到问题再留言吧:
tee /etc/syslog-ng/conf.d/tingtao_org_slave.conf << EOF
source s_network_slave {
network(
transport("udp")
port(515)
);
};
parser p_ngx_slave {
csv-parser(
columns("NGX.status","NGX.remote_addr")
flags(escape-double-char,strip-whitespace)
delimiters(",")
quote-pairs('""[]')
);
};
filter facility_6_slave { facility(local6); };
filter notmyip_slave {
not in-list("/etc/syslog-ng/mysvrip.txt", value("NGX.remote_addr"));
};
########## 测试的
destination badip_slave {
program("/badip.sh"
template("\${NGX.status}|\${NGX.remote_addr}\n" )
);
};
log {
source(s_network_slave);
parser(p_ngx_slave);
filter(facility_6_slave);
filter(notmyip_slave);
destination(badip_slave);
};
EOF
/etc/init.d/syslog-ng restart
这里面有几个过滤,一个notmyip是因为wp有些自动化任务是由服务器发起的,所以来自服务器的ip需要直通,同时我这里是前后端节点分离,所以服务器ip不是唯一的,需要单独跳过去。
下面看nginx里面站点的server配置,这里是相关的一部分:
geo $remote_addr $ipgeo {
default 0;
服务器ip1 1;
服务器ip2 1;
服务器ip3 1;
服务器ip4 1;
127.0.0.1 1;
172.16.0.0/16 1;
}
geo $host $svripgeo {
default 0;
服务器ip1 1;
服务器ip2 1;
服务器ip3 1;
服务器ip4 1;
127.0.0.1 1;
172.16.0.0/16 1;
}
#几个很明显的非法请求直接踢了
if ($server_protocol ~* "HTTP/1.0") {
return 444;
}
if ($server_protocol = "") {
return 444;
}
if ($http_user_agent = "") {
return 444;
}
if ($host = "_") {
return 444;
}
location ~ /\. {
return 444;
}
if ($http_referer ~* "jokes.go2live.cn|simplesite.com" )
{
return 404;
}
####### 主机头为服务器IP的部分
set $badrequest "";
#主机头为服务器ip
if ( $svripgeo = 1) {
set $badrequest "fu";
}
#客户端并不是服务器ip
if ( $ipgeo = 0) {
set $badrequest "${badrequest}ck";
}
if ( $badrequest = fuck) {
return 444;
}
####### 主机头为服务器IP的部分结束
set $badrequest2 "";
#主机头在默认站点上
if ($host = "_") {
set $badrequest2 "fu";
}
if ($http_user_agent = "") {
set $badrequest2 "${badrequest2}ck";
}
if ( $badrequest2 = fuck) {
return 444;
}
######## 很明显的恶意请求地址,这个各位自行统计
location ~* ^/(-/|/1.php|/shell.php|wp-content/plugins/backup-backup/|wp-json/|wordpress/|dns-query) {
return 444;
}
#禁止指定UA及UA为空的访问,这个各位自行统计
if ( $http_user_agent ~* "nvdorz|pdrlabs|Mozzila|xfa1|node-fetch|^$" )
{
return 444;
}
简单说明
这里面除了一个还在观察的返回404,其他全444,因为除了444其他的http状态实际上都会反馈内容给客户端,被人CC或者说DDOS的时候,其实很简单的操作就能把服务器带宽拖满,而用444则是直接在服务器端丢弃连接,不做任何后续处理,最大限度提升服务器方面的抗压能力。
两个geo来确定到底是不是服务器以及内网发来的请求,如果是,则是我日常测试什么的,跳过,否则就踢。这些做扫描工具的也真是花心思了,我观察到很多次主机头为127.0.0.1这种。
然后服务器上做了一个默认站点,主机头为"_",所有进这里的直接踢,因为自己的站点全都有明确的server段进行承接,而其他的诸如用ip的,用一些乱七八糟主机头的全都是恶意请求。
某些url是站点内不存在的,而一看路径就知道请求这个地址的一定是恶意客户端,也直接踢。
所有的请求都发日志给syslog-ng,然后syslog-ng会开启前面写明的/badip.sh来进行后续处理,下面是/badip.sh代码:
tee /badip.sh << EOF
#!/bin/bash
while read line ; do
if [ -n "\$line" ]; then
判断状态码,然后分离IP出来,做后续处理
插数据库
提交给防火墙封锁IP
else
echo "空字符串"
# >> /root/z/badsql.txt
fi
done
EOF
chmod +x /badip.sh
不知为何,syslog-ng经常会发一个空行给脚本,所以这个要判断下,走到这一步,插库和封IP也就是很简单的事了,根据自己情况写进去就可以了。
其实也可以在syslog-ng的判断里面加一个状态码的过滤,我以前也这样干过,这样会更简单而且shell接受的数据少,系统压力就会小,但是后续逻辑的处理能力会较低。
反正都很灵活了,根据实际情况去处理吧。
巧的是,syslog-ng的运行身份是root,所以不用考虑权限问题。
本文分几次写完,我也要赚钱养家,这才是主业……所以思路断断续续的,而且因为这玩意涉及到web、日志、防火墙、数据库、脚本等很多方面,我做这整套用了挺长时间的,所以可能会有些漏掉的部分,回头想到了再补充吧。
因为我自用的版本过于细致,所以抽取了一部分写成了这篇文章。
对是否是恶意请求的判断实际上可以发生在4个方面,nginx里面、nginx+lua、syslog-ng、shell,而这4个点都可以实现相同或者相似结果。前两个的话可以设置444,这样效果最好,但都已经能操作防火墙了,那其实这个判断放在哪里都差距不太大……但若遇到有针对性的全连接(俗称CC)攻击的时候,放在前两个会显著提升抗打击能力的。
我自己是把完整的日志记录以后,还单独记录了一份状态码大于399的,便于统计分析。周末百无聊赖的时候跟这些小崽子斗个法,也可以打发一部分时间。
本文只是一个思路,尽可能快的捕捉到恶意行为,然后丢给防火墙封了它,当然永远都会有新的恶意行为出现,根据情况再处理就好了。
完全按照本文写的去配置,应该是可以运行的,但shell部分我没写出来,所以封IP是做不到的,ua和url的部分我也删的差不多了,各位要根据自己情况去填充。
然后nginx需要找一下路径然后配置加载ipgeo什么的模块,这种事就自己搞吧。
其实聪明如你,再动一下脑子,会发现既然本文的做法行得通,那么做一下延伸的话,嗯。。。其实也可以实现ftp、数据库等大概所有服务的自动防御。动动脑子动动手,同样是挨揍,但你就是最靓的仔!
网上所有类似文章呢,告诉你的无非是怎样的行为返回403,以这个姿势挨揍不太疼,或者怎样的姿势挨揍的时候看起来不失风度,哈哈。
我的思路则不同,通过几个部分的衔接,让恶意请求的IP永远出不了招,这也就是文章开头所说的“主动一点”。虽然理论上不可能完全杜绝攻击行为,但可以大幅度提升攻击成本,这个还是可以做到的。
本文做法应该适用于所有vps、云机、独服,只是在原有web环境增加一个syslog-ng,而这个实际分配的内存不到15M,并且syslog-ng安装的时候会自动替换系统自带的rsyslog,所以实际上并没增加系统资源开销,我有一个512M内存的虚拟机都跑的顺溜。

