@yottameta/yotta-vetter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/NOTICE +11 -0
- package/README.md +64 -0
- package/SKILL.md +76 -0
- package/bin/install.js +163 -0
- package/install.sh +132 -0
- package/package.json +35 -0
- package/references/checklist.md +43 -0
- package/references/vetting-report-template.md +41 -0
- package/scripts/test_yotta_vetter.py +111 -0
- package/scripts/vetter_rules.py +261 -0
- package/scripts/yotta_vetter.py +576 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""yotta-vetter(元审)测试套件。
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
python3 scripts/test_yotta_vetter.py
|
|
6
|
+
覆盖:check 干净/恶意/自扫、四阶段清单、JSON 与报告、严重级过滤、
|
|
7
|
+
V3 元安联动、source 来源检查(格式/404 降级)、GBK 控制台加固。
|
|
8
|
+
"""
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
HERE = Path(__file__).resolve().parent
|
|
18
|
+
SCRIPT = HERE / "yotta_vetter.py"
|
|
19
|
+
FIX = HERE.parent.parent.parent / ".tmp" / "audit-fixtures"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run_cli(args, env=None):
|
|
23
|
+
full = dict(os.environ)
|
|
24
|
+
if env:
|
|
25
|
+
full.update(env)
|
|
26
|
+
return subprocess.run(
|
|
27
|
+
[sys.executable, str(SCRIPT)] + args,
|
|
28
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
29
|
+
env=full, timeout=60,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class VetterCliTest(unittest.TestCase):
|
|
34
|
+
def test_check_clean(self):
|
|
35
|
+
r = run_cli(["check", str(FIX / "clean-skill"), "--no-color"])
|
|
36
|
+
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
|
|
37
|
+
self.assertIn("SAFE TO INSTALL", r.stdout)
|
|
38
|
+
|
|
39
|
+
def test_check_evil_verdict_and_linkage(self):
|
|
40
|
+
r = run_cli(["check", str(FIX / "evil-skill"), "--no-color"])
|
|
41
|
+
self.assertEqual(r.returncode, 3, r.stdout + r.stderr)
|
|
42
|
+
self.assertIn("DO NOT INSTALL", r.stdout)
|
|
43
|
+
self.assertIn("yotta-security-audit --target skill", r.stderr) # V3 联动
|
|
44
|
+
|
|
45
|
+
def test_check_self_no_high_critical(self):
|
|
46
|
+
"""自扫:无 high/critical(urllib 来源检查为预期 medium,规则表已豁免)。"""
|
|
47
|
+
r = run_cli(["check", str(SCRIPT.parent), "--no-color"])
|
|
48
|
+
self.assertIn(r.returncode, (0, 1), r.stdout + r.stderr)
|
|
49
|
+
self.assertNotIn("[HIGH]", r.stdout)
|
|
50
|
+
self.assertNotIn("[CRITICAL]", r.stdout)
|
|
51
|
+
|
|
52
|
+
def test_check_missing_path_exit4(self):
|
|
53
|
+
r = run_cli(["check", "C:/definitely/not/exists"])
|
|
54
|
+
self.assertEqual(r.returncode, 4, r.stdout + r.stderr)
|
|
55
|
+
|
|
56
|
+
def test_check_json(self):
|
|
57
|
+
r = run_cli(["check", str(FIX / "evil-skill"), "--json"])
|
|
58
|
+
self.assertEqual(r.returncode, 3, r.stderr)
|
|
59
|
+
data = json.loads(r.stdout)
|
|
60
|
+
self.assertEqual(data["tool"], "yotta-vetter")
|
|
61
|
+
self.assertGreaterEqual(data["summary"]["critical"], 1)
|
|
62
|
+
self.assertTrue(all("rule_id" in f for f in data["findings"]))
|
|
63
|
+
|
|
64
|
+
def test_check_report_md(self):
|
|
65
|
+
with tempfile.TemporaryDirectory() as td:
|
|
66
|
+
rep = Path(td) / "vetting-report.md"
|
|
67
|
+
r = run_cli(["check", str(FIX / "evil-skill"), "--report", str(rep)])
|
|
68
|
+
self.assertEqual(r.returncode, 3, r.stderr)
|
|
69
|
+
self.assertTrue(rep.exists())
|
|
70
|
+
txt = rep.read_text(encoding="utf-8")
|
|
71
|
+
self.assertIn("SKILL VETTING REPORT", txt)
|
|
72
|
+
self.assertIn("决策记录", txt)
|
|
73
|
+
|
|
74
|
+
def test_check_severity_filter(self):
|
|
75
|
+
r = run_cli(["check", str(FIX / "evil-skill"),
|
|
76
|
+
"--severity", "critical", "--no-color"])
|
|
77
|
+
self.assertEqual(r.returncode, 3, r.stderr)
|
|
78
|
+
self.assertNotIn("[MEDIUM]", r.stdout)
|
|
79
|
+
|
|
80
|
+
def test_inventory_missing_skill_md(self):
|
|
81
|
+
with tempfile.TemporaryDirectory() as td:
|
|
82
|
+
Path(td, "README.md").write_text("no skill", encoding="utf-8")
|
|
83
|
+
r = run_cli(["check", td, "--no-color"])
|
|
84
|
+
self.assertIn("缺少 SKILL.md", r.stdout)
|
|
85
|
+
|
|
86
|
+
def test_source_bad_format_exit4(self):
|
|
87
|
+
r = run_cli(["source", "not-a-slash-spec"])
|
|
88
|
+
self.assertEqual(r.returncode, 4, r.stdout + r.stderr)
|
|
89
|
+
|
|
90
|
+
def test_source_missing_repo_exit1(self):
|
|
91
|
+
r = run_cli(["source", "github:YottaMeta/__no_such_repo_xyz__"])
|
|
92
|
+
self.assertEqual(r.returncode, 1, r.stdout + r.stderr)
|
|
93
|
+
|
|
94
|
+
def test_bad_command_exit4(self):
|
|
95
|
+
r = run_cli(["frobnicate"])
|
|
96
|
+
self.assertEqual(r.returncode, 4, r.stdout + r.stderr)
|
|
97
|
+
|
|
98
|
+
def test_gbk_console_no_crash(self):
|
|
99
|
+
env = dict(os.environ)
|
|
100
|
+
env["PYTHONIOENCODING"] = "gbk"
|
|
101
|
+
r = subprocess.run(
|
|
102
|
+
[sys.executable, str(SCRIPT), "check", str(FIX / "clean-skill"), "--no-color"],
|
|
103
|
+
capture_output=True, env=env, timeout=60)
|
|
104
|
+
self.assertEqual(r.returncode, 0, r.stderr.decode("gbk", errors="replace"))
|
|
105
|
+
self.assertNotIn(b"UnicodeEncodeError", r.stderr)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])
|
|
110
|
+
ok = unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful()
|
|
111
|
+
sys.exit(0 if ok else 1)
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""vetter_rules.py — 与 yotta-security-audit/scripts/audit_rules.py 的同步副本(勿手改)。
|
|
3
|
+
|
|
4
|
+
本文件由 YottaSkills 仓库 tools/sync-vetter-rules.py 自动生成;
|
|
5
|
+
修改规则请改 yotta-security-audit/scripts/audit_rules.py,然后重新运行同步。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from collections import namedtuple
|
|
11
|
+
|
|
12
|
+
# 单条规则:规则号 / 检测器名 / 严重级 / 正则源码 / 描述 / 置信度(0-100)
|
|
13
|
+
Rule = namedtuple("Rule", ["id", "detector", "severity", "pattern", "description", "confidence"])
|
|
14
|
+
|
|
15
|
+
MAX_LINE_LEN = 500
|
|
16
|
+
|
|
17
|
+
# 严重级从低到高(用于排序与 exit code 语义:0=干净/仅 low,1=medium,2=high,3=critical,4=错误)
|
|
18
|
+
SEVERITY_ORDER = ("info", "low", "medium", "high", "critical")
|
|
19
|
+
|
|
20
|
+
# 严重级 → 数值(exit code 语义)
|
|
21
|
+
SEVERITY_VALUE = {"info": 0, "low": 0, "medium": 1, "high": 2, "critical": 3}
|
|
22
|
+
|
|
23
|
+
PATTERN_RULES = [
|
|
24
|
+
# ── DownloadExec 下载即执行 ───────────────────────────────────────────
|
|
25
|
+
Rule("DEX-001", "DownloadExec", "critical",
|
|
26
|
+
r"(?i)\bcurl\b[^\n|;]{0,120}\|\s*(?:ba)?sh\b",
|
|
27
|
+
"curl 下载内容通过管道交给 shell 执行", 95),
|
|
28
|
+
Rule("DEX-002", "DownloadExec", "critical",
|
|
29
|
+
r"(?i)\bwget\b[^\n|;]{0,120}\|\s*(?:ba)?sh\b",
|
|
30
|
+
"wget 下载内容通过管道交给 shell 执行", 95),
|
|
31
|
+
Rule("DEX-003", "DownloadExec", "critical",
|
|
32
|
+
r"(?i)\bcurl\b[^\n|;&]{0,120}-[^\s]{0,20}o\s+\S+[^\n|;&]{0,80}(?:&&|;)\s*(?:ba)?sh\b",
|
|
33
|
+
"curl 下载到文件后立即交给 shell 执行", 90),
|
|
34
|
+
Rule("DEX-004", "DownloadExec", "critical",
|
|
35
|
+
r"(?i)\bfetch\s*\([^\n;]{0,200}\)\s*\.\s*then\s*\([^\n;]{0,80}\beval\b",
|
|
36
|
+
"JS fetch 结果交给 eval 执行", 85),
|
|
37
|
+
Rule("DEX-005", "DownloadExec", "critical",
|
|
38
|
+
r"(?i)\burllib\s*\.\s*request\s*\.\s*urlopen\s*\([^\n;]{0,200}\)[^\n;]{0,80}\bexec\b",
|
|
39
|
+
"Python urllib 下载结果交给 exec 执行", 85),
|
|
40
|
+
Rule("DEX-006", "DownloadExec", "critical",
|
|
41
|
+
r"(?i)\bwget\b[^\n|;&]{0,120}-[^\s]{0,20}o\s+\S+[^\n|;&]{0,80}(?:&&|;)\s*(?:ba)?sh\b",
|
|
42
|
+
"wget 下载到文件后立即交给 shell 执行", 90),
|
|
43
|
+
Rule("DEX-007", "DownloadExec", "critical",
|
|
44
|
+
r"(?i)\b(?:powershell|pwsh)\b[^\n;]{0,120}(?:-enc|enc(?:odedcommand)?)\b",
|
|
45
|
+
"PowerShell 编码命令执行", 80),
|
|
46
|
+
|
|
47
|
+
# ── Obfuscation 混淆执行 ──────────────────────────────────────────────
|
|
48
|
+
Rule("OBF-001", "Obfuscation", "high",
|
|
49
|
+
r"\beval\s*\(\s*[^\"'\x600-9]",
|
|
50
|
+
"eval 传入非字面量参数(可能执行外部输入)", 80),
|
|
51
|
+
Rule("OBF-002", "Obfuscation", "high",
|
|
52
|
+
r"(?<!\.)\bexec\s*\(\s*[^\"'\x600-9]",
|
|
53
|
+
"exec 传入非字面量参数", 80),
|
|
54
|
+
Rule("OBF-003", "Obfuscation", "high",
|
|
55
|
+
r"(?:\\x[0-9a-fA-F]{2}){6,}",
|
|
56
|
+
"连续十六进制转义序列(编码字符串)", 70),
|
|
57
|
+
Rule("OBF-004", "Obfuscation", "high",
|
|
58
|
+
r"chr\s*\(\s*\d+\s*\)\s*\+\s*chr\s*\(\s*\d+\s*\)(?:\s*\+\s*chr\s*\(\s*\d+\s*\)){2,}",
|
|
59
|
+
"chr() 拼接链(逐字符构造字符串)", 85),
|
|
60
|
+
Rule("OBF-005", "Obfuscation", "high",
|
|
61
|
+
r"String\s*\.\s*fromCharCode\s*\([^)]*,[^)]*,[^)]*,[^)]*\)",
|
|
62
|
+
"String.fromCharCode 多参数构造", 70),
|
|
63
|
+
Rule("OBF-006", "Obfuscation", "high",
|
|
64
|
+
r"\batob\s*\(\s*['\"][A-Za-z0-9+/=]{40,}['\"]\s*\)",
|
|
65
|
+
"atob 解码超长编码串", 65),
|
|
66
|
+
Rule("OBF-007", "Obfuscation", "high",
|
|
67
|
+
r"(?i)(?:(?:exec|eval|system)\s*\(\s*(?:base64\.)?b64decode\s*\(|(?:base64\.)?b64decode\s*\([^)]*\)\s*[^\n;]{0,60}\b(?:exec|eval|system)\b)",
|
|
68
|
+
"base64 解码后执行", 90),
|
|
69
|
+
Rule("OBF-008", "Obfuscation", "medium",
|
|
70
|
+
r"\[::\s*-1\s*\]",
|
|
71
|
+
"字符串反转切片(常见混淆手法,需结合上下文)", 40),
|
|
72
|
+
|
|
73
|
+
# ── Persistence 持久化 ────────────────────────────────────────────────
|
|
74
|
+
Rule("PER-001", "Persistence", "high",
|
|
75
|
+
r"(?i)\bcrontab\s+-(?:e|r)\b",
|
|
76
|
+
"修改 crontab(持久化)", 78),
|
|
77
|
+
Rule("PER-002", "Persistence", "high",
|
|
78
|
+
r"(?i)(?:>>|>)\s*[^\n;]{0,60}/etc/cron(?:\.d)?/",
|
|
79
|
+
"写入系统 crontab 目录", 80),
|
|
80
|
+
Rule("PER-003", "Persistence", "high",
|
|
81
|
+
r"(?i)\bcron\b[^\n;]{0,40}(?:@reboot|@daily|@hourly)",
|
|
82
|
+
"cron 定时任务(含重启执行)", 70),
|
|
83
|
+
Rule("PER-004", "Persistence", "high",
|
|
84
|
+
r"(?i)launchctl\s+(?:load|bootstrap|submit)",
|
|
85
|
+
"macOS launchctl 加载持久化任务", 80),
|
|
86
|
+
Rule("PER-005", "Persistence", "high",
|
|
87
|
+
r"(?i)(?:Library/(?:LaunchAgents|LaunchDaemons)|launchd\.plist|(?:>>|>)\s*[^\n;]{0,60}\.plist)",
|
|
88
|
+
"macOS 启动代理/守护(LaunchAgents/LaunchDaemons plist)持久化", 70),
|
|
89
|
+
Rule("PER-006", "Persistence", "high",
|
|
90
|
+
r"(?i)systemctl\s+(?:enable|start)\b",
|
|
91
|
+
"systemd 服务启用(持久化)", 60),
|
|
92
|
+
Rule("PER-007", "Persistence", "high",
|
|
93
|
+
r"(?i)(?:>>|>)\s*[^\n;]{0,80}/etc/systemd/system/",
|
|
94
|
+
"写入 systemd 服务文件", 75),
|
|
95
|
+
Rule("PER-008", "Persistence", "high",
|
|
96
|
+
r"(?i)(?:>>|>)\s*[^\n;]{0,80}/(?:etc/rc\.local|etc/rc\.d/)",
|
|
97
|
+
"写入 rc.local / rc.d 启动脚本", 80),
|
|
98
|
+
Rule("PER-009", "Persistence", "high",
|
|
99
|
+
r"(?i)(?:>>|>)\s*[^\n;]{0,80}\.(?:bashrc|zshrc|profile|bash_profile)\b",
|
|
100
|
+
"写入 shell 配置文件(持久化)", 78),
|
|
101
|
+
Rule("PER-010", "Persistence", "high",
|
|
102
|
+
r"(?i)HKEY_(?:CURRENT_USER|LOCAL_MACHINE)[^\n;]{0,80}(?:CurrentVersion\\)?Run(?:Once)?\b",
|
|
103
|
+
"Windows 注册表启动项", 80),
|
|
104
|
+
Rule("PER-011", "Persistence", "medium",
|
|
105
|
+
r"(?i)HKEY_[^\n;]{0,120}(?:AppInit_DLLs|UserInitMprLogonScript)",
|
|
106
|
+
"Windows 注册表全局持久化点(AppInit_DLLs/登录脚本)", 85),
|
|
107
|
+
|
|
108
|
+
# ── Exfiltration 数据外传 ─────────────────────────────────────────────
|
|
109
|
+
Rule("EXF-001", "Exfiltration", "high",
|
|
110
|
+
r"(?i)\b(?:zip|tar)\b[^\n;]{0,120}(?:-r\b|cf\b)[^\n;]{0,120}(?:\bcurl\b|\bwget\b|requests\.post|urllib)",
|
|
111
|
+
"打包后外传(zip/tar 压缩并上传)", 85),
|
|
112
|
+
Rule("EXF-002", "Exfiltration", "high",
|
|
113
|
+
r"(?i)(?:shutil\.make_archive|zipfile\.ZipFile)[^\n;]{0,120}[^\n;]{0,120}(?:requests\.post|urllib\.request|ftp)",
|
|
114
|
+
"Python 归档后上传", 85),
|
|
115
|
+
Rule("EXF-003", "Exfiltration", "high",
|
|
116
|
+
r"(?i)(?:\.env[^\n;]{0,80}(?:\bcurl\b|\bwget\b|requests\.post|urllib)|(?:\bcurl\b|\bwget\b|requests\.post|urllib)[^\n;]{0,80}\.env)",
|
|
117
|
+
"读取 .env 后外传", 88),
|
|
118
|
+
Rule("EXF-004", "Exfiltration", "high",
|
|
119
|
+
r"(?i)(?:(?:id_rsa|id_ed25519|\.ssh)[^\n;]{0,80}(?:\bcurl\b|\bwget\b|requests\.post|urllib|ftp)|(?:\bcurl\b|\bwget\b|requests\.post|urllib|ftp)[^\n;]{0,80}(?:id_rsa|id_ed25519|\.ssh))",
|
|
120
|
+
"读取 SSH 私钥后外传", 92),
|
|
121
|
+
Rule("EXF-005", "Exfiltration", "high",
|
|
122
|
+
r"(?i)(?:(?:Login\sData|Cookies\.sqlite|\.aws\\credentials)[^\n;]{0,80}(?:\bcurl\b|\bwget\b|requests\.post|urllib)|(?:\bcurl\b|\bwget\b|requests\.post|urllib)[^\n;]{0,80}(?:Login\sData|Cookies\.sqlite|\.aws\\credentials))",
|
|
123
|
+
"读取浏览器/云凭据后外传", 90),
|
|
124
|
+
|
|
125
|
+
# ── CredentialTheft 凭据窃取 ──────────────────────────────────────────
|
|
126
|
+
Rule("CRE-001", "CredentialTheft", "critical",
|
|
127
|
+
r"(?i)osascript[^\n;]{0,120}(?:password|passphrase)",
|
|
128
|
+
"macOS 弹窗套取密码", 90),
|
|
129
|
+
Rule("CRE-002", "CredentialTheft", "critical",
|
|
130
|
+
r"(?i)security\s+find-generic-password|keychain",
|
|
131
|
+
"访问 macOS keychain 凭据", 85),
|
|
132
|
+
Rule("CRE-003", "CredentialTheft", "high",
|
|
133
|
+
r"(?i)(?:id_rsa|id_ed25519|id_dsa)\.?(?:pub)?\b",
|
|
134
|
+
"读取 SSH 私钥文件", 80),
|
|
135
|
+
Rule("CRE-004", "CredentialTheft", "high",
|
|
136
|
+
r"(?i)\.aws[/\\](?:credentials|config)\b",
|
|
137
|
+
"读取 AWS 凭据文件", 85),
|
|
138
|
+
Rule("CRE-005", "CredentialTheft", "high",
|
|
139
|
+
r"(?i)(?:win32crypt|DPAPI|CryptUnprotectData)",
|
|
140
|
+
"Windows DPAPI 解密调用", 85),
|
|
141
|
+
Rule("CRE-006", "CredentialTheft", "medium",
|
|
142
|
+
r"(?i)\b(?:MEMORY\.md|USER\.md|SOUL\.md|IDENTITY\.md)\b",
|
|
143
|
+
"访问智能体记忆/身份文件(需确认必要性)", 60),
|
|
144
|
+
Rule("CRE-007", "CredentialTheft", "medium",
|
|
145
|
+
r"(?i)(?:cookie|session)[^\n;]{0,60}(?:steal|exfil|upload|post)",
|
|
146
|
+
"Cookie/会话窃取相关操作", 75),
|
|
147
|
+
|
|
148
|
+
# ── NetworkCall 网络调用(含反向 shell)───────────────────────────────
|
|
149
|
+
Rule("NET-001", "NetworkCall", "critical",
|
|
150
|
+
r"(?i)\bnc\s+[-A-Za-z0-9. ]{0,40}-e\b",
|
|
151
|
+
"netcat 反向 shell(-e 参数)", 95),
|
|
152
|
+
Rule("NET-002", "NetworkCall", "critical",
|
|
153
|
+
r"(?i)bash\s+-i\s*>\s*&?\s*/dev/tcp/",
|
|
154
|
+
"bash /dev/tcp 反向 shell", 95),
|
|
155
|
+
Rule("NET-003", "NetworkCall", "critical",
|
|
156
|
+
r"(?i)(?:socket|connect)\s*\([^\n;]{0,80}(?:receiver|attacker|hacker|remote)[^\n;]{0,40}\d{2,5}\)",
|
|
157
|
+
"连接疑似攻击者地址的 socket", 85),
|
|
158
|
+
Rule("NET-004", "NetworkCall", "medium",
|
|
159
|
+
r"(?i)\bsocket\s*\.\s*(?:socket|create_connection|connect)\b",
|
|
160
|
+
"原始 socket 连接(需确认目标)", 60),
|
|
161
|
+
Rule("NET-005", "NetworkCall", "medium",
|
|
162
|
+
r"(?i)requests\s*\.\s*(?:get|post|put|patch|delete|request)\s*\(",
|
|
163
|
+
"HTTP 客户端调用(需确认目标)", 40),
|
|
164
|
+
Rule("NET-006", "NetworkCall", "medium",
|
|
165
|
+
r"(?i)urllib\s*\.\s*request\b",
|
|
166
|
+
"urllib 网络调用(需确认目标)", 40),
|
|
167
|
+
Rule("NET-007", "NetworkCall", "medium",
|
|
168
|
+
r"(?i)\bfetch\s*\(\s*['\"]",
|
|
169
|
+
"JS fetch 网络调用(需确认目标)", 40),
|
|
170
|
+
Rule("NET-008", "NetworkCall", "medium",
|
|
171
|
+
r"(?i)\b(?:curl|wget|httpie|aria2c)\b\s+[-'\"A-Za-z0-9_.:/?=&%]",
|
|
172
|
+
"命令行下载工具调用(需确认目标)", 40),
|
|
173
|
+
Rule("NET-009", "NetworkCall", "low",
|
|
174
|
+
r"(?i)https?://",
|
|
175
|
+
"文本中出现 URL(需结合上下文)", 20),
|
|
176
|
+
|
|
177
|
+
# ── PrivilegeEscalation 权限提升 ──────────────────────────────────────
|
|
178
|
+
Rule("PRI-001", "PrivilegeEscalation", "high",
|
|
179
|
+
r"(?i)\bchmod\s+[0-7]*[267][0-7]{2}\b",
|
|
180
|
+
"chmod 设置 setuid/setgid/sticky 权限位", 85),
|
|
181
|
+
Rule("PRI-002", "PrivilegeEscalation", "high",
|
|
182
|
+
r"(?i)\bchmod\s+777\b",
|
|
183
|
+
"chmod 777 全权限", 70),
|
|
184
|
+
Rule("PRI-003", "PrivilegeEscalation", "high",
|
|
185
|
+
r"(?i)\bsetuid\s*\(|setgid\s*\(",
|
|
186
|
+
"调用 setuid/setgid", 80),
|
|
187
|
+
Rule("PRI-004", "PrivilegeEscalation", "medium",
|
|
188
|
+
r"(?i)usermod\s+-aG\s+(?:wheel|sudo|admin)\b|net\s+localgroup\s+administrators\s+\S+\s*/add",
|
|
189
|
+
"把用户加入管理员组", 85),
|
|
190
|
+
Rule("PRI-005", "PrivilegeEscalation", "low",
|
|
191
|
+
r"(?i)\bsudo\b",
|
|
192
|
+
"使用 sudo(需确认必要性)", 25),
|
|
193
|
+
|
|
194
|
+
# ── SocialEngineering 社会工程命名 ────────────────────────────────────
|
|
195
|
+
Rule("SOC-001", "SocialEngineering", "medium",
|
|
196
|
+
r"(?i)(?:airdrop|claim\s+reward|free\s+nft|verify\s+your\s+account|security\s+update\s+required|seed\s+phrase|2fa\s+bypass)",
|
|
197
|
+
"社会工程高频话术", 70),
|
|
198
|
+
Rule("SOC-002", "SocialEngineering", "medium",
|
|
199
|
+
r"(?i)(?:metamask|wallet|private\s+key\s+backup|助记词|钱包)",
|
|
200
|
+
"加密货币钱包相关命名", 55),
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
# 敏感文件名(文件名级匹配,CredentialTheft 辅助)
|
|
204
|
+
SENSITIVE_FILENAMES = [
|
|
205
|
+
("id_rsa", "SSH 私钥", "high", 85),
|
|
206
|
+
("id_ed25519", "SSH 私钥", "high", 85),
|
|
207
|
+
("id_dsa", "SSH 私钥", "high", 85),
|
|
208
|
+
("credentials", "云服务凭据文件", "high", 80),
|
|
209
|
+
(".netrc", "网络凭据文件", "high", 80),
|
|
210
|
+
(".pgpass", "数据库口令文件", "high", 85),
|
|
211
|
+
("history", "shell 历史文件", "medium", 60),
|
|
212
|
+
(".env", "环境变量文件", "medium", 45),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
# 外传敏感文件组合(EXF 规则之外的补充,按文件名 + 行内网络调用)
|
|
216
|
+
# 安装钩子可疑关键字(yotta_audit.py PostInstallHookDetector 使用;本文件为签名数据,自扫豁免)
|
|
217
|
+
POSTINSTALL_SUSPICIOUS = r"(?i)(curl|wget|bash|sh\s|python|node\s+-e|eval|powershell|/tmp|%temp%)"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
EXFIL_SENSITIVE = {
|
|
221
|
+
"id_rsa": "SSH 私钥",
|
|
222
|
+
"id_ed25519": "SSH 私钥",
|
|
223
|
+
"id_dsa": "SSH 私钥",
|
|
224
|
+
"credentials": "云凭据",
|
|
225
|
+
"Login Data": "浏览器登录数据",
|
|
226
|
+
"Cookies": "浏览器 Cookie",
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
_COMPILED = {}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def get_rules(detector=None):
|
|
233
|
+
"""返回全部规则(或指定检测器的规则)。"""
|
|
234
|
+
if detector is None:
|
|
235
|
+
return list(PATTERN_RULES)
|
|
236
|
+
return [r for r in PATTERN_RULES if r.detector == detector]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def compile_rules():
|
|
240
|
+
"""预编译所有规则正则,返回 {rule_id: compiled}。"""
|
|
241
|
+
if _COMPILED:
|
|
242
|
+
return _COMPILED
|
|
243
|
+
for r in PATTERN_RULES:
|
|
244
|
+
try:
|
|
245
|
+
_COMPILED[r.id] = re.compile(r.pattern)
|
|
246
|
+
except re.error as e:
|
|
247
|
+
raise ValueError("规则 %s 正则编译失败: %s" % (r.id, e))
|
|
248
|
+
return _COMPILED
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def severity_value(sev):
|
|
252
|
+
"""严重级 → exit code 数值(0=干净/仅 low,1=medium,2=high,3=critical)。"""
|
|
253
|
+
return SEVERITY_VALUE.get(sev, 0)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def severity_rank(sev):
|
|
257
|
+
"""严重级 → 排序权重(越大越严重)。"""
|
|
258
|
+
try:
|
|
259
|
+
return SEVERITY_ORDER.index(sev)
|
|
260
|
+
except ValueError:
|
|
261
|
+
return 0
|