Nginx+Lua实现SQL注入防护:轻量级WAF配置与实战指南
1. 项目概述最近在梳理线上Web服务的防护策略发现很多针对数据库的直接攻击SQL注入依然是绕不开的“老朋友”。虽然应用层有ORM框架和参数化查询但在网关层面加一道防线总归是多一份安心。我选择了在Nginx这一层用Lua脚本来实现一个轻量级的SQL注入防火墙。这个方案的好处是性能损耗极低部署灵活不侵入业务代码能拦截掉大部分自动化扫描和初级攻击。如果你也在用Nginx做反向代理或Web服务器想在不改动后端代码的情况下增强安全性那这套配置思路值得你花十分钟了解一下。简单说这个配置的核心就是利用Nginx的ngx_http_lua_module模块在请求到达后端PHP、Java或Python应用之前对请求的URL参数、POST数据、Cookie甚至User-Agent进行实时检测一旦发现疑似SQL注入的特征就直接在Nginx层面返回403禁止访问攻击流量根本到不了你的应用服务器。下面我就把从环境准备、规则配置到调试优化的完整过程拆开揉碎了讲给你听。2. 核心思路与架构设计2.1 为什么选择Nginx Lua首先得想明白防SQL注入的手段很多为什么偏偏选这个组合从我的经验看主要是三个原因性能、灵活性和无侵入性。性能Nginx本身以高性能著称而Lua脚本在Nginx中运行是通过LuaJIT即时编译的速度非常快。将过滤逻辑放在Nginx这一层相比在每一个后端应用里都写一遍过滤逻辑或者引入一个沉重的Web应用防火墙WAF硬件其资源消耗几乎可以忽略不计。对于高并发场景这一点至关重要。灵活性Lua语法简洁规则可以写得非常灵活。你可以轻松地自定义检测规则比如针对自家业务特有的API参数进行特殊处理或者快速响应新型的攻击模式。今天看到一个新的SQL注入绕过技巧明天就能把对应的正则规则更新上去响应速度比等待商业WAF厂商更新规则库要快得多。无侵入性这是我最看重的一点。你不需要修改任何后端Java、Go或PHP的代码。所有的防护逻辑都前置在Nginx配置里。对于维护一个庞大且历史悠久的系统来说这种“手术刀”式的方案风险最低。即使防护规则出了点小问题比如误拦截了正常请求也只需要回滚Nginx配置不会影响核心业务逻辑。2.2 整体防护流程设计整个防火墙的工作流程可以想象成一道安检门。当一个HTTP请求到达Nginx时会依次经过以下几个检查点白名单检查首先核对请求的IP、URL是否在信任的白名单内。如果是直接放行后续所有检查跳过。这通常用于放行监控系统、内部API调用或已知安全的爬虫。黑名单检查检查请求IP是否在已知的攻击者黑名单中。如果是直接拒绝。这个名单可以动态更新比如将短时间内触发多次拦截的IP加入其中。SQL注入规则检测这是核心环节。对请求的多个部分进行扫描$args(GET查询参数)如?id1中的1。$request_uri(原始请求URI)包含参数的完整路径。$request_body(POST请求体)如表单提交的JSON或x-www-form-urlencoded数据。$http_cookie(Cookie内容)。$http_user_agent(浏览器标识)。 将这些内容与预定义的SQL注入特征正则表达式进行匹配。处置动作一旦在任何环节匹配到攻击特征立即执行预设动作。通常是直接返回403 Forbidden并记录日志。更高级的可以返回一个伪造的错误页面或者将请求重定向到一个“蜜罐”页面来拖延攻击者。整个流程在access_by_lua_block阶段执行这是Nginx处理请求的早期阶段能在请求进入后端之前就完成拦截最大化地节省后端资源。2.3 工具与组件选型这里我们不从零造轮子而是基于一个成熟的开源项目进行定制。我选择的是loveshell/ngx_lua_waf。这个项目在Github上Star数不少社区活跃规则也比较全面。它本身提供了防SQL注入、XSS、文件包含等多种Web攻击的规则我们主要取其SQL注入防护部分并根据自身业务进行裁剪和强化。注意直接使用开源WAF规则可能存在误报。商业WAF的规则库经过大量线上流量训练和人工修正误报率控制得很好。而开源规则更偏向于“宁可错杀不可放过”我们需要花时间调整避免影响正常用户。你需要确保的Nginx环境支持Lua模块。通常通过包管理器安装的nginx-full或自行编译时加入--with-http_lua_module参数都可以。我们接下来的操作基于Ubuntu/Debian系统CentOS/RHEL系列在包名上略有不同但思路完全一致。3. 环境准备与核心配置详解3.1 Nginx与Lua模块安装如果你的系统还没有Nginx或者现有的Nginx不支持Lua我们需要先搞定环境。对于Ubuntu/Debian# 更新软件包列表 sudo apt-get update # 安装包含Lua模块的Nginx版本。通常nginx-extras包会包含更多模块。 sudo apt-get install nginx-extras lua5.3 liblua5.3-dev # 验证Nginx安装及版本确认包含http_lua_module sudo nginx -V 21 | grep lua如果输出中包含--with-http_lua_module说明安装成功。对于CentOS/RHELEPEL源中的Nginx可能默认不包含Lua模块。更可靠的方式是通过OpenResty的Yum源安装OpenResty它包含了增强的Nginx和LuaJIT。或者如果你必须使用标准Nginx就需要手动编译。# 添加OpenResty仓库推荐一站式解决 sudo yum install yum-utils sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo sudo yum install openresty # OpenResty的Nginx二进制文件通常是openresty配置文件路径也可能不同如/usr/local/openresty/nginx/conf/请注意调整后续命令。安装完成后先启动Nginx并设置开机自启sudo systemctl start nginx sudo systemctl enable nginx访问你的服务器IP应该能看到Nginx的欢迎页面。3.2 部署ngx_lua_waf防火墙规则接下来我们获取并部署核心的防火墙规则。# 1. 切换到合适的目录比如Nginx配置目录下 cd /etc/nginx # 2. 克隆WAF项目 sudo git clone https://github.com/loveshell/ngx_lua_waf.git waf # 3. 进入目录查看结构 cd /etc/nginx/waf ls -la你会看到类似以下结构config.lua # 主配置文件 init.lua # 初始化脚本 wafconf/ # 规则目录 ├── args # GET参数规则 ├── post # POST数据规则 ├── url # URL路径规则 ├── user-agent # User-Agent规则 ├── cookie # Cookie规则 └── whiteurl # URL白名单3.3 核心配置文件解析与调优现在我们来仔细看看config.lua这是防火墙的大脑。-- 配置文件通常开头是这样的 RulePath /etc/nginx/waf/wafconf/ -- 规则存放路径 attacklog on -- 是否开启攻击日志 logdir /var/log/nginx/hack/ -- 攻击日志存放目录 UrlDenyon -- 是否拦截URL攻击 Redirecton -- 是否重定向攻击请求 CookieMatchon -- 是否检查Cookie postMatchon -- 是否检查POST数据 whiteModuleon -- 是否开启URL白名单 black_fileExt{php,jsp} -- 禁止访问的文件扩展名 ipWhitelist{127.0.0.1} -- IP白名单 ipBlocklist{1.0.0.1} -- IP黑名单 CCDenyoff -- 是否开启CC攻击防护 CCrate100/60 -- CC防护阈值60秒内100次请求你需要重点调整的几个地方RulePath确认这个路径和你实际放置wafconf目录的路径一致。logdir确保这个目录存在且Nginx进程用户通常是www-data或nginx有写入权限。sudo mkdir -p /var/log/nginx/hack/ sudo chown -R www-data:www-data /var/log/nginx/hack/ # 根据你的Nginx用户调整black_fileExt根据你的业务调整。如果你的网站就是提供.php文件访问那肯定不能加php。这里通常用于防止访问某些敏感备份文件如.bak,.sql,.tar.gz等。我一般会加上black_fileExt{php, jsp, asp, aspx, bak, sql, tar.gz, zip, rar, old, swp}注意这里对.php等的拦截指的是直接通过URL访问这些源文件。如果你的网站是PHP动态网站用户访问的是/index.php这种由Nginx交给PHP-FPM处理的路径不会触发此拦截。此规则主要用于防止扫描器探测/index.php.bak这类备份文件。ipWhitelist把你的管理服务器IP、监控系统IP加进去避免自己的操作被拦截。CCDeny和CCrateCC攻击防护对于防刷接口、防爬虫很有用但初期建议先设为off。等SQL注入防护稳定后再根据业务压力情况开启并调整阈值。“100/60”表示60秒内同一IP访问超过100次则触发防护。3.4 将WAF集成到Nginx主配置这是最关键的一步让Nginx在处理每个请求时都调用我们的Lua防火墙。打开你的Nginx站点配置文件通常位于/etc/nginx/sites-available/default或/etc/nginx/conf.d/下的某个文件。在server块中添加以下配置server { listen 80; server_name your_domain.com; # 1. 设置Lua包路径和共享内存用于CC防护等 lua_package_path /etc/nginx/waf/?.lua;;; lua_shared_dict limit 10m; # 定义10M的共享内存区域名为limit # 2. 初始化WAF。这里加载config.lua和init.lua。 init_by_lua_file /etc/nginx/waf/init.lua; # 3. 在访问阶段执行WAF检测 access_by_lua_file /etc/nginx/waf/waf.lua; # 你原有的location配置比如根目录或反向代理 location / { root /var/www/html; index index.html index.htm; # 如果你的后端是PHP可能需要这样的配置 # try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; } # 4. 可选为拦截请求自定义错误页面 error_page 403 /403.html; location /403.html { root /var/www/html; internal; # 标记为内部位置只能由Nginx内部重定向访问 } }配置解析与注意事项lua_package_path告诉Nginx去哪里找我们写的Lua模块。;;表示在原有路径后追加。init_by_lua_file在NginxMaster进程启动时执行一次用于加载全局配置和规则。这里加载init.lua它会读取config.lua和wafconf/下的规则文件到内存中。规则修改后必须重载或重启Nginx才能生效。access_by_lua_file在Nginx处理请求的access阶段对每一个请求执行waf.lua。这是执行检测和拦截逻辑的地方。lua_shared_dict定义了一块所有NginxWorker进程之间共享的内存。这对于实现IP级别的频率限制CC防护是必须的因为每个Worker进程独立需要共享计数信息。error_page 403当WAF拦截请求时Nginx会返回403状态码。这里配置了一个自定义的403页面提升用户体验或者迷惑攻击者。记得创建/var/www/html/403.html这个文件。配置完成后一定要测试配置文件语法是否正确sudo nginx -t如果显示“syntax is ok”和“test is successful”就可以安全地重载配置了sudo systemctl reload nginx重载reload是平滑重启不会断开现有连接是线上服务更新的推荐方式。4. SQL注入规则深度解析与定制开源WAF的规则是基础但直接使用往往误报率不低。我们必须理解其原理并学会调整。4.1 规则文件结构与语法打开一个规则文件例如/etc/nginx/waf/wafconf/args用于检测GET参数(?:updatexml|extractvalue|exp|geometrycollection|polygon|multipoint|linestring|multilinestring|multipolygon)\s*\( select.(from|limit) (?:(union(.*?)select)) having|sleep|benchmark\s*\( \b(?:d(?:eclare|rop)|i(?:nsert|nto)|create|exec)\b [\s\S]*?(?:--|#|\/\*)每一行都是一个正则表达式。当请求参数中的字符串匹配上任何一行时就会被判定为攻击。常见规则模式解读关键字检测如select.from、union.*?select、insert into、drop table等。这是最基础的检测。函数与操作检测如updatexml()、extractvalue()是MySQL的报错注入函数sleep()、benchmark()是时间盲注函数exp()是溢出报错注入函数。注释符检测如--、#、/* */SQL注入中常用注释符来截断后续SQL语句。编码与混淆绕过检测一些高级规则会包含十六进制、URL编码、双重编码的变种。例如SELECT可能被写为SEL%45CTURL编码或0x53454c454354十六进制。开源规则在这方面通常较弱。4.2 如何降低误报调整与白名单误报是自建WAF最大的痛点。比如一个新闻网站有一篇标题为《Sleep函数在编程中的使用》的文章URL可能是/article?id123文章内容里包含“sleep”这个词。如果规则简单地匹配sleep这个正常请求就会被拦截。调整策略精确化正则表达式避免过于宽泛的匹配。例如将sleep改为sleep\s*\(这样只匹配sleep(函数调用而不是单词本身。使用白名单ngx_lua_waf提供了whiteurl文件URL白名单和config.lua中的ipWhitelistIP白名单。URL白名单在wafconf/whiteurl文件中每行写一个正则表达式匹配的URL将跳过所有WAF检查。例如如果你的搜索接口/api/search允许用户输入复杂查询容易误报可以添加^/api/search$IP白名单在config.lua的ipWhitelist表中添加IP。这对于后端服务器间内部调用、运维管理平台非常有用。注释掉过于激进的规则仔细阅读args、post等文件对于你确认会引发大量误报且防护价值不高的规则可以在行首加--Lua注释符暂时禁用。务必在测试环境充分验证后再操作线上。4.3 增强防护添加自定义规则开源规则可能覆盖不了所有情况你需要根据业务和最新的攻击趋势添加规则。案例防御布尔盲注的LIKE语句一些布尔盲注会使用LIKE子句进行逐字符猜测。我们可以添加规则\blike\s[\%]这个规则匹配like ‘%或like “%这种模式。添加步骤编辑对应的规则文件如/etc/nginx/waf/wafconf/args。在文件末尾新增一行写上你的正则表达式。保存文件。重载Nginx配置sudo systemctl reload nginx。因为规则是在init_by_lua_file阶段加载到内存的修改规则文件后必须重载。重要心得添加新规则后一定要用正常的业务流量进行测试。可以先将Nginx日志级别调为info或debug观察一段时间确保没有误拦截正常请求。也可以编写简单的测试脚本模拟用户行为进行自动化测试。5. 实战测试与效果验证配置好了不测试等于没做。我们需要模拟攻击验证防火墙是否生效同时也要验证正常业务是否畅通。5.1 搭建测试环境为了安全强烈建议在一个独立的测试服务器或虚拟机上操作。安装一个简单的带数据库的Web应用用于测试比如DVWADamn Vulnerable Web Application或者一个自己写的有SQL注入漏洞的PHP页面。例如创建一个简单的测试文件/var/www/html/test.php?php // 警告此代码存在严重SQL注入漏洞仅用于测试切勿用于生产环境 $id $_GET[id] ?? 1; $conn new mysqli(localhost, test_user, test_pass, test_db); if ($conn-connect_error) die(Connection failed: . $conn-connect_error); $sql SELECT * FROM users WHERE id . $id; // 这里没有过滤存在注入 $result $conn-query($sql); if ($result-num_rows 0) { while($row $result-fetch_assoc()) { echo id: . $row[id]. - Name: . $row[name]; } } else { echo 0 results; } $conn-close(); ?5.2 模拟SQL注入攻击使用浏览器或curl命令进行测试基础注入测试尝试访问http://your_server_ip/test.php?id1%20or%2011--%20-这会被规则\bor\b或--匹配到应该返回403。联合查询注入测试http://your_server_ip/test.php?id1%20union%20select%201,2,3--%20-这会被union.*?select规则匹配。报错注入测试http://your_server_ip/test.php?id1%20and%20updatexml(1,concat(0x7e,(select%20user())),1)--%20-这会被updatexml\s*\(规则匹配。时间盲注测试http://your_server_ip/test.php?id1%20and%20sleep(5)--%20-这会被sleep\s*\(规则匹配。观察结果如果配置正确上述请求都应该收到403 Forbidden响应而不是执行SQL语句或返回数据库错误信息。同时检查攻击日志目录/var/log/nginx/hack/应该能看到以日期命名的日志文件里面记录了拦截的详细信息包括攻击IP、时间、拦截的URL、匹配的规则等。这是事后审计和攻击分析的重要依据。5.3 验证正常业务请求这是更关键的一步确保你的防护不会“误伤友军”。测试正常请求访问http://your_server_ip/test.php?id1。这应该能正常返回数据库中的结果如果数据库里有id1的用户。测试边界情况参数包含正常英文单词如/search?qsleep搜索“sleep”。参数包含数字和特殊符号如/api/data?range1-100。POST请求提交JSON数据如{username: admin--}注意这里的单引号和注释符可能会被误判。你需要根据业务判断是否将其加入白名单或调整规则。测试方法手动测试用浏览器和开发者工具逐个测试关键业务接口。自动化测试使用Postman、JMeter或编写Python脚本模拟用户会话遍历主要功能点。监控日志在测试期间密切观察Nginx的错误日志(/var/log/nginx/error.log)和WAF攻击日志。如果发现大量403错误来自真实用户IP说明存在误报需要立即排查。6. 高级调优与运维管理配置上线只是开始持续的运维和调优才能让这套防火墙真正发挥作用。6.1 性能监控与影响评估虽然Lua WAF很轻量但仍需关注其性能影响。监控Nginx指标请求吞吐量 (RPS)对比开启WAF前后的变化。平均响应时间关注是否因规则匹配而增加。Nginx Worker进程CPU和内存使用率使用top或htop命令查看nginxworker进程的资源消耗。使用ngx.log进行调试在waf.lua的关键逻辑处添加日志可以了解匹配过程和耗时。但生产环境要谨慎避免日志泛滥。ngx.log(ngx.NOTICE, Checking args: , ngx.var.args)压力测试使用wrk或ab工具对关键接口进行压测对比开启/关闭WAF时的性能数据。wrk -t12 -c400 -d30s http://your_server_ip/test_normal_page6.2 规则动态更新与版本管理规则需要与时俱进。你需要建立一个更新流程。订阅安全情报关注OWASP、CNVD、安全厂商博客了解新型SQL注入手法。测试新规则在测试环境验证新规则的有效性和误报率。版本化管理配置将/etc/nginx/waf/目录纳入Git版本控制。每次修改规则或配置都进行提交方便回滚和审计。自动化更新谨慎可以编写脚本定期从可信源拉取规则更新并在测试后自动应用到生产环境。但务必设置人工审核环节自动化更新规则风险极高。6.3 与其他安全措施联动Nginx Lua WAF是纵深防御体系中的一层不应是唯一的一层。与网络防火墙联动当WAF在短时间内拦截同一个IP多次后可以通过Lua脚本调用系统命令如iptables或API将该IP加入系统层面的防火墙黑名单实现更长时间的封禁。注意在Nginx Worker中执行系统命令有性能和安全风险需谨慎设计通常建议通过日志分析工具如FluentdElasticsearch离线分析后再通过脚本批量操作iptables。日志分析与告警将/var/log/nginx/hack/的日志接入ELKElasticsearch, Logstash, Kibana或Splunk等日志分析平台。可以设置告警规则例如同一IP在1分钟内触发超过10次SQL注入拦截则发送邮件或短信告警。作为反向代理时的配置如果你的Nginx主要用作反向代理proxy_pass到后端应用服务器WAF配置的位置至关重要。必须确保access_by_lua_file指令在location块中且在proxy_pass指令之前执行。location /yourapp/ { access_by_lua_file /etc/nginx/waf/waf.lua; # 先执行WAF检查 proxy_pass http://backend_server; # 通过检查后再转发 proxy_set_header Host $host; ... }7. 常见问题排查与解决实录在实际部署和运维中我踩过不少坑。这里把典型问题和解决方法列出来希望能帮你节省时间。7.1 WAF完全不生效攻击请求畅通无阻症状模拟的SQL注入攻击可以正常执行返回数据库结果或错误Nginx没有返回403。排查步骤检查Nginx错误日志sudo tail -f /var/log/nginx/error.log。在测试攻击时观察是否有Lua相关的错误信息。最常见的是Lua模块未加载或脚本语法错误。验证Lua模块确保nginx -V输出中包含--with-http_lua_module。如果不包含你需要重新编译Nginx或安装nginx-extras包。检查配置文件语法运行sudo nginx -t确保没有语法错误。特别注意lua_package_path和文件路径是否正确。检查WAF日志目录权限确保/var/log/nginx/hack/目录存在且Nginx进程用户如www-data对其有写权限。权限问题会导致WAF初始化失败而静默退出。在WAF脚本中加日志临时在waf.lua文件开头添加ngx.log(ngx.ERR, WAF script loaded and executing)重载Nginx后访问页面看错误日志中是否有这条记录。如果没有说明脚本根本没被执行检查access_by_lua_file指令的位置和路径。7.2 误报太多正常请求被拦截症状用户反馈无法登录、搜索失败、页面显示403。排查步骤定位被拦截的请求查看/var/log/nginx/hack/下的日志找到被拦截的正常请求记录。日志里会写明匹配到的规则内容和拦截部分args/post/url等。分析匹配规则根据日志中的规则内容去对应的规则文件如args里找到该行正则表达式。理解误报原因分析正常请求的参数为何会匹配攻击规则。例如用户昵称叫“Union”在查询时参数?nameUnion可能触发union规则。解决方案优化规则使正则更精确。例如将union改为union\sselect。添加白名单如果这个误报的接口是固定的将其URL添加到wafconf/whiteurl白名单文件中。临时禁用规则如果某条规则过于激进且误报频繁可以在行首加--注释掉但必须评估安全风险。测试验证修改后重载Nginx并复现之前被误报的用户操作确认问题解决。7.3 性能明显下降服务器负载升高症状开启WAF后网站响应变慢服务器CPU使用率上升。排查步骤检查规则复杂度过于复杂的正则表达式尤其是包含[\s\S]*?这种贪婪匹配且范围广的会严重消耗CPU。使用工具测试正则表达式的效率。检查规则数量wafconf目录下每个文件的行数是否过多成千上万条规则逐行匹配开销必然大。定期清理过期、无效或重复的规则。使用ngx.location.capture进行子请求有些WAF实现会为了获取POST数据而发起子请求这是非常耗性能的操作。确保你的WAF脚本是直接读取ngx.var.request_body需要显式配置lua_need_request_body on;。进行性能剖析可以使用OpenResty的ngx-lua-profiler工具或者简单地在代码前后记录时间戳找出最耗时的检测函数或规则。优化策略分阶段检测先进行快速、简单的关键字匹配如union、select如果匹配上再进行更复杂的正则匹配。设置检测开关在config.lua中为不同的检测项如postMatch、CookieMatch提供开关对不必要检查的请求类型可以关闭。升级硬件如果经过优化后性能仍不满足要求考虑升级服务器CPU。7.4 攻击日志没有记录症状请求被拦截返回403但/var/log/nginx/hack/目录下没有日志文件。排查步骤检查config.lua设置确认attacklog on并且logdir路径正确。检查目录权限这是最常见的原因。确保日志目录存在且Nginx进程用户有写入权限。可以手动切换用户测试sudo -u www-data touch /var/log/nginx/hack/test.log。检查磁盘空间使用df -h命令确保磁盘未满。查看Nginx错误日志在写入攻击日志时发生错误如权限不足会在Nginx错误日志中体现。7.5 重载Nginx后配置不生效症状修改了config.lua或wafconf/下的规则文件后执行sudo systemctl reload nginx但新规则似乎没起作用。原因与解决规则是在Nginx启动时由init_by_lua_file加载到内存中的。reload操作会重新加载配置文件但不会重新执行init_by_lua_fileMaster进程初始化只发生一次。修改规则后需要重启Nginx服务sudo systemctl restart nginx。注意重启会断开现有连接对线上服务有影响。建议在低峰期操作或者采用双机热备轮流重启的方式。也可以考虑将规则存储在Redis等外部缓存中实现动态加载但这会显著增加架构复杂度。这套Nginx Lua防火墙的配置从原理到实践从部署到调优基本就这些内容了。它不是一个一劳永逸的银弹而是一个需要你持续维护和调整的“活”系统。初期花费时间调试规则、处理误报是不可避免的但一旦稳定下来它就能为你后端的应用服务器提供一个安静、可靠的防护屏障。我的体会是安全是一个过程这套自建WAF的方案最大的价值在于让你更贴近流量更了解攻击者的手法从而在整体安全架构上做出更明智的决策。