BunkerWeb 安全加固指南

BunkerWeb 1.6.16 安全加固实战:插件链机制、WAF 攻防闭环实测(CRS 规则命中与审计日志逐段解读)、IP/国家黑白名单对照、自签证书与 Let's Encrypt 签发流程及两层真实报错排障、误拦截定位与 SecRuleRemoveById 修复闭环

BunkerWeb2026/10/19

BunkerWeb 安全加固指南

目标:在已部署的 BunkerWeb 1.6.16 环境上完成安全加固闭环:讲清插件链执行顺序,逐项实测 WAF 拦截与放行的对照回显、IP/国家访问控制、HTTPS 证书(自签 + Let's Encrypt 流程与真实报错)、日志审计手段,并完整演示一次"误拦截 → 定位规则 → 排除 → 恢复"的排障闭环。环境与配置基础见《BunkerWeb Docker Compose 部署指南》《BunkerWeb 1.6.16 配置参数详解》。

一、环境信息

  • 操作系统:Ubuntu 22.04.4 LTS(x86_64),Docker 29.8.1(Compose v5.5.1)
  • BunkerWeb 1.6.16(bunkerweb 数据面内置 nginx/1.30.5,OWASP CRS v4.29.0——审计日志实测回显版本),PostgreSQL 18 配置库
  • 受管站点:app.local(反代后端静态站点),入口:宿主 8117(HTTP)/ 9443(HTTPS,自签证书实测启用)
  • 测试客户端:宿主机 curl(数据面观测源 IP 为 bw-services 网桥网关 10.89.47.1,内网地址)
  • 实测日期:2026-10-19;所有回显均为真实命令输出摘录,攻击载荷仅用于本机自有环境验证

二、加固基线与前置条件

本文实测开始时的加固基线(在部署篇 compose 基础上的增量,均为已验证配置):

      GENERATE_SELF_SIGNED_SSL: "yes"        # 全局自签证书(内网环境 HTTPS 方案)
      SELF_SIGNED_SSL_SUBJ: "/CN=app.local/"
      app.local_COUNTRY_BLACKLIST: "CN"      # 国家黑名单示例

前置检查:

cd /opt/data/bunkerweb-1-6-16 && docker compose ps

实测回显(五个服务全部健康):

NAME                            IMAGE                                  STATUS
bunkerweb-1-6-16-waf            bunkerity/bunkerweb:1.6.16             Up 17 minutes (healthy)
bunkerweb-1-6-16-scheduler      bunkerity/bunkerweb-scheduler:1.6.16   Up 30 seconds (healthy)
bunkerweb-1-6-16-ui             bunkerity/bunkerweb-ui:1.6.16          Up 27 minutes (healthy)
bunkerweb-1-6-16-db             postgres:18-alpine                     Up 31 minutes
bunkerweb-1-6-16-myapp          nginx:alpine                           Up 31 minutes

加固前自查三项:① 配置库连接与下发链路正常(scheduler 日志出现 Generator successfully executed !);② 站点 HTTP 可达(curl -H 'Host: app.local' http://127.0.0.1:8117/ 200);③ 知道怎么看数据面日志(docker logs bunkerweb-1-6-16-waf,访问/错误/审计日志全部输出到容器 stderr)。

三、安全机制与插件链

BunkerWeb 数据面对每个请求按固定顺序执行插件链。实测数据面启动日志打印了完整顺序(docker logs bunkerweb-1-6-16-waf):

2026/10/19 06:40:17 [notice] 57#57: [INIT] plugins order for phase access : ["ssl","whitelist","letsencrypt","blacklist","greylist","country","dnsbl","bunkernet","crowdsec","reversescan","limit","authbasic","misc","cors","securitytxt","robotstxt","antibot","mtls"]
2026/10/19 06:40:17 [notice] 57#57: [INIT] plugins order for phase preread : ["whitelist","blacklist","greylist","country","dnsbl","reversescan"]

读链要点(依据官方文档 Integrations/Security tuning 与上述实测顺序):

  1. ssl 最先执行:有证书的站点会把 HTTP 请求 301 到 HTTPS(第八节实测);
  2. whitelist 早于一切黑名单类插件:命中白名单的 IP 直接放行后续所有访问类检查(实测见 5.2);
  3. blacklist → country → dnsbl 构成 IP 信誉三连;
  4. limit(限速)与 antibot(人机挑战)在访问阶段后段;
  5. ModSecurity+CRS 引擎独立于上述插件链(phase 1/2),拦截判定走 anomaly score(第四节实测)。

这条链路决定了排障时的观察顺序:先看插件链日志(ACCESS 行),再看 ModSecurity 审计日志。

四、WAF 防护实测闭环

4.1 拦截与放行对照

对同一路径分别发起正常请求与三类攻击特征请求:

curl -sk -o /dev/null -w '正常请求:      %{http_code}\n' -H 'Host: app.local' https://127.0.0.1:9443/
curl -sk -o /dev/null -w 'SQL 注入探测:  %{http_code}\n' -H 'Host: app.local' 'https://127.0.0.1:9443/?id=1%20UNION%20SELECT%201,2,3'
curl -sk -o /dev/null -w '路径遍历探测:  %{http_code}\n' -H 'Host: app.local' 'https://127.0.0.1:9443/?file=../../etc/passwd'

实测回显:

正常请求:      200
SQL 注入探测:  403
路径遍历探测:  403

403 的默认拦截页关键内容(响应体实测节选):

<title>403 | Forbidden</title>
<h1 class="error-code">403</h1>

4.2 规则命中链与审计日志解读

SQL 注入请求的完整判定链在数据面日志中清晰可见(docker logs bunkerweb-1-6-16-waf)。先看异常评分规则逐条命中(节选):

2026/10/19 06:30:56 [warn] ... ModSecurity: Warning. detected SQLi using libinjection.
[id "942100"] [msg "SQL Injection Attack Detected via libinjection"]
[data "Matched Data: 1UE1 found within ARGS:id: 1 UNION SELECT 1,2,3"] [ver "OWASP_CRS/4.29.0"]
...
[id "942190"] [msg "Detects SQL code execution and information gathering attempts"]
...
[id "942360"] [msg "Detects concatenated basic SQL injection and SQLLFI attempts"]

再看最终阻断判定(error 级):

2026/10/19 06:30:56 [error] ... [client 10.89.47.1] ModSecurity: Access denied with code 403 (phase 2).
Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `15' )
[id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 15)"] [ver "OWASP_CRS/4.29.0"]

解读:三条 SQLi 规则(942100 libinjection 检测、942190、942360 正则检测)各贡献 5 分,累计 anomaly score 15 ≥ 阈值 5,触发 949110(Inbound Anomaly Score Exceeded)以 403 阻断。CRS 4.x 的评分模型是理解 WAF 行为的关键:单条规则命中不一定拦截,累计分数过阈值才阻断(阈值由 CRS 防投机位 paranoia level 与异常评分设置控制)。

ModSecurity 审计日志与访问/错误日志一起输出到容器 stderr(实测 /var/log/bunkerweb/modsec_audit.log 为指向 /proc/1/fd/2 的符号链接,即 docker logs 可见)。审计日志按事务分段:---A--(事务元信息:时间戳/源 IP/端口)、---B--(完整请求行与请求头)、---F--(响应行与响应头)。SQL 注入请求的审计段实测(节选):

---hDM1G1Lh---A--
[19/Oct/2026:06:05:26 +0000] 179238992615.740605 10.89.47.1 45638 10.89.47.3 8080
---hDM1G1Lh---B--
GET /?id=1%20UNION%20SELECT%201,2,3 HTTP/1.1
Host: app.local
User-Agent: curl/7.81.0
---hDM1G1Lh---F--
HTTP/1.1 403
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'; ...
X-Content-Type-Options: nosniff

顺带可确认 BunkerWeb 默认注入的响应安全头(X-Frame-Options、Referrer-Policy、CSP、X-Content-Type-Options 等均已在 403 响应中出现),无需额外配置。

五、访问控制实测

5.1 IP 黑名单

站点级配置(compose 环境变量方式,改后 docker compose up -d):

app.local_USE_BLACKLIST: "yes"
app.local_BLACKLIST_IP: "10.89.47.1"     # 测试客户端 IP

实测请求(含正常请求全部被拒):

curl -s -o /dev/null -w 'HTTP %{http_code}\n' -H 'Host: app.local' http://127.0.0.1:8117/
HTTP 403

数据面日志回显:

[2026-10-19 06:06:41 +0000] [ACCESS] [30] [ℹ️ ] - ACCESS app.local /: denied access from blacklist : IP is blacklisted (info : ip), client: 10.89.47.1

5.2 IP 白名单

改为白名单同 IP(先移除黑名单):

app.local_USE_WHITELIST: "yes"
app.local_WHITELIST_IP: "10.89.47.1"

数据面日志回显(攻击请求放行证据):

[2026-10-19 06:05:26 +0000] [ACCESS] [30] [ℹ️ ] - ACCESS app.local /?id=1%20UNION%20SELECT%201,2,3: whitelist returned status 0 : IP is whitelisted (info : ip), client: 10.89.47.1

实测边界(重要):白名单放行的是 BunkerWeb 插件链(后续 blacklist/country/limit/antibot 等不再执行,bad behavior 计数也停止增长),但 ModSecurity CRS 引擎独立执行——同一 SQL 注入请求仍被 CRS 评分阻断(403)。可信 IP 需要同时豁免 CRS 时,用第九节的 SecRuleRemoveById 或站点级 ModSecurity 开关,不要假设"白名单=全放行"。

5.3 国家维度(country 插件)

配置国家黑名单(阻止中国大陆来源):

app.local_COUNTRY_BLACKLIST: "CN"

GeoIP 数据库由调度器作业自动维护,实测日志(docker logs bunkerweb-1-6-16-scheduler):

[2026-10-19 06:40:14 +0000] [JOBS.MMDB-COUNTRY] [35] [ℹ️ ] - Fetching DB-IP metadata for Country database
[2026-10-19 06:40:15 +0000] [JOBS.MMDB-COUNTRY] [35] [ℹ️ ] - Current Country MMDB matches DB-IP SHA-1, skipping download

实测结论(内网边界):本环境客户端 IP 为 10.89.47.1(RFC1918 内网),GeoIP MMDB 中无国家记录,故 country-blacklist=CN 不命中,请求正常放行(HTTPS 200,且日志无 country 拒绝记录)。国家黑白名单只对公网 IP 生效;内网测试想验证国家逻辑,需从具备公网出口的环境发起请求,或临时使用 COUNTRY_WHITELIST(只放行指定国家)观察内网 IP 的"无国家记录"行为差异。该行为与官方 MMDB 数据源(DB-IP)设计一致。

💡 技术拓展:与 IP 黑白名单(精确 IP/CIDR)不同,国家维度基于 GeoIP 数据库把 IP 映射到 ISO 国家代码再匹配名单,适合"业务只服务某国用户"的场景;两者可叠加——公网管理端口用国家白名单收窄来源,再对已确认的恶意 IP 精确封禁。

六、HTTPS 与证书管理

6.1 自签证书(内网环境实测闭环)

内网域名无公网 TLD,Let's Encrypt 无法签发(6.2 实测),自签证书是内网 HTTPS 的正确姿势。配置(全局):

GENERATE_SELF_SIGNED_SSL: "yes"
SELF_SIGNED_SSL_SUBJ: "/CN=app.local/"

调度器证书生成作业实测日志:

[2026-10-19 06:40:14 +0000] [SELF-SIGNED] [35] [ℹ️ ] - Generating self-signed certificate for app.local
[2026-10-19 06:40:14 +0000] [SELF-SIGNED] [35] [ℹ️ ] - Successfully generated self-signed certificate for app.local

HTTPS 实测(curl -k 忽略自签校验):

curl -sk -o /dev/null -w 'HTTPS %{http_code}\n' -H 'Host: app.local' https://127.0.0.1:9443/
echo | openssl s_client -connect 127.0.0.1:9443 -servername app.local 2>/dev/null | openssl x509 -noout -subject -issuer -dates

实测回显:

HTTPS 200
subject=CN = app.local
issuer=CN = app.local
notBefore=Oct 19 06:40:14 2026 GMT
notAfter=Oct 19 06:40:14 2027 GMT

证书由 BunkerWeb 作业自动生成(subject/issuer 均为 CN=app.local,有效期 1 年——与默认设置 self-signed-ssl-expiry=1y 一致),到期由调度器周期作业自动轮换。启用 HTTPS 后的连带行为:ssl 插件自动将 HTTP 请求 301 到 HTTPS(实测日志与响应头):

2026/10/19 06:41:01 [notice] ... [ACCESS] ssl redirect to https://app.local/ : redirect to HTTPS, client: 10.89.47.1, server: app.local
curl -si -H 'Host: app.local' http://127.0.0.1:8117/ | grep -i location
Location: https://app.local/

6.2 Let's Encrypt 签发流程与实测报错

Let's Encrypt 是公网站点的推荐方案,BunkerWeb 内置 Certbot 作业自动完成"注册 ACME 账户 → 下单 → HTTP-01 挑战 → 签发 → 定期续期"。站点级启用配置:

app.local_AUTO_LETS_ENCRYPT: "yes"
app.local_EMAIL_LETS_ENCRYPT: "<你的有效邮箱>"
# 建议先走 staging(测试环境)避免消耗生产配额:
USE_LETS_ENCRYPT_STAGING: "yes"

本环境实测(内网域名 app.local)拿到了两层真实报错,正好构成公网部署时的排障对照:

实测报错一:邮箱被 ACME 拒绝(使用 admin@example.com 时,docker logs bunkerweb-1-6-16-scheduler):

[2026-10-19 06:43:10 +0000] [LETS-ENCRYPT.NEW.CERTBOT] [35] [ℹ️ ] - Unable to register an account with ACME server. The ACME server believes admin@example.com is an invalid email address. Please ensure it is a valid email and attempt registration again.
[2026-10-19 06:43:10 +0000] [SCHEDULER] [35] [❌] - Error while executing job 'certbot-new' from plugin 'letsencrypt'
[2026-10-19 06:43:10 +0000] [SCHEDULER] [35] [❌] - Jobs failed in run_once(): letsencrypt/certbot-new

排查:Let's Encrypt 拒绝保留域(example.com 等)邮箱;换成真实可收信的邮箱格式。

实测报错二:域名无公网 TLD(改用有效格式邮箱 + staging 后,账户注册成功,卡在下单校验):

[2026-10-19 06:45:49 +0000] [LETS-ENCRYPT.NEW.CERTBOT] [35] [ℹ️ ] - An unexpected error occurred:
[2026-10-19 06:45:49 +0000] [LETS-ENCRYPT.NEW.CERTBOT] [35] [ℹ️ ] - Invalid identifiers requested :: Cannot issue for "app.local": Domain name does not end with a valid public suffix (TLD)
[2026-10-19 06:45:49 +0000] [SCHEDULER] [35] [❌] - Error while executing job 'certbot-new' from plugin 'letsencrypt'

排查:app.local 无公网 TLD,Let's Encrypt 不予签发——这是本环境(内网)无法走通 Let's Encrypt 的根本原因,也是 6.1 采用自签的依据。公网域名(如 example.com 购买的真实域名,A 记录指向 BunkerWeb 入口)则可正常签发。

公网部署检查单(依据实测与官方流程):① 域名公网解析到 BunkerWeb 入口 80 端口(HTTP-01 挑战经 80 回访);② 邮箱用真实地址(报错一);③ 先 USE_LETS_ENCRYPT_STAGING=yes 验证全流程再切生产(避免触发速率配额);④ 证书/续期由 certbot-new/certbot-renew 作业自动管理,日志中作业名以 LETS-ENCRYPT.* 前缀出现。

实测完毕后移除上述两项站点级设置并 docker compose up -d 回滚(本环境最终保留自签方案)。

七、日志与审计

BunkerWeb 1.6 的日志体系(实测确认的三个来源):

日志位置内容
访问日志数据面容器 stderr(docker logs bunkerweb-1-6-16-waf)每请求一行:站点、源 IP、会话指纹、请求行、状态码、UA
错误/规则日志同上(error/warn 级)ModSecurity 规则命中详情、插件链 ACCESS 行、告警
审计日志同上(---A--/---B--/---F-- 分段)拦截事务完整取证:请求元信息、原始请求、响应头

访问日志样例(实测,含封禁会话指纹):

app.local 10.89.47.1 - 3e745dc20c949f0079a15deabe412 - [19/Oct/2026:06:41:01 +0000] "GET / HTTP/1.1" 301 162 "-" "curl/7.81.0"

审计建议:① 生产环境将 LOG_TYPES 增配 file/syslog 输出到集中日志(stderr 仅适合容器采集);② 按 unique_id 关联访问日志与审计分段(4.2 中的 unique_id 两处一致);③ 定期在 UI 的 Logs 页面复查 403/429 分布,异常聚集即结合 5.1 黑名单处置;④ 关键站点的拦截事务审计分段建议接入 SIEM 做攻击趋势统计。

八、误拦截排障

8.1 复现一次典型误拦截

业务上"合法"的文件路径参数(如文件下载/预览功能的 path 参数)会被 CRS 的文件访问规则命中:

curl -sk -o /dev/null -w 'HTTP %{http_code}\n' -H 'Host: app.local' 'https://127.0.0.1:9443/download?path=/etc/passwd'

实测回显:

HTTP 403

数据面规则日志(docker logs bunkerweb-1-6-16-waf)提取命中规则:

[id "930120"] [msg "OS File Access Attempt"]
[id "932160"] [msg "Remote Command Execution: Unix Shell Code Found"]

两条规则累计 anomaly score 超阈值 → 949110 阻断(同 4.2 机制)。

8.2 排障四步法(实测闭环)

第一步:定位规则 ID——从 error 日志提取 [id "9xxxxx"](8.1 得到 930120、932160),确认是哪条规则误伤;

第二步:评估排除范围——业务确需传递此类参数时,仅对受影响站点移除该规则,不要全局关闭 CRS;

第三步:注入规则排除配置——通过 UI(Services → 站点 → Custom configs → 类型选 modsec)或数据库注入:

docker exec -i bunkerweb-1-6-16-db psql -U bunkerweb -d db -c "
INSERT INTO bw_custom_configs (service_id, type, name, data, checksum, method, is_draft)
VALUES ('app.local', 'modsec', 'remove-false-positive.conf',
        convert_to('SecRuleRemoveById 930120 932160', 'UTF8'),
        'manual-fp-01', 'ui', false);"
docker restart bunkerweb-1-6-16-scheduler   # 触发全量重渲染下发

第四步:验证放行与恢复——同一请求实测从 403 变 404(CRS 放行后请求到达后端,后端无 /download 路由返回 404——404 说明拦截已解除、流量已进入业务层),正常业务请求仍 200:

HTTPS 404   # /download?path=/etc/passwd(放行到后端)
HTTPS 200   # /(业务正常)

排障完成后按相同方式清理(实测清理必须三处齐动,否则配置会被回写,详见《BunkerWeb 1.6.16 配置参数详解》第七节):

docker stop bunkerweb-1-6-16-scheduler
docker exec -i bunkerweb-1-6-16-db psql -U bunkerweb -d db -c "DELETE FROM bw_custom_configs;"
docker run --rm -v bunkerweb-1-6-16_bw-storage:/d alpine sh -c 'rm -f /d/configs/modsec/app.local/remove-false-positive.conf'
docker restart bunkerweb-1-6-16-ui
docker start bunkerweb-1-6-16-scheduler

清理后复测同一请求恢复 403(CRS 拦截回归),实测回显:

HTTPS 403

排除策略补充:比 SecRuleRemoveById 更精细的手段包括——调整 CRS 防投机位(站点级 paranoia level,降低误报但削弱检测)、crs_plugins_before 上下文为特定 URI 添加排除规则(SecRuleRemoveById 配合 ctl:ruleRemoveTargetById 只排除指定参数)、或对确认无害的路径用 whitelist 自定义配置整体豁免。原则:排除范围最小化,先试参数级,再试规则级,最后才是站点级。

九、常见问题

Q1:开启 HTTPS 后所有 HTTP 请求都变 301?

实测确认这是 ssl 插件的默认行为(日志 ssl redirect to https://app.local/ : redirect to HTTPS,响应 Location: https://app.local/)。内网客户端不支持 HTTPS 时可设置站点级 AUTO_REDIRECT_HTTP_TO_HTTPS=no 关闭;生产建议保持强制跳转。

Q2:bad behavior 把自己封了怎么解?

办公出口 IP 被误封(连续 403/404 达到阈值 10)时:短期用白名单(WHITELIST_IP 加办公网段,实测白名单后 bad behavior 计数停止增长);长期在 UI 调整 BAD_BEHAVIOR_THRESHOLD/BAD_BEHAVIOR_PERIOD(默认 10 次/1 分钟)与 BAN_TIME(默认 1d)。封禁状态可在 UI Bans 页面查看与手动解除。

Q3:日志里偶见 using uninitialized "reason" variable while logging request?

实测在 301 重定向请求的日志中出现(BunkerWeb 1.6.16 数据面 log 格式的小瑕疵),不影响拦截与代理功能,升级版本关注 changelog 即可:

2026/10/19 06:40:33 [warn] 3869#3869: *6424 using uninitialized "reason" variable while logging request, client: 10.89.47.1, server: app.local

Q4:连续测试时突然全是 429?

两套限速机制叠加所致:单 URL 限速(limit-req-rate 默认 2r/s,实测 200→429×3)与 bad behavior 行为封禁(连续异常请求累计 10 次即封 1 天)。测试环境可临时调高速率、从 UI 解除封禁或用白名单豁免测试源。

Q5:国家封禁配置后内网测试不生效?

GeoIP 数据库不含 RFC1918 内网地址,country 黑白名单对内网 IP 无国家可匹配(5.3 实测结论)。验证国家逻辑请用公网出口环境。

十、参考链接