关于 detecting-network-scanning-with-ids-signatures
面向 SOC 分析师和安全工程师的网络扫描检测技能,介绍使用 IDS 签名、阈值告警和告警关联发现侦察活动。
主要功能
- 01编写 Suricata 扫描检测规则
- 02配置阈值与告警抑制
- 03分析扫描告警并生成报告
- 04关联扫描与后续攻击迹象
适用场景
- 01调查网络扫描相关安全事件
- 02构建检测规则与威胁狩猎查询
- 03验证扫描活动监控覆盖
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill detecting-network-scanning-with-ids-signatures安装后在 Claude Code 中输入/detecting-network-scanning-with-ids-signatures确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSEreferences/api-reference.mdscripts/agent.py详细介绍
使用 IDS 签名检测网络扫描
概述
网络扫描通常是攻击的第一阶段,攻击者使用 Nmap、Masscan、ZMap 和自定义扫描器等工具枚举存活主机、开放端口、运行的服务和操作系统版本。检测此类侦察活动可提供潜在攻击的早期预警。Suricata 和 Snort 等 IDS/IPS 系统可以通过基于签名的检测(匹配已知扫描器数据包模式)、基于阈值的检测(统计一段时间内的连接尝试次数)和异常检测(识别异常流量模式)来识别扫描。本技能涵盖编写和部署用于扫描检测的 IDS 签名、配置基于阈值的告警,以及将扫描活动与下游攻击指标关联起来。
何时使用
- 在调查需要使用 IDS 签名检测网络扫描的安全事件时
- 在为该领域构建检测规则或威胁狩猎查询时
- 当 SOC 分析师需要针对此类分析的结构化流程时
- 在验证相关攻击技术的安全监控覆盖范围时
前置条件
- 以 IDS/IPS 模式部署的 Suricata 7.0+ 或 Snort 3.0+
- 用于流量可见性的网络 TAP 或 SPAN 端口
- 已启用 Emerging Threats 规则集
- 用于告警分析的日志基础设施(ELK Stack、Splunk)
- 对正常网络流量模式有基本了解
核心概念
扫描技术与检测指标
| 扫描类型 | Nmap 标志 | 数据包特征 | 检测方法 |
|---|---|---|---|
| TCP SYN | -sS | 仅 SYN 标志,无完成 | 有 SYN 但无 SYN/ACK 响应模式 |
| TCP Connect | -sT | 完整三次握手 | 单一来源的多次连接 |
| TCP FIN | -sF | 仅 FIN 标志 | 对关闭端口发送 FIN(RST 响应) |
| TCP Xmas | -sX | FIN+PSH+URG 标志 | 异常的标志组合 |
| TCP NULL | -sN | 未设置任何标志 | 零标志 TCP 数据包 |
| UDP Scan | -sU | 向多个端口发送 UDP | ICMP 端口不可达响应 |
| ACK Scan | -sA | 仅 ACK 标志(防火墙探测) | 未经请求的 ACK 数据包 |
| SYN/ACK Scan | 自定义 | 无先前 SYN 的 SYN+ACK | 状态违规 |
| OS Fingerprint | -O | 异常的 TCP 选项/窗口大小 | 特定的选项组合 |
| Version Detect | -sV | 服务探测字符串 | 已知的探测载荷 |
Nmap 时序模板
| 模板 | Nmap 标志 | 速度 | 检测难度 |
|---|---|---|---|
| Paranoid | -T0 | 每 5 分钟 1 次探测 | 非常困难 |
| Sneaky | -T1 | 每 15 秒 1 次探测 | 困难 |
| Polite | -T2 | 每 0.4 秒 1 次探测 | 中等 |
| Normal | -T3 | 默认并行度 | 容易 |
| Aggressive | -T4 | 并行,1.25 秒超时 | 非常容易 |
| Insane | -T5 | 最大并行度 | 轻而易举 |
工作流
步骤 1:部署 Suricata 扫描检测规则
创建 /var/lib/suricata/rules/scan-detection.rules:
# === TCP Scan Detection ===
# Detect TCP SYN scan (high volume SYN without completion)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP SYN Scan Detected"; flags:S,12; threshold:type both,track by_src,count 30,seconds 10; classtype:attempted-recon; sid:5000001; rev:2;)
# Detect TCP FIN scan
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP FIN Scan"; flags:F,12; threshold:type both,track by_src,count 20,seconds 60; classtype:attempted-recon; sid:5000002; rev:1;)
# Detect TCP Xmas scan (FIN+PSH+URG)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP Xmas Tree Scan"; flags:FPU,12; classtype:attempted-recon; sid:5000003; rev:1;)
# Detect TCP NULL scan (no flags)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP NULL Scan"; flags:0,12; classtype:attempted-recon; sid:5000004; rev:1;)
# Detect ACK scan (firewall probing)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP ACK Scan"; flags:A,12; flow:stateless; threshold:type both,track by_src,count 50,seconds 30; classtype:attempted-recon; sid:5000005; rev:1;)
# Detect SYN+ACK scan (unusual stateless probe)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP SYN-ACK Scan"; flags:SA,12; flow:stateless; threshold:type both,track by_src,count 30,seconds 30; classtype:attempted-recon; sid:5000006; rev:1;)
# === UDP Scan Detection ===
# Detect UDP port scan
alert udp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN UDP Port Scan"; threshold:type both,track by_src,count 30,seconds 10; classtype:attempted-recon; sid:5000010; rev:1;)
# === Nmap Specific Detection ===
# Detect Nmap OS fingerprinting (T1 probe - ECN SYN)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN Nmap OS Fingerprint Probe"; flags:SEC,12; window:1; classtype:attempted-recon; sid:5000020; rev:1;)
# Detect Nmap window scan (specific window size patterns)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN Nmap Window Size Probe"; flags:A,12; flow:stateless; window:1024; threshold:type both,track by_src,count 10,seconds 30; classtype:attempted-recon; sid:5000021; rev:1;)
# Detect Nmap version detection probes
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN Nmap Service Version Probe"; flow:established; content:"HELP"; depth:4; threshold:type both,track by_src,count 5,seconds 60; classtype:attempted-recon; sid:5000022; rev:1;)
# Detect Nmap scripting engine (NSE)
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN Nmap NSE HTTP Script"; http.user_agent; content:"Nmap Scripting Engine"; classtype:attempted-recon; sid:5000023; rev:1;)
# === Masscan Detection ===
# Detect Masscan SYN scan (specific window size)
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN Masscan SYN Scan Detected"; flags:S,12; window:1024; threshold:type both,track by_src,count 100,seconds 10; classtype:attempted-recon; sid:5000030; rev:1;)
# === Internal Scan Detection ===
# Detect internal host scanning (lateral movement recon)
alert tcp $HOME_NET any -> $HOME_NET any (msg:"SCAN Internal Network Scan Detected"; flags:S,12; threshold:type both,track by_src,count 50,seconds 30; classtype:attempted-recon; sid:5000040; rev:1;)
# Detect internal ICMP sweep
alert icmp $HOME_NET any -> $HOME_NET any (msg:"SCAN Internal ICMP Sweep"; itype:8; threshold:type both,track by_src,count 30,seconds 10; classtype:attempted-recon; sid:5000041; rev:1;)
步骤 2:配置基于阈值的检测
编辑 /etc/suricata/threshold.config:
# Suppress scan alerts from authorized vulnerability scanners
suppress gen_id 1, sig_id 5000001, track by_src, ip 10.0.5.100
suppress gen_id 1, sig_id 5000001, track by_src, ip 10.0.5.101
# Rate-limit scan alerts to prevent log flooding
rate_filter gen_id 1, sig_id 5000001, track by_src, count 5, seconds 300, new_action alert, timeout 600
rate_filter gen_id 1, sig_id 5000040, track by_src, count 3, seconds 300, new_action alert, timeout 600
# Event filter for critical internal scans
event_filter gen_id 1, sig_id 5000040, type both, track by_src, count 1, seconds 60
步骤 3:扫描检测分析脚本
#!/usr/bin/env python3
"""Analyze IDS alerts for network scanning activity and generate reports."""
import json
import sys
from collections import defaultdict
from datetime import datetime
class ScanDetector:
"""Correlate IDS alerts to identify scanning campaigns."""
def __init__(self):
self.scan_events = defaultdict(lambda: {
'source_ip': '',
'target_ips': set(),
'target_ports': set(),
'scan_types': set(),
'alert_count': 0,
'first_seen': None,
'last_seen': None,
'signatures': defaultdict(int),
})
def process_eve_json(self, filepath: str):
"""Process Suricata EVE JSON alert log."""
with open(filepath, 'r') as f:
for line in f:
try:
event = json.loads(line)
if event.get('event_type') != 'alert':
continue
alert = event.get('alert', {})
sig = alert.get('signature', '')
if 'SCAN' not in sig:
continue
src_ip = event.get('src_ip', '')
dst_ip = event.get('dest_ip', '')
dst_port = event.get('dest_port', 0)
ts = datetime.fromisoformat(
event['timestamp'].replace('Z', '+00:00')
)
scanner = self.scan_events[src_ip]
scanner['source_ip'] = src_ip
scanner['target_ips'].add(dst_ip)
scanner['target_ports'].add(dst_port)
scanner['alert_count'] += 1
scanner['signatures'][sig] += 1
if 'SYN' in sig:
scanner['scan_types'].add('SYN Scan')
elif 'FIN' in sig:
scanner['scan_types'].add('FIN Scan')
elif 'Xmas' in sig:
scanner['scan_types'].add('Xmas Scan')
elif 'NULL' in sig:
scanner['scan_types'].add('NULL Scan')
elif 'UDP' in sig:
scanner['scan_types'].add('UDP Scan')
elif 'Nmap' in sig:
scanner['scan_types'].add('Nmap Detected')
elif 'Masscan' in sig:
scanner['scan_types'].add('Masscan Detected')
elif 'Internal' in sig:
scanner['scan_types'].add('Internal Scan')
if scanner['first_seen'] is None or ts < scanner['first_seen']:
scanner['first_seen'] = ts
if scanner['last_seen'] is None or ts > scanner['last_seen']:
scanner['last_seen'] = ts
except (json.JSONDecodeError, KeyError, ValueError):
continue
def generate_report(self):
"""Generate scan detection report."""
scanners = sorted(
self.scan_events.values(),
key=lambda x: x['alert_count'],
reverse=True
)
print(f"\n{'='*70}")
print("NETWORK SCAN DETECTION REPORT")
print(f"{'='*70}")
print(f"Unique Scanning Sources: {len(scanners)}\n")
for scanner in scanners:
targets = len(scanner['target_ips'])
ports = len(scanner['target_ports'])
duration = (scanner['last_seen'] - scanner['first_seen']).total_seconds() \
if scanner['first_seen'] and scanner['last_seen'] else 0
is_internal = scanner['source_ip'].startswith(('10.', '172.', '192.168.'))
severity = "CRITICAL" if is_internal else \
"HIGH" if targets > 50 or ports > 100 else "MEDIUM"
print(f"[{severity}] Scanner: {scanner['source_ip']}")
print(f" Type: {'INTERNAL' if is_internal else 'EXTERNAL'}")
print(f" Scan Types: {', '.join(scanner['scan_types'])}")
print(f" Target Hosts: {targets}, Target Ports: {ports}")
print(f" Total Alerts: {scanner['alert_count']}")
print(f" Duration: {duration:.0f}s")
print(f" First Seen: {scanner['first_seen']}")
print(f" Top Signatures:")
for sig, count in sorted(
scanner['signatures'].items(), key=lambda x: x[1], reverse=True
)[:5]:
print(f" - {sig}: {count}")
print()
if __name__ == '__main__':
detector = ScanDetector()
log_file = sys.argv[1] if len(sys.argv) > 1 else '/var/log/suricata/eve.json'
detector.process_eve_json(log_file)
detector.generate_report()
响应手册
- 分类 - 确定扫描来自授权的扫描器还是未知来源
- 丰富 - 在威胁情报源中查询源 IP
- 评估范围 - 统计唯一目标和端口数量,以衡量扫描广度
- 阻止 - 将具有攻击性的外部扫描器加入防火墙阻止列表
- 调查内部 - 内部扫描可能表明主机已被入侵;进行隔离并调查
- 关联 - 检查扫描之后是否紧跟漏洞利用尝试
最佳实践
- 将已授权的扫描器加入白名单 - 忽略来自已知漏洞扫描器 IP 的警报
- 优先关注内部扫描 - 内部扫描的严重性高于外部扫描(表明可能已遭到入侵)
- 调整阈值 - 根据环境调整阈值;/16 网络会产生更多扫描噪声
- 关联其他警报 - 将扫描检测与漏洞利用警报结合,以掌握攻击链全貌
- 基于时间进行分析 - 在非正常时段(凌晨 3 点)进行的扫描应提高优先级
- 限制警报速率 - 防止大量扫描警报产生的噪声淹没 SIEM
参考资料
评论
支持 Markdown