Files
MeshSate/scripts/full_audit.py
zhaolei 2daa3fcd3c fix: 对账 P0-P2 一轮全消 (CSV想拦35→0, CSV想放5→0)
引擎修复:
- policy.yaml allowed_extensions 补 .java/.log/.ttf/.dwg/.odt 等 20+ 类型 (白名单 gate 被拒根因)
- structure.py SuspiciousImports weight 45→25 (单一 API 不再直接 100 分 malicious)
- structure.py 新增 EncryptedArchive 显式检测 (flag_bits bit0=1)
- rules/rule_labels.yaml 新增 EncryptedArchive → 加密压缩包

样本修复:
- L0-020/021 zip_bomb 重写: DEFLATED 50×50MB/20×20MB → 膨胀比 1027x/1023x ✅ ArchiveBombSuspect reject
- L0-030 nested_traversal 重写: 真 ../穿越 + 内层 zip → PathTraversal malicious
- L0-024 encrypted 重写: flag_bits bit0=1 → EncryptedArchive suspicious

最终 P0-P2 关键修复项 9/9 通过:
  ✅ NEG-007 java clean (白名单放行)
  ✅ NEG-011 log clean (白名单放行)
  ✅ NEG-015 ttf clean (白名单放行)
  ✅ NEG-016 dwg clean (白名单放行)
  ✅ NEG-020 odt clean (白名单放行)
  ✅ L0-020 zip_bomb reject ArchiveBombSuspect
  ✅ L0-021 small_bomb reject ArchiveBombSuspect
  ✅ L0-024 encrypted suspicious EncryptedArchive
  ✅ L0-030 nested malicious PathTraversal
2026-10-29 12:02:36 +08:00

129 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""完整靶场对账:CSV 原始预期 vs 引擎实际输出。
逐样本打印:case_id / 文件名 / CSV 预期(裁决+关键规则)/ 引擎实际(裁决+规则)/ 差距分类
"""
import sys, json, csv, asyncio, time
from pathlib import Path
sys.path.insert(0, '/app')
async def run():
from app.main import scanner
loop = asyncio.get_running_loop()
# 读 CSV
csv_rows = {}
with open('/tmp/yongli.csv', encoding='utf-8') as f:
for row in csv.DictReader(f):
csv_rows[row['用例ID'].strip()] = row
# 读 index.json(用于归类 L7 旧样本等)
idx = json.load(open('/tmp/regen_v2.json')) if Path('/tmp/regen_v2.json').exists() else {}
# 先从宿主机拷
print('=== 开始全量靶场对账 ===\n')
# 扫描所有文件
from pathlib import Path as P
samples_dir = P('/app/tests/samples')
results = []
files = sorted(p for p in samples_dir.iterdir()
if p.is_file() and p.name not in ('index.json',) and not p.name.startswith('_'))
# 先用 regen_v2 的 index 拿 case_id
old_idx = json.loads(samples_dir.joinpath('index.json').read_text())
meta_map = {s['filename']: s for s in old_idx['samples']}
for fpath in files:
meta = meta_map.get(fpath.name, {})
cid = meta.get('case_id', '')
# CSV 原始预期
csv_exp = csv_rows.get(cid, {})
csv_verdict = csv_exp.get('预期结果', '')
csv_name = csv_exp.get('用例名称', '')
# 引擎实际
try:
outcome = await loop.run_in_executor(None, lambda fp=fpath: scanner.scan_file(
file_path=str(fp), original_filename=fp.name,
task_id=f"chk-{fp.name}", channel="audit", client_id="console",
))
except Exception as e:
results.append({
'cid': cid, 'file': fpath.name, 'layer': meta.get('category', ''),
'csv_verdict': csv_verdict, 'csv_name': csv_name,
'eng_verdict': 'error', 'eng_rules': [], 'score': 0, 'reason': str(e),
})
continue
eng_rules = sorted({f.rule for f in outcome.findings})
results.append({
'cid': cid, 'file': fpath.name, 'layer': meta.get('category', ''),
'csv_verdict': csv_verdict, 'csv_name': csv_name,
'eng_verdict': outcome.verdict, 'eng_rules': eng_rules,
'score': outcome.score,
})
# 分类差距
def classify(r):
csv_v = r['csv_verdict']
eng_v = r['eng_verdict']
eng_reject = eng_v in ('reject', 'malicious')
eng_pass = eng_v in ('pass', 'clean')
if not csv_v:
return 'NO_CSV'
# CSV 预期 reject
if any(k in csv_v for k in ['拦截', '告警', '高危', '拒绝', '命中']) and eng_pass:
return 'CSV想拦→引擎放'
# CSV 预期 pass
if any(k in csv_v for k in ['无风险', '无告警', '无命中', '正常解析']) and eng_reject:
return 'CSV想放→引擎拦'
# verdict 类型不匹配
if '拦截' in csv_v and eng_v == 'suspicious':
return 'CSV要拦截→引擎仅可疑'
if '低危' in csv_v and eng_reject:
return 'CSV说低危→引擎拦截'
if '低危' in csv_v and eng_pass:
return 'CSV说低危→引擎放(合理,低危=放行)'
return 'OK'
# 打印汇总
from collections import Counter, defaultdict
gap_cnt = Counter()
gap_by_layer = defaultdict(Counter)
for r in results:
gap = classify(r)
gap_cnt[gap] += 1
gap_by_layer[r['layer']][gap] += 1
print('=== 总体分类 ===')
for gap, cnt in gap_cnt.most_common():
print(f' {gap:20s}: {cnt}')
print()
print('=== 按层 × 分类 ===')
for layer in sorted(set(r['layer'] for r in results)):
total = sum(1 for r in results if r['layer'] == layer)
ok = sum(1 for r in results if r['layer'] == layer and classify(r) == 'OK')
print(f' {layer:5s}: {ok}/{total} OK')
for gap, cnt in gap_by_layer[layer].most_common():
if gap != 'OK':
print(f' └ {gap}: {cnt}')
print()
# 逐类打印差距详情
for gap_type in ['CSV想拦→引擎放', 'CSV想放→引擎拦', 'CSV要拦截→引擎仅可疑', 'CSV说低危→引擎拦截', 'NO_CSV']:
items = [r for r in results if classify(r) == gap_type]
if not items:
continue
print(f'=== {gap_type} ({len(items)} 条) ===')
for r in items:
print(f" {r['cid']:10s} [{r['layer']:3s}] {r['file']:45s}")
print(f" CSV预期: {r['csv_verdict'][:80]}")
print(f" 引擎输出: verdict={r['eng_verdict']:10s} rules={r['eng_rules'][:5]}")
print()
asyncio.run(run())