文章目录
- Pre
- [Nginx 下载](#Nginx 下载)
- `content_by_lua_file`
- 实战
- 小结
Pre
指令 | 所处处理阶段 | 使用范围 | 解释 |
---|---|---|---|
init_by_lua / init_by_lua_file |
loading-config | http | Nginx Master进程加载配置时执行;通常用于初始化全局配置/预加载Lua模块 |
init_worker_by_lua / init_worker_by_lua_file |
starting-worker | http | 每个Nginx Worker进程启动时调用的计时器,如果Master进程不允许则只会在init_by_lua 之后调用;通常用于定时拉取配置/数据,或者后端服务的健康检查 |
set_by_lua / set_by_lua_file |
rewrite | server, server if, location, location if | 设置Nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快。 |
rewrite_by_lua / rewrite_by_lua_file |
rewrite | http, server, location, location if | rewrite阶段处理,可以实现复杂的转发/重定向逻辑。 |
access_by_lua / access_by_lua_file |
access | http, server, location, location if | 请求访问阶段处理,用于访问控制。 |
content_by_lua / content_by_lua_file |
content | location, location if | 内容处理器,接收请求处理并输出响应。 |
header_filter_by_lua / header_filter_by_lua_file |
output-header-filter | http, server, location, location if | 设置header和cookie。 |
body_filter_by_lua / body_filter_by_lua_file |
output-body-filter | http, server, location, location if | 对响应数据进行过滤,比如截断、替换。 |
log_by_lua / log_by_lua_file |
log | http, server, location, location if | log阶段处理,比如记录访问量/统计平均响应时间。 |
Nginx - 整合lua 实现对POST请求的参数拦截校验(不使用Openresty) 中我们不仅要修改lua脚本,nginx.cnf也需要跟着调整,比较繁琐,这里我们借助 content_by_lua_file
来优化一下,达到仅修改lua脚本就可以完成业务校验的目的
Nginx 下载
Nginx 1.27.1 + x86 + lua + gmssl
java
配置lua_package_path
nginx/x86/lua_package_path 目录下
cd lua-resty-core-master/ && make install PREFIX=/opt/nginx
cd lua-resty-lrucache-master/ && make install PREFIX=/opt/nginx
配置nginx.conf
nginx.conf
http {
lua_package_path "/opt/nginx/lib/lua/?.lua;;";
lua_package_cpath "/root/nginx/sbin/lib/lua/5.1/?.so;/root/nginx/sbin/lib/?.so;;";
}
启动nginx
export LD_LIBRARY_PATH=./lib/
./nginx -p ../ -c conf/nginx.conf
content_by_lua_file
content_by_lua_file
是 Nginx 的 ngx_http_lua_module
模块提供的一条指令,它允许 Nginx 在处理请求时,执行一个外部的 Lua 脚本文件。此指令通常用于在请求处理的 content phase(内容阶段)中,执行自定义的 Lua 代码,从而动态生成响应内容。
当请求到达 Nginx 配置中定义的 location
或其他处理块时,Nginx 会加载并执行指定路径的 Lua 文件,这样你可以用 Lua 脚本来完成诸如内容生成、响应修改、请求处理等操作。
使用场景
- 动态内容生成:在不依赖外部应用程序的情况下,生成动态响应内容(如 JSON、HTML、XML 等)。
- 数据校验与处理:通过 Lua 脚本对请求进行复杂的数据处理、验证和修改。
- 高级请求路由与控制:可以基于请求的参数或其他条件来控制请求的处理逻辑,甚至实现负载均衡或重定向。
- 缓存与优化:在 Lua 脚本中操作 Nginx 的缓存、共享内存等机制,做一些高级的缓存策略或优化工作。
基本语法
java
location /some/path {
content_by_lua_file /path/to/your/lua/script.lua;
}
content_by_lua_file
指令指定了一个外部 Lua 文件的路径,当请求匹配这个 location
时,Nginx 会执行该 Lua 文件中的代码,并生成响应内容。
详细说明
- Nginx Content Phase :
content_by_lua_file
指令属于 content phase ,这意味着它会在请求处理的最后阶段执行,也就是当请求已经匹配到某个location
时,Nginx 会处理响应内容。 - 外部 Lua 文件:指定的是 Lua 脚本文件的路径,通常需要提供绝对路径或者相对路径,确保 Nginx 能找到这个文件。
- Lua 解释器 :
ngx_http_lua_module
会自动启动 Lua 解释器来执行该脚本,执行过程通常是非阻塞的,因此适合高并发环境。
工作原理
- 请求到达 Nginx 时,Nginx 根据配置的
location
查找匹配的路径。 - 如果请求的
location
配置了content_by_lua_file
,则 Nginx 会在 content phase 加载并执行指定的 Lua 文件。 - Lua 文件中的代码执行后会返回生成的响应内容,Nginx 会把该内容作为 HTTP 响应返回给客户端。
示例
假设有一个 Lua 文件 validate_post.lua
,需要对 POST 请求进行校验并生成相应的响应。
- 配置 Nginx
nginx
server {
listen 80;
location /submit {
content_by_lua_file /path/to/your/lua/validate_post.lua;
}
}
- Lua 文件
validate_post.lua
lua
-- validate_post.lua
-- 读取请求体
ngx.req.read_body()
local args = ngx.req.get_post_args()
-- 校验必填参数
if not args.name or not args.email then
ngx.status = ngx.HTTP_BAD_REQUEST
ngx.say("Missing required parameters: name or email")
return
end
-- 校验邮箱格式
if not string.match(args.email, "^[a-zA-Z0-9_+&*-]+%.[a-zA-Z0-9_+&*-]+@([a-zA-Z0-9-]+%.)+[a-zA-Z]{2,7}$") then
ngx.status = ngx.HTTP_BAD_REQUEST
ngx.say("Invalid email format")
return
end
-- 校验通过
ngx.say("Request is valid!")
content_by_lua_file
指令会加载validate_post.lua
文件并执行其中的代码。- Lua 脚本中会校验请求的 POST 参数
name
和email
,如果校验失败,则返回 400 错误和相应的错误消息;如果校验通过,则返回 "Request is valid!"。
优点
- 高性能:Lua 脚本在 Nginx 内部运行,性能非常高,适合高并发环境。
- 灵活性:通过 Lua 脚本可以实现复杂的逻辑,而无需依赖外部应用服务器。
- 易于管理:将 Lua 脚本分离成独立的文件,便于管理和维护。
- 无缝集成:可以与现有的 Nginx 配置结合,避免了额外的服务或依赖。
注意事项
- 错误处理 :需要确保 Lua 脚本中有适当的错误处理机制。由于 Lua 脚本的执行可能会出现错误(例如参数解析失败),应该使用
ngx.status
和ngx.say
返回清晰的错误信息。 - 性能影响:尽管 Lua 本身非常高效,但如果 Lua 脚本过于复杂,或者需要频繁的 I/O 操作,可能会影响 Nginx 的性能。在设计时应考虑性能瓶颈。
- 依赖 Lua 模块 :需要在编译 Nginx 时加入
ngx_http_lua_module
模块,或者通过 OpenResty 来使用这个功能。
实战
漏洞分析
从描述中可以看出,这是一个典型的 NoSQL 注入漏洞,攻击者通过构造恶意输入,试图绕过应用程序的逻辑,直接操作数据库。
NoSQL 注入是一种利用应用程序对用户输入处理不当,向 NoSQL 数据库注入恶意操作符(如 $ne
、$gt
等)的攻击方式。攻击者可以通过这种方式绕过应用程序的逻辑,直接操作数据库,导致数据泄露、篡改或删除。
攻击者通过构造恶意 JSON 数据,将 page
、startDate
和 endDate
参数的值替换为 NoSQL 操作符(如 $ne
),试图绕过应用程序的逻辑校验。
恶意请求示例:
json
{
"page": { "$ne": "1" },
......
......
"startDate": "2024-06-{ \"$ne\": \"1\" }9",
"endDate": "2024-{ \"$ne\": \"1\" }2-{ \"$ne\": \"1\" }9"
}
响应结果:
- 服务器返回了
500
错误,并提示 JSON 解析失败。 - 虽然本次攻击未成功,但暴露了应用程序对输入参数的处理存在安全隐患。
借助 Nginx + Lua 修复
Nginx 是一个高性能的 Web 服务器和反向代理服务器,而 Lua 是一种轻量级、高效的脚本语言。通过将 Lua 嵌入到 Nginx 中,我们可以在请求到达后端应用之前,对用户输入进行实时检测和过滤,从而有效防御 NoSQL 注入攻击。
1. 环境准备
确保 Nginx 已安装并支持 Lua 模块。
2. Nginx 配置
在 Nginx 配置文件中,添加 Lua 脚本的路径和共享内存区域,并配置 Lua 脚本处理请求。
nginx
user root;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# 定义共享内存区域,用于 Lua 脚本缓存
lua_shared_dict my_cache 10m;
# 设置 Lua 模块搜索路径
lua_package_path "/opt/nginx/lib/lua/?.lua;;";
lua_package_cpath "/root/nginx/sbin/lib/lua/5.1/?.so;/root/nginx/sbin/lib/?.so;;";
# 启用请求体读取
lua_need_request_body on;
server {
listen 8989;
server_name localhost;
location /api {
# 使用 Lua 脚本处理请求
content_by_lua_file /opt/nginx/lib/lua/business_handler.lua;
}
}
}
3. Lua 脚本实现
在 Lua 脚本中,实现对请求体的解析和 NoSQL 注入检测。
business_handler.lua
lua
local inject_check = require "mongo_inject_check"
-- 业务处理主函数
local function handle_request()
-- 记录请求日志
inject_check.log_request()
-- 进行注入检查
if not inject_check.validate_request_body() then
return ngx.exit(ngx.HTTP_BAD_REQUEST)
end
-- 正常处理请求
ngx.status = ngx.HTTP_OK
ngx.header.content_type = "application/json"
ngx.say('{"status": "OK"}')
end
-- 执行请求处理
handle_request()
mongo_inject_check.lua
lua
local json = require("cjson")
local _M = {}
-- 检查是否包含 MongoDB 操作符
local function check_mongo_operators(str)
local operators = {
"$ne", "$gt", "$lt", "$gte", "$lte",
"$in", "$nin", "$regex", "$where", "$exist"
}
str = string.lower(str)
for _, op in ipairs(operators) do
if string.find(str, op, 1, true) then
ngx.log(ngx.WARN, "Operator injection detected: " .. op)
return false
end
end
return true
end
-- 主验证函数
function _M.validate_request_body()
-- 读取请求体
ngx.req.read_body()
local body = ngx.req.get_body_data()
if not body then
ngx.log(ngx.ERR, "Empty request body")
ngx.status = ngx.HTTP_BAD_REQUEST
ngx.header.content_type = "application/json"
ngx.say(json.encode({
status = ngx.HTTP_BAD_REQUEST,
message = "Empty request body"
}))
return false
end
-- 检查请求体中是否包含 MongoDB 操作符
local is_valid = check_mongo_operators(body)
if not is_valid then
ngx.log(ngx.ERR, "Potential injection detected")
ngx.status = ngx.HTTP_BAD_REQUEST
ngx.header.content_type = "application/json"
ngx.say(json.encode({
status = ngx.HTTP_BAD_REQUEST,
message = "Potential injection detected"
}))
return false
end
return true
end
-- 记录请求日志
function _M.log_request()
local request_method = ngx.var.request_method
local request_uri = ngx.var.request_uri
local client_ip = ngx.var.remote_addr
ngx.log(ngx.INFO, string.format(
"Request - Method: %s, URI: %s, IP: %s",
request_method, request_uri, client_ip
))
end
return _M
测试与验证
恶意请求
NGINX ERROR日志如下
小结
通过 Nginx + Lua 的组合,我们可以在网关层实现对 NoSQL 注入攻击的有效防御。这种方案具有以下优势:
- 高性能:Lua 脚本轻量高效,对请求处理性能影响极小。
- 灵活性:可以根据具体需求扩展检测逻辑。
- 安全性:在请求到达后端应用之前完成检测,有效降低攻击风险。