网友提到,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内存的虚拟机都跑的顺溜。

作者 听涛

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注