上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器的Debian系统中部署WAF防火墙,抵御跨境支付系统的SQL注入攻击?

发布人:Minchunlin 发布时间:2025-09-13 11:11 阅读量:778


那天凌晨 1:40,部署在香港荃湾机房的支付风控的告警:国内用户通过我们香港集群下单时,/api/pay/charge 的 5xx 报表开始抖;同时,日志里冒出一串典型的 1' OR '1'='1。熟悉的味道——SQL 注入在撞门。

我没有选择把流量切回 Cloud 侧的“保守”模式(会影响转化),而是决定直接在业务入口的 Debian 12(bookworm) Nginx 反向代理层落地 ModSecurity + OWASP CRS 的 WAF,配合几条和支付域模型贴身的规则,先拦住再说。

下面是我那晚的完整部署与优化流程,细到新手能照抄,老手也不浪费时间。

一、现场环境与目标

1)硬件与网络(香港机柜)

参数
机型 1U 单路,Intel® Xeon Silver(24 核),超线程开启
内存 64 GB DDR4-3200(vm.swappiness=1
磁盘 2 × 1.92 TB NVMe(RAID1,XFS,noatime
网卡 2 × 10 GbE,Bond0 (802.3ad),上联双路
系统 Debian 12(bookworm),内核 6.x,systemd
角色 L7 反代 + WAF(Nginx)→ 上游应用(支付网关集群)

2)流量与性能基线(上线前)

指标 数值(p95)
峰值 QPS 6.8k(主要为 /api/pay/* JSON POST)
入站带宽 1.6 Gbps
业务延迟 48 ms(HK→广州/深圳),95ms(SEA)
错误率 0.12%(偶发上游超时)

目标:在不开启全站挑战/JS 检测的前提下,拦截 99% 以上 SQLi,业务 p95 延迟增加 ≤ 6ms,峰值 QPS 损耗 ≤ 15%。

二、安装方案选择与原理确认(1 分钟决策)

Debian 12 官方仓库直接提供 libmodsecurity3(WAF 引擎库)与 Nginx 连接器模块 libnginx-mod-http-modsecurity,无需源码编译,可靠省时。

规则集采用 OWASP CRS 3.x(核心规则集,覆盖 SQLi/XSS/LFI 等 Top 10 类形)。

如果你的仓库缺少连接器,才考虑源码编译(当晚我没用这个兜底,但这里给出参考)。

三、快速部署:在 Debian 12 上启用 Nginx + ModSecurity + CRS

以下命令都在 root 或 sudo 下执行。域名以 pay.example.com 为例。

1)安装组件

apt update
apt install -y nginx libmodsecurity3 libmodsecurity-dev \
  libnginx-mod-http-modsecurity modsecurity-crs

验证模块已装:

nginx -V 2>&1 | grep -i modsecurity || echo "动态模块将通过 load_module 加载"
dpkg -l | egrep "libmodsecurity3|libnginx-mod-http-modsecurity|modsecurity-crs"

上面两个包分别对应 WAF 引擎库 与 Nginx 连接器,CRS 为规则集。

2)启用 Nginx 动态模块

文件:/etc/nginx/modules-enabled/90-modsecurity.conf(若不存在则创建)

load_module modules/ngx_http_modsecurity_module.so;

3)初始化 ModSecurity

# 生成主配置
mkdir -p /etc/nginx/modsec
cp /etc/modsecurity/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf

编辑 /etc/nginx/modsec/modsecurity.conf 的关键项(精简示例):

SecRuleEngine On
SecAuditEngine RelevantOnly
SecAuditLog /var/log/modsec_audit.log
SecAuditLogFormat JSON
SecRequestBodyAccess On
SecRequestBodyLimit 13107200        # 12.5MB:足够支付票据/收据
SecRequestBodyNoFilesLimit 5242880
SecPcreMatchLimit 100000            # PCRE2 + JIT 环境下适中
SecPcreMatchLimitRecursion 100000

Debian 的 libmodsecurity3 已采用 PCRE2;可用 pcre2test -Cjit 检查 JIT。

4)接入 OWASP CRS

# Debian 的 CRS 安装路径
ls -l /usr/share/modsecurity-crs
mkdir -p /etc/nginx/modsec/crs
cp -r /usr/share/modsecurity-crs/* /etc/nginx/modsec/crs/

# 启用 crs-setup.conf:如有样例文件则复制
[ -f /etc/nginx/modsec/crs/crs-setup.conf.example ] && \
  cp /etc/nginx/modsec/crs/crs-setup.conf.example /etc/nginx/modsec/crs/crs-setup.conf

创建 主规则汇入 文件 /etc/nginx/modsec/main.conf:

# 先加载引擎主配置
Include /etc/nginx/modsec/modsecurity.conf

# 告诉引擎:JSON 请求体使用 JSON 处理器(支付接口大多 application/json)
SecRule REQUEST_HEADERS:Content-Type "application/json" \
    "id:100001,phase:1,pass,t:none,ctl:requestBodyProcessor=JSON"

# 加载 CRS
Include /etc/nginx/modsec/crs/crs-setup.conf
Include /etc/nginx/modsec/crs/rules/*.conf

CRS 是官方社区主力规则集,默认就覆盖 SQLi。

5)在 Nginx 站点中开启 WAF

编辑你的 server 配置(如 /etc/nginx/sites-available/pay.conf):

server {
    listen 80;
    listen 443 ssl http2;
    server_name pay.example.com;

    # ... SSL 略(生产务必启用 TLS1.2/1.3,OCSP,HSTS)

    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/main.conf;

    location /api/pay/ {
        proxy_pass http://pay_upstream;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_read_timeout 30s;
    }
}

upstream pay_upstream {
    least_conn;
    server 10.20.0.11:8080 max_fails=2 fail_timeout=10s;
    server 10.20.0.12:8080 max_fails=2 fail_timeout=10s;
    keepalive 256;
}

检测并重载:

nginx -t && systemctl reload nginx

四、支付域模型的“贴身”强化规则(拦 SQLi,少误杀)

通用规则挡大头,业务规则压住长尾。以下是我在 main.conf 里追加的“轻量但致命”的几条。

注意:下面示例使用 ModSecurity 的 @detectSQLi(libinjection)与参数白名单思路,仅对我们支付域模型,你可以按需改键名/正则。

# 1)对关键信息强约束:订单号/金额应为限定格式
SecRule ARGS_GET:order_id "!^\d{6,18}$" \
    "id:110010,phase:2,deny,status:403,log,msg:'order_id must be 6-18 digits'"

SecRule ARGS|ARGS_NAMES|REQUEST_BODY "@rx \"\\b(amount|price)\\b\"" \
    "id:110011,phase:2,pass,log,ctl:ruleRemoveById=942100"

SecRule REQUEST_BODY_JSON:amount "!^\d{1,6}(?:\.\d{1,2})?$" \
    "id:110012,phase:2,deny,status:403,log,msg:'invalid amount format'"

# 2)对高风险字段直接做 SQLi 检测(GET/POST/JSON 都扫)
SecRule ARGS|ARGS_NAMES|REQUEST_COOKIES|REQUEST_HEADERS "@detectSQLi" \
    "id:110020,phase:2,deny,status:403,log,msg:'SQLi detected in args/headers'"

SecRule REQUEST_BODY_JSON "@detectSQLi" \
    "id:110021,phase:2,deny,status:403,log,msg:'SQLi detected in JSON body'"

# 3)典型情景白名单(减少误报)
# 产品名里可能出现 'select plan' 等字样,放宽 product_name
SecRuleUpdateTargetByTag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION" "!ARGS:product_name"
SecRuleUpdateTargetByTag "OWASP_CRS/WEB_ATTACK/SQL_INJECTION" "!REQUEST_BODY_JSON:product_name"

规则顺序:先强约束(便宜、快),再行为检测(detectSQLi),最后白名单做减法。这样既快又稳。

五、联动:速拦 → 取证 → 复盘

1)日志与审计(不触碰敏感持卡信息)

Nginx access log(摘掉敏感字段,保留关键上下文):

log_format waf_json escape=json
  '{ "time":"$time_iso8601", "client":"$remote_addr", "host":"$host", '
  '"uri":"$request_uri", "status":$status, "rt":$request_time, '
  '"up_rt":"$upstream_response_time", "req_len":$request_length, '
  '"body_size":$body_bytes_sent, "ua":"$http_user_agent", '
  '"x_req_id":"$request_id" }';

access_log /var/log/nginx/pay_access.log waf_json;

ModSecurity 审计已设置为 JSON,结合 jq/ELK 立刻分流分析:

tail -f /var/log/modsec_audit.log | jq '.msg, .transaction.request.uri'

合规提醒(PCI DSS):不要把 PAN/CVV 原样落日志;上游应用应在进入反代前就进行脱敏,或使用字段级加密后在后端解密。WAF 侧一律不记录敏感载荷。

2)快速验证(安全自测,不冲生产)

正常:

curl -sS -o /dev/null -w '%{http_code}\n' \
 -H "Content-Type: application/json" \
 -d '{"order_id":"20250913000123","amount":"199.99"}' \
 https://pay.example.com/api/pay/charge

恶意(演示用):

curl -i -H "Content-Type: application/json" \
 -d '{"order_id":"1 OR 1=1","amount":"199.99"}' \
 https://pay.example.com/api/pay/charge
# 预期:403 & ModSecurity 记录

六、性能与延迟:怎么把代价压到业务能接受

1)Nginx 基础调优

/etc/nginx/nginx.conf 关键项:

worker_processes auto;
worker_rlimit_nofile 262144;

events { worker_connections  65535; use epoll; }

http {
  sendfile on; tcp_nopush on; tcp_nodelay on;
  keepalive_timeout  65s;
  client_body_buffer_size 64k;
  client_max_body_size 10m;   # 结合 SecRequestBodyLimit
  # gzip/br 视流量类型决定
}

2)ModSecurity 的性价比配置

PCRE2 JIT:确认启用(上文),可显著降低复杂正则开销。

  • 请求体限制:按支付载荷设定 SecRequestBody{,NoFiles}Limit,避免过度扫描大文件。
  • 有的放矢:只在 /api/pay/ 开启 modsecurity on;,静态与图片域名不启。
  • 白名单减法:对明确“常误杀”的键做 SecRuleUpdateTargetByTag 排除,减少无用匹配。

3)上线后的真实数据(开启 WAF 当晚)

指标 上线前 上线后(15 分钟窗口)
峰值 QPS 6.8k 6.1k(-10.3%)
p95 网关延迟 48 ms 53 ms(+5 ms)
403 拦截率(SQLi 类) 99.2%(审计样本)
误报率(人工抽样) 0.18%(白名单后 <0.05%)

这组数据达成了当晚的目标:延迟 +5ms,吞吐损耗 ~10%,换来稳定的 SQLi 拦截。

七、那晚踩过的坑与我当场怎么填

JSON 没被解析:最初没显式声明 JSON 处理器,导致规则命中率低。
修复:在 main.conf 里加了 ctl:requestBodyProcessor=JSON(上文示例)。

产品名误杀:"select plan" 被当成 SQLi。
修复:对 product_name 用 SecRuleUpdateTargetByTag 排除 SQLi 目标(上文示例)。

上游 502 抖动:CRS 默认扫描 + 限流叠加,个别慢接口被误判掉链。
修复:支付接口分层:对 /api/pay/charge 开较严,对 /api/pay/query 放宽;并把 proxy_read_timeout 调到 30s。

审计日志爆量:RelevantOnly 仍多。
修复:接入 ELK 的索引生命周期策略(ILM),并分 index:waf-audit-YYYY.MM.DD。

动态模块未加载:忘了 load_module。
修复:统一以 modules-enabled/90-modsecurity.conf 管理,增 nginx -t precheck 的 GitHook。

八、进一步加固(第二天白天做的)

  • 地理/ASN 细粒度:结合 geoip2 或自建 IP 信誉表,对“新 ASN + 高失败率”的来源提高敏感度(仅调 WAF 灵敏度,不做地域歧视)。
  • 速率限制:在 /api/pay/ 增加 limit_req(按 X-Request-Id/账户维度更合理)。
  • 蓝绿规则:把自定义规则拆为 main-business.conf,灰度发布(按 map $cookie_sticky 选择是否加载)。
  • 上游参数强类型:后端网关仍要做参数化查询(WAF 不是银弹)。
  • 备选方案兜底:若某时段攻击过猛,可以短时切换到云侧/前置高防的托管 WAF策略,再回切(本次没有动用)。

九、完整目录结构(上线时的落地)

/etc/nginx/
├── nginx.conf
├── modules-enabled/
│   └── 90-modsecurity.conf    # load_module ...
├── sites-available/
│   └── pay.conf               # server + upstream
├── sites-enabled/
│   └── pay.conf -> ../sites-available/pay.conf
└── modsec/
    ├── modsecurity.conf
    ├── main.conf              # Include 引擎 + CRS + JSON ctl + 业务规则
    ├── main-business.conf     # (次日拆分出的业务规则)
    └── crs/
        ├── crs-setup.conf
        └── rules/*.conf

十、应急与回滚剧本(我机房里贴在架子上的那张纸)

观察窗口:规则改动后 5 分钟内,Grafana 四套看板(QPS/RT/4xx/上游 5xx)不得离眼。

流量分流:Nginx 层标签灰度(或 LVS 层),异常即回滚到“仅日志、不开惩罚”的 SecRuleEngine DetectionOnly。

审计抽样:每 1000 条 403 抽 30 条人工复核,误报 >0.5% 即回减规则。

工单告知:风控、客服同步模板:出现人机验证/被拦提示如何申诉。

十一、我为什么选这条路(以及你可以怎么变体)

  • 用官方仓库的包:libmodsecurity3 + libnginx-mod-http-modsecurity 稳、快、可追溯;出问题能“apt pin + 快速回退”。
  • CRS + 业务小规则:CRS 吃下 80%,剩余 20% 用你自己的业务知识补齐,误报率好控。
  • 变体:如果你环境特殊(比如 Nginx 版本自行编译、或仓库缺模块),再用 源码路径装连接器。

系统稳定下来时,机房外开始泛白。那晚我们拦住了异常的注入流量,交易成功率没怎么掉,支付页面也不再卡顿。我抓起那杯被空调风吹成常温的冰美式,补上变更记录,给白班同事留了两页纸的“坑位图”。

这就是我在香港服务器的 Debian 系统里,把 WAF 从零到一搭起来、并在跨境支付场景下压住 SQL 注入的全过程。
如果你也在做类似的事,建议从“先跑起来,再贴身调优”开始:CRS 护住大面,业务规则补齐长尾,日志和灰度把握节奏。等到哪天深夜警报又响起,你会发现,那杯咖啡还能喝得上。

附:快速命令清单(方便你复制)

# 安装
apt install -y nginx libmodsecurity3 libmodsecurity-dev \
  libnginx-mod-http-modsecurity modsecurity-crs

# 启用模块
echo 'load_module modules/ngx_http_modsecurity_module.so;' \
  > /etc/nginx/modules-enabled/90-modsecurity.conf

# 初始化配置
mkdir -p /etc/nginx/modsec
cp /etc/modsecurity/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf
cp -r /usr/share/modsecurity-crs /etc/nginx/modsec/crs
[ -f /etc/nginx/modsec/crs/crs-setup.conf.example ] && \
  cp /etc/nginx/modsec/crs/crs-setup.conf.example /etc/nginx/modsec/crs/crs-setup.conf

# 主规则文件(按上文 main.conf 内容)
$EDITOR /etc/nginx/modsec/main.conf

# Nginx 站点开启 WAF(按上文 server/upstream)
$EDITOR /etc/nginx/sites-available/pay.conf
ln -sf /etc/nginx/sites-available/pay.conf /etc/nginx/sites-enabled/pay.conf

# 生效
nginx -t && systemctl reload nginx

参考:Debian 的 ModSecurity 库与 Nginx 连接器包说明、OWASP CRS 项目页(确认名称与作用)。

目录结构
全文