1. 项目概述为什么我们需要一个轻量级WAF在今天的网络环境中Web应用防火墙WAF早已不是大型企业的专属。无论是个人博客、小型电商站点还是内部的管理系统都面临着SQL注入、跨站脚本XSS、路径遍历等常见攻击的威胁。传统的商业WAF方案固然强大但往往伴随着高昂的成本和复杂的部署流程对于个人开发者或中小团队来说显得有些“杀鸡用牛刀”。这时一个基于开源组件、能够快速部署且性能可控的WAF方案就显得极具吸引力。OpenResty这个基于Nginx和LuaJIT的高性能Web平台为我们提供了绝佳的土壤。它允许我们直接在Nginx的请求处理流程中嵌入Lua脚本这意味着我们可以用极低的性能损耗实现请求和响应的深度检查与过滤这正是WAF的核心功能。而ModSecurity作为一款久经考验的开源WAF引擎其庞大的规则库如OWASP CRS是防御已知威胁的宝贵财富。所以这个项目的核心价值在于利用OpenResty的高性能处理能力作为“引擎”结合ModSecurity的成熟规则库作为“武器库”在5分钟级别的时间内搭建一个属于你自己的、可高度定制化的开源WAF。它不仅能有效拦截常见Web攻击还能让你深入理解WAF的工作原理为后续的规则定制和性能调优打下基础。无论你是运维工程师、安全爱好者还是希望为自己项目增加一道安全防线的开发者这个实践都极具意义。2. 核心组件选型与架构解析在动手之前我们必须搞清楚几个核心组件的关系以及为什么选择它们。这决定了我们搭建的WAF是否高效、稳定。2.1 OpenResty不只是Nginx很多人会把OpenResty简单地理解为“Nginx的Lua增强版”这其实低估了它的能力。OpenResty的核心是将Nginx变成了一个完整的Web应用服务器。它通过ngx_lua模块将LuaJIT虚拟机无缝嵌入到Nginx的各个请求处理阶段如access_by_lua*,body_filter_by_lua*,log_by_lua*。这意味着你可以在请求生命周期的几乎任何时刻执行自定义的Lua逻辑。对于WAF来说这个特性是革命性的。我们可以在access阶段认证和权限检查执行请求头、请求URI的检测在rewrite阶段进行URL重写和攻击特征匹配更重要的是我们可以通过access_by_lua*在请求体还未被上游应用读取时就完成对POST数据等内容的检查一旦发现攻击立即返回403等状态码阻断请求而无需将恶意流量传递到后端。这种“请求边处理边检查”的模式是高性能WAF的基石。选择OpenResty而非原生NginxLua模块手动编译主要是因为其开箱即用和生态完整。OpenResty的发行版已经集成了数十个常用的Nginx模块和Lua库省去了繁琐的依赖管理和编译过程让我们能专注于WAF逻辑本身。2.2 ModSecurity与规则库站在巨人的肩膀上ModSecurity是一个跨平台的、开源的Web应用防火墙引擎。它本身是一个安全工具包提供了请求/响应分析、攻击特征匹配、日志记录等核心功能。它的强大之处在于其规则语言SecLanguage和庞大的社区规则集。我们本次搭建的重点之一就是导入ModSecurity的规则。最著名、最常用的规则集莫过于OWASP Core Rule Set (CRS)。CRS提供了针对SQLi、XSS、RCE、文件包含等数十种攻击类型的数千条检测规则。直接使用CRS相当于直接接入了全球安全社区的经验积累能防御绝大多数已知的自动化攻击和常见漏洞利用尝试。但是ModSecurity原生是一个Apache模块也有Nginx的版本modsecurity-nginx连接器。在我们的方案中我们并非直接运行ModSecurity for Nginx模块而是采取一种更灵活、对OpenResty更友好的方式将ModSecurity的规则“翻译”或“适配”成能在OpenResty的Lua环境中运行的逻辑。这是因为原生的modsecurity-nginx模块与OpenResty的Lua协程模型在集成上可能存在一些复杂性和性能开销。我们的目标是提取规则的精髓正则表达式、检测条件、动作用Lua来实现匹配和动作执行。2.3 整体架构设计我们的轻量级WAF架构可以清晰地分为三层接入层OpenResty接收所有HTTP/HTTPS流量是流量的唯一入口。检测引擎层Lua脚本这是我们WAF的核心。我们将编写或配置Lua脚本这些脚本内嵌了从ModSecurity规则转化而来的检测逻辑。脚本运行在OpenResty的上下文中对请求的各个部分方法、URI、头部、参数、Body进行实时分析。动作执行层根据检测结果执行相应动作。通常包括通过PASS未发现威胁请求被转发至后端真实服务器proxy_pass。阻断DENY发现明确攻击立即中断连接向客户端返回403 Forbidden或自定义错误页面。记录LOG无论是否阻断都将详细的检测日志攻击类型、匹配的规则、请求片段记录到文件或发送到远程日志服务器用于后续审计和分析。这个架构的优势在于轻量、高性能和灵活可控。所有逻辑集中在OpenResty内无需额外进程间通信减少了延迟。同时用Lua实现规则让我们可以非常方便地添加自定义规则、调整规则灵敏度、或者与业务逻辑进行深度集成。3. 五分钟快速部署OpenResty安装与基础配置“5分钟搞定”并非虚言前提是有一个干净的Linux环境如CentOS 7/8, Ubuntu 20.04/22.04。我们这里以Ubuntu 22.04为例。3.1 一键安装OpenRestyOpenResty官方提供了预编译的软件包安装非常便捷。首先导入官方GPG密钥并添加软件源sudo apt-get update sudo apt-get install -y software-properties-common sudo add-apt-repository -y deb https://openresty.org/package/ubuntu $(lsb_release -sc) main sudo apt-get update然后安装OpenResty全家桶sudo apt-get install -y openresty这个命令会安装OpenResty核心、Nginx以及常用的Lua库。安装完成后OpenResty的二进制文件是openresty其配置文件目录通常位于/usr/local/openresty/nginx/conf/通过源码编译安装或/etc/openresty/通过包管理安装。你可以通过which openresty和openresty -V来确认安装路径和编译参数。注意在一些使用1panel等面板的环境中可能已经自带了OpenResty。你需要确认其版本和安装路径。启动方式可能是systemctl start openresty或通过面板的服务管理功能。如果存在冲突建议先卸载面板自带的版本或使用我们新安装的版本并注意修改默认的监听端口如从80改为8080避免冲突。3.2 启动与验证启动OpenResty服务sudo systemctl start openresty sudo systemctl enable openresty # 设置开机自启检查服务状态和默认页面sudo systemctl status openresty curl http://localhost如果看到OpenResty的欢迎页面说明服务已经正常运行。默认的配置文件通常位于/usr/local/openresty/nginx/conf/nginx.conf。在继续之前建议备份原始配置sudo cp /etc/openresty/nginx.conf /etc/openresty/nginx.conf.backup3.3 基础WAF配置骨架我们并不直接修改主配置文件而是采用include的方式让结构更清晰。在/etc/openresty/下创建一个专门存放WAF配置的目录sudo mkdir -p /etc/openresty/waf/ sudo mkdir -p /etc/openresty/waf/rules/ sudo mkdir -p /etc/openresty/waf/logs/接下来创建我们的核心WAF Lua脚本。我们将创建一个名为waf.lua的文件它将是我们的总控中心sudo vim /etc/openresty/waf/waf.lua我们先搭建一个最简单的框架这个框架定义了WAF的工作流程-- /etc/openresty/waf/waf.lua local _M {} _M.version 1.0 -- 规则加载模块 local rule_loader require waf.rule_loader -- 检测引擎模块 local check_engine require waf.check_engine -- 日志记录模块 local logger require waf.logger function _M.access() -- 获取当前请求的变量 local ctx { remote_addr ngx.var.remote_addr, request_uri ngx.var.request_uri, request_method ngx.var.request_method, args ngx.req.get_uri_args(), headers ngx.req.get_headers(), -- 注意获取请求体需要在合适的阶段且可能影响性能 } -- 1. 加载规则可缓存避免每次请求都加载 local rules rule_loader.load_rules() -- 2. 执行检测 local action, matched_rule check_engine.run(ctx, rules) -- 3. 根据检测结果执行动作 if action DENY then -- 记录攻击日志 logger.log_attack(ctx, matched_rule) -- 返回403并阻断 return ngx.exit(ngx.HTTP_FORBIDDEN) elseif action LOG then -- 仅记录日志不阻断 logger.log_attack(ctx, matched_rule) end -- 如果action是PASS则什么都不做继续向下执行 end return _M这个骨架包含了模块化的思想。现在我们需要修改Nginx主配置在需要防护的server块中引入这个WAF逻辑。编辑/etc/openresty/nginx.conf在http块内添加以下内容用于加载我们的WAF模块路径http { ... # 添加Lua包路径指向我们的WAF目录 lua_package_path /etc/openresty/waf/?.lua;;; # 初始化共享字典用于缓存规则或限流计数 lua_shared_dict waf_cache 10m; ... }然后在你想要保护的网站对应的server配置块中在location /处理之前加入access_by_lua_file指令server { listen 80; server_name your_domain.com; # 启用WAF检测 access_by_lua_file /etc/openresty/waf/waf.lua; location / { proxy_pass http://your_backend_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 可以定义一个用于输出错误日志的location location /waf_denied { internal; # 内部访问不对外暴露 default_type text/html; content_by_lua_block { ngx.say(Request Blocked by WAF) } } }配置完成后检查配置语法并重载Nginxsudo openresty -t sudo systemctl reload openresty至此一个具备基础框架的WAF就已经“挂载”到了你的Web服务器前。虽然它现在还没有任何实际的检测规则但流程已经打通。接下来就是为其注入灵魂——规则。4. ModSecurity规则解析与Lua化导入实战这是整个项目的核心难点和亮点。我们不是简单地安装一个ModSecurity模块而是理解其规则并将其转化为Lua可执行的逻辑。4.1 理解ModSecurity规则格式一条典型的ModSecurity规则以CRS v3为例看起来像这样SecRule ARGS rx (\bunion\b.*?\bselect\b|\bselect\b.*?\bunion\b) \ id:942100,\ phase:2,\ block,\ t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,\ msg:SQL Injection Attack Detected,\ logdata:Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME},\ tag:attack-sqli,\ severity:CRITICAL我们来拆解关键部分SecRule: 规则定义开始。ARGS: 检查的变量这里是所有请求参数GET和POST。其他常见变量有REQUEST_URI,REQUEST_HEADERS,REQUEST_BODY等。rx: 操作符表示使用正则表达式匹配。(\bunion\b.*?\bselect\b|\bselect\b.*?\bunion\b): 正则表达式用于检测常见的SQL联合查询注入模式。phase:2: 规则执行的阶段。phase:1是请求头phase:2是请求体。在我们的Lua WAF中可以对应到access_by_lua*阶段。block: 动作表示阻断请求。t:none,...: 变换函数链transformation用于对变量进行预处理如URL解码、HTML实体解码、转小写等以绕过简单的混淆。id, msg, tag, severity: 规则的元信息用于日志和标识。我们的任务就是将成千上万条这样的规则转化为Lua中的数据结构如表和匹配函数。4.2 规则转换策略与Lua实现我们不可能手动转换所有CRS规则。一个实用的策略是先实现一个核心的、支持基本规则语法的Lua检测引擎然后编写脚本将ModSecurity规则文件.conf解析并转换为这个引擎能识别的Lua规则表。首先创建规则加载模块/etc/openresty/waf/rule_loader.lua-- /etc/openresty/waf/rule_loader.lua local _M {} local lrucache require resty.lrucache local shared_dict ngx.shared.waf_cache -- 创建一个简单的LRU缓存缓存解析后的规则 local rule_cache, err lrucache.new(100) -- 缓存100条规则解析结果 if not rule_cache then ngx.log(ngx.ERR, failed to create rule cache: , err) end -- 规则数据结构 -- rules { -- { id 942100, phase 2, var ARGS, operator rx, pattern union.*select, transform {lowercase}, action block, msg SQLi Attack }, -- ... -- } function _M.load_rules() -- 首先尝试从缓存获取 local cached_rules shared_dict:get(compiled_rules) if cached_rules then return cached_rules end -- 缓存未命中从文件加载 local rule_files { /etc/openresty/waf/rules/request-942-application-attack-sqli.conf, /etc/openresty/waf/rules/request-943-application-attack-session-fixation.conf, -- 添加更多规则文件... } local all_rules {} for _, file_path in ipairs(rule_files) do local rules_from_file _M.parse_modsec_file(file_path) if rules_from_file then for _, rule in ipairs(rules_from_file) do table.insert(all_rules, rule) end end end -- 将规则表序列化后存入共享字典缓存设置过期时间如3600秒 shared_dict:set(compiled_rules, all_rules, 3600) return all_rules end -- 这是一个简化的解析函数实际解析ModSecurity规则需要更复杂的逻辑 function _M.parse_modsec_file(file_path) local rules {} local file, err io.open(file_path, r) if not file then ngx.log(ngx.ERR, failed to open rule file: , file_path, error: , err) return nil end for line in file:lines() do line line:match(^%s*(.-)%s*$) -- 去除首尾空白 if line and line ~ and not line:match(^#) then -- 忽略空行和注释 -- 这里需要实现一个真正的ModSecurity规则解析器 -- 作为示例我们假设规则已经被预处理成简单的Lua表格式 -- 实际项目中你可能需要先使用Python/Go等语言编写一个规则转换器将.conf文件批量转换成.lua文件 if line:match(^{.*}$) then local ok, rule_table pcall(loadstring(return .. line)) if ok and rule_table then table.insert(rules, rule_table) end end end end file:close() return rules end return _M由于完整解析ModSecurity规则文件非常复杂一个更可行的工程化方案是两步走离线转换使用一个外部脚本如Python读取OWASP CRS的.conf文件解析每条SecRule将其转换为一个更简单的、Lua易读的JSON或Lua表格式的文件。这个脚本只需要在规则更新时运行。在线加载我们的rule_loader.lua只负责加载这个已经转换好的、格式清晰的规则文件。例如转换后的规则文件rules.lua可能看起来像这样-- /etc/openresty/waf/rules/compiled_rules.lua local _M { { id 942100, phase 2, variables {ARGS, ARGS_NAMES, REQUEST_BODY}, operator rx, pattern [[\bunion\b.*?\bselect\b|\bselect\b.*?\bunion\b]], transformations {none, urlDecodeUni, htmlEntityDecode, lowercase}, action block, msg SQL Injection Attack Detected, tag attack-sqli, severity CRITICAL }, -- ... 成千上万条其他规则 } return _M然后rule_loader.lua的load_rules函数就简化为function _M.load_rules() local cached_rules shared_dict:get(compiled_rules) if cached_rules then return cached_rules end -- 直接require转换好的规则文件 local ok, rules_table pcall(require, rules.compiled_rules) if not ok then ngx.log(ngx.ERR, failed to load compiled rules: , rules_table) return {} end shared_dict:set(compiled_rules, rules_table, 3600) return rules_table end4.3 检测引擎的实现有了规则我们需要一个引擎来执行它们。创建/etc/openresty/waf/check_engine.lua-- /etc/openresty/waf/check_engine.lua local _M {} -- 变换函数映射表 local transformations { none function(val) return val end, lowercase function(val) return string.lower(val) end, urlDecodeUni function(val) -- 实现一个简单的URL解码实际需要更完善的实现 return val:gsub(%%(%x%x), function(h) return string.char(tonumber(h, 16)) end) end, htmlEntityDecode function(val) -- 实现简单的HTML实体解码实际需要更完善的实现 local replacements {[lt;] , [gt;] , [amp;] , [quot;] \} for k, v in pairs(replacements) do val val:gsub(k, v) end return val end, -- 可以添加更多变换函数... } -- 应用变换函数链 local function apply_transformations(value, trans_list) if not trans_list or #trans_list 0 then return value end local result value for _, trans_name in ipairs(trans_list) do local trans_func transformations[trans_name] if trans_func then result trans_func(result) else ngx.log(ngx.WARN, unknown transformation: , trans_name) end end return result end -- 检查单个变量是否匹配某条规则 local function check_variable(var_value, rule) if not var_value or type(var_value) ~ string then return false end -- 应用变换 local transformed_value apply_transformations(var_value, rule.transformations) -- 根据操作符进行匹配 if rule.operator rx then -- 正则表达式匹配 local from, to, captures ngx.re.find(transformed_value, rule.pattern, joi) -- j启用PCRE JITo编译一次i忽略大小写如果规则需要 if from then return true, transformed_value:sub(from, to) end elseif rule.operator contains then -- 包含匹配 if string.find(transformed_value, rule.pattern, 1, true) then -- plain text search return true, rule.pattern end end -- 可以扩展更多操作符如beginsWith, endsWith, eq等 return false end -- 主检测函数 function _M.run(ctx, rules) if not rules then return PASS end for _, rule in ipairs(rules) do -- 根据规则中定义的变量从ctx中获取值进行检查 for _, var_name in ipairs(rule.variables) do local var_values {} if var_name ARGS then -- 获取所有查询字符串参数的值 for _, v in pairs(ctx.args or {}) do if type(v) table then for _, subv in ipairs(v) do table.insert(var_values, subv) end else table.insert(var_values, v) end end elseif var_name REQUEST_URI then table.insert(var_values, ctx.request_uri) elseif var_name REQUEST_METHOD then table.insert(var_values, ctx.request_method) elseif var_name REQUEST_HEADERS then for k, v in pairs(ctx.headers or {}) do table.insert(var_values, k .. : .. v) end -- 需要处理POST body这里比较复杂涉及到ngx.req.read_body()和ngx.req.get_post_args() end -- 对获取到的每个值进行检查 for _, val in ipairs(var_values) do local is_match, matched_data check_variable(tostring(val), rule) if is_match then -- 匹配成功返回阻断动作和规则信息 return rule.action:upper(), {id rule.id, msg rule.msg, matched matched_data, var var_name} end end end end return PASS -- 所有规则都未匹配 end return _M这个检测引擎是一个高度简化的版本但它阐述了核心原理遍历规则 - 根据变量名从请求中提取数据 - 应用变换函数 - 使用操作符主要是正则进行匹配 - 返回结果。真实的工业级实现需要考虑性能优化如规则分组、快速失败、更完整的变换函数库、对JSON/XML等结构化body的解析以及处理文件上传等复杂情况。5. 高级配置、性能调优与运维实践一个能用于生产环境的WAF除了核心检测功能还需要考虑性能、可维护性和可观测性。5.1 规则管理与更新规则来源首选OWASP CRS。你可以从GitHubhttps://github.com/coreruleset/coreruleset下载最新版本。我们的“离线转换脚本”就需要针对CRS v3的规则文件进行解析。规则更新流程在测试环境下载新版CRS使用转换脚本生成新的compiled_rules.lua。在测试环境进行全面的回归测试确保新规则不会阻断正常的业务请求误报。将新的规则文件同步到生产服务器的/etc/openresty/waf/rules/目录下。通过API或信号通知OpenResty重载规则。一种简单的方式是让rule_loader.lua检查规则文件的修改时间mtime如果发现更新则清空共享字典缓存触发重新加载。也可以发送HUP信号给OpenResty进程但更优雅的方式是提供一个内部API接口location /waf-admin/reload-rules { internal; # 只允许内部访问 allow 127.0.0.1; deny all; content_by_lua_block { local shared_dict ngx.shared.waf_cache shared_dict:delete(compiled_rules) ngx.say(Rules cache cleared.) } }然后通过curl -X POST http://localhost/waf-admin/reload-rules来触发重载。5.2 性能优化要点Lua代码在Nginx中运行虽然高效但不合理的实现仍会成为性能瓶颈。缓存缓存缓存规则缓存如上所述一定要将解析后的规则缓存在lua_shared_dict中避免每个请求都去解析文件。正则表达式编译缓存ngx.re中的正则表达式如果使用o选项会被编译并缓存。确保在循环或频繁调用的函数中正则模式是常量或来自缓存。复杂运算结果缓存对于一些复杂的变换或检查结果如果在一定时间内不变如IP地理位置可以考虑缓存。减少不必要的检查白名单对于已知安全的IP、URL路径如静态文件/static/,/uploads/可以在WAF逻辑最前端直接跳过所有检测。规则分组与阶段并非所有规则都需要在每个请求中检查。可以将规则按phase或tag分组根据请求特征决定启用哪些组。例如对GET请求可能不需要检查REQUEST_BODY相关的规则。优化Lua代码避免在热路径中创建大量临时表Lua的垃圾回收GC可能带来波动。在_M.run这样的高频函数中尽量减少临时对象的分配。使用JIT编译OpenResty使用的LuaJIT对大多数Lua代码都能很好地进行即时编译。确保你的代码是JIT友好的例如避免使用某些不被JIT支持的Lua原语如ipairs在5.1版本下某些情况。调整Nginx/OpenResty参数lua_shared_dict大小根据规则数量和并发量调整。worker_processes设置为CPU核心数。worker_connections根据服务器内存和预期并发连接调整。5.3 日志记录与监控“只阻断不记录”的WAF是盲目的。详细的日志对于分析攻击趋势、调整规则、排查误报至关重要。完善/etc/openresty/waf/logger.lualocal _M {} local cjson require cjson function _M.log_attack(ctx, matched_rule) local log_entry { time ngx.localtime(), remote_addr ctx.remote_addr, request_uri ctx.request_uri, request_method ctx.request_method, rule_id matched_rule.id, rule_msg matched_rule.msg, matched_var matched_rule.var, matched_data matched_rule.matched, -- 注意记录匹配到的数据可能包含敏感信息生产环境需脱敏或哈希处理 user_agent ctx.headers[User-Agent] or , severity matched_rule.severity or UNKNOWN } -- 使用ngx.log记录到Nginx error log级别为WARN ngx.log(ngx.WARN, cjson.encode(log_entry)) -- 同时可以写入到单独的日志文件或发送到远程syslog/ELK/Sentry等系统 local file, err io.open(/etc/openresty/waf/logs/waf_attack.log, a) if file then file:write(cjson.encode(log_entry) .. \n) file:close() end end -- 还可以记录通过PASS的请求用于审计但要注意日志量 function _M.log_access(ctx) -- ... 实现访问日志记录 end return _M在Nginx配置中可以将WAF日志定向到独立的文件http { ... # 定义WAF日志格式 log_format waf_json escapejson { time:$time_local, remote_addr:$remote_addr, request:$request, status:$status, waf_action:$waf_action, # 需要设置变量 waf_rule_id:$waf_rule_id, http_user_agent:$http_user_agent }; # 在server中配置 server { ... set $waf_action ; set $waf_rule_id ; access_by_lua_block { local waf require waf local action, rule waf.access() if action DENY then ngx.var.waf_action DENY ngx.var.waf_rule_id rule.id else ngx.var.waf_action PASS end } access_log /var/log/nginx/waf_access.log waf_json; ... } }5.4 常见问题与排查技巧误报False Positive太高阻塞了正常用户排查首先查看WAF日志找到被阻断的请求和触发的规则ID。使用curl或浏览器开发者工具模拟正常用户请求看是否触发相同规则。解决禁用规则在转换后的规则集中找到对应ID的规则将其action改为log仅记录或直接注释掉。调整规则分析规则的正则表达式是否过于宽泛。有时需要修改规则增加更精确的边界条件\b或排除特定模式。添加白名单对于特定的、安全的参数或路径在WAF逻辑中增加前置白名单检查。心得永远不要在生产环境直接启用全套CRS而不经过测试。建议先在action设为log的模式下运行一段时间分析日志确认没有大量误报后再开启block模式。漏报False Negative攻击未被拦截排查确认请求是否经过了WAF检测的location。检查规则是否已正确加载查看错误日志。使用已知的攻击Payload如 OR 11 --进行测试。解决确保检测阶段覆盖如果攻击在POST Body中确保你的WAF在access阶段读取了请求体ngx.req.read_body()并且规则中包含了REQUEST_BODY变量。更新规则库攻击技术在进化确保你使用的CRS是最新版本。添加自定义规则针对特定的应用漏洞或攻击模式编写你自己的Lua规则。这是自建WAF的最大优势。性能影响显著服务器负载升高排查使用openresty -V查看是否启用了--with-debug生产环境应关闭。使用ngx.location.capture或第三方模块如lua-resty-bench)进行压测对比开启和关闭WAF时的QPS和延迟。解决优化正则一些复杂的正则表达式是性能杀手。使用更高效的正则或将其拆分成多条简单规则。限制检查范围对静态资源如图片、CSS、JS的请求在Nginx层面通过location匹配直接跳过WAF。升级硬件WAF的正则匹配是CPU密集型操作更强的CPU会直接提升处理能力。考虑分层防护将基于正则的精确匹配放在第二层第一层先用更简单的规则如IP黑名单、频率限制过滤掉大部分恶意流量。OpenResty重启后规则丢失原因lua_shared_dict是内存缓存进程重启后消失。解决在init_by_lua*阶段在Master进程启动时执行一次预加载规则到共享字典或者确保每次请求时如果缓存不存在就从磁盘加载。我们之前的rule_loader模块已经实现了缓存机制重启后第一个请求会触发加载。如何测试WAF是否生效基础测试访问http://your_server/?id1 OR 11。如果返回403或自定义拦截页面说明SQLi规则可能生效了。使用专业工具使用sqlmap、XSStrike等自动化扫描工具务必在授权环境下测试观察其请求是否被拦截并查看WAF日志。发送测试Payload有专门的WAF测试字符串如scriptalert(1)/script测试XSS、../../etc/passwd测试路径遍历。搭建这样一个WAF从“能用”到“好用”还有很长的路要走。它需要你持续地维护规则、分析日志、调优性能。但这个过程本身就是对你Web安全体系理解的一次深度提升。这个自建的WAF可能没有商业产品那样华丽的界面和自动化的威胁情报但它给你带来了无与伦比的透明度和控制力。你可以精确地知道每一条规则在做什么可以根据自己业务的特殊性量身定制防护策略这在应对一些针对性强的攻击时往往能起到奇效。