【实战】Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP

news2024/10/6 12:14:35

大家好,我是冰河~~

自己搭建的网站刚上线,短信接口就被一直攻击,并且攻击者不停变换IP,导致阿里云短信平台上的短信被恶意刷取了几千条,加上最近工作比较忙,就直接在OpenResty上对短信接口做了一些限制,采用OpenResty+Lua的方案成功动态封禁了频繁刷短信接口的IP。

一、临时解决方案

由于事情比较紧急,所以,当发现这个问题时,就先采用快速的临时方案解决。

(1)查看Nginx日志发现被攻击的IP 和接口

[root@binghe ~]# tail -f /var/log/nginx/access.log

发现攻击者一直在用POST请求 /fhtowers/user/getVerificationCode这个接口

在这里插入图片描述

(2)用awk和grep脚本过滤nginx日志,提取攻击短信接口的ip(一般这个接口是用来发注册验证码的,一分钟如果大于10次请求的话就不是正常的访问请求了,大家根据自己的实际情况更改脚本)并放到一个txt文件中去,然后重启nginx

[root@binghe ~]# cat denyip.sh
#!/bin/bash
nginx_home=/usr/local/openresty/nginx
log_path=/var/log/nginx/access.log
tail -n5000 $log_path | grep  getVerification | awk '{print $1}' |sort | uniq -c | sort -nr -k1 | head -n 100 |awk '{if($1>10)print ""$2""}' >$nginx_home/denyip/blocksip.txt
/usr/bin/nginx -s reload

(3)设置Nginx去读取用脚本过滤出来的blocksip.txt(注意一下,我这里的Nginx是用的openresty,自带识别lua语法的,下面会有讲openresty的用法)

location =  /fhtowers/user/getVerificationCode {  #短信接口
access_by_lua '
   local f = io.open("/usr/local/openresty/nginx/denyip/blocksip.txt")   #黑名单列表
   for line in f:lines() do
		  if ngx.var.http_x_forwarded_for == line then   #如果ip在黑名单列表里直接返回403
				 ngx.exit(ngx.HTTP_FORBIDDEN)
		  end
  end
 ';
 proxy_pass http://appservers;   #不在名单里就转发给后台的tomcat服务器
}

(4)把过滤脚本放进crontab任务里,一分钟执行一次

[root@binghe ~]# crontab -e
*/1 * * * * sh /root/denyip.sh

(5)查看一下效果,发现攻击者的请求都被返回403并拒绝了

在这里插入图片描述

二、OpenResty+Lua方案

临时方案有效果后,再将其调整成使用OpenResty+Lua脚本的方案,来一张草图。

在这里插入图片描述

接下来,就是基于OpenResty和Redis实现自动封禁访问频率过高的IP。

2.1 安装OpenResty

安装使用 OpenResty,这是一个集成了各种 Lua 模块的 Nginx 服务器,是一个以Nginx为核心同时包含很多第三方模块的Web应用服务器,使用Nginx的同时又能使用lua等模块实现复杂的控制。

(1)安装编译工具、依赖库

[root@test1 ~]# yum -y install readline-devel pcre-devel openssl-devel gcc

(2)下载openresty-1.13.6.1.tar.gz 源码包,并解压;下载ngx_cache_purge模块,该模块用于清理nginx缓存;下载nginx_upstream_check_module模块,该模块用于ustream健康检查。

[root@test1 ~]# cd /usr/local/
[root@test1 local]# wget https://openresty.org/download/openresty-1.13.6.1.tar.gz
[root@test1 local]# tar -zxvf openresty-1.13.6.1.tar.gz
[root@test1 local]# cd openresty-1.13.6.1/bundle
[root@test1 local]# wget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz
[root@test1 local]# tar -zxvf ngx_cache_purge-2.3.tar.gz
[root@test1 local]# wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/v0.3.0.tar.gz
[root@test1 local]# tar -zxvf v0.3.0.tar.gz

(3)配置需安装的模块

# ./configure --help可查询需要安装的模块并编译安装
[root@test1 openresty-1.13.6.1]# ./configure --prefix=/usr/local/openresty --with-luajit --with-http_ssl_module --user=root --group=root --with-http_realip_module --add-module=./bundle/ngx_cache_purge-2.3/ --add-module=./bundle/nginx_upstream_check_module-0.3.0/ --with-http_stub_status_module 
[root@test1 openresty-1.13.6.1]# make && make install

(4)创建一个软链接方便启动停止

[root@test1 ~]# ln -s /usr/local/openresty/nginx/sbin/nginx   /bin/nginx

(5)启动nginx

[root@test1 ~]# nginx  #启动
[root@test1 ~]# nginx  -s reload   #reload配置

如果启动时候报错找不到PID的话就用以下命令解决(如果没有更改过目录的话,让它去读nginx的配置文件就好了)

[root@test1 ~]# /usr/local/openresty/nginx/sbin/nginx  -c /usr/local/openresty/nginx/conf/nginx.conf 

在这里插入图片描述

随后,打开浏览器访问页面。

在这里插入图片描述

(6)在Nginx上测试一下能否使用Lua脚本

[root@test1 ~]# vim /usr/local/openresty/nginx/conf/nginx.conf

在server里面加一个

location /lua {
	default_type text/plain;
	content_by_lua ‘ngx.say(“hello,lua!);
}

在这里插入图片描述

加完后重新reload配置。

[root@test1 ~]# nginx  -s reload

在浏览器里输入 ip地址/lua,出现下面的字就表示Nginx能够成功使用lua了

在这里插入图片描述

2.2 安装Redis

(1)下载、解压、编译安装

[root@test1 ~]# cd /usr/local/
[root@test1 local]# wget http://download.redis.io/releases/redis-6.0.1.tar.gz
[root@test1 local]# tar -zxvf redis-6.0.1.tar.gz
[root@test1 local]# cd redis-6.0.1
[root@test1 redis-6.0.1]# make
[root@test1 redis-6.0.1]# make install

(2)查看是否安装成功

[root@test1 redis-6.0.1]# ls -lh /usr/local/bin/
[root@test1 redis-6.0.1]# redis-server -v
Redis server v=3.2.5 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=dae2abf3793b309d

(3)配置redis 创建dump file、进程pid、log目录

[root@test1 redis-6.0.1]# cd /etc/
[root@test1 etc]# mkdir redis
[root@test1 etc]# cd /var/
[root@test1 var]# mkdir redis
[root@test1 var]# cd redis/
[root@test1 redis]# mkdir  data log  run

(4)修改配置文件

[root@test1 redis]# cd /usr/local/redis-6.0.1/
[root@test1 redis-6.0.1]# cp redis.conf /etc/redis/6379.conf
[root@test1 redis-6.0.1]# vim /etc/redis/6379.conf
#绑定的主机地址
bind 192.168.1.222
#端口
port 6379
#认证密码(方便测试不设密码,注释掉)
#requirepass 
#pid目录
pidfile /var/redis/run/redis_6379.pid
#log存储目录
logfile /var/redis/log/redis.log
#dump目录
dir /var/redis/data
#Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize yes

(5)设置启动方式

[root@test1 redis-6.0.1]# cd /usr/local/redis-6.0.1/utils/
[root@test1 utils]# cp redis_init_script /etc/init.d/redis
[root@test1 utils]# vim /etc/init.d/redis   #根据自己实际情况修改

/etc/init.d/redis文件的内容如下。

#!/bin/sh
#
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.

REDISPORT=6379
EXEC=/usr/local/bin/redis-server
CLIEXEC=/usr/local/bin/redis-cli

PIDFILE=/var/run/redis_${REDISPORT}.pid
CONF="/etc/redis/${REDISPORT}.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $CLIEXEC -p $REDISPORT shutdown
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac

增加执行权限,并启动Redis。

[root@test1 utils]# chmod a+x /etc/init.d/redis   #增加执行权限
[root@test1 utils]# service redis start     #启动redis

(6)查看redis是否启动

在这里插入图片描述

2.3 Lua访问Redis

(1)连接redis,然后添加一些测试参数

[root@test1 utils]# redis-cli -h 192.168.1.222 -p 6379
192.168.1.222:6379> set "123" "456"
OK

(2)编写连接Redis的Lua脚本

[root@test1 utils]# vim /usr/local/openresty/nginx/conf/lua/redis.lua
local redis = require "resty.redis"
local conn = redis.new()
conn.connect(conn, '192.168.1.222', '6379')     #根据自己情况写ip和端口号 
local res = conn:get("123")
if res==ngx.null then
    ngx.say("redis集群中不存在KEY——'123'")
    return
end
ngx.say(res)

(3)在nginx.conf配置文件中的server下添加以下location

[root@test1 utils]# vim /usr/local/openresty/nginx/conf/nginx.conf
location /lua_redis {
	default_type text/plain;
	content_by_lua_file /usr/local/openresty/nginx/conf/lua/redis.lua;
}

随后重新reload配置。

[root@test1 utils]# nginx  -s reload   #重启一下Nginx

(4)验证Lua访问Redis的正确性

在浏览器输入ip/lua_redis, 如果能看到下图的内容表示Lua可以访问Redis。

在这里插入图片描述

准备工作已经完成,现在要实现OpenResty+Lua+Redis自动封禁并解封IP了。3.4

2.4 OpenResty+Lua实现

(1)添加访问控制的Lua脚本(只需要修改Lua脚本中连接Redis的IP和端口即可)

ok, err = conn:connect(“192.168.1.222”, 6379)

注意:如果在Nginx或者OpenResty的上层有用到阿里云的SLB负载均衡的话,需要修改一下脚本里的所有…ngx.var.remote_addr,把remote_addr替换成从SLB获取真实IP的字段即可,不然获取到的IP全都是阿里云SLB发过来的并且是处理过的IP,同时,这些IP全都是一个网段的,根本没有办法起到封禁的效果)。

完整的Lua脚本如下所示。

[root@test1 lua]# vim /usr/local/openresty/nginx/conf/lua/access.lua
local ip_block_time=300 --封禁IP时间(秒)
local ip_time_out=30    --指定ip访问频率时间段(秒)
local ip_max_count=20 --指定ip访问频率计数最大值(秒)
local BUSINESS = ngx.var.business --nginx的location中定义的业务标识符,也可以不加,不过加了后方便区分

--连接redis
local redis = require "resty.redis"  
local conn = redis:new()  
ok, err = conn:connect("192.168.1.222", 6379)  
conn:set_timeout(2000) --超时时间2秒

--如果连接失败,跳转到脚本结尾
if not ok then
    goto FLAG
end

--查询ip是否被禁止访问,如果存在则返回403错误代码
is_block, err = conn:get(BUSINESS.."-BLOCK-"..ngx.var.remote_addr)  
if is_block == '1' then
    ngx.exit(403)
    goto FLAG
end

--查询redis中保存的ip的计数器
ip_count, err = conn:get(BUSINESS.."-COUNT-"..ngx.var.remote_addr)

if ip_count == ngx.null then --如果不存在,则将该IP存入redis,并将计数器设置为1、该KEY的超时时间为ip_time_out
    res, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr, 1)
	res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out)
else
    ip_count = ip_count + 1 --存在则将单位时间内的访问次数加1
  
    if ip_count >= ip_max_count then --如果超过单位时间限制的访问次数,则添加限制访问标识,限制时间为ip_block_time
        res, err = conn:set(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, 1)
        res, err = conn:expire(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, ip_block_time)
	else
        res, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr,ip_count)
		res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out)
    end
end

-- 结束标记
::FLAG::
local ok, err = conn:close()

(2)在需要做访问限制的location里加两段代码即可,这里用刚才的/lua做演示

[root@test1 lua]# vim /usr/local/openresty/nginx/conf/nginx.conf

在这里插入图片描述

主要是添加如下配置。

access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;

其中,set $business “lua” 是为了把IP放进Redis的时候标明是哪个location的,可以不加这个配置。

随后,重新reload配置。

[root@test1 lua]# nginx -s reload #修改完后重启nginx

(3)打开浏览器访问192.168.1.222/lua 并一直按F5刷新。

在这里插入图片描述

随后,连接Redis,查看IP的访问计数。

[root@test1 ~]# redis-cli -h 192.168.1.222 -p 6379

发现redis已经在统计访问lua这个网页ip的访问次数了

在这里插入图片描述

这个key的过期时间是30秒,如果30秒没有重复访问20次这个key就会消失,所以说正常用户一般不会触发这个封禁的脚本。

在这里插入图片描述

当30秒内访问超过了20次,发现触发脚本了,变成了403

在这里插入图片描述

再次查看Redis的key,发现多了一个lua-block-192.168.1.158,过期时间是300秒,就是说在300秒内这个ip无法继续访问192.168.1.222/lua这个页面了。

在这里插入图片描述

过五分钟后再去访问这个页面,又可以访问了。

在这里插入图片描述

这个脚本的目的很简单:一个IP如果在30秒内其访问次数达到20次则表明该IP访问频率太快了,因此将该IP封禁5分钟。同时由于计数的KEY在Redis中的超时时间设置成了30秒,所以如果两次访问间隔时间大于30秒将会重新开始计数。

大家也可以将这个脚本优化成,第一次封禁5分钟,第二次封禁半小时,第三次封禁半天,第四次封禁三天,第五次永久封禁等等。

好了,今天就到这儿吧,我是冰河,我们下期见~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2191857.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vivado viterbi decoder license

Viterbi Decoder 打卡以上链接 添加后next后, 会发送lic文件到邮件,vivado导入lic即可

Latex技巧——参考文献中加入url和doi

有的期刊要求在参考文献里加入url或者doi, 例如下图中蓝色的字体。 在bib里编辑为下图中note行,也就是利用\href命令。\href后第一个{}内为网址,第二个{}为在参考文献中显示的蓝色文字。一般来说,两个{}内的文字相同。若遇到有些网址有下划线…

不是哥们,真有人拿 AI 来面试啊?

大家好,我是八哥。如今 AI 已经渗透到了我们的生活、求职、工作等方方面面。就拿求职来说,今年的美团校招,已经用上了 AI 来当面试官,降本增效。 我自己也当过面试官,其实对 AI 面试官还是挺好奇的。毕竟每个候选人的…

UOM无人机空域快速申请技术详解

UOM无人机空域快速申请技术详解主要包括以下几个步骤: 一、准备阶段 1. 实名登记:首先,您需要在相应的民航部门进行无人机的实名登记,这是合法飞行的前提。 2. 了解规定:熟悉并遵守当地关于无人机飞行的法律法规&am…

C语言:预编译过程的剖析

目录 一.预定义符号和#define定义常量 二.#define定义宏 三.宏和函数的对比 四、#和##运算符 五、条件编译 在之前,我们已经介绍了.c文件在运行的过程图解,大的方面要经过两个方面。 一、翻译环境 1.预处理(预编译) 2.编译 3…

SpringBoot在线教育系统:从零到一的构建过程

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理微服务在线教育系统的相关信息成为必然。开…

如何禁止电脑蓝牙?分享五个有效方法,快@企业老板来看! 内行人都在用的方法哦,不妨一试!

在现代办公环境中,电脑蓝牙功能的便捷性无可否认,但随之而来的安全风险也不容忽视。 尤其对于企业而言,蓝牙的开放可能引发数据泄露、未经授权的设备连接等一系列安全问题。 因此,如何有效地禁止电脑蓝牙,成为了许多…

终于有人把多模态大模型讲这么详细了

多模态大型语言模型(Multimodal Large Language Models, MLLM)的出现是建立在大型语言模型(Large Language Models, LLM)和大型视觉模型(Large Vision Models, LVM)领域不…

付费计量系统通用功能(14)

11.18 Function_Object功能目标 11.18.1 Function_Object definition功能目标定义 A Function_Object is an abstract notation of defining the attributes of a function. 功能目标是一个抽象的功能属性的定义 Definition of a function object should stat…

Linux plt表调用汇编代码分析

linux调用共享库中的函数时通过plt表和got表实现位置无关代码,过程中涉及到lazy binding,即在第一调用外部函数时解析被调用的函数地址并将地址写入到got表,后续调用则不需要解析函数地址,具体过程如下 1.c程序如下 #include &l…

基于Node2Vec的图嵌入实现过程

目录 一、引言二、Node2Vec(原理)2.1 随机游走(Random Walk)2.2 嵌入学习2.3 Node2Vec 的优势 三、使用 Node2Vec 进行图嵌入(实践)3.1 读取和转换 JSON 文件为 Graph 对象3.2 训练 Node2Vec 模型3.3 二维嵌…

Python+Django微信小程序前后端人脸识别登录注册

程序示例精选 PythonDjango微信小程序前后端人脸识别登录注册 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《PythonDjango微信小程序前后端人脸识别登录注册》编写代码,代码整…

2024年10月6日历史上的今天大事件早读

23年10月06日西汉“新莽政权”领袖王莽被刺身亡 1866年10月06日清政府批准筹设天津机器局 1905年10月06日俄国爆发铁路工人大罢工 1913年10月06日中、英西姆拉会商“西藏问题” 1927年10月06日阿尔-乔尔森主演第一部有声电影 1940年10月06日新四军获黄桥决战胜利 1949年1…

字节跳动员工玩转 AI 的 100 种办法

今年,字节跳动在国内上线了 AI 应用开发平台——扣子。用户可以根据自己的想法在扣子上创建 chat bot(聊天机器人),甚至能利用扣子 API 上线一个产品。 扣子上有丰富的插件工具,如果用户希望 bot 具备某个功能&#x…

sql-labs靶场第九关测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、寻找注入点 2、注入数据库 ①寻找注入方法 ②爆库,查看数据库名称 ③爆表,查看security库的所有表 ④爆列,查看users表的所有列 ⑤成功获取用户名…

2024年,现在做全职的AI产品经理,时机对不对?

就在最近,Chatgpt又更新了,推出了新版本名字叫做canvas。 这个版本可以支持对AI的对话内容进行编辑了,也就是以前prompt一次性生成的结果总是很难控制输出结果,新版新增了可以在结果内容与用户进行交互的产品入口,最终…

(20)MATLAB使用卡方(chi-square)分布生成Nakagami-m分布

文章目录 前言一、使用卡方分布函数生成Nakagami分布随机变量二、MATLAB仿真1.仿真代码2.运行结果 前言 Nakagami分布随机变量的生成,可以使用gamma分布实现,也可以使用卡方(chi-square)分布随机变量生成。使用伽马(g…

AI 写作工具汇总

🐣个人主页 可惜已不在 🐤这篇在这个专栏AI_可惜已不在的博客-CSDN博客 🐥有用的话就留下一个三连吧😼 目录 前言: 正文: ​ 前言: 在信息时代的浪潮中,AI 写作应运而生。它以强大的算法和海量的数据为支撑&…

文本处理 — JavaScript 中的字符串——WEB开发系列44

掌握它所出现的文本是很有用的,字符串是最基本的数据类型之一,承载着我们与计算机之间的交流。它们可以用来表示文本、传递信息、构建用户界面等。JavaScript作为一种广泛使用的编程语言,为字符串提供了强大而灵活的处理功能。 一、创建字符串…

图解 Stable Diffusion 如何实现文生图

前言 1引言 Stable Diffusion 是 2022 年发布的深度学习文生图模型。它主要用于根据文本的描述产生详细图像,尽管它也可以应用于其他任务,如内补和外补绘制,以及在提示词指导下产生图生图的翻译。 算法原理 Stable Diffusion 是一种扩散模…