fix: 六引擎误报根因修复 — 解包递归、YARA 恒真规则、签名分级、real_type 溢出
检测范围与证据链修复,不改裁决口径(口径改动在下一条提交)。 - extractor:zip/tar 成员在超限分支外漏加回扫描队列,解包后内容永远不参与检测; zip 目录条目被当成内嵌对象;tar 超限成员先落盘不删。递归真正生效后深扫耗时可测。 - clamav:_HIGH/_MEDIUM/_UNWANTED 关键词是小写,而签名名是 "Win.Trojan..." 大小写混排, 分级判定恒不命中全走默认档;改统一大写匹配。超过 clamd MaxFileSize 的裸奔区间 (1G~1.5G) 新增 ClamAVSkippedSizeLimit(30) — 按"不可扫描不放行"进复核。 - yara:pe.yar C1 系列改用导入表判定,不再靠裸子串命中字符串表; linux.yar C7 多条规则条件恒真(裸 nc/fetch 之类)已收紧;C8/C3 相应收窄。 - structure:DangerousExecutableExtension 是载体信号而非恶意行为,critical 权重 45→35, 不再作为把中低置信度信号一起顶过 block_score 的底座。 - scanner/documents:real_type 拼进 libmagic 对 MSI/OLE 的元数据串实测 334+ 字符, 超列宽导致整条 scan_tasks INSERT 连 findings 一起丢(被拒文件不留痕)→ 列宽 256→512,migrate.sql 补 ALTER。
This commit is contained in:
@@ -21,6 +21,9 @@ from app.engines.base import Engine, EngineResult, Finding, ScanTarget, Severity
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# cross_line 规则用的续行归一:换行(及其两侧空白)压成一个空格
|
||||
NEWLINE_RUN_RE = re.compile(r"\s*[\r\n]+\s*")
|
||||
|
||||
|
||||
class KeywordEngine(Engine):
|
||||
"""内网特征词表匹配引擎。"""
|
||||
@@ -94,12 +97,13 @@ class KeywordEngine(Engine):
|
||||
if not text:
|
||||
return result
|
||||
|
||||
# ---- 预处理:合并换行符以支持跨行关键词 ----
|
||||
# 攻击者会把 Invoke-WebRequest 这种关键字用 CRLF 拆成
|
||||
# "Invoke-Web\r\nRequest" 来绕过基于单行的匹配。
|
||||
# 这里把所有 \r\n / \r / \n 替换为空,让跨行关键词重新拼接。
|
||||
# 注意:不影响纯文本/多行语义,我们匹配的都是单行关键词或跨行命令。
|
||||
text = text.replace("\r\n", "").replace("\r", "").replace("\n", "")
|
||||
# ---- 预处理 ----
|
||||
# 默认按原始行匹配:把整篇文本的换行全部删除会让上一行行尾和下一行行首
|
||||
# 黏成一句话(Ruby 源码 "…breed\n+fetch()…" 曾被当成 curl 特征命中)。
|
||||
# cross_line: true 的规则再跑一遍"换行压成空格"的文本,用于兜住
|
||||
# wget \ + 换行 + -O payload 这类续行绕过;保留分隔符,不产生黏连。
|
||||
text_norm = text.replace("\r\n", "\n").replace("\r", "\n")
|
||||
text_joined = NEWLINE_RUN_RE.sub(" ", text_norm)
|
||||
|
||||
for rule in self._compiled:
|
||||
match_mode = rule.get("match", "contains")
|
||||
@@ -113,23 +117,29 @@ class KeywordEngine(Engine):
|
||||
if match_mode == "regex":
|
||||
compiled = rule.get("_re")
|
||||
if compiled:
|
||||
for m in compiled.finditer(text):
|
||||
snippet = text[max(0, m.start() - 10): m.end() + 10]
|
||||
hits.append(snippet.strip())
|
||||
scan_targets = [text_norm]
|
||||
if rule.get("cross_line"):
|
||||
scan_targets.append(text_joined)
|
||||
for body in scan_targets:
|
||||
for m in compiled.finditer(body):
|
||||
snippet = body[max(0, m.start() - 10): m.end() + 10]
|
||||
hits.append(" ".join(snippet.split()))
|
||||
if len(hits) >= 3:
|
||||
break
|
||||
if len(hits) >= 3:
|
||||
break
|
||||
|
||||
elif match_mode == "any_of":
|
||||
keywords = rule.get("keywords", [])
|
||||
for kw in keywords:
|
||||
if kw.lower() in text.lower():
|
||||
if kw.lower() in text_norm.lower():
|
||||
hits.append(kw)
|
||||
if len(hits) >= 3:
|
||||
break
|
||||
|
||||
elif match_mode == "contains":
|
||||
pattern = rule.get("pattern", "")
|
||||
if pattern and (pattern.lower() in text.lower()):
|
||||
if pattern and (pattern.lower() in text_norm.lower()):
|
||||
hits.append(pattern)
|
||||
|
||||
if hits:
|
||||
|
||||
Reference in New Issue
Block a user