CDN 与 WAF 精讲教程 / 第13章 日志与分析
第13章 日志与分析
安全防护的最后也是最关键的一环:日志分析。没有完善的日志收集、分析和告警体系,WAF 形同虚设。本章讲解 CDN/WAF 日志的收集、分析和安全审计实践。
13.1 日志体系架构
13.1.1 日志收集架构
日志收集与分析架构:
┌─────────────────────────────────────────────────────────────────┐
│ 日志来源 │
│ ├── CDN 边缘日志 (Nginx / Cloudflare Logs) │
│ ├── WAF 审计日志 (ModSecurity / 自定义 WAF) │
│ ├── 应用访问日志 (App Access Log) │
│ ├── 应用错误日志 (App Error Log) │
│ ├── 系统日志 (syslog / journald) │
│ └── 数据库审计日志 (MySQL Audit / pgAudit) │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────┐
│ 日志传输层 │
│ ├── Filebeat / Fluentd / Vector / Promtail │
│ ├── Kafka (缓冲层,可选) │
│ └── 直连 Elasticsearch / Loki │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────┐
│ 存储与索引 │
│ ├── Elasticsearch (全文索引,结构化查询) │
│ ├── Loki (标签索引,轻量级) │
│ └── ClickHouse (列式存储,大数据量) │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────┐
│ 分析与可视化 │
│ ├── Kibana (Elasticsearch 生态) │
│ ├── Grafana (Loki / Prometheus 生态) │
│ └── 自定义仪表盘 │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────┐
│ 告警与响应 │
│ ├── 邮件 / 短信 / 企业微信 / 钉钉 │
│ ├── PagerDuty / OpsGenie (On-Call) │
│ └── SOAR (安全编排自动化响应) │
└─────────────────────────────────────────────────────────────────┘
13.2 日志格式标准
13.2.1 WAF 日志字段
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
timestamp | datetime | 请求时间 | 2026-05-10T12:00:00Z |
client_ip | string | 客户端 IP | 203.0.113.50 |
method | string | HTTP 方法 | GET |
uri | string | 请求路径 | /api/users?id=1' |
status | int | 响应状态码 | 403 |
user_agent | string | UA | sqlmap/1.0 |
rule_id | string | 触发规则 ID | 942100 |
rule_msg | string | 规则描述 | SQL Injection Attack |
severity | string | 严重级别 | CRITICAL |
action | string | 处置动作 | BLOCK |
anomaly_score | int | 异常评分 | 10 |
request_body | string | 请求体 | (encoded) |
response_time | int | 响应耗时 (ms) | 5 |
13.2.2 Nginx JSON 日志格式
# Nginx JSON 日志格式
log_format json_combined escape=json
'{'
'"timestamp":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"upstream_addr":"$upstream_addr",'
'"upstream_response_time":"$upstream_response_time",'
'"ssl_protocol":"$ssl_protocol",'
'"ssl_cipher":"$ssl_cipher"'
'}';
access_log /var/log/nginx/access.json json_combined;
13.2.3 ModSecurity 审计日志
--a]--
[10/May/2026:12:00:00 +0800] 1715347200 203.0.113.50 54321 192.0.2.1 443
--A]--
GET /api/users?id=1%27+UNION+SELECT+*+FROM+users-- HTTP/1.1
Host: example.com
User-Agent: sqlmap/1.0
Accept: */*
--B]--
HTTP/1.1 403 Forbidden
--H]--
Message: Warning. detected SQLi using libinj. [file "/etc/nginx/modsecurity/crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"] [line "42"] [id "942100"] [msg "SQL Injection Attack Detected via libinj"] [data "Matched Data: UNION SELECT found within ARGS:id: 1' UNION SELECT * FROM users--"] [severity "CRITICAL"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-sqli"] [tag "OWASP_CRS"] [tag "paranoia-level/1"]
Action: Intercepted (phase 2)
--Z]--
13.3 ELK 部署
13.3.1 Docker Compose 快速部署
# docker-compose.yml - ELK + Filebeat
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=changeme
- "ES_JAVA_OPTS=-Xms2g -Xmx2g"
volumes:
- es_data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- elk
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\"\\|\"status\":\"yellow\"'"]
interval: 10s
timeout: 5s
retries: 5
kibana:
image: docker.elastic.co/kibana/kibana:8.12.0
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=changeme
ports:
- "5601:5601"
networks:
- elk
depends_on:
elasticsearch:
condition: service_healthy
filebeat:
image: docker.elastic.co/beats/filebeat:8.12.0
user: root
volumes:
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/log/nginx:/var/log/nginx:ro
- /var/log/modsecurity:/var/log/modsecurity:ro
- filebeat_data:/usr/share/filebeat/data
networks:
- elk
depends_on:
elasticsearch:
condition: service_healthy
volumes:
es_data:
filebeat_data:
networks:
elk:
driver: bridge
13.3.2 Filebeat 配置
# filebeat.yml
filebeat.inputs:
# Nginx JSON 日志
- type: filestream
paths:
- /var/log/nginx/access.json
parsers:
- ndjson:
target: ""
add_error_key: true
fields:
log_type: nginx_access
fields_under_root: true
# ModSecurity 审计日志
- type: filestream
paths:
- /var/log/modsecurity/modsec_audit.log
parsers:
- multiline:
pattern: '^\-\-'
negate: true
match: after
fields:
log_type: modsecurity
fields_under_root: true
output.elasticsearch:
hosts: ["http://elasticsearch:9200"]
username: "elastic"
password: "changeme"
index: "waf-logs-%{+yyyy.MM.dd}"
setup.template:
name: "waf-logs"
pattern: "waf-logs-*"
setup.ilm.enabled: false
13.4 攻击检测分析
13.4.1 KQL 查询示例
# Kibana KQL 查询示例
# 1. 查询所有被拦截的 SQL 注入攻击
rule_id: "942*" AND action: "BLOCK"
# 2. 查询特定 IP 的攻击记录
client_ip: "203.0.113.50"
# 3. 查询过去 1 小时的攻击 Top 10 IP
# (使用 Kibana Visualization → Top 10 → Terms Aggregation → client_ip)
# 4. 查询 XSS 攻击
uri: "<script" OR uri: "javascript:" OR uri: "onerror"
# 5. 查询可疑爬虫
user_agent: ("sqlmap" OR "nikto" OR "nmap" OR "masscan")
# 6. 查询 403 响应
status: 403
# 7. 查询慢请求 (超过 5 秒)
request_time: >5
13.4.2 攻击统计仪表盘
Kibana 安全仪表盘设计:
┌──────────────────────────────────────────────────────────────┐
│ 安全概览仪表盘 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐│
│ │ 总请求数 │ │ 攻击拦截数 │ │ 拦截率 ││
│ │ 1,234,567 │ │ 12,345 │ │ 1.0% ││
│ └──────────────┘ └──────────────┘ └──────────────────────┘│
│ │
│ ┌──────────────────────────────────────────────────────────┐│
│ │ 攻击趋势图 (时间序列) ││
│ │ [====攻击量折线图====] ││
│ └──────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────┐ ┌──────────────────────────────────┐│
│ │ 攻击类型分布 (饼图) │ │ Top 10 攻击 IP (柱状图) ││
│ │ SQLi: 45% │ │ 1. 203.0.113.50 (2,345) ││
│ │ XSS: 30% │ │ 2. 198.51.100.10 (1,234) ││
│ │ RCE: 10% │ │ 3. 192.0.2.99 (987) ││
│ │ Other: 15% │ │ ││
│ └─────────────────────┘ └──────────────────────────────────┘│
│ │
│ ┌──────────────────────────────────────────────────────────┐│
│ │ 最近攻击日志 (表格) ││
│ │ 时间 | IP | 规则 | URI | 动作 ││
│ │ ... ││
│ └──────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────┘
13.5 实时告警
13.5.1 告警规则设计
| 告警级别 | 条件 | 响应时间 | 通知方式 |
|---|---|---|---|
| P0 紧急 | 大规模 DDoS / 数据泄露 | 5 分钟 | 电话 + 短信 |
| P1 严重 | WAF 高频拦截(>100/min) | 15 分钟 | 短信 + 企业微信 |
| P2 警告 | 可疑 IP 反复触发规则 | 1 小时 | 企业微信 |
| P3 信息 | 新攻击模式出现 | 24 小时 | 邮件 |
13.5.2 ElastAlert 告警规则
# elastalert_rules/high_frequency_attack.yaml
name: "高频攻击告警"
type: frequency
index: waf-logs-*
# 条件:5 分钟内同 IP 攻击超过 50 次
num_events: 50
timeframe:
minutes: 5
filter:
- term:
action: "BLOCK"
query_key: client_ip
alert:
- "email"
- "slack"
email:
- "[email protected]"
slack:
slack_webhook_url: "https://hooks.slack.com/services/xxx"
slack_channel: "#security-alerts"
slack_username: "WAF Alert"
alert_text: |
⚠️ 高频攻击告警
攻击 IP: {0}
5 分钟内拦截次数: {1}
最近攻击规则: {2}
时间: {3}
alert_text_args:
- client_ip
- num_hits
- rule_msg
- timestamp
13.6 安全审计
13.6.1 审计日志保留策略
| 日志类型 | 保留周期 | 存储位置 | 合规要求 |
|---|---|---|---|
| WAF 审计日志 | 90 天热 + 1 年冷 | ES + S3/OSS | 等保 2.0 |
| CDN 访问日志 | 30 天热 + 6 个月冷 | ES + 对象存储 | — |
| 系统日志 | 6 个月 | ES + 归档 | 等保 2.0 |
| 数据库审计 | 1 年 | 专用存储 | 等保/PCI DSS |
13.6.2 等保 2.0 日志要求
等保 2.0 (GB/T 22239-2019) 日志要求:
├── 应当记录:
│ ├── 用户登录/登出日志
│ ├── 重要操作行为日志
│ ├── 安全事件日志
│ ├── 异常访问日志
│ └── 管理员操作日志
│
├── 日志内容:
│ ├── 事件日期和时间
│ ├── 用户标识
│ ├── 事件类型
│ ├── 事件是否成功
│ └── 其他与审计相关的信息
│
└── 保留要求:
├── 保存时间 ≥ 6 个月(三级等保)
└── 定期备份,防止篡改/丢失
13.7 注意事项
⚠️ 日志量控制:WAF 日志量巨大。建议只记录被拦截的请求和可疑请求,正常请求使用采样记录。
⚠️ 敏感信息脱敏:日志中的请求体可能包含密码、信用卡号等敏感信息,需在采集时脱敏。
⚠️ 日志完整性:防止日志被篡改。建议开启日志签名或使用 WORM(Write Once Read Many)存储。
⚠️ 性能影响:高频日志写入可能影响 WAF 性能。建议使用异步日志写入。
13.8 扩展阅读
- Elastic Stack Documentation — ELK 官方文档
- Grafana Loki — 轻量级日志系统
- OWASP Logging Cheat Sheet — 日志最佳实践
- ElastAlert — 基于 ES 的告警框架
本章小结
| 主题 | 核心要点 |
|---|---|
| 架构 | 来源 → 传输 (Filebeat) → 存储 (ES) → 可视化 (Kibana) |
| 日志格式 | JSON 结构化,包含 rule_id/severity/action |
| 攻击分析 | KQL 查询 + 仪表盘 + Top N 分析 |
| 实时告警 | ElastAlert/Watcher 按频率/阈值触发 |
| 审计合规 | 等保 2.0 要求 ≥ 6 个月保留 |
下一章:第14章 Docker 部署 →