nginx limit_req限流及监控日志封禁恶意访问IP 拒绝访问deny | 加入防火墙firewalld
此篇不推荐,查看更好的方案 博客:cc 防护资料
本文介绍两种封禁ip的方法,一个是nginx的deny指令,一个是firewall-cmd ipset 加入防火墙,写在一起了,选择一种即可。
另做了很多日志,所以看起来代码很多,可以精简一下。
并不一定依赖 PHP 脚本,只不过在 shell 命令不是很熟悉的情况下,可以采取文中的思路来实现。
关于误封蜘蛛,文末介绍了反查IP和信任IP(段)的方法。
前两图为 deny 指令模式的配置
firewalld 模式初始化
centos7 默认安装 firewalld,没有需要安装 yum install firewalld -y
如果刚开启防火墙,注意开放http服务和各种端口避免业务受影响
systemctl enable firewalld
systemctl start firewalld
# 开放http https服务
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
# 创建ipset 黑名单 badips
firewall-cmd --permanent --new-ipset=badips --type=hash:ip
# 将 badips 加入 rich-rule
#http
firewall-cmd --permanent --zone=public --add-rich-rule=\
'rule family=ipv4 source ipset=badips service name=http drop'
# https
firewall-cmd --permanent --zone=public --add-rich-rule=\
'rule family=ipv4 source ipset=badips service name=https drop'
# 封禁ip 加入 badips
firewall-cmd --permanent --zone=public --ipset=badips --add-entry=111.111.222.29
# reload才能生效
firewall-cmd --reload
firewalld 参考 www.cnblogs.com/cash/p/13294208.ht...
ipset 视频参考
www.bilibili.com/video/BV1AR4y1A7H...
目录结构
block.sh
# 日志所在目录
LOG_DIR=/data/wwwlogs
# 切割日志存放目录
CUT_LOG_DIR=${LOG_DIR}/cut
# 筛选出来的IP对应的日志存放目录
DENY_IP_LOG_DIR=${LOG_DIR}/deny
# 待切割日志
ACCESS_LOG=${LOG_DIR}/access.log
# 筛选出来的IP
DENY_IP_TXT=ip.txt
# 是否执行下一步操作:封禁IP
NEXT=false
# 筛选条件,大于N条
NUM=10
echo $(date +%Y"."%m"."%d" "%k":"%M":"%S)
echo "==BLOCK.SH========run start...============="
# =======================切割日志=================================
echo "==CUT access.log .."
# 清除N天前的记录
find ${CUT_LOG_DIR} -mtime +1 -exec rm -rf {} \;
CUT_LOG=${CUT_LOG_DIR}/$(date +%Y-%m-%d+%H:%M).access.log
PID=/run/nginx.pid
mv ${ACCESS_LOG} ${CUT_LOG}
kill -USR1 `cat $PID`
echo "==CUT access.log done"
# =====================用切割出来的日志筛选IP================================
awk '{print $1}' $CUT_LOG \
| sort -rn \
| uniq -c \
| sort -rn \
| awk -v n=$NUM '{if($1>n&&$2)print $2}' \
> $DENY_IP_TXT
# =====================筛选的IP日志保存=========================
for i in `awk '{print $0}' ${DENY_IP_TXT}`
do
echo "==BLOCK.SH== add ${i} 's log to /deny"
grep $i $CUT_LOG \
|awk '{print $0}' \
> $DENY_IP_LOG_DIR/$(date "+%Y-%m-%d")"_"${i}".log"
NEXT=true
done
# =====================将IP交予ban.php加入nginx黑名单=========================
if [ "$NEXT" = true ];
then
# 由 php 生成执行脚本
echo "==BLOCK.SH== deny ips sent to ban.php"
php RUN.php
# 执行脚本
echo "==BLOCK.SH== run tmpsh.."
sh tmpsh.sh
# 清空
echo "" > tmpsh.sh
echo "" > $DENY_IP_TXT
else
echo "==BLOCK.SH== deny ip empty! Dont need handle";
fi
echo -e "==BLOCK.SH========run end=============\n";
RUN.php
<?php
error_reporting(0);
const IP_TXT = 'ip.txt';// 格式:每行一个ip
const DENY_CONF = '/etc/nginx/deny.conf';
const DENY_CONF_BACKUP_DIR = '/data/denyconfbackup/';
const BADIPS_XML = '/etc/firewalld/ipsets/badips.xml';
const BADIPS_XML_BACKUP_DIR = '/data/badips/';
const TMP_SH = 'tmpsh.sh';
const SHELL_HISTORY_LOG = 'shell_history.log';
echo '==BAN.PHP== run start.. =============' . PHP_EOL;
// 从 ip.txt 读取 ip
function getIpFromContext($file)
{
$handle = @fopen($file, "r");
if (!$handle) {
die('fail open file: ip.txt');
}
$ips = [];
// 逐行取出
while (!feof($handle)) {
$buffer = fgets($handle, 4096);
preg_match(
'/^([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/',
$buffer,
$matches
);
if (isset($matches[0])) {
$ips[] = $matches[0];
}
}
fclose($handle);
return $ips;
}
// 过滤白名单
function filterAllow(array $ips)
{
// 白名单
$allows = [
'127.0.0.1',
// ..
];
foreach ($ips as $index => $ip) {
foreach ($allows as $allow) {
if (strpos($ip, $allow) === 0) {
unset($ips[$index]);
continue 2;
}
}
}
return $ips;
}
// 将生成的 shell 命令写入执行脚本
function writeShellToTmpsh(string $shell)
{
echo '==BAN.PHP== white shell to tmpsh.sh.. =============' . PHP_EOL;
// 备份记录
file_put_contents(SHELL_HISTORY_LOG, PHP_EOL . date('Y-m-d H:i:s', time()) . PHP_EOL, FILE_APPEND);
file_put_contents(SHELL_HISTORY_LOG, $shell, FILE_APPEND);
file_put_contents(TMP_SH, $shell);
}
// 添加到 nginx deny.conf
function addIpToDenyConf(array $ips)
{
// 取出原conf文件内容
$content = file_get_contents(DENY_CONF);
$deny_ip_arr = explode(PHP_EOL, $content);
$scripts = [];
foreach ($ips as $ip) {
$script = "deny {$ip};";
$exist = array_search($script, $deny_ip_arr);
if ($exist === false) {
$scripts[] = $script;
}
}
if (!empty($scripts))
{
// 备份原来的 deny.conf
$copy = DENY_CONF_BACKUP_DIR . date('Y-m-d H:i:s', time()) . '.conf';
copy(DENY_CONF, $copy);
foreach ($scripts as $script) {
echo '==BAN.PHP== add ' . $script . ' to nginx deny config ' . DENY_CONF . PHP_EOL;
file_put_contents(DENY_CONF, $script . PHP_EOL, FILE_APPEND);
}
$shell = 'nginx -s reload';
writeShellToTmpsh($shell);
}
}
// 添加到 firewall
function addIpToFirewalld(array $ips)
{
// 备份
$copy = BADIPS_XML_BACKUP_DIR . date('Y-m-d H:i:s', time()) . '.xml';
copy(BADIPS_XML, $copy);
$template = "firewall-cmd --permanent --zone=public --ipset=badips --add-entry=%s";
$scripts = [];
foreach ($ips as $ip) {
$scripts[] = sprintf($template, $ip);
}
$scripts[] = 'firewall-cmd --reload';
$shell = implode(PHP_EOL, $scripts);
writeShellToTmpsh($shell);
}
// 读取 block.sh 写入的 ip
$source = getIpFromContext(IP_TXT);
if (empty($source)) {
die('source empty!');
}
// 过滤白名单
$ips = filterAllow($source);
if (empty($ips)) {
die('no ips');
}
// addIpToDenyConf($ips); // deny 模式
addIpToFirewalld($ips); // 防火墙模式
运行log
2022.05.21 20:45:41
==BLOCK.SH========run start...=============
==CUT access.log ..
==CUT access.log done
==BLOCK.SH== add 111.111.222.29 's log to /deny
==BLOCK.SH== deny ips sent to RUN.php
==BAN.PHP== run start.. =============
==BAN.PHP== white shell to tmpsh.sh.. =============
==BLOCK.SH== run tmpsh..
success
success
==BLOCK.SH========run end=============
shell log
2022-05-21 12:51:16
firewall-cmd --permanent --zone=public --ipset=badips --add-entry=111.111.222.29
firewall-cmd --reload
关于判断蜘蛛
百度官方说法 ziyuan.baidu.com/college/articlein...
360不支持反查IP,但公布了IP段,可以放进信任IP
spider.txt
sm.cn
baidu.com
baidu.jp
sogou.com
bytedance.com
# 是否蜘蛛
is_spider() {
# 反查IP 45.152.11.106.in-addr.arpa domain name pointer shenmaspider-106-11-152-45.crawl.sm.cn.
hostname=`host $1 | grep -f /data/spider.txt | awk '{print $5}'`
if [ ! -z $hostname ];
then
# 反查域名 shenmaspider-106-11-152-45.crawl.sm.cn has address 106.11.152.45
hostip=`host $hostname | awk '{print $4}'`
if [ "$hostip" = "$1" ];
then
return
fi
fi
echo $1
}
# 判断是否为蜘蛛
filter_spider() {
while read line || [[ -n ${line} ]]
do
is_spider $line
done
}
然后可以在管道中使用
awk '{print $1}' $CUT_LOG \
| sort -rn \
| uniq -c \
| sort -rn \
| awk -v n=$NUM '{if($1>n&&$2)print $2}' \
| filter_trust \
| filter_spider \
> $DENY_IP_TXT
关于信任IP
trust_ips.txt (示例为360官方蜘蛛IP段,www.so.com/help/spider_ip.html )
^180.153.232.*
^180.153.234.*
^180.153.236.*
^180.163.220.*
^42.236.101.*
^42.236.102.*
^42.236.103.*
^42.236.10.*
^42.236.12.*
^42.236.13.*
^42.236.14.*
^42.236.15.*
^42.236.16.*
^42.236.17.*
^42.236.46.*
^42.236.48.*
^42.236.49.*
^42.236.50.*
^42.236.51.*
^42.236.52.*
^42.236.53.*
^42.236.54.*
^42.236.55.*
^42.236.99.*
filter_trust() {
while read line || [[ -n ${line} ]]
do
echo $line | grep -v -f 'trust_ips.txt'
done
}
然后可以在管道中使用
awk '{print $1}' $CUT_LOG \
| sort -rn \
| uniq -c \
| sort -rn \
| awk -v n=$NUM '{if($1>n&&$2)print $2}' \
| filter_trust \
| filter_spider \
> $DENY_IP_TXT
本作品采用《CC 协议》,转载必须注明作者和本文链接