nginx limit_req限流及监控日志封禁恶意访问IP 拒绝访问deny | 加入防火墙firewalld

此篇不推荐,查看更好的方案 博客:开源一下基于 openresty 写的 cc 防御部分

nginx limit_req限流及监控日志加入IP拒绝访问

本文介绍两种封禁ip的方法,一个是nginx的deny指令,一个是firewall-cmd ipset 加入防火墙,写在一起了,选择一种即可。

另做了很多日志,所以看起来代码很多,可以精简一下。

并不一定依赖 PHP 脚本,只不过在 shell 命令不是很熟悉的情况下,可以采取文中的思路来实现。

关于误封蜘蛛,文末介绍了反查IP和信任IP(段)的方法。

nginx limit_req限流及监控日志封禁恶意访问IP 拒绝访问deny | 加入防火墙firewalld

前两图为 deny 指令模式的配置

nginx 限流及监控日志加入IP拒绝访问

nginx 限流及监控日志加入IP拒绝访问

firewalld 模式初始化
centos7 默认安装 firewalld,没有需要安装 yum install firewalld -y

如果刚开启防火墙,注意开放http服务和各种端口避免业务受影响

systemtcl 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...

目录结构

nginx limit_req限流及监控日志IP拒绝访问

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

# 筛选条件,大于NNUM=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 协议》,转载必须注明作者和本文链接
welcome come back
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
95
粉丝
24
喜欢
156
收藏
348
排名:324
访问:2.9 万
私信
所有博文
社区赞助商