商业安全产品昂贵且黑盒,开源方案虽灵活但需整合。企业面临的常见问题包括:
本文目标:
互联网流量
│
▼
┌─────────────────┐
│ Nginx + │ <- WAF (ModSecurity)
│ ModSecurity │ 拦截 SQLi/XSS/文件包含
└─────────────────┘
│
▼
┌─────────────────┐
│ 应用服务器 │ <- 业务后端
└─────────────────┘
│
▼
┌─────────────────┐
│ Suricata │ <- 网络流量镜像,检测扫描、暴力破解
└─────────────────┘
│
▼
┌─────────────────┐
│ ELK Stack │ <- 日志存储、索引、可视化
│ (Filebeat → │
│ Elasticsearch │
│ → Kibana) │
└─────────────────┘
│
▼
┌─────────────────┐
│ 漏洞扫描器 │ <- 定时任务,扫描内部资产
└─────────────────┘
│
▼
┌─────────────────┐
│ 自动化响应 │ <- Python 脚本,接收告警并封禁 IP
└─────────────────┘技术选型:
docker-compose-waf.yml:
version: '3.8'
services:
nginx-waf:
image: owasp/modsecurity:nginx
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./modsecurity/:/etc/modsecurity/
- ./crs/:/etc/modsecurity-crs/
- ./logs/:/var/log/nginx/
restart: unless-stopped启用 OWASP CRS,并自定义规则封禁恶意 IP。
modsecurity/modsecurity.conf(片段):
# 开启审计日志
SecAuditEngine On
SecAuditLog /var/log/nginx/modsec_audit.log
SecAuditLogParts ABCDEFGHIJKZ
# 加载 CRS 规则
Include /etc/modsecurity-crs/crs-setup.conf
Include /etc/modsecurity-crs/rules/*.conf
# 自定义规则:拒绝请求头包含 'evil-agent' 的请求
SecRule REQUEST_HEADERS:User-Agent "evil-agent" \
"id:1001,phase:1,deny,status:403,msg:'Blocked evil user agent'"
# 自定义规则:限制单个 IP 每分钟请求数(防暴力破解)
SecRule IP:REQ_COUNT "@gt 100" \
"id:1002,phase:1,deny,status:429,msg:'Rate limit exceeded'"
SecRule IP:REQ_COUNT "@eq 1" \
"id:1003,phase:1,pass,nolog,setvar:IP.REQ_COUNT=1,expirevar:IP.REQ_COUNT=60"发送恶意请求:
curl -X POST http://localhost/login \
-d "username=admin' OR '1'='1&password=test" \
-H "User-Agent: evil-agent"预期返回 403。
sudo apt install suricata
sudo suricata-update # 更新规则
sudo systemctl enable suricata/etc/suricata/suricata.yaml 关键配置:
af-packet:
- interface: eth0
cluster-id: 99
cluster-type: cluster_flow
defrag: yes
use-mmap: yes启用社区规则和自定义规则:
/etc/suricata/rules/local.rules:
# 检测 SQL 注入特征(简化)
alert http any any -> any any (msg:"SQL Injection detected"; flow:to_server; content:"SELECT"; http_uri; pcre:"/(\%27)|(\')|(\-\-)|(\%23)/i"; sid:10001; rev:1;)
# 检测暴力破解(多次登录失败)
alert http any any -> any any (msg:"Possible brute force"; flow:to_server; content:"login"; http_uri; threshold:type both, track by_src, count 10, seconds 60; sid:10002; rev:1;)
# 检测端口扫描
alert tcp any any -> any any (msg:"Port scan detected"; flags:S; threshold:type both, track by_dst, count 20, seconds 10; sid:10003; rev:1;)Suricata 支持输出到 Elasticsearch(通过 eve.json 和 Filebeat)。
安装 Filebeat:
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.10.0-amd64.deb
sudo dpkg -i filebeat-8.10.0-amd64.deb配置 Filebeat(/etc/filebeat/filebeat.yml):
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/suricata/eve.json
json.keys_under_root: true
json.overwrite_keys: true
output.elasticsearch:
hosts: ["localhost:9200"]
username: "elastic"
password: "changeme"
setup.kibana:
host: "localhost:5601"启动:
sudo systemctl start filebeat编写轻量级扫描器,检测常见 Web 漏洞(SQL 注入、XSS、目录遍历)。
vuln_scanner.py:
import requests
import concurrent.futures
from urllib.parse import urljoin
import sys
import time
class WebVulnScanner:
def __init__(self, base_url, max_workers=10):
self.base_url = base_url if base_url.endswith('/') else base_url + '/'
self.session = requests.Session()
self.session.headers.update({'User-Agent': 'VulnScanner/1.0'})
self.max_workers = max_workers
self.results = []
self.payloads = {
'sqli': [
"'",
"admin' OR '1'='1",
"1' AND '1'='1' -- ",
"' UNION SELECT NULL, NULL, NULL -- "
],
'xss': [
"<script>alert('XSS')</script>",
"<img src=x onerror=alert(1)>",
"javascript:alert('XSS')"
],
'path_traversal': [
"../../../../etc/passwd",
"..\\..\\..\\windows\\win.ini",
"%2e%2e%2fetc/passwd"
]
}
self.test_params = ['id', 'user', 'query', 'page', 'cat', 'prod', 'file']
def scan_parameter(self, url, param, payload):
"""在指定参数上尝试注入payload"""
for payload_value in payload:
test_url = url + '?' + param + '=' + requests.utils.quote(payload_value)
try:
resp = self.session.get(test_url, timeout=5)
if self._detect_vulnerability(resp, payload_value):
self.results.append({
'url': test_url,
'param': param,
'payload': payload_value,
'vuln_type': self._classify(response_text=resp.text, payload=payload_value)
})
return # 发现漏洞就不再测该参数
except:
pass
def _detect_vulnerability(self, resp, payload):
"""检测响应中是否存在漏洞迹象"""
text = resp.text.lower()
# SQL 注入特征
if 'sql' in text or 'mysql' in text or 'ora-0' in text or 'warning' in text:
if payload in resp.text:
return True
# XSS 特征
if '<script>' in text or 'onerror=' in text or 'alert(' in text:
return True
# 路径遍历特征:包含 /etc/passwd 或 root:
if 'root:' in text or 'win.ini' in text or '[extensions]' in text:
return True
return False
def _classify(self, response_text, payload):
if 'sql' in response_text.lower() or 'mysql' in response_text.lower():
return 'SQL Injection'
if '<script>' in response_text or 'alert(' in response_text:
return 'XSS'
if 'root:' in response_text or 'win.ini' in response_text:
return 'Path Traversal'
return 'Unknown'
def scan_url(self, url):
"""扫描单个URL的所有参数"""
print(f"[*] Scanning {url}")
for param in self.test_params:
for vuln_type, payload_list in self.payloads.items():
for payload in payload_list:
self.scan_parameter(url, param, [payload])
return self.results
def run(self, urls):
"""多线程扫描多个URL"""
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
futures = {executor.submit(self.scan_url, url): url for url in urls}
for future in concurrent.futures.as_completed(futures):
try:
future.result()
except Exception as e:
print(f"Error scanning: {e}")
return self.results
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python vuln_scanner.py http://target.com")
sys.exit(1)
target = sys.argv[1]
scanner = WebVulnScanner(target)
# 自动爬取链接(简化:只扫描首页)
result = scanner.scan_url(target)
for r in result:
print(f"VULN: {r['url']} - {r['vuln_type']} - Payload: {r['payload']}")
# 可增加邮件告警或写入数据库将此脚本部署为 Cron Job:
0 2 * * * cd /opt/scanner && python vuln_scanner.py http://internal-app.com >> scan.logdocker-compose-elk.yml:
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.10.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data
kibana:
image: docker.elastic.co/kibana/kibana:8.10.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
logstash:
image: docker.elastic.co/logstash/logstash:8.10.0
volumes:
- ./logstash-pipeline/:/usr/share/logstash/pipeline/
ports:
- "5044:5044"
volumes:
esdata:Logstash 管道 logstash-pipeline/logstash.conf:
input {
beats {
port => 5044
}
}
filter {
# 解析 Suricata JSON
if [event_type] {
json {
source => "message"
}
}
# 提取客户端 IP
if [src_ip] {
mutate {
rename => { "src_ip" => "client_ip" }
}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "security-logs-%{+YYYY.MM.dd}"
}
}在 Kibana 中创建告警:当 5 分钟内同一 IP 触发 WAF 或 Suricata 告警次数 > 100,触发 webhook。
告警 Webhook 接收器(Python):
# alert_receiver.py
from flask import Flask, request
import subprocess
import json
app = Flask(__name__)
@app.route('/block', methods=['POST'])
def block_ip():
data = request.json
if 'client_ip' in data:
ip = data['client_ip']
# 调用防火墙封禁(iptables)
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip, '-j', 'DROP'])
# 同时记录到数据库
print(f"Blocked {ip}")
return 'OK'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)配置 Kibana 告警 Action 指向 http://alert-receiver:5000/block。
整合封禁、通知、报告功能。
response_automation.py:
import requests
import json
import subprocess
import logging
from datetime import datetime
logging.basicConfig(filename='/var/log/security_response.log', level=logging.INFO)
def query_elasticsearch(query):
"""从 Elasticsearch 查询告警数据"""
es_url = "http://localhost:9200/security-logs-*/_search"
resp = requests.post(es_url, json=query)
return resp.json()
def get_aggressive_ips(threshold=50, time_range='5m'):
"""获取最近时间内请求频率过高的IP"""
query = {
"size": 0,
"aggs": {
"top_ips": {
"terms": {
"field": "client_ip.keyword",
"size": 10,
"order": {"doc_count": "desc"},
"min_doc_count": threshold
},
"aggs": {
"timestamp": {
"max": {"field": "@timestamp"}
}
}
}
},
"query": {
"range": {
"@timestamp": {
"gte": f"now-{time_range}"
}
}
}
}
result = query_elasticsearch(query)
buckets = result['aggregations']['top_ips']['buckets']
return [(b['key'], b['doc_count']) for b in buckets]
def block_ip(ip):
"""调用防火墙封禁IP"""
try:
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip, '-j', 'DROP'], check=True)
logging.info(f"Blocked IP {ip}")
return True
except Exception as e:
logging.error(f"Failed to block {ip}: {e}")
return False
def send_dingtalk(message):
"""发送钉钉告警"""
webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxx"
payload = {
"msgtype": "text",
"text": {"content": message}
}
requests.post(webhook, json=payload)
def main():
ips = get_aggressive_ips(threshold=30, time_range='2m')
for ip, count in ips:
if block_ip(ip):
msg = f"自动封禁恶意IP: {ip},请求次数: {count},时间: {datetime.now()}"
send_dingtalk(msg)
# 可选:写入数据库留存
if __name__ == "__main__":
main()将此脚本设置为每分钟执行的 Cron:
* * * * * python3 /opt/security/response_automation.py在 Kibana 中创建如下可视化面板:
使用 Elasticsearch 聚合查询,例如:
{
"size": 0,
"aggs": {
"attack_types": {
"terms": {
"field": "alert.signature_id.keyword"
}
}
}
}本文构建了一套完整的开源安全防护体系,涵盖:
进一步提升方向:
安全没有银弹,但通过自动化工具链的有机组合,我们可以大幅降低响应时间,将安全运营从“被动救火”转变为“主动防御”。
免责声明:本文所有代码仅供授权测试和学习使用,未经许可不得对他人系统实施扫描或攻击。
关于作者:安全架构师,多年攻防实战与安全产品研发经验。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。