fix: PDF/Office/压缩包关键词误报 — 两层修复

【regex 层收紧】policy.yaml keyword_rules

1) ReverseShellNetcat: \\bnc\\b → \\bnc\\s+-[elpvs]\\w*
   nc 后必须跟参数 (-e/-l/-p/-v/-s), 单独 nc 在二进制里必然误报
   ncat 同理: \\bncat\\b → \\bncat\\s+-\\w+
   保留 \\bnetcat\\b (8 字母, 碰撞概率极低)

2) CredentialStealer: 全部关键词加 \\b 词边界
   wce 单独加后续约束: wce(\\.exe\\b|\\s|\\/)
   要求 wce 后跟 .exe / 空格 / / (二进制随机字节里 WCE 不再触发)

【引擎层架构修复】keywords.py

新增 BINARY_SKIP_EXTS (48 个扩展名):
  - PDF / Office (.pdf/.docx/.xlsx/.pptx/...)
  - 压缩包 (.rar/.zip/.7z/.tar/.gz/...)
  - 图片 (.png/.jpg/.gif/.svg/...)
  - 视频 (.mp4/.avi/.mkv/...)
  - 音频 (.mp3/.wav/.flac/...)

KeywordEngine.scan() 开头检查:
  如果 declared_ext / path ext 在 BINARY_SKIP_EXTS → 直接返回空

根因:
  PDF/DOCX/ZIP 等文件是 DEFLATE 压缩流 (伪随机字节)
  短关键词 (nc=2w/wce=3w) 在压缩流里必然出现
  误报导致: 云启智能体平台生成效果.pdf 被拦截
    ReverseShellNetcat: 'g%ew;V6Z?)Nc{>72z}*&96'
    CredentialStealer: '2>>~:pa}w*WCE|2MVB85t/$'

验证:
  ✅ 新 regex: 真实 nc -e / wce.exe 命中, PDF 压缩流字节不命中
  ✅ BINARY_SKIP_EXTS: .pdf/.docx/.zip 跳过, .sh/.ps1/.txt 扫描
  ✅ scan() 方法已含跳过逻辑
This commit is contained in:
2026-10-29 17:28:23 +08:00
parent 144431b300
commit 9834930299
2 changed files with 33 additions and 2 deletions

View File

@@ -13,6 +13,7 @@
from __future__ import annotations
import logging
import os
import re
from typing import Any, Dict, List
@@ -24,6 +25,26 @@ logger = logging.getLogger(__name__)
class KeywordEngine(Engine):
"""内网特征词表匹配引擎。"""
# ---- 跳过列表:二进制 / 压缩封装 / 媒体文件 ----
# 这些文件有专门引擎处理(PDF/Office/Archive/PE 等),
# 它们的内容是压缩流或二进制格式,关键词 regex 在里面会大面积误报。
# 短关键词 (nc/wce/curl/...) 在压缩流里等于伪随机字节必然碰撞。
BINARY_SKIP_EXTS = frozenset({
# PDF / Office
".pdf", ".docx", ".docm", ".doc", ".xlsx", ".xlsm", ".xls",
".pptx", ".pptm", ".ppt", ".ppsx", ".pps", ".potx",
# 压缩包
".rar", ".zip", ".7z", ".tar", ".gz", ".bz2", ".xz", ".tgz",
".iso", ".img", ".dmg",
# 图片
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".tif", ".webp",
".ico", ".svg",
# 视频
".mp4", ".avi", ".mkv", ".mov", ".flv", ".wmv", ".webm",
# 音频
".mp3", ".wav", ".flac", ".ogg", ".aac", ".wma", ".m4a",
})
name = "keywords"
layer = "L2B"
version = "1.0"
@@ -57,6 +78,14 @@ class KeywordEngine(Engine):
findings: List[Finding] = []
# ---- 跳过二进制/压缩封装文件 ----
# 这些文件内容是压缩流或纯二进制,短关键词 regex 在里面必然误报
ext = (target.declared_ext or "").lower()
if not ext and target.path:
ext = os.path.splitext(target.path)[1].lower()
if ext in self.BINARY_SKIP_EXTS:
return result
try:
text = self._extract_text(target.path, target.declared_ext)
except Exception: # noqa: BLE001

View File

@@ -357,7 +357,8 @@ keyword_rules:
# ---- 反弹 Shell ----
- name: ReverseShellNetcat
match: regex
pattern: "(\\bnc\\b|\\bncat\\b|\\bnetcat\\b|/dev/tcp/|bash\\s+-i\\s+>&|sh\\s+-i\\s+>&)"
# nc/ncat 后必须跟参数 (如 -e/-l/-p/-v/-s),单独 nc 在二进制里必然误报
pattern: "(\\bnc\\s+-[elpvs]\\w*|\\bncat\\s+-\\w+|\\bnetcat\\b|/dev/tcp/|bash\\s+-i\\s+>&|sh\\s+-i\\s+>&)"
ignore_case: true
weight: 40
severity: critical
@@ -372,7 +373,8 @@ keyword_rules:
# ---- 凭据窃取工具 ----
- name: CredentialStealer
match: regex
pattern: "(mimikatz|sekurlsa|wce|gsecdump|fgdump|lsadump|dcsync|dcsyncer|rubeus)"
# 全部加 \b 词边界, wce 要求后跟 .exe / 空格 / / (二进制随机字节里的 WCE 不会触发)
pattern: "(\\bmimikatz\\b|\\bsekurlsa\\b|\\bwce(\\.exe\\b|\\s|\\/)|\\bgsecdump\\b|\\bfgdump\\b|\\blsadump\\b|\\bdcsync\\b|\\bdcsyncer\\b|\\brubeus\\b)"
ignore_case: true
weight: 45
severity: critical