写点什么

OpenResty 入门以及 WAF 防御实战

  • 2023-10-15
    湖北
  • 本文字数:11155 字

    阅读完需:约 37 分钟

OpenResty 入门以及 WAF 防御实战

简介

在官网上对 OpenResty 是这样介绍的(http://openresty.org):


“OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。”


“OpenResty 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。”


“OpenResty 的目标是让你的 Web 服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。”


从以上官网描述里我们可以知道,OpenResty 官网对其定位是以 Nginx 为核心集成 Lua,打造一个兼具开发效率和高性能的服务端开发平台。


OpenResty 的核心是基于 Nginx 的一个 C 模块(lua-Nginx-module),该模块将 LuaJIT 嵌入到 Nginx 服务器中,并对外提供一套完整的 Lua API,透明地支持非阻塞 I/O,提供了轻量级线程、定时器等高级抽象。


我们可以用 Lua 语言来进行字符串和数值运算、查询数据库、发送 HTTP 请求、执行定时任务、调用外部命令等,还可以用 FFI 的方式调用外部 C 函数。这基本上可以满足服务端开发需要的所有功能。


掌握好了 OpenResty,我们就可以同时拥有脚本语言的开发效率和迭代速度,以及 Nginx C 模块的高并发和高性能优势。


下面为大家介绍本文大纲


  • OpenResty 的 hello world 该怎么写

  • 快速上手 Lua 脚本语言

  • OpenResty 用到的 Nginx 知识

  • OpenResty 在网关安全中如何应用

OpenResty 的 hello world 该怎么写

OpenResty 的安装

OpenResty 的安装有多种方法,比如使用操作系统的包管理器、源码编译或者 docker 镜像。推荐优先使用 yum、apt-get、brew 这类包管理系统,来安装 OpenResty。


对于 Mac OS X 或 macOS 用户,强烈推荐您使用 homebrew 包管理工具安装 OpenResty。可以直接使用下面 这一条命令:


brew install openresty/brew/openresty
复制代码


对于一些常见的 Linux 发行版本(Ubuntu、Debian、CentOS、RHEL、Fedora、OpenSUSE、Alpine 和 Amazon Linux), OpenResty 提供 官方预编译包。确保首先用这种方式来安装。这里用 CentOS 举例,可以使用如下方式,

CentOS 9 或者更新版本

# add the yum repo:wget https://openresty.org/package/centos/openresty2.reposudo mv openresty2.repo /etc/yum.repos.d/openresty.repo
# update the yum index:sudo yum check-update
复制代码

CentOS 8 或者更老版本

# add the yum repo:wget https://openresty.org/package/centos/openresty.reposudo mv openresty.repo /etc/yum.repos.d/openresty.repo
# update the yum index:sudo yum check-update
复制代码


然后就可以像下面这样安装软件包,比如 openresty:


sudo yum install -y openresty
复制代码

Docker 安装

Docker 安装的方式就最为简单了,只需要输入以下命令,就可以获取打包好的镜像。


docker pull openresty/openresty
复制代码

目录结构

安装 OpenResty 成功后的目录结构如下(以默认安装目录为例):


/usr/local/openresty/                          #安装主目录├── bin                                     #存放可执行文件├── luajit                                  #LuaJIT运行库├── lualib                                  #Lua组件├── Nginx                                   #Nginx核心运行平台├── pod                                     #参考手册(restydoc)使用的数据└── site                                    #包管理工具(opm)使用的数据
复制代码

启动服务

yum 安装完后,就可以直接运行 openresty 命令,启动 OpenResty 服务。


/usr/local/openresty/bin/openresty         #启动OpenResty服务
复制代码


OpenResty 默认开启了 localhost:80 服务,使用 wget 或者 curl 这样的工具就可以验证 OpenResty 是否正常工作:


curl http://localhost:80                   #curl命令发送HTTP请求
复制代码


下面是一些其他常用命令,


/usr/local/openresty/bin/openresty  -s stop       #停止 OpenResty 服务/usr/local/openresty/bin/openresty  -s reload     #重新加载 Nginx 配置文件/usr/local/openresty/bin/openresty  -t            #检查 Nginx 配置文件是否正确/usr/local/openresty/bin/openresty  -c            #指定配置文件启动
复制代码


OpenResty 的操作命令跟 Nginx 保持一致。可以执行 openresty -h 以及 nginx -h 对比看出,


命令行工具 resty

如果你想安装命令行工具 resty,那么可以像下面这样安装 openresty-resty 包:


sudo yum install -y openresty-resty
复制代码


resty 是一个 cli 工具,可以使用 -e 参数可以在命令行里直接执行 Lua 代码,我们可以在命令行执行如下命令,


[root@VM-4-5-centos ~]# resty -e "print('hello world')"hello OpenResty
复制代码


resty 工具还有很多选项用于配置行为,非常灵活,-e 之外较常用的有


-c                :指定最大并发连接数(默认值是64);-I                :指定Lua库的搜索路径;-l                :指定加载某个Lua库;--http-conf       :定制在http域里的指令;--main-include    :定制在main域里的指令;--shdict          :定制使用的共享内存(参见10.2节);--resolve-ipv6    :允许解析ipv6的地址。
复制代码


想了解完整的列表,可以查看 resty -h 命令。


包管理工具 opm


跟大多数语言一样有包管理工具一样,OpenResty 也有自己的包管理工具 opm(OpenResty Package Manager),opm 在 openresty-opm 包里,安装命令如下,


sudo yum install -y openresty-opm
复制代码


opm 是 OpenResty 自带的包管理器,在你安装好 OpenResty 之后,就可以直接使用。一些常见用法如下,


opm search    http                            #搜索关键字httpopm search    kafka                           #搜索关键字kafkaopm get       agentzh/lua-resty-http          #安装组件,注意需要sudoopm info      agentzh/lua-resty-http          #显示组件的版本、作者等信息opm remove    agentzh/lua-resty-http          #移除组件,同样需要sudoopm --install-dir=/opt    get xxx             #把组件安装到/opt目录下opm --cwd                 get xxx             #安装到当前目录的/resty_modules下
复制代码

编写 hello world

在上文中我们使用命令行工具 resty 写了一个比较简单的 OpenResty 程序,没有 master 进程,也不会监听端口。下面让我们写一个需要启动 OpenResty 服务的 hello world。


首先找到 OpenResty 安装目录下 nginx/conf/nginx.conf 文件,在 server 下新增 OpenResty 的 content_by_lua 指令,里面嵌入了 ngx.say 的代码:


server {        listen       88;        server_name  localhost;
location / { root html; index index.html index.htm; }
location /hello { content_by_lua ' ngx.say("hello, world") '; }}
复制代码


接着我们执行 openresty -s reload 命令,重新加载 nginx.conf 配置文件。没有报错的话,OpenResty 的服务就已经成功启动了。


最后使用 curl 命令,来查看结果的返回:


[root@VM-4-5-centos conf]# curl localhost:88/hellohello, world
复制代码


到这里,一个真正的 OpenResty 开发的 hello world 程序就完成了。

快速上手 Lua 脚本语言

Lua 环境

我们不用专门去安装标准 Lua 5.1 之类的环境,因为 OpenResty 已经不再支持标准 Lua,而只支持 LuaJIT。这里我介绍的 Lua 语法,也是和 LuaJIT 兼容的部分,而不是基于最新的 Lua 5.3,这一点需要特别注意。


在 OpenResty 的安装目录下,可以找到 LuaJIT 的目录和可执行文件。在 CentOS 系统下,LuaJIT 的目录如下,


[root@VM-4-5-centos luajit]# cd /usr/local/openresty/luajit/bin/[root@VM-4-5-centos bin]# lltotal 536lrwxrwxrwx 1 root root     18 Oct 12 11:22 luajit -> luajit-2.1.0-beta3-rwxr-xr-x 1 root root 547728 Jul 18 12:38 luajit-2.1.0-beta3
复制代码


我们可以执行 cp luajit /usr/local/bin/ 将 luajit 文件复制到 /usr/local/bin/ 目录下,进而可以直接使用 luajit 命令。


查看 LuaJIT 的版本号,


[root@VM-4-5-centos ~]# luajit  -vLuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2022 Mike Pall. https://luajit.org/
复制代码


执行 lua 脚本,


[root@VM-4-5-centos ~]# echo 'print("hello world")' > 1.lua[root@VM-4-5-centos ~]# cat 1.luaprint("hello world")[root@VM-4-5-centos ~]# luajit 1.luahello world[root@VM-4-5-centos ~]#
复制代码


也可以使用 resty 来直接运行,它最终也是用 LuaJIT 来执行的,


[root@VM-4-5-centos ~]# resty -e 'print("hello world")'hello world
复制代码

基本语法

变量

在 Lua 中声明变量,可以如下代码所示,


local a = 'hello'b = "world"
复制代码


加了 local 关键字,用于声明局部变量。


不加 local 关键字的话,变量默认是全局的。

注释

两个减号是单行注释


-- 注释
复制代码


多行注释


--[[ 多行注释 多行注释 --]]
复制代码

行尾结束

Lua 中代码的行尾结束都不需要添加特殊字符,这跟 Java 不同(Java 在行尾需要添加 ;)。


local a = 'a'print(a)
复制代码

数据类型

Lua 中的数据类型不多,你可以通过 type 函数来返回一个值的类型,比如下面这样的操作:


[root@VM-4-5-centos ~]# resty -e 'print(type("hello world"))>  print(type(print))>  print(type(true))>  print(type(360.0))>  print(type({}))>  print(type(nil))>  '
复制代码


打印如下,


stringfunctionbooleannumbertablenil
复制代码


这几种就是 Lua 中的基本数据类型了。下面我们来简单介绍一下它们。

字符串

在 Lua 中,有三种方式可以表达一个字符串:单引号、双引号,以及长括号([[]]),示例如下,


新建 str.lua 文件,写入以下内容,


local s = 'a'local s1 = "b"local s2 = [[c]]
print(s)print(s1)print(s2)
复制代码


执行 luajit str.lua 返回结果如下,


abc
复制代码


在 Lua 中,字符串拼接采用 .. 的方式,示例如下,


编辑 str.lua 文件,写入以下内容,


local s = 'a'local s1 = "b"local s2 = [[c]]
print(s)print(s1)print(s2)
local s3 =s .. s1 ..s2print(s3)
复制代码


执行 luajit str.lua 返回结果如下,


abcabc
复制代码

布尔值

在 Lua 中,只有 nil 和 false 为假,其他都为 true,包括 0 和空字符串也为真。我们可以用示例印证一下:


新建 bool.lua 脚本文件,写入以下内容,


local a = 0local bif a then  print("true")enda = ""if a then  print("true")end
print(b)
复制代码


执行 luajit str.lua 返回结果如下,


truetruenil
复制代码


在 Lua 中,空值就是 nil。如果你定义了一个变量,但没有赋值,它的默认值就是 nil,对应的就是上面示例代码的局部变量 b。

数字

Lua 的 number 类型,是用双精度浮点数来实现的。值得一提的是,LuaJIT 支持 dual-number(双数)模式,也就是说,LuaJIT 会根据上下文来用整型来存储整数,而用双精度浮点数来存放浮点数。示例如下,


新建 number.lua 脚本文件,写入以下内容,


print(type(2))print(type(2.2))print(type(0.2))print(type(2e+1))print(type(0.2e-1))print(type(7.8263692594256e-06))
print(2 + 2)print(2 + 22.2)
复制代码


执行 luajit number.lua 返回结果如下,


numbernumbernumbernumbernumbernumber424.2
复制代码

函数

函数在 Lua 中是一等公民,你可以把函数存放在一个变量中,也可以当作另外一个函数的入参和出参。示例如下,


新建 fun.lua 文件,写入以下代码,


-- 阶乘function factorial1(n)    if n == 0 then        return 1    else        return n * factorial1(n - 1)    endendprint(factorial1(5))factorial2 = factorial1print(factorial2(5))
复制代码


执行 luajit fun.lua 返回结果如下,


120120
复制代码

分支控制

Lua 提供了以下两种分支控制结构语句:


  • if 语句

  • if...else 语句

  • if...elseif...else 语句

if 语句

Lua if 语句语法格式如下:


if(布尔表达式)then   --[ 在布尔表达式为 true 时执行的语句 --]end
复制代码


以下是一个判断变量 a 的值是否小于 20 的示例,


新建 if1.lua,写入以下内容,


--[ 定义变量 --]a = 10;
--[ 使用 if 语句 --]if (a < 20) then --[ if 条件为 true 时打印以下信息 --] print("a 小于 20" );endprint("a 的值为:", a);
复制代码


执行 luajit if1.lua 返回结果如下,


a 小于 20a 的值为:  10
复制代码

if...else 语句

Lua if 语句可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。


Lua if...else 语句语法格式如下:


if(布尔表达式)then   --[ 布尔表达式为 true 时执行该语句块 --]else   --[ 布尔表达式为 false 时执行该语句块 --]end
复制代码


以下是一个判断变量 a 值的示例,


新建 if2.lua,写入以下内容,


--[ 定义变量 --]a = 100;--[ 检查条件 --]if( a < 20 )then   --[ if 条件为 true 时执行该语句块 --]   print("a 小于 20" )else   --[ if 条件为 false 时执行该语句块 --]   print("a 大于 20" )endprint("a 的值为 :", a)
复制代码


执行 luajit if2.lua 返回结果如下,


a 大于 20a 的值为 :  100
复制代码

if...elseif...else 语句

Lua if 语句可以与 elseif...else 语句搭配使用, 在 if 条件表达式为 false 时执行 elseif...else 语句代码块,用于检测多个条件语句。


Lua if...elseif...else 语句语法格式如下:


if( 布尔表达式 1)then   --[ 在布尔表达式 1 为 true 时执行该语句块 --]
elseif( 布尔表达式 2)then --[ 在布尔表达式 2 为 true 时执行该语句块 --]
elseif( 布尔表达式 3)then --[ 在布尔表达式 3 为 true 时执行该语句块 --]else --[ 如果以上布尔表达式都不为 true 则执行该语句块 --]end
复制代码


以下是一个判断变量 a 值的示例,


新建 if3.lua,写入以下内容,


--[ 定义变量 --]a = 100
--[ 检查布尔条件 --]if( a == 10 )then --[ 如果条件为 true 打印以下信息 --] print("a 的值为 10" )elseif( a == 20 )then --[ if else if 条件为 true 时打印以下信息 --] print("a 的值为 20" )elseif( a == 30 )then --[ if else if condition 条件为 true 时打印以下信息 --] print("a 的值为 30" )else --[ 以上条件语句没有一个为 true 时打印以下信息 --] print("没有匹配 a 的值" )endprint("a 的真实值为: ", a )
复制代码


执行 luajit if3.lua 返回结果如下,


没有匹配 a 的值a 的真实值为:   100
复制代码

循环

Lua 编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。


Lua 编程语言中 for 语句有两大类:


  • 数值 for 循环

  • 泛型 for 循环

数值 for 循环

Lua 编程语言中数值 for 循环语法格式:


for var=exp1,exp2,exp3 do    <执行体>end
复制代码


var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为 1。示例如下,


新建 for1.lua 文件,写入以下内容,


function f(x)    print("function")    return x*2end
for i = 1, f(5) do print(i)end
复制代码


执行 luajit for1.lua 返回结果如下,


function12345678910
复制代码

泛型 for 循环

泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句。


Lua 编程语言中泛型 for 循环语法格式:


--打印数组a的所有值local a = {"one", "two", "three"}for i, v in ipairs(a) do    print(i, v)end
复制代码


i 是数组索引值,v 是对应索引的数组元素值。ipairs 是 Lua 提供的一个迭代器函数,用来迭代数组。


将以上内容下入 for2.lua 文件,打印结果如下,


1  one2  two3  three
复制代码

Lua 模块与包

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。


Lua 提供了一个名为 require 的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:


require("cjson")-- 或者require "cjson"
复制代码


Lua 比较小巧,内置的标准库并不多。在 OpenResty 的环境中默认支持了一些官方模块,如 cjson 可以直接使用,其他的一些第三方库则需要先使用 lua_package_path 指令配置 OpenResty 的文件寻址路径,又或者直接使用 opm 包管理工具来安装一些第三方模块。


OpenResty 中默认启用了下面列表的绝大部分组件,想要了解更多 OpenResty 相关组件的话,可以翻阅官网说明 https://openresty.org/cn/components.html


LuaJITArrayVarNginxModuleAuthRequestNginxModuleCoolkitNginxModuleDrizzleNginxModuleEchoNginxModuleEncryptedSessionNginxModuleFormInputNginxModuleHeadersMoreNginxModule...
复制代码


本文的 Lua 语法介绍到这里就足够在 OpenResty 中编写 lua 脚本了,想要了解更多 Lua 内容,如 table、文件、调式等可以自行翻阅 https://www.runoob.com/lua/lua-tutorial.html 网站。

OpenResty 用到的 Nginx 知识

内置常量和变量

OpenResty 在内置 Lua 引擎中新增了一些常用的内置变量如下所示。



OpenResty 在内置 Lua 引擎中新增了一些常用的内置常量大致如下所示。




这些内置变量和常量都可以在 Lua 脚本中直接使用。

配置指令

OpenResty 定义了一系列 Nginx 配置指令,用于配置何时运行用户 Lua 脚本以及如何返回 Lua 脚本的执行结果,这些指令可以直接在 nginx.conf 配置文件中使用。


OpenResty 定义的 Nginx 配置指令大致如下所示。



这些指令中有 9 个 *_by_lua 指令,它们和 Nginx 的关系如下图所示



其中,init_by_lua 只会在 Master 进程被创建时执行,init_worker_by_lua 只会在每个 Worker 进程被创建时执行。其他的 *_by_lua 指令则是由终端请求触发,会被反复执行。


所以在 init_by_lua 阶段,我们可以预先加载 Lua 模块和公共的只读数据,这样可以利用操作系统的 COW(copy on write)特性,来节省一些内存。


对于业务代码来说,其实大部分的操作都可以在 content_by_lua 里面完成,但更推荐的做法,是根据不同的功能来进行拆分,比如下面这样:


  • set_by_lua:设置变量;

  • rewrite_by_lua:转发、重定向等;

  • access_by_lua:准入、权限等;

  • content_by_lua:生成返回内容;

  • header_filter_by_lua:应答头过滤处理;

  • body_filter_by_lua:应答体过滤处理;

  • log_by_lua:日志记录。


利用这些阶段的特性,我们可以一些通用逻辑进行拆分处理,比如我们可以在 access 阶段解密,在 body filter 阶段加密就可以了,在 content 阶段的代码是不用做任何修改的。


# 加密协议版本location /test {    access_by_lua '...';        # 请求体解密    content_by_lua '...';       # 处理请求,不需要关心通信协议    body_filter_by_lua '...';   # 应答体加密}
复制代码

OpenResty 在网关安全中如何应用

WAF 介绍

Web 应用防火墙(Web Application Firewall,简称 WAF)对网站或者 App 的业务流量进行恶意特征识别及防护,在对流量清洗和过滤后,将正常、安全的流量返回给服务器,避免网站服务器被恶意入侵导致性能异常等问题,从而保障网站的业务安全和数据安全。

常见 Web 应用攻击防护

  • 防御一些常见常见威胁:SQL 注入、XSS 跨站、WebShell 上传、后门攻击、命令注入、非法 HTTP 协议请求、常见 Web 服务器漏洞攻击、CSRF、核心文件非授权访问、路径穿越、网站被扫描等。

  • CC 恶意攻击防护:控制单一源 IP 的访问频率,基于重定向跳转验证、人机识别等。针对海量慢速请求攻击,根据统计响应码及 URL 请求分布、异常 Referer 及 User-Agent 特征识别,结合网站精准防护规则综合防护。

  • 网站隐身:不对攻击者暴露站点地址,避免其绕过 Web 应用防火墙直接攻击。

相关产品

目前 WAF 相关产品主要有三类:


  • 硬件 WAF:效果好,但是贵!

  • 软件 WAF:效果还算可以,能用,有开源产品!

  • 云厂商 WAF:云厂商的 WAF 都很贵!


鉴于极客精神(白嫖万岁 😎),这里介绍几款业内开源的 WAF 产品,


  • 长亭科技的雷池社区版,主页地址:https://waf-ce.chaitin.cn/

  • ModSecurity,主页地址:https://www.modsecurity.org/

  • Coraza,主页地址:https://coraza.io/

  • VeryNginx,主页地址:https://github.com/alexazhou/VeryNginx

  • NAXSI,主页地址:https://github.com/nbs-system/naxsi

  • NGX_WAF,主页地址:https://github.com/ADD-SP/ngx_waf

  • 南墙,主页地址:https://waf.uusec.com/

  • JANUSEC,主页地址:https://www.janusec.com/

  • HTTPWAF,主页地址:https://github.com/httpwaf/httpwaf2.0

  • 锦衣盾,主页地址:https://www.jxwaf.com/


对于以上 WAF 产品的一些评价指标如下:


  • 防护效果:主要是两个维度,能不能防住攻击,会不会影响普通用户

  • 技术先进性:防护引擎的技术竞争力,是否具备对抗高级攻击的能力

  • 项目质量:本文将以功能完整性、开源代码质量、文档完整性等角度作为评价依据

  • 社区认可度:反映了项目在用户社区中的声誉和影响力,本文将以 GitHub Star 数作为评价依据

  • 社区活跃度:是潜力的体现,活跃度越高发展越快,本文将以社区用户的参与度和作者维护项目的积极性作为


最终的的得分如下,



需要注意的是软件 WAF 一般在第 7 层中进行防御(osi 模型),并非能够防御所有类型的攻击,比如 ddos 攻击就不能防御。不过一般云厂商提供的 WAF 产品也有携带了 ddos 攻击防御的支持,比如阿里云。

OpenResty 在 WAF 中的应用

使用 OpenResty 作为流量入口时,我们可以通过编写一些 Lua 脚本来实现 WAF 防御的功能。Lua 脚本可以在 Nginx 配置文件中指定,在不同的阶段执行。


对于防火墙功能,我们通常可以在 access_by_lua 阶段执行 Lua 脚本,用于匹配请求或响应的头部或内容,并根据匹配结果决定是否放行数据包或返回错误信息。


下面我将给大家演示如何使用 OpenResty 实现一个基于 Lua 的 WAF(Web Application Firewall)功能。用来识别和阻止常见的 Web 攻击,如 cc 防御、ip 黑名单、ua 参数校验等。

cc 防御

  1. 修改 nginx.conf 文件,加入 access_by_lua_file cc.lua 指令,


http {  # 声明一个 10m 大小的共享内存 cc_dict  lua_shared_dict cc_dict 10m;  lua_package_path "/usr/local/openresty/nginx/conf/lua/waf/?.lua;/usr/local/openresty/lualib/?.lua;";  ...  server {    listen       88;    server_name  localhost;
# 在access阶段执行 cc 防御插件 access_by_lua_file cc.lua;
location / { ... } }}
复制代码


  1. 新建 cc.lua 脚本,写入以下内容,


-- 获取客户端iplocal function getClientIp()    IP  = ngx.var.remote_addr    if IP == nil then        IP  = "unknown"    end    return IPend
local function denyCC() local uri=ngx.var.uri ccCount=100 ccSeconds=6 local access_uri = getClientIp()..uri local limit = ngx.shared.cc_dict local req,_=limit:get(access_uri) if req then if req > ccCount then ngx.exit(503) return true else limit:incr(access_uri,1) end else limit:set(access_uri,1,ccSeconds) end return falseend
if denyCC() then returnend
复制代码


  1. 重启 OpenResty 服务,就完成了 cc 防御功能。


openresty -s  reload
复制代码

ip 黑名单

  1. 修改 nginx.conf 文件,加入 access_by_lua_file ip_block.lua 指令,


http {  lua_package_path "/usr/local/openresty/nginx/conf/lua/waf/?.lua;/usr/local/openresty/lualib/?.lua;";  ...  server {    listen       88;    server_name  localhost;
# 在access阶段执行 ip_block 防御插件 access_by_lua_file ip_block.lua;
location / { ... } }}
复制代码


  1. 新建 ip_block.lua 脚本,写入以下内容,


local cjson = require "cjson"
local function read_json(var) file = io.open(var,"r") if file==nil then return end str = file:read("*a") file:close() list = cjson.decode(str) return listend
local function getClientIp() IP = ngx.var.remote_addr if IP == nil then IP = "unknown" end return IPend
local function blockIpCheck() local ipBlockList=read_json('/usr/local/openresty/nginx/conf/lua/waf/ip_block.json') if next(ipBlockList) ~= nil then for _,ip in pairs(ipBlockList) do if getClientIp()==ip then ngx.exit(403) return true end end end return falseend
if blockIpCheck() then returnend
复制代码


  1. /usr/local/openresty/nginx/conf/lua/waf 目录下新建 ip_block.json 文件,写入我们要加入黑名单的 ip,


["58.48.224.7"]
复制代码


  1. 重启 OpenResty 服务,就完成了 ip 黑名单功能。


openresty -s  reload
复制代码

ua 拦截

  1. 修改 nginx.conf 文件,加入 access_by_lua_file ua.lua 指令,


http {  lua_package_path "/usr/local/openresty/nginx/conf/lua/waf/?.lua;/usr/local/openresty/lualib/?.lua;";  ...  server {    listen       88;    server_name  localhost;
# 在access阶段执行 ua 防御插件 access_by_lua_file ua.lua;
location / { ... } }}
复制代码


  1. 新建 ua.lua 脚本,写入以下内容,


local ngxMatch=ngx.re.matchlocal cjson = require "cjson"
local function read_json(var) file = io.open(var,"r") if file==nil then return end str = file:read("*a") file:close() list = cjson.decode(str) return listend
function ua() local ua = ngx.var.http_user_agent local userAgents=read_json('/usr/local/openresty/nginx/conf/lua/waf/user_agent.json') if next(userAgents) ~= nil then for _,rule in pairs(userAgents) do if rule ~="" and ngxMatch(ua,rule,"isjo") then ngx.exit(403) return true end end end return falseend
if ua() then returnend
复制代码


  1. /usr/local/openresty/nginx/conf/lua/waf 目录下新建 user_agent.json 文件,写入我们要加入黑名单的 ua 信息,


["Chrome/116.0.0.0"]
复制代码


  1. 重启 OpenResty 服务,就完成了 ua 拦截功能。


openresty -s  reload
复制代码

相关资料

  • OpenResty 官网:https://openresty.org/cn/benchmark.html

  • 菜鸟教程:https://www.runoob.com/lua/lua-tutorial.html

  • 《OpenResty 完全开发指南》:https://weread.qq.com/web/bookDetail/fec3240071848696fec3572

  • 《OpenResty 从入门到实战》:https://time.geekbang.org/column/intro/186?code=hkx6qkdp47iccvn0yf40aowqzyzzchyykmswfogb90g%3D

总结

自此本文介绍了 OpenResty 入门以及使用 Lua 脚本实现一些常见的网关安全功能等。需要注意的就是大家在已有的 Nginx 服务迁移到 OpenResty 上来时,记得注意 OpenResty 版本,Nginx 与 OpenResty 相同版本情况下,OpenResty 官方是保证完全兼容的。


最后感谢大家阅读,希望本文能对你有所帮助。


关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

发布于: 2023-10-15阅读数: 2
用户头像

waynaqua 2020-03-10 加入

五年经验后端开发,混迹中小厂

评论

发布
暂无评论
OpenResty 入门以及 WAF 防御实战_nginx_越长大越悲伤_InfoQ写作社区