# Nginx 开源免费 WAF 防火墙宝塔插件 **Repository Path**: web/bt_ngx_lua_waf ## Basic Information - **Project Name**: Nginx 开源免费 WAF 防火墙宝塔插件 - **Description**: Nginx 开源免费 WAF 防火墙基于 OpenResty (ngx_lua) 的高性能 Web 应用防火墙,宝塔面板(BT Panel)插件。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-15 - **Last Updated**: 2026-10-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Nginx 开源免费 WAF 防火墙 基于 OpenResty (`ngx_lua`) 的高性能 Web 应用防火墙,宝塔面板(BT Panel)插件。 > **Gitee 仓库**:https://gitee.com/web/bt_ngx_lua_waf > > **作者**:xiaogg --- ## 目录 - [功能特性](#功能特性) - [运行原理](#运行原理) - [请求过滤流程](#请求过滤流程) - [文件结构](#文件结构) - [规则格式](#规则格式) - [配置说明](#配置说明) - [安装部署](#安装部署) - [API 接口](#api-接口) - [技术要点](#技术要点) - [与 free_waf 的关系](#与-free_waf-的关系) - [许可证](#许可证) --- ## 功能特性 - **零磁盘 IO 运行时**:`init_by_lua` 阶段一次性加载所有配置和规则到内存,`access_by_lua` 阶段纯内存匹配,性能接近原生 Nginx - **CC 攻击防护**:基于 `shared_dict` 的滑动窗口频次统计 + CC2 挑战验证,支持自定义阈值、检测周期、自适应封禁时长、自动永久拉黑阈值、每小时请求硬上限 - **恶意 HEAD 零容忍拦截**:首次命中即封 IP(对齐 free_waf),爬虫 UA 和白名单 URI 放行 - **多维度攻击拦截**:URL 路径、URL 黑名单、GET 参数、POST 内容、Cookie、User-Agent、扫描器特征头 - **IP 黑白名单**:支持精确 IP 匹配及 IP 段范围匹配,独立响应状态码 - **海外 IP 拦截**:基于国内 IP 段列表自动拦截海外访问(含 SSL 验证 IP 白名单豁免) - **文件上传保护**:禁止上传目录脚本执行 + 全局/站点两级扩展名黑名单 - **ThinkPHP 漏洞防护**:内置 ThinkPHP 5.x RCE 和 3.x 日志泄露防护规则 - **域名映射表**:`domains.json` 构建 O(1) 精确查找,自动将绑定域名(如 www.example.com)归一化到站点主名 - **站点级独立配置**:全局 → 站点三态配置(开启/关闭/继承),每个站点可单独启停任意防护模块 - **站点专属防护**:每个站点可配置专属 URL 白名单、禁止 PHP 路径、禁止路径访问、禁止扩展名、URI 自定义拦截规则(url_rule_ex)、URI 参数校验(url_tell) - **规则精准跳过**:基于 hash set 的 `disable_rule` 机制,O(1) 时间复杂度跳过指定站点的禁用规则 - **CDN / 反向代理支持**:自定义 `cdn_header` 从请求头获取真实客户端 IP,支持多头部按顺序回溯 - **系统防火墙集成**:支持将 IP 封禁同步写入 `iptables` / `firewalld` / `ufw`,实现全端口封锁 - **分类拦截模板**:GET、POST、Cookie、User-Agent 各模块独立拦截页面和 HTTP 状态码 - **统计与日志**:分类统计每个模块和每个站点的拦截数,JSON 格式日志含完整 HTTP 请求包 - **安全锁机制**:IP 封禁时使用 `shared_dict:add` 实现并发安全的一次性封锁,避免重复写文件 - **自动重载生效**:Python 后端每次修改配置自动执行 `nginx -t && nginx reload`,零停机热更新 --- ## 运行原理 ``` ┌─────────────────────────────────────────────────────────────────┐ │ Nginx 启动 / reload │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ init_by_lua_file → init.lua │ │ │ │ • 加载 config.json / site.json 到全局变量 │ │ │ │ • 加载 rule/*.json 规则文件到内存数组 │ │ │ │ • 预加载 html/*.html 拦截模板 │ │ │ │ • 加载 domains.json → 构建 domains_map (O(1) 域名归一) │ │ │ │ • 初始化 shared_dict 统计计数 + 历史恢复与校准 │ │ │ │ • 构建站点级 disable_rule hash set │ │ │ │ • 提取站点专属白名单 / PHP禁止路径 / 禁止上传扩展名 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ │ access_by_lua_file → waf.lua │ │ • 纯内存匹配,零磁盘 IO,零 JSON 解析 │ │ • domains_map O(1) 归一化绑定域名 → 站点主名 │ │ • get_effective_cfg() 合并全局开关 + 站点三态配置 │ │ • 短路匹配:规则命中即返回,后续规则不再遍历 │ │ • shared_dict 实现 IP 封禁、CC 计数、自动永久拉黑 │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 请求过滤流程 ``` 请求进入(domains_map 归一化域名 → 加载生效配置) │ ├─► ① IP 白名单 → 放行 │ ├─► ② IP 黑名单 → 拦截 │ ├─► ③ 已封锁 IP(drop 检查)→ 拦截 │ ├─► ④ URL 白名单 → 放行 │ ├─► ⑤ 恶意 HEAD 请求拦截 → 拦截(零容忍,首次命中即封IP) │ ├─► ⑥ URL 黑名单检查 → 拦截 │ ├─► ⑦ 海外 IP 拦截(SSL验证IP豁免)→ 拦截 │ ├─► ⑧ CC 攻击检测(滑动窗口 + CC2挑战验证 + 自动永久拉黑)→ 拦截 │ ├─► ⑨ 扫描器特征检测(Cookie/Header/Args)→ 拦截 │ ├─► ⑩ UA 恶意规则 → 拦截 │ ├─► ⑪ URL 路径规则 → 拦截 │ ├─► ⑫ GET 参数规则 → 拦截 │ ├─► ⑬ Cookie 规则 → 拦截 │ ├─► ⑭ ThinkPHP 漏洞防护(5.x RCE + 3.x 日志泄露)→ 拦截 │ ├─► ⑮ POST 检查(内容攻击 + 文件上传扩展名黑名单 + disable_rule 跳过)→ 拦截 │ ├─► ⑯ 站点级自定义规则(PHP禁止路径 / 禁止路径 / 禁止扩展名 / url_rule / url_tell)→ 拦截 │ └─► 放行 ``` 每个环节命中即短路返回,不做多余检查,最大化性能。模块开关由 `get_effective_cfg()` 根据全局配置与站点三态配置计算得出。 --- ## 文件结构 ``` bt_ngx_waf/ ├── info.json # 宝塔插件元数据(版本、作者、安装入口) ├── install.sh # 安装 / 更新 / 卸载脚本 ├── migrate.py # 版本升级迁移脚本 ├── bt_ngx_waf_main.py # Python 后端 API(面板数据读写 + 自动生成 domains.json + 系统防火墙集成) ├── index.html # 前端管理界面 ├── icon.png # 插件图标 ├── conf/ │ └── bt_ngx_waf.conf # Nginx 配置片段(shared_dict + lua hook) └── data/ ├── config.json # 全局配置模板 ├── site.json # 站点级配置存储(支持多站点 + 三态继承) ├── domains.json # 域名映射表(Python 后端自动生成) ├── total.json # 统计计数持久化(含分类计数 + 站点级计数 + 校准) ├── drop_ip.log # IP 封禁历史日志 ├── operation.log # 操作日志 ├── lua/ │ ├── init.lua # 初始化脚本(init_by_lua,仅 reload 时执行) │ ├── waf.lua # 请求过滤主逻辑(access_by_lua,每请求执行 + API 端点分流) │ └── api.lua # Lua 端 API 接口(仅 127.0.0.1 可访问,已整合进 waf.lua handle_api) ├── rule/ │ ├── args.json # GET 参数规则(SQL注入/XSS等) │ ├── cookie.json # Cookie 攻击规则 │ ├── ip_black.json # IP 黑名单(支持精确匹配和IP段) │ ├── ip_white.json # IP 白名单 │ ├── post.json # POST 内容规则 │ ├── url.json # URL 路径规则 │ ├── url_black.json # URL 黑名单规则(独立于 url 规则) │ ├── url_white.json # URL 白名单规则 │ ├── whiteurl.json # URL 白名单规则(兼容旧版命名) │ ├── user_agent.json # User-Agent 规则(恶意爬虫/扫描器) │ ├── scan_black.json # 扫描器特征规则(Cookie / Header / Args) │ ├── head_white.json # HEAD 请求白名单规则(爬虫UA自动放行) │ └── cn.json # 国内 IP 段列表(海外拦截用) └── html/ ├── get.html # GET/URL 拦截页面 ├── post.html # POST 拦截页面 ├── cookie.html # Cookie 拦截页面 └── user_agent.html # UA 拦截页面 ``` **运行时目录**(安装后): ``` /www/server/bt_ngx_waf/ ├── lua/*.lua # Lua 脚本(来自 data/lua/) ├── rule/*.json # 规则文件(来自 data/rule/,保留用户自定义) ├── html/*.html # 拦截模板(来自 data/html/,保留用户自定义) ├── config.json # 全局配置(面板修改后自动 reload) ├── site.json # 站点配置 ├── domains.json # 域名映射表(Python 后端写入,与 site.json 同步) ├── total.json # 统计持久化 └── drop_ip.log # IP 封禁历史 ``` --- ## 规则格式 规则文件采用 JSON 数组格式,每条规则为一个子数组: ```json [启用状态, "正则表达式", "规则说明", 是否自定义] ``` | 字段 | 类型 | 说明 | |------|------|------| | 启用状态 | number | `1` = 启用,`0` = 禁用 | | 正则表达式 | string | PCRE 正则语法,自动添加 `ijo` flag(不区分大小写、UTF-8、优化) | | 规则说明 | string | 中文描述,便于在面板管理 | | 是否自定义 | number | `0` = 官方内置,`1` = 用户自定义 | **示例**: ```json [ [1, "(?:select|union|insert|update|delete)\\s+", "SQL注入", 0], [1, "", "XSS攻击", 1], [0, "已禁用的规则", "这条不会生效", 0] ] ``` IP 黑白名单(`ip_black.json` / `ip_white.json`)支持两种格式: ```json [ "192.168.1.1", "10.0.0.1", [ [173, 224, 195, 2], [173, 224, 195, 10] ], [ [203, 106, 0, 1], [203, 106, 0, 255] ] ] ``` --- ## 配置说明 ### 全局配置 (config.json) | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `version` | number | `2` | 配置文件版本号 | | `open` | boolean | `true` | WAF 总开关 | | `log` | boolean | `true` | 攻击日志开关 | | `log_dir` | string | `/www/wwwlogs/bt_ngx_waf/` | 日志存储目录 | | `log_save` | number | `30` | 日志保留天数 | | `Redirect` | boolean | `true` | 拦截后是否返回 302 跳转 | | `post_max_size` | number | `10485760` | POST Body 最大字节数(10MB) | | `retry` | number | `5` | N 秒内累计多少次拦截后封锁 IP | | `retry_cycle` | number | `60` | N 秒统计周期 | | `retry_time` | number | `600` | 基础封锁时长(秒),多次累积自适应延长 | | `black_fileExt` | array | `["php", "jsp"]` | 全局禁止上传脚本扩展名 | | `ipWhitelist` | array | `["127.0.0.1"]` | 全局 IP 白名单(可在 rule/ip_white.json 扩展) | | `ipBlocklist` | array | `[]` | 全局 IP 黑名单(可在 rule/ip_black.json 扩展) | | `cc.open` | boolean | `true` | CC 防护开关 | | `cc.limit` | number | `100` | 周期内最大请求次数 | | `cc.cycle` | number | `60` | 检测周期(秒) | | `cc.endtime` | number | `300` | 基础 CC 封锁时长(秒) | | `cc.status` | number | `444` | CC 拦截 HTTP 状态码 | | `cc.auto_black_count` | number | `4` | 被 CC 拦截累计达 N 次后升级为**永久拉黑**(TTL=0,0=关闭) | | `cc.force_black_per_hour` | number | `36000` | 每小时请求硬上限,超过直接永久拉黑(0=关闭) | | `head.open` | boolean | `true` | 恶意 HEAD 请求拦截开关(首次命中即封 IP,爬虫 UA 放行) | | `head.status` | number | `444` | HEAD 请求拦截状态码 | | `get.open` | boolean | `true` | GET/URL 模块总开关 | | `get.status` | number | `403` | GET/URL 拦截状态码 | | `url.open` | boolean | `true` | URL 路径规则开关 | | `url.status` | number | `403` | URL 拦截状态码 | | `url.reqfile` | string | `get.html` | URL 拦截模板 | | `args.open` | boolean | `true` | GET 参数规则开关 | | `args.status` | number | `403` | GET 参数拦截状态码 | | `args.reqfile` | string | `get.html` | GET 参数拦截模板 | | `post.open` | boolean | `true` | POST 规则开关 | | `post.status` | number | `403` | POST 拦截状态码 | | `post.reqfile` | string | `post.html` | POST 拦截模板 | | `cookie.open` | boolean | `true` | Cookie 规则开关 | | `cookie.status` | number | `403` | Cookie 拦截状态码 | | `cookie.reqfile` | string | `cookie.html` | Cookie 拦截模板 | | `user_agent.open` | boolean | `true` | User-Agent 规则开关 | | `user_agent.status` | number | `403` | UA 拦截状态码 | | `user_agent.reqfile` | string | `user_agent.html` | UA 拦截模板 | | `white_module.open` | boolean | `true` | URL 白名单开关 | | `scan.open` | boolean | `true` | 扫描器防护开关 | | `scan.status` | number | `444` | 扫描器拦截状态码 | | `drop_abroad.open` | boolean | `false` | 海外 IP 拦截开关 | | `drop_abroad.status` | number | `444` | 海外 IP 拦截状态码 | | `block.status` | number | `444` | IP 被封锁后的拦截状态码 | | `ipBlocklist_status` | number | `444` | IP 黑名单拦截状态码 | | `from_data` | boolean | `true` | 是否记录 multipart form-data 完整数据到日志 | ### 站点配置 (site.json) 每个站点字段支持**三态**配置:`null` = 继承全局,`true` = 强制开启,`false` = 强制关闭 ```json { "example.com": { "open": true, "cdn": false, "cdn_header": ["x-forwarded-for", "x-real-ip"], "cc": { "open": true, "limit": 80, "cycle": 30, "endtime": 600, "auto_black_count": 4, "force_black_per_hour": 36000 }, "disable_rule": { "args": [2, 5], "post": [1] }, "whiteurl": ["^/api/public/", "^/upload/"], "disable_php_path": ["^/uploads/", "^/data/"], "disable_upload_ext": ["php", "jsp", "asp", "exe"], "disable_path": ["^/admin/backup/", "^/config/"], "disable_ext": ["bak", "sql", "ini"], "url_rule": [["^/api/order", "price.*=.*99999"], ["^/api/user", "role.*=.*admin"]], "url_tell": [["^/api/internal", "token", "secret123"]], "scan": true, "drop_abroad": false, "head": true, "retry": 3, "retry_time": 300, "retry_cycle": 60 } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `open` | boolean/null | 站点 WAF 总开关 | | `cdn` | boolean/null | 是否启用 CDN 模式(从请求头获取真实 IP) | | `cdn_header` | array/null | CDN 真实 IP 头名称列表,按顺序回溯 | | `cc.open` | boolean/null | 站点级 CC 防护开关 | | `cc.limit` | number/null | 周期内最大请求次数 | | `cc.cycle` | number/null | 检测周期(秒) | | `cc.endtime` | number/null | 基础 CC 封锁时长(秒) | | `cc.auto_black_count` | number/null | 被 CC 拦截累计达 N 次后升级为永久拉黑 | | `cc.force_black_per_hour` | number/null | 每小时请求硬上限,超过直接永久拉黑(0=关闭) | | `cc.increase` | boolean/null | 是否开启 CC2 挑战验证模式 | | `disable_rule` | object/null | 站点禁用的规则索引(`{args: [0,1], post: [2]}`),O(1) hash set 跳过 | | `whiteurl` | array/null | 站点专属 URL 白名单正则(命中则跳过所有检查) | | `disable_php_path` | array/null | 禁止 PHP 解析的路径正则(如 `^/uploads/`) | | `disable_upload_ext` | array/null | 站点专属禁止上传扩展名(覆盖全局 black_fileExt) | | `disable_path` | array/null | 禁止直接访问的路径正则(如备份目录、配置目录) | | `disable_ext` | array/null | 禁止直接访问的文件扩展名(如 `.bak`、`.sql`) | | `url_rule` | array/null | 站点级 URI 自定义拦截 `[[uri_pattern, params_regex], ...]` | | `url_tell` | array/null | 站点级 URI 参数校验 `[[uri_pattern, param_name, expected_value], ...]`,实际值≠预期值则拦截 | | `scan` | boolean/null | 扫描器防护开关 | | `drop_abroad` | boolean/null | 海外 IP 拦截开关 | | `head` | boolean/null | 恶意 HEAD 请求拦截开关 | | `retry` / `retry_cycle` / `retry_time` | number/null | IP 封禁参数(覆盖全局) | --- ## 安装部署 ### 前置条件 - **Nginx / OpenResty**:必须编译 `lua-nginx-module` 支持 - **Lua 版本**:5.1+(与 OpenResty 内置一致) - **宝塔面板**:7.x / 8.x / 正式版均可 - **互斥说明**:不能与宝塔 `free_waf` 插件共存 ### 安装步骤 ```bash # 方式一:通过宝塔面板插件市场直接安装 https://gitee.com/web/bt_ngx_lua_waf/releases/download/latest/bt_ngx_waf.zip # 方式二:源码部署 cd /www/server/panel/plugin/bt_ngx_waf bash install.sh install # 方式三:更新现有安装 bash install.sh update # 卸载 bash install.sh uninstall ``` ### 安装脚本做了什么 1. 创建 `/www/server/bt_ngx_waf/` 运行目录和 `/www/wwwlogs/bt_ngx_waf/` 日志目录 2. 拷贝 Lua 脚本、规则文件、HTML 模板(保留用户已修改的文件) 3. 写入 `bt_ngx_waf.conf` 到 Nginx 虚拟主机配置目录 4. 自动 `nginx -t && nginx reload` 生效 ### 手动生效(可选) ```bash # 如果安装脚本自动重载失败,手动执行 /etc/init.d/nginx configtest && /etc/init.d/nginx reload # 验证 shared_dict 是否加载 curl -s http://127.0.0.1/bt_ngx_waf_api?action=ping # 应返回 {"status":"ok"} ``` ### 热更新配置 修改任何配置文件(`config.json` / `site.json` / `rule/*.json`)后: ```bash /etc/init.d/nginx reload ``` 规则和配置在 `init_by_lua` 阶段重新加载到内存,零停机生效。 --- ## API 接口 ### Lua 端 API(仅限 127.0.0.1) 仅限 127.0.0.1 内网访问,防止外部探测。在 `waf.lua` 的 API 端点分流中统一处理。 访问方式:`http://127.0.0.1/bt_ngx_waf_api?action=xxx` | 接口 | 说明 | |------|------| | `?action=ping` | 健康检查,返回 `{"status":"ok"}` | | `?action=get_total` | 获取全局拦截统计(总数 + 分类计数 + 站点级计数 + 启动时间) | | `?action=get_site_total&siteName=xxx` | 获取指定站点的拦截统计(含所有分类计数) | | `?action=get_drop_ip` | 获取当前活跃的封禁 IP 列表(从 `bt_ngx_waf_drop` 字典遍历) | | `?action=remove_drop_ip&ip=xxx` | 解除指定 IP 的封禁(同时清理 `bt_ngx_waf_drop` 和 `bt_ngx_waf_cc`) | | `?action=clean_drop_ip` | 清空所有封禁 IP(同时清理两个字典的封锁记录) | ### Python 后端管理接口 `bt_ngx_waf_main.py` 提供完整的管理接口(通过宝塔面板前端调用),每次写配置自动执行 `nginx -t && nginx reload`: **全局配置类** | 接口 | 说明 | |------|------| | `getConfig` | 读取全局配置 | | `saveConfig` | 保存全局配置 | | `get_html_tpl` | 获取拦截模板内容 | | `save_html_tpl` | 保存拦截模板 | **站点配置类** | 接口 | 说明 | |------|------| | `getSiteConfig` | 读取所有站点配置 | | `saveSiteConfig` | 保存所有站点配置(自动同步生成 domains.json) | | `getSiteConfig_byname&siteName=xxx` | 按名称读取单个站点配置 | | `getSiteDetail&siteName=xxx` | 获取站点详情(含域名列表) | | `getSiteRule&siteName=xxx` | 获取站点专属规则 | | `addSiteRule&siteName=xxx` | 添加站点专属规则(url_rule_ex / url_tell / disable_path 等) | | `getSiteDisableRule&siteName=xxx` | 获取站点禁用的规则索引列表 | | `addSiteDisableRule&siteName=xxx` | 添加站点禁用规则索引 | | `addSiteWhiteurl&siteName=xxx` | 添加站点专属 URL 白名单 | | `getSiteCdnHeader&siteName=xxx` | 获取站点 CDN 真实 IP 头配置 | | `addSiteCdnHeader&siteName=xxx` | 保存站点 CDN 真实 IP 头配置 | **规则管理类** | 接口 | 说明 | |------|------| | `getRule&name=xxx` | 读取指定类型规则文件(args/post/url/cookie/user_agent 等) | | `saveRule&name=xxx` | 保存规则文件 | | `addRule` | 添加自定义规则(指定 type + pattern + description) | | `deleteRule` | 删除自定义规则 | | `saveScanRule` | 保存扫描器特征规则(scan_black.json) | | `saveHeadWhite` | 保存 HEAD 请求白名单规则(head_white.json) | | `addUrlWhite` | 添加全局 URL 白名单规则 | | `addUrlBlack` | 添加全局 URL 黑名单规则 | | `addIpWhite` | 添加 IP 白名单 | | `addIpBlack` | 添加 IP 黑名单 | | `addCnip` | 添加国内 IP 段(扩展 cn.json) | **系统防火墙集成** | 接口 | 说明 | |------|------| | `addFirewallBlackIp&ip=xxx` | 将 IP 同步写入系统防火墙(自动检测 iptables / firewalld / ufw),实现全端口封锁 | **统计与日志类** | 接口 | 说明 | |------|------| | `getTotal` | 获取全局拦截统计(从 shared_dict 读取) | | `getTotalAll` | 获取全量拦截统计数据 | | `getLogs` / `getGlLogs` | 获取攻击日志列表(解析 `{site}_{date}_sec.log`) | | `getLog&siteName=xxx&date=xxx` | 获取指定站点指定日期的攻击日志 | | `getLogsList` | 获取日志文件列表 | | `cleanLogs` | 清理过期日志文件 | | `getSafeLogs` / `getSafeLogsIp` | 获取 IP 封锁历史(解析 drop_ip.log) | | `getBlockHistory` | 获取封锁历史详情 | | `cleanWafDropIp` | 清空所有活跃封禁 IP(同步清理 shared_dict) | | `cleanDropHistory` | 清空封禁历史日志(drop_ip.log) | --- ## 技术要点 ### 性能设计 - **零磁盘 IO(请求阶段)**:`init_by_lua` 阶段完成所有文件读取,`access_by_lua` 阶段仅操作内存变量和 `shared_dict` - **短路匹配**:规则命中即返回,后续规则不再遍历 - **Hash set 跳过**:`disable_rule` 使用 Lua table 构建 hash set,O(1) 判断站点是否跳过某规则 - **预加载模板**:HTML 拦截模板在 `init_by_lua` 阶段一次性读入内存变量 - **lazy init**:`arrip()` 等字符串处理函数延迟到真正需要时才执行 ### shared_dict 隔离 使用四个独立的共享内存字典,避免与其他插件或应用冲突: | 字典名 | 用途 | 默认大小 | |--------|------|----------| | `bt_ngx_waf` | 统计计数、分类统计、站点级统计 | 20m | | `bt_ngx_waf_cc` | CC 攻击频次统计、CC2 挑战验证 | 10m | | `bt_ngx_waf_drop` | IP 封禁记录、并发安全锁 | 10m | | `bt_ngx_waf_drop_sum` | IP 封禁累计次数(用于自适应延长) | 10m | ### 安全锁机制 IP 封禁时使用 `shared_dict:add(lock_key, 1, 1)` 实现分布式安全锁: - 只有拿到锁的那个请求执行实际封禁和写日志 - 其他并发请求检测到锁后只更新状态,避免重复写文件 ### 禁止在 access 阶段执行的操作 | 禁止操作 | 原因 | |----------|------| | `io.open()` / `io.close()` | 阻塞磁盘 IO,严重影响性能 | | `cjson.decode()` | JSON 解析耗时,应在 init 阶段完成 | | `socket.connect()` | 网络 IO 阻塞 | | `ngx.location.capture()` | 可能导致请求嵌套死循环 | --- ## 与 free_waf 的关系 `bt_ngx_waf` 与宝塔 `free_waf` 是**互斥的两个防火墙插件**,架构上有以下区别: | 对比项 | bt_ngx_waf | free_waf | |--------|------------|----------| | 基础 | Lua 全量重写 | 传统 Nginx + Lua 混合 | | 运行时磁盘 IO | 零 | 有(规则逐行读取) | | 配置格式 | JSON(结构化) | 纯文本(每行一条规则) | | 规则加载 | init 阶段一次性载入数组 | access 阶段逐行遍历文件 | | 站点级配置 | 完整三态继承 | 基础支持 | | CC 防护 | 滑动窗口 + CC2 挑战 | 基础频次限制 | | 依赖 | OpenResty + lua-cjson | OpenResty + lua-cjson | 本项目参考了 `ngx_lua_waf` 和 `free_waf` 的防护逻辑,结合现代 OpenResty 最佳实践进行了重构优化。 ### 关于 webshell 查杀模块 bt_ngx_waf **未实现** webshell 查杀模块。如需服务器端木马检测功能,请到宝塔面板插件市场搜索 **"木马检测"** 插件安装使用。 --- ## 许可证 本项目基于开源许可证发布,具体请查看仓库中的 LICENSE 文件。