v1.0.4
This commit is contained in:
@@ -18,8 +18,13 @@
|
||||
### 调试开关(JSRPC 与调用 wx.cloud)
|
||||

|
||||
|
||||
### 安全扫描
|
||||
待优化
|
||||
### 敏感信息提取
|
||||
|
||||

|
||||
|
||||
<img src="https://s1.galgame.fun/imgb/u55/20260416_69e0be0c38e39.png" alt="PixPin_2026-04-16_18-45-59" />
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +65,7 @@ pip install -r requirements.txt
|
||||
16815, 16965, 17037, 17071, 17127, 18055, 18151, 18787, 18891, 18955, 19027, 19201
|
||||
- 推荐微信版本为4.1.0.30
|
||||
- 下载地址[weixin/4.1.0.30](https://github.com/vs-olitus/wx-version/releases/tag/4.1.0.30)
|
||||
|
||||
|
||||
## 使用方法
|
||||
|
||||
### GUI 模式(推荐)
|
||||
@@ -82,7 +87,7 @@ python gui.py
|
||||
python main.py
|
||||
|
||||
# 自定义端口
|
||||
python main.py --debug-port 9421 --cdp-port 62000
|
||||
python main.py --cdp-port 62000
|
||||
|
||||
# 开启详细日志
|
||||
python main.py --debug-main --debug-frida
|
||||
|
||||
@@ -72,11 +72,6 @@ const hookOnLoadScene = (a1, sceneOffsets) => {
|
||||
}
|
||||
send("[hook] hook scene condition -> 1101");
|
||||
miniappScenePtr.writeInt(1101);
|
||||
|
||||
// TODO: customize debugging endpoint
|
||||
// const websocketServerStringPtr = passArgs.add(8).readPointer().add(520);
|
||||
// VERBOSE && console.log("[hook] hook websocket server, original: ", websocketServerStringPtr.readUtf8String());
|
||||
// websocketServerStringPtr.writeUtf8String("ws://127.0.0.1:8189/");
|
||||
};
|
||||
|
||||
const patchOnLoadStart = (base, config) => {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
-7
@@ -7,9 +7,9 @@ CDP_PORT = 62000
|
||||
|
||||
|
||||
class CliOptions:
|
||||
def __init__(self, debug_port: int, cdp_port: int, debug_main: bool, debug_frida: bool,
|
||||
def __init__(self, cdp_port: int, debug_main: bool, debug_frida: bool,
|
||||
scripts_dir: str = "", script_files: list = None):
|
||||
self.debug_port = debug_port
|
||||
self.debug_port = DEBUG_PORT # 小程序硬编码 9421,不可修改
|
||||
self.cdp_port = cdp_port
|
||||
self.debug_main = debug_main
|
||||
self.debug_frida = debug_frida
|
||||
@@ -35,18 +35,16 @@ def parse_cli_options() -> CliOptions:
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=f"""Examples:
|
||||
python main.py
|
||||
python main.py --debug-port 9421 --cdp-port 62000
|
||||
python main.py --cdp-port 62000
|
||||
python main.py --debug-main --debug-frida
|
||||
|
||||
Default ports:
|
||||
Debug server: {DEBUG_PORT}
|
||||
Debug server: {DEBUG_PORT} (fixed)
|
||||
CDP proxy: {CDP_PORT}
|
||||
|
||||
Usage:
|
||||
Connect Chrome DevTools to: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:<cdp-port>"""
|
||||
)
|
||||
parser.add_argument("--debug-port", type=str, default=None,
|
||||
help=f"Remote debug server port (default: {DEBUG_PORT})")
|
||||
parser.add_argument("--cdp-port", type=str, default=None,
|
||||
help=f"CDP proxy server port (default: {CDP_PORT})")
|
||||
parser.add_argument("--debug-main", action="store_true", default=False,
|
||||
@@ -72,7 +70,6 @@ Usage:
|
||||
script_files.append(os.path.abspath(f))
|
||||
|
||||
return CliOptions(
|
||||
debug_port=parse_port("--debug-port", args.debug_port, DEBUG_PORT),
|
||||
cdp_port=parse_port("--cdp-port", args.cdp_port, CDP_PORT),
|
||||
debug_main=args.debug_main,
|
||||
debug_frida=args.debug_frida,
|
||||
|
||||
+14
-1
@@ -25,6 +25,7 @@ class CloudAuditor:
|
||||
"""启动全局 Hook:注入 JS,安装 hook,启动自动扫描新 frame"""
|
||||
if self._enabled:
|
||||
return {"ok": True, "already": True}
|
||||
self._injected = False # 强制重新注入(连接可能已更换)
|
||||
await self.inject()
|
||||
result = await self.engine.evaluate_js(
|
||||
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
|
||||
@@ -52,11 +53,23 @@ class CloudAuditor:
|
||||
pass
|
||||
|
||||
async def poll(self):
|
||||
"""拉取新捕获的云函数调用,只返回增量"""
|
||||
"""拉取新捕获的云函数调用,只返回增量。
|
||||
自动检测 Hook 是否丢失(页面切换后),并重新注入。
|
||||
"""
|
||||
if not self._enabled:
|
||||
return []
|
||||
try:
|
||||
await self.inject()
|
||||
# 检查 Hook 是否仍然存活(页面导航后可能丢失)
|
||||
alive_result = await self.engine.evaluate_js(
|
||||
"(function(){try{return window.cloudAudit&&window.cloudAudit._hooked?'1':'0'}catch(e){return '0'}})()",
|
||||
timeout=3.0)
|
||||
alive = self._extract_value(alive_result)
|
||||
if alive != '1':
|
||||
# Hook 丢失(页面切换/刷新),重新安装
|
||||
await self.engine.evaluate_js(
|
||||
"JSON.stringify(window.cloudAudit.installHook())", timeout=5.0)
|
||||
self._seen_count = 0 # 重置计数器,新上下文从 0 开始
|
||||
result = await self.engine.evaluate_js(
|
||||
"JSON.stringify(window.cloudAudit.getHookedCalls())", timeout=5.0)
|
||||
value = self._extract_value(result)
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
extract_worker.py — 独立子进程 worker
|
||||
由 GUI 通过 subprocess 启动,避免阻塞主 UI
|
||||
|
||||
支持两种模式:
|
||||
1. decompile —— 解密解包单个小程序
|
||||
2. scan —— 扫描已解包目录的敏感信息
|
||||
|
||||
通信协议 (stdout JSON lines):
|
||||
{"type": "progress", "done": N, "total": N}
|
||||
{"type": "log", "msg": "..."}
|
||||
{"type": "result", "data": {...}}
|
||||
{"type": "error", "msg": "..."}
|
||||
|
||||
命令行:
|
||||
python extract_worker.py decompile --packages-dir DIR --appid APPID --output-dir DIR
|
||||
python extract_worker.py scan --scan-dir DIR --output-dir DIR [--custom-patterns JSON_FILE]
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
def _init_stdout():
|
||||
"""Windows 下强制 stdout 使用 UTF-8,避免 GBK 编码错误"""
|
||||
if sys.platform == "win32":
|
||||
import io
|
||||
sys.stdout = io.TextIOWrapper(
|
||||
sys.stdout.buffer, encoding="utf-8", errors="replace"
|
||||
)
|
||||
sys.stderr = io.TextIOWrapper(
|
||||
sys.stderr.buffer, encoding="utf-8", errors="replace"
|
||||
)
|
||||
|
||||
|
||||
def _emit(obj):
|
||||
"""输出一行 JSON 到 stdout"""
|
||||
print(json.dumps(obj, ensure_ascii=False), flush=True)
|
||||
|
||||
|
||||
def do_decompile(args):
|
||||
"""解密解包单个小程序"""
|
||||
src_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
base_dir = os.path.dirname(src_dir)
|
||||
if base_dir not in sys.path:
|
||||
sys.path.insert(0, base_dir)
|
||||
|
||||
from src.wxapkg import find_wxapkg_files, extract_wxapkg
|
||||
|
||||
packages_dir = args.packages_dir
|
||||
appid = args.appid
|
||||
output_dir = args.output_dir
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
_emit({"type": "log", "msg": f"正在查找 {appid} 的 wxapkg 文件..."})
|
||||
|
||||
# 查找该 appid 的包
|
||||
all_pkgs = find_wxapkg_files(packages_dir)
|
||||
pkgs = [p for p in all_pkgs if p["appid"] == appid]
|
||||
|
||||
if not pkgs:
|
||||
_emit({"type": "log", "msg": f"未找到 {appid} 的 wxapkg 文件"})
|
||||
_emit({"type": "result", "data": {"appid": appid, "files": 0, "extracted": 0}})
|
||||
return
|
||||
|
||||
_emit({"type": "log", "msg": f"发现 {len(pkgs)} 个 wxapkg 文件"})
|
||||
|
||||
decompile_dir = os.path.join(output_dir, "decompiled")
|
||||
os.makedirs(decompile_dir, exist_ok=True)
|
||||
|
||||
total = len(pkgs)
|
||||
extracted_total = 0
|
||||
|
||||
for i, pkg in enumerate(pkgs, 1):
|
||||
try:
|
||||
files = extract_wxapkg(pkg["path"], decompile_dir, appid)
|
||||
extracted_total += len(files)
|
||||
_emit({"type": "log", "msg": f" 解包 {pkg['name']}: {len(files)} 个文件"})
|
||||
except Exception as e:
|
||||
_emit({"type": "log", "msg": f" 解包失败 {pkg['name']}: {e}"})
|
||||
_emit({"type": "progress", "done": i, "total": total})
|
||||
|
||||
_emit({"type": "result", "data": {
|
||||
"appid": appid,
|
||||
"files": total,
|
||||
"extracted": extracted_total,
|
||||
"decompile_dir": decompile_dir,
|
||||
}})
|
||||
_emit({"type": "log", "msg": f"反编译完成! 共提取 {extracted_total} 个文件"})
|
||||
|
||||
|
||||
def do_scan(args):
|
||||
"""扫描已解包的目录"""
|
||||
src_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
base_dir = os.path.dirname(src_dir)
|
||||
if base_dir not in sys.path:
|
||||
sys.path.insert(0, base_dir)
|
||||
|
||||
from src.extractor import Extractor
|
||||
|
||||
scan_dir = args.scan_dir
|
||||
output_dir = args.output_dir
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
extractor = Extractor()
|
||||
|
||||
# 加载自定义正则
|
||||
if args.custom_patterns and os.path.isfile(args.custom_patterns):
|
||||
try:
|
||||
with open(args.custom_patterns, "r", encoding="utf-8") as f:
|
||||
custom = json.load(f)
|
||||
loaded = 0
|
||||
for name, info in custom.items():
|
||||
# 兼容新格式 {"regex": str, "enabled": bool} 和旧格式 str
|
||||
if isinstance(info, dict):
|
||||
if not info.get("enabled", True):
|
||||
continue
|
||||
pat = info.get("regex", "")
|
||||
else:
|
||||
pat = info
|
||||
if pat:
|
||||
extractor.add_custom_pattern(name, pat)
|
||||
loaded += 1
|
||||
_emit({"type": "log", "msg": f"已加载 {loaded} 个自定义正则"})
|
||||
except Exception as e:
|
||||
_emit({"type": "log", "msg": f"加载自定义正则失败: {e}"})
|
||||
|
||||
_emit({"type": "log", "msg": f"正在扫描目录: {scan_dir}"})
|
||||
|
||||
def on_progress(done, total):
|
||||
_emit({"type": "progress", "done": done, "total": total})
|
||||
|
||||
scan_result = extractor.scan_directory(scan_dir, on_progress=on_progress)
|
||||
|
||||
# 导出
|
||||
result_dir = output_dir
|
||||
html_path = os.path.join(result_dir, "report.html")
|
||||
json_path = os.path.join(result_dir, "report.json")
|
||||
Extractor.export_html(scan_result, html_path)
|
||||
Extractor.export_json(scan_result, json_path)
|
||||
|
||||
summary = Extractor.get_summary(scan_result)
|
||||
total_findings = sum(summary.values())
|
||||
|
||||
_emit({"type": "result", "data": {
|
||||
"files_scanned": scan_result.get("files_scanned", 0),
|
||||
"elapsed": scan_result.get("elapsed", 0),
|
||||
"findings": total_findings,
|
||||
"summary": summary,
|
||||
"html_path": html_path,
|
||||
"json_path": json_path,
|
||||
"result_dir": result_dir,
|
||||
}})
|
||||
_emit({"type": "log", "msg": f"扫描完成! 发现 {total_findings} 条敏感信息,耗时 {scan_result.get('elapsed', 0)}s"})
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
# decompile 子命令
|
||||
p_dec = subparsers.add_parser("decompile")
|
||||
p_dec.add_argument("--packages-dir", required=True)
|
||||
p_dec.add_argument("--appid", required=True)
|
||||
p_dec.add_argument("--output-dir", required=True)
|
||||
|
||||
# scan 子命令
|
||||
p_scan = subparsers.add_parser("scan")
|
||||
p_scan.add_argument("--scan-dir", required=True)
|
||||
p_scan.add_argument("--output-dir", required=True)
|
||||
p_scan.add_argument("--custom-patterns", default="")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "decompile":
|
||||
do_decompile(args)
|
||||
elif args.command == "scan":
|
||||
do_scan(args)
|
||||
else:
|
||||
_emit({"type": "error", "msg": "未知命令,使用 decompile 或 scan"})
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_init_stdout()
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
_emit({"type": "error", "msg": f"worker 异常: {traceback.format_exc()}"})
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,978 @@
|
||||
"""
|
||||
extractor.py — 敏感信息提取引擎
|
||||
直接使用 heartk_fast.py 的完整正则规则,保证提取结果一致
|
||||
支持自定义正则规则,自定义类别会出现在导出报告中
|
||||
|
||||
用法:
|
||||
from src.extractor import Extractor
|
||||
ext = Extractor()
|
||||
ext.add_custom_pattern("视频", r"video_url\s*=\s*['\"](.+?)['\"]")
|
||||
results = ext.scan_directory("/path/to/dir")
|
||||
Extractor.export_html(results, "report.html")
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
from typing import Dict, List, Optional, Any, Callable
|
||||
|
||||
try:
|
||||
import chardet
|
||||
except ImportError:
|
||||
chardet = None
|
||||
|
||||
# ============================================================
|
||||
# 1. 所有正则规则 (从 heartk_fast.py 原封不动复制)
|
||||
# ============================================================
|
||||
|
||||
STATIC_EXTENSIONS = ['.jpg', '.png', '.gif', '.css', '.svg', '.ico', '.js']
|
||||
|
||||
CATEGORIES = [
|
||||
"ip", "ip_port", "domain", "path", "incomplete_path",
|
||||
"url", "sfz", "mobile", "mail", "jwt", "algorithm", "secret", "static"
|
||||
]
|
||||
|
||||
# ---------- extract_info 对应的正则 ----------
|
||||
|
||||
EXTRACT_PATTERNS: Dict[str, re.Pattern] = {
|
||||
'sfz': re.compile(
|
||||
r"""['"]((\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3})|(\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)))['"]"""),
|
||||
'mobile': re.compile(
|
||||
r"""['\"](1(3([0-35-9]\d|4[1-8])|4[14-9]\d|5([\d]\d|7[1-79])|66\d|7[2-35-8]\d|8\d{2}|9[89]\d)\d{7})['\"]"""),
|
||||
'mail': re.compile(
|
||||
r"""['\"][a-zA-Z0-9\._\-]*@[a-zA-Z0-9\._\-]{1,63}\.((?!js|css|jpg|jpeg|png|ico)[a-zA-Z]{2,})['\"]"""),
|
||||
'ip': re.compile(
|
||||
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/.*?)?['\"]"""),
|
||||
'ip_port': re.compile(
|
||||
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}(\/.*?)?['\"]"""),
|
||||
'domain': re.compile(
|
||||
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?[a-zA-Z0-9\-\.]*?\.(xin|com|cn|net|com\.cn|vip|top|cc|shop|club|wang|xyz|luxe|site|news|pub|fun|online|win|red|loan|ren|mom|net\.cn|org|link|biz|bid|help|tech|date|mobi|so|me|tv|co|vc|pw|video|party|pics|website|store|ltd|ink|trade|live|wiki|space|gift|lol|work|band|info|click|photo|market|tel|social|press|game|kim|org\.cn|games|pro|men|love|studio|rocks|asia|group|science|design|software|engineer|lawyer|fit|beer|tw|我爱你|中国|公司|网络|在线|网址|网店|集团|中文网)(\:\d{1,5})?(\/)?['\"]"""),
|
||||
'path': re.compile(
|
||||
r"""['\"](?:\/|\.\.\/)(?:[^\/\>\< \)\(\{\}\,\'\"\\\n][^\>\< \)\(\{\}\,\'\"\\\n]*?)['\"]"""),
|
||||
'incomplete_path': re.compile(
|
||||
r"""['\"][^\/\>\< \)\(\{\}\,\'\"\\\n][\w\/]*?\/[\w\/]*?['\"]"""),
|
||||
'url': re.compile(
|
||||
r"""['\"](([a-zA-Z0-9]+:)?\/\/)?[a-zA-Z0-9\-\.]*?\.(xin|com|cn|net|com\.cn|vip|top|cc|shop|club|wang|xyz|luxe|site|news|pub|fun|online|win|red|loan|ren|mom|net\.cn|org|link|biz|bid|help|tech|date|mobi|so|me|tv|co|vc|pw|video|party|pics|website|store|ltd|ink|trade|live|wiki|space|gift|lol|work|band|info|click|photo|market|tel|social|press|game|kim|org\.cn|games|pro|men|love|studio|rocks|asia|group|science|design|software|engineer|lawyer|fit|beer|tw|我爱你|中国|公司|网络|在线|网址|网店|集团|中文网)(\:\d{1,5})?(\/.*?)?['\"]"""),
|
||||
'jwt': re.compile(
|
||||
r"""['\"](ey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|ey[A-Za-z0-9_\/+-]{10,}\.[A-Za-z0-9._\/+-]{10,})['\"]"""),
|
||||
'algorithm': re.compile(
|
||||
r"""\W(Base64\.encode|Base64\.decode|btoa|atob|CryptoJS\.AES|CryptoJS\.DES|JSEncrypt|rsa|KJUR|\$\.md5|md5|sha1|sha256|sha512)[\(\.]""", re.IGNORECASE),
|
||||
}
|
||||
|
||||
# ---------- nuclei_regex (727条秘密检测规则) ----------
|
||||
|
||||
_NUCLEI_REGEX_SOURCES = [
|
||||
"zopim[_-]?account[_-]?key", "zhuliang[_-]?gh[_-]?token",
|
||||
"zensonatypepassword", "zendesk[_-]?travis[_-]?github",
|
||||
"yt[_-]?server[_-]?api[_-]?key", "yt[_-]?partner[_-]?refresh[_-]?token",
|
||||
"yt[_-]?partner[_-]?client[_-]?secret", "yt[_-]?client[_-]?secret",
|
||||
"yt[_-]?api[_-]?key", "yt[_-]?account[_-]?refresh[_-]?token",
|
||||
"yt[_-]?account[_-]?client[_-]?secret", "yangshun[_-]?gh[_-]?token",
|
||||
"yangshun[_-]?gh[_-]?password", "www[_-]?googleapis[_-]?com",
|
||||
"wpt[_-]?ssh[_-]?private[_-]?key[_-]?base64", "wpt[_-]?ssh[_-]?connect",
|
||||
"wpt[_-]?report[_-]?api[_-]?key", "wpt[_-]?prepare[_-]?dir",
|
||||
"wpt[_-]?db[_-]?user", "wpt[_-]?db[_-]?password", "wporg[_-]?password",
|
||||
"wpjm[_-]?phpunit[_-]?google[_-]?geocode[_-]?api[_-]?key",
|
||||
"wordpress[_-]?db[_-]?user", "wordpress[_-]?db[_-]?password",
|
||||
"wincert[_-]?password", "widget[_-]?test[_-]?server",
|
||||
"widget[_-]?fb[_-]?password[_-]?3", "widget[_-]?fb[_-]?password[_-]?2",
|
||||
"widget[_-]?fb[_-]?password",
|
||||
"widget[_-]?basic[_-]?password[_-]?5", "widget[_-]?basic[_-]?password[_-]?4",
|
||||
"widget[_-]?basic[_-]?password[_-]?3", "widget[_-]?basic[_-]?password[_-]?2",
|
||||
"widget[_-]?basic[_-]?password",
|
||||
"watson[_-]?password", "watson[_-]?device[_-]?password",
|
||||
"watson[_-]?conversation[_-]?password", "wakatime[_-]?api[_-]?key",
|
||||
"vscetoken", "visual[_-]?recognition[_-]?api[_-]?key",
|
||||
"virustotal[_-]?apikey", "vip[_-]?github[_-]?deploy[_-]?key[_-]?pass",
|
||||
"vip[_-]?github[_-]?deploy[_-]?key",
|
||||
"vip[_-]?github[_-]?build[_-]?repo[_-]?deploy[_-]?key",
|
||||
"v[_-]?sfdc[_-]?password", "v[_-]?sfdc[_-]?client[_-]?secret",
|
||||
"usertravis", "user[_-]?assets[_-]?secret[_-]?access[_-]?key",
|
||||
"user[_-]?assets[_-]?access[_-]?key[_-]?id", "use[_-]?ssh",
|
||||
"us[_-]?east[_-]?1[_-]?elb[_-]?amazonaws[_-]?com",
|
||||
"urban[_-]?secret", "urban[_-]?master[_-]?secret", "urban[_-]?key",
|
||||
"unity[_-]?serial", "unity[_-]?password",
|
||||
"twitteroauthaccesstoken", "twitteroauthaccesssecret",
|
||||
"twitter[_-]?consumer[_-]?secret", "twitter[_-]?consumer[_-]?key",
|
||||
"twine[_-]?password", "twilio[_-]?token", "twilio[_-]?sid",
|
||||
"twilio[_-]?configuration[_-]?sid",
|
||||
"twilio[_-]?chat[_-]?account[_-]?api[_-]?service",
|
||||
"twilio[_-]?api[_-]?secret", "twilio[_-]?api[_-]?key",
|
||||
"trex[_-]?okta[_-]?client[_-]?token", "trex[_-]?client[_-]?token",
|
||||
"travis[_-]?token", "travis[_-]?secure[_-]?env[_-]?vars",
|
||||
"travis[_-]?pull[_-]?request", "travis[_-]?gh[_-]?token",
|
||||
"travis[_-]?e2e[_-]?token", "travis[_-]?com[_-]?token",
|
||||
"travis[_-]?branch", "travis[_-]?api[_-]?token",
|
||||
"travis[_-]?access[_-]?token", "token[_-]?core[_-]?java",
|
||||
"thera[_-]?oss[_-]?access[_-]?key", "tester[_-]?keys[_-]?password",
|
||||
"test[_-]?test", "test[_-]?github[_-]?token", "tesco[_-]?api[_-]?key",
|
||||
"svn[_-]?pass", "surge[_-]?token", "surge[_-]?login",
|
||||
"stripe[_-]?public", "stripe[_-]?private",
|
||||
"strip[_-]?secret[_-]?key", "strip[_-]?publishable[_-]?key",
|
||||
"stormpath[_-]?api[_-]?key[_-]?secret", "stormpath[_-]?api[_-]?key[_-]?id",
|
||||
"starship[_-]?auth[_-]?token", "starship[_-]?account[_-]?sid",
|
||||
"star[_-]?test[_-]?secret[_-]?access[_-]?key", "star[_-]?test[_-]?location",
|
||||
"star[_-]?test[_-]?bucket", "star[_-]?test[_-]?aws[_-]?access[_-]?key[_-]?id",
|
||||
"staging[_-]?base[_-]?url[_-]?runscope", "ssmtp[_-]?config", "sshpass",
|
||||
"srcclr[_-]?api[_-]?token",
|
||||
"square[_-]?reader[_-]?sdk[_-]?repository[_-]?password",
|
||||
"sqssecretkey", "sqsaccesskey", "spring[_-]?mail[_-]?password",
|
||||
"spotify[_-]?api[_-]?client[_-]?secret", "spotify[_-]?api[_-]?access[_-]?token",
|
||||
"spaces[_-]?secret[_-]?access[_-]?key", "spaces[_-]?access[_-]?key[_-]?id",
|
||||
"soundcloud[_-]?password", "soundcloud[_-]?client[_-]?secret",
|
||||
"sonatypepassword", "sonatype[_-]?token[_-]?user",
|
||||
"sonatype[_-]?token[_-]?password", "sonatype[_-]?password",
|
||||
"sonatype[_-]?pass", "sonatype[_-]?nexus[_-]?password",
|
||||
"sonatype[_-]?gpg[_-]?passphrase", "sonatype[_-]?gpg[_-]?key[_-]?name",
|
||||
"sonar[_-]?token", "sonar[_-]?project[_-]?key",
|
||||
"sonar[_-]?organization[_-]?key",
|
||||
"socrata[_-]?password", "socrata[_-]?app[_-]?token",
|
||||
"snyk[_-]?token", "snyk[_-]?api[_-]?token",
|
||||
"snoowrap[_-]?refresh[_-]?token", "snoowrap[_-]?password",
|
||||
"snoowrap[_-]?client[_-]?secret", "slate[_-]?user[_-]?email",
|
||||
"slash[_-]?developer[_-]?space[_-]?key", "slash[_-]?developer[_-]?space",
|
||||
"signing[_-]?key[_-]?sid", "signing[_-]?key[_-]?secret",
|
||||
"signing[_-]?key[_-]?password", "signing[_-]?key",
|
||||
"setsecretkey", "setdstsecretkey", "setdstaccesskey",
|
||||
"ses[_-]?secret[_-]?key", "ses[_-]?access[_-]?key",
|
||||
"service[_-]?account[_-]?secret", "sentry[_-]?key",
|
||||
"sentry[_-]?secret", "sentry[_-]?endpoint",
|
||||
"sentry[_-]?default[_-]?org", "sentry[_-]?auth[_-]?token",
|
||||
"sendwithus[_-]?key", "sendgrid[_-]?username", "sendgrid[_-]?user",
|
||||
"sendgrid[_-]?password", "sendgrid[_-]?key", "sendgrid[_-]?api[_-]?key",
|
||||
"sendgrid", "selion[_-]?selenium[_-]?host", "selion[_-]?log[_-]?level[_-]?dev",
|
||||
"segment[_-]?api[_-]?key", "secretid", "secretkey", "secretaccesskey",
|
||||
"secret[_-]?key[_-]?base",
|
||||
"secret[_-]?9", "secret[_-]?8", "secret[_-]?7", "secret[_-]?6",
|
||||
"secret[_-]?5", "secret[_-]?4", "secret[_-]?3", "secret[_-]?2",
|
||||
"secret[_-]?11", "secret[_-]?10", "secret[_-]?1", "secret[_-]?0",
|
||||
"sdr[_-]?token", "scrutinizer[_-]?token", "sauce[_-]?access[_-]?key",
|
||||
"sandbox[_-]?aws[_-]?secret[_-]?access[_-]?key",
|
||||
"sandbox[_-]?aws[_-]?access[_-]?key[_-]?id", "sandbox[_-]?access[_-]?token",
|
||||
"salesforce[_-]?bulk[_-]?test[_-]?security[_-]?token",
|
||||
"salesforce[_-]?bulk[_-]?test[_-]?password",
|
||||
"sacloud[_-]?api", "sacloud[_-]?access[_-]?token[_-]?secret",
|
||||
"sacloud[_-]?access[_-]?token",
|
||||
"s3[_-]?user[_-]?secret", "s3[_-]?secret[_-]?key",
|
||||
"s3[_-]?secret[_-]?assets", "s3[_-]?secret[_-]?app[_-]?logs",
|
||||
"s3[_-]?key[_-]?assets", "s3[_-]?key[_-]?app[_-]?logs", "s3[_-]?key",
|
||||
"s3[_-]?external[_-]?3[_-]?amazonaws[_-]?com",
|
||||
"s3[_-]?bucket[_-]?name[_-]?assets", "s3[_-]?bucket[_-]?name[_-]?app[_-]?logs",
|
||||
"s3[_-]?access[_-]?key[_-]?id", "s3[_-]?access[_-]?key",
|
||||
"rubygems[_-]?auth[_-]?token", "rtd[_-]?store[_-]?pass", "rtd[_-]?key[_-]?pass",
|
||||
"route53[_-]?access[_-]?key[_-]?id",
|
||||
"ropsten[_-]?private[_-]?key", "rinkeby[_-]?private[_-]?key",
|
||||
"rest[_-]?api[_-]?key", "repotoken",
|
||||
"reporting[_-]?webdav[_-]?url", "reporting[_-]?webdav[_-]?pwd",
|
||||
"release[_-]?token", "release[_-]?gh[_-]?token",
|
||||
"registry[_-]?secure", "registry[_-]?pass", "refresh[_-]?token",
|
||||
"rediscloud[_-]?url", "redis[_-]?stunnel[_-]?urls",
|
||||
"randrmusicapiaccesstoken", "rabbitmq[_-]?password",
|
||||
"quip[_-]?token", "qiita[_-]?token", "pypi[_-]?passowrd",
|
||||
"pushover[_-]?token", "publish[_-]?secret", "publish[_-]?key",
|
||||
"publish[_-]?access", "project[_-]?config",
|
||||
"prod[_-]?secret[_-]?key", "prod[_-]?password", "prod[_-]?access[_-]?key[_-]?id",
|
||||
"private[_-]?signing[_-]?password", "pring[_-]?mail[_-]?username",
|
||||
"preferred[_-]?username", "prebuild[_-]?auth",
|
||||
"postgresql[_-]?pass", "postgresql[_-]?db",
|
||||
"postgres[_-]?env[_-]?postgres[_-]?password",
|
||||
"postgres[_-]?env[_-]?postgres[_-]?db", "plugin[_-]?password",
|
||||
"plotly[_-]?apikey", "places[_-]?apikey", "places[_-]?api[_-]?key",
|
||||
"pg[_-]?host", "pg[_-]?database", "personal[_-]?secret", "personal[_-]?key",
|
||||
"percy[_-]?token", "percy[_-]?project",
|
||||
"paypal[_-]?client[_-]?secret", "passwordtravis", "parse[_-]?js[_-]?key",
|
||||
"pagerduty[_-]?apikey", "packagecloud[_-]?token",
|
||||
"ossrh[_-]?username", "ossrh[_-]?secret", "ossrh[_-]?password",
|
||||
"ossrh[_-]?pass", "ossrh[_-]?jira[_-]?password",
|
||||
"os[_-]?password", "os[_-]?auth[_-]?url",
|
||||
"org[_-]?project[_-]?gradle[_-]?sonatype[_-]?nexus[_-]?password",
|
||||
"org[_-]?gradle[_-]?project[_-]?sonatype[_-]?nexus[_-]?password",
|
||||
"openwhisk[_-]?key", "open[_-]?whisk[_-]?key",
|
||||
"onesignal[_-]?user[_-]?auth[_-]?key", "onesignal[_-]?api[_-]?key",
|
||||
"omise[_-]?skey", "omise[_-]?pubkey", "omise[_-]?pkey", "omise[_-]?key",
|
||||
"okta[_-]?oauth2[_-]?clientsecret", "okta[_-]?oauth2[_-]?client[_-]?secret",
|
||||
"okta[_-]?client[_-]?token",
|
||||
"ofta[_-]?secret", "ofta[_-]?region", "ofta[_-]?key",
|
||||
"octest[_-]?password", "octest[_-]?app[_-]?username",
|
||||
"octest[_-]?app[_-]?password", "oc[_-]?pass",
|
||||
"object[_-]?store[_-]?creds", "object[_-]?store[_-]?bucket",
|
||||
"object[_-]?storage[_-]?region[_-]?name", "object[_-]?storage[_-]?password",
|
||||
"oauth[_-]?token", "numbers[_-]?service[_-]?pass",
|
||||
"nuget[_-]?key", "nuget[_-]?apikey", "nuget[_-]?api[_-]?key",
|
||||
"npm[_-]?token", "npm[_-]?secret[_-]?key", "npm[_-]?password",
|
||||
"npm[_-]?email", "npm[_-]?auth[_-]?token", "npm[_-]?api[_-]?token",
|
||||
"npm[_-]?api[_-]?key", "now[_-]?token", "non[_-]?token",
|
||||
"node[_-]?pre[_-]?gyp[_-]?secretaccesskey",
|
||||
"node[_-]?pre[_-]?gyp[_-]?github[_-]?token",
|
||||
"node[_-]?pre[_-]?gyp[_-]?accesskeyid", "node[_-]?env",
|
||||
"ngrok[_-]?token", "ngrok[_-]?auth[_-]?token",
|
||||
"nexuspassword", "nexus[_-]?password",
|
||||
"new[_-]?relic[_-]?beta[_-]?token", "netlify[_-]?api[_-]?key",
|
||||
"nativeevents", "mysqlsecret", "mysqlmasteruser",
|
||||
"mysql[_-]?username", "mysql[_-]?user", "mysql[_-]?root[_-]?password",
|
||||
"mysql[_-]?password", "mysql[_-]?hostname", "mysql[_-]?database",
|
||||
"my[_-]?secret[_-]?env",
|
||||
"multi[_-]?workspace[_-]?sid", "multi[_-]?workflow[_-]?sid",
|
||||
"multi[_-]?disconnect[_-]?sid", "multi[_-]?connect[_-]?sid",
|
||||
"multi[_-]?bob[_-]?sid",
|
||||
"minio[_-]?secret[_-]?key", "minio[_-]?access[_-]?key",
|
||||
"mile[_-]?zero[_-]?key", "mh[_-]?password", "mh[_-]?apikey",
|
||||
"mg[_-]?public[_-]?api[_-]?key", "mg[_-]?api[_-]?key",
|
||||
"mapboxaccesstoken",
|
||||
"mapbox[_-]?aws[_-]?secret[_-]?access[_-]?key",
|
||||
"mapbox[_-]?aws[_-]?access[_-]?key[_-]?id",
|
||||
"mapbox[_-]?api[_-]?token", "mapbox[_-]?access[_-]?token",
|
||||
"manifest[_-]?app[_-]?url", "manifest[_-]?app[_-]?token",
|
||||
"mandrill[_-]?api[_-]?key", "managementapiaccesstoken",
|
||||
"management[_-]?token", "manage[_-]?secret", "manage[_-]?key",
|
||||
"mailgun[_-]?secret[_-]?api[_-]?key", "mailgun[_-]?pub[_-]?key",
|
||||
"mailgun[_-]?pub[_-]?apikey", "mailgun[_-]?priv[_-]?key",
|
||||
"mailgun[_-]?password", "mailgun[_-]?apikey", "mailgun[_-]?api[_-]?key",
|
||||
"mailer[_-]?password", "mailchimp[_-]?key", "mailchimp[_-]?api[_-]?key",
|
||||
"mail[_-]?password", "magento[_-]?password",
|
||||
"magento[_-]?auth[_-]?username", "magento[_-]?auth[_-]?password",
|
||||
"lottie[_-]?upload[_-]?cert[_-]?key[_-]?store[_-]?password",
|
||||
"lottie[_-]?upload[_-]?cert[_-]?key[_-]?password",
|
||||
"lottie[_-]?s3[_-]?secret[_-]?key",
|
||||
"lottie[_-]?happo[_-]?secret[_-]?key", "lottie[_-]?happo[_-]?api[_-]?key",
|
||||
"looker[_-]?test[_-]?runner[_-]?client[_-]?secret",
|
||||
"ll[_-]?shared[_-]?key", "ll[_-]?publish[_-]?url",
|
||||
"linux[_-]?signing[_-]?key",
|
||||
"lighthouse[_-]?api[_-]?key",
|
||||
"lektor[_-]?deploy[_-]?username", "lektor[_-]?deploy[_-]?password",
|
||||
"leanplum[_-]?key", "kxoltsn3vogdop92m",
|
||||
"kubeconfig", "kubecfg[_-]?s3[_-]?path", "kovan[_-]?private[_-]?key",
|
||||
"keystore[_-]?pass",
|
||||
"kafka[_-]?rest[_-]?url", "kafka[_-]?instance[_-]?name",
|
||||
"kafka[_-]?admin[_-]?url", "jwt[_-]?secret",
|
||||
"jdbc[_-]?host", "jdbc[_-]?databaseurl",
|
||||
"itest[_-]?gh[_-]?token", "ios[_-]?docs[_-]?deploy[_-]?token",
|
||||
"internal[_-]?secrets",
|
||||
"integration[_-]?test[_-]?appid", "integration[_-]?test[_-]?api[_-]?key",
|
||||
"index[_-]?name", "ij[_-]?repo[_-]?username", "ij[_-]?repo[_-]?password",
|
||||
"hub[_-]?dxia2[_-]?password", "homebrew[_-]?github[_-]?api[_-]?token",
|
||||
"hockeyapp[_-]?token",
|
||||
"heroku[_-]?token", "heroku[_-]?email", "heroku[_-]?api[_-]?key",
|
||||
"hb[_-]?codesign[_-]?key[_-]?pass", "hb[_-]?codesign[_-]?gpg[_-]?pass",
|
||||
"hab[_-]?key", "hab[_-]?auth[_-]?token",
|
||||
"grgit[_-]?user", "gren[_-]?github[_-]?token",
|
||||
"gradle[_-]?signing[_-]?password", "gradle[_-]?signing[_-]?key[_-]?id",
|
||||
"gradle[_-]?publish[_-]?secret", "gradle[_-]?publish[_-]?key",
|
||||
"gpg[_-]?secret[_-]?keys", "gpg[_-]?private[_-]?key",
|
||||
"gpg[_-]?passphrase", "gpg[_-]?ownertrust",
|
||||
"gpg[_-]?keyname", "gpg[_-]?key[_-]?name",
|
||||
"google[_-]?private[_-]?key(?:[_-]?id)?",
|
||||
"google[_-]?maps[_-]?api[_-]?key", "google[_-]?client[_-]?secret",
|
||||
"google[_-]?client[_-]?id", "google[_-]?client[_-]?email",
|
||||
"google[_-]?account[_-]?type", "gogs[_-]?password",
|
||||
"gitlab[_-]?user[_-]?email",
|
||||
"github[_-]?tokens", "github[_-]?token", "github[_-]?repo",
|
||||
"github[_-]?release[_-]?token", "github[_-]?pwd", "github[_-]?password",
|
||||
"github[_-]?oauth[_-]?token", "github[_-]?oauth", "github[_-]?key",
|
||||
"github[_-]?hunter[_-]?username", "github[_-]?hunter[_-]?token",
|
||||
"github[_-]?deployment[_-]?token",
|
||||
"github[_-]?deploy[_-]?hb[_-]?doc[_-]?pass",
|
||||
"github[_-]?client[_-]?secret", "github[_-]?auth[_-]?token",
|
||||
"github[_-]?auth", "github[_-]?api[_-]?token", "github[_-]?api[_-]?key",
|
||||
"github[_-]?access[_-]?token",
|
||||
"git[_-]?token", "git[_-]?name", "git[_-]?email",
|
||||
"git[_-]?committer[_-]?name", "git[_-]?committer[_-]?email",
|
||||
"git[_-]?author[_-]?name", "git[_-]?author[_-]?email",
|
||||
"ghost[_-]?api[_-]?key", "ghb[_-]?token",
|
||||
"gh[_-]?unstable[_-]?oauth[_-]?client[_-]?secret",
|
||||
"gh[_-]?token", "gh[_-]?repo[_-]?token", "gh[_-]?oauth[_-]?token",
|
||||
"gh[_-]?oauth[_-]?client[_-]?secret",
|
||||
"gh[_-]?next[_-]?unstable[_-]?oauth[_-]?client[_-]?secret",
|
||||
"gh[_-]?next[_-]?unstable[_-]?oauth[_-]?client[_-]?id",
|
||||
"gh[_-]?next[_-]?oauth[_-]?client[_-]?secret",
|
||||
"gh[_-]?email", "gh[_-]?api[_-]?key",
|
||||
"gcs[_-]?bucket", "gcr[_-]?password",
|
||||
"gcloud[_-]?service[_-]?key", "gcloud[_-]?project", "gcloud[_-]?bucket",
|
||||
"ftp[_-]?username", "ftp[_-]?user", "ftp[_-]?pw", "ftp[_-]?password",
|
||||
"ftp[_-]?login", "ftp[_-]?host", "fossa[_-]?api[_-]?key",
|
||||
"flickr[_-]?api[_-]?secret", "flickr[_-]?api[_-]?key",
|
||||
"flask[_-]?secret[_-]?key", "firefox[_-]?secret",
|
||||
"firebase[_-]?token", "firebase[_-]?project[_-]?develop",
|
||||
"firebase[_-]?key", "firebase[_-]?api[_-]?token", "firebase[_-]?api[_-]?json",
|
||||
"file[_-]?password", "exp[_-]?password", "eureka[_-]?awssecretkey",
|
||||
"env[_-]?sonatype[_-]?password", "env[_-]?secret[_-]?access[_-]?key",
|
||||
"env[_-]?secret", "env[_-]?key",
|
||||
"env[_-]?heroku[_-]?api[_-]?key", "env[_-]?github[_-]?oauth[_-]?token",
|
||||
"end[_-]?user[_-]?password", "encryption[_-]?password",
|
||||
"elasticsearch[_-]?password", "elastic[_-]?cloud[_-]?auth",
|
||||
"dsonar[_-]?projectkey", "dsonar[_-]?login",
|
||||
"droplet[_-]?travis[_-]?password", "dropbox[_-]?oauth[_-]?bearer",
|
||||
"doordash[_-]?auth[_-]?token",
|
||||
"dockerhubpassword", "dockerhub[_-]?password",
|
||||
"docker[_-]?token", "docker[_-]?postgres[_-]?url",
|
||||
"docker[_-]?password", "docker[_-]?passwd", "docker[_-]?pass",
|
||||
"docker[_-]?key", "docker[_-]?hub[_-]?password",
|
||||
"digitalocean[_-]?ssh[_-]?key[_-]?ids",
|
||||
"digitalocean[_-]?ssh[_-]?key[_-]?body",
|
||||
"digitalocean[_-]?access[_-]?token", "dgpg[_-]?passphrase",
|
||||
"deploy[_-]?user", "deploy[_-]?token", "deploy[_-]?secure",
|
||||
"deploy[_-]?password",
|
||||
"ddgc[_-]?github[_-]?token", "ddg[_-]?test[_-]?email[_-]?pw",
|
||||
"ddg[_-]?test[_-]?email",
|
||||
"db[_-]?username", "db[_-]?user", "db[_-]?pw", "db[_-]?password",
|
||||
"db[_-]?host", "db[_-]?database", "db[_-]?connection",
|
||||
"datadog[_-]?app[_-]?key", "datadog[_-]?api[_-]?key",
|
||||
"database[_-]?username", "database[_-]?user", "database[_-]?port",
|
||||
"database[_-]?password", "database[_-]?name", "database[_-]?host",
|
||||
"danger[_-]?github[_-]?api[_-]?token", "cypress[_-]?record[_-]?key",
|
||||
"coverity[_-]?scan[_-]?token", "coveralls[_-]?token",
|
||||
"coveralls[_-]?repo[_-]?token", "coveralls[_-]?api[_-]?token",
|
||||
"cos[_-]?secrets",
|
||||
"conversation[_-]?username", "conversation[_-]?password",
|
||||
"contentful[_-]?v2[_-]?access[_-]?token",
|
||||
"contentful[_-]?test[_-]?org[_-]?cma[_-]?token",
|
||||
"contentful[_-]?php[_-]?management[_-]?test[_-]?token",
|
||||
"contentful[_-]?management[_-]?api[_-]?access[_-]?token[_-]?new",
|
||||
"contentful[_-]?management[_-]?api[_-]?access[_-]?token",
|
||||
"contentful[_-]?integration[_-]?management[_-]?token",
|
||||
"contentful[_-]?cma[_-]?test[_-]?token",
|
||||
"contentful[_-]?access[_-]?token",
|
||||
"consumerkey", "consumer[_-]?key", "conekta[_-]?apikey",
|
||||
"coding[_-]?token", "codecov[_-]?token", "codeclimate[_-]?repo[_-]?token",
|
||||
"codacy[_-]?project[_-]?token",
|
||||
"cocoapods[_-]?trunk[_-]?token", "cocoapods[_-]?trunk[_-]?email",
|
||||
"cn[_-]?secret[_-]?access[_-]?key", "cn[_-]?access[_-]?key[_-]?id",
|
||||
"clu[_-]?ssh[_-]?private[_-]?key[_-]?base64", "clu[_-]?repo[_-]?url",
|
||||
"cloudinary[_-]?url[_-]?staging", "cloudinary[_-]?url",
|
||||
"cloudflare[_-]?email", "cloudflare[_-]?auth[_-]?key",
|
||||
"cloudflare[_-]?auth[_-]?email", "cloudflare[_-]?api[_-]?key",
|
||||
"cloudant[_-]?service[_-]?database", "cloudant[_-]?processed[_-]?database",
|
||||
"cloudant[_-]?password", "cloudant[_-]?parsed[_-]?database",
|
||||
"cloudant[_-]?order[_-]?database", "cloudant[_-]?instance",
|
||||
"cloudant[_-]?database", "cloudant[_-]?audited[_-]?database",
|
||||
"cloudant[_-]?archived[_-]?database",
|
||||
"cloud[_-]?api[_-]?key", "clojars[_-]?password", "client[_-]?secret",
|
||||
"cli[_-]?e2e[_-]?cma[_-]?token",
|
||||
"claimr[_-]?token", "claimr[_-]?superuser", "claimr[_-]?db",
|
||||
"claimr[_-]?database",
|
||||
"ci[_-]?user[_-]?token", "ci[_-]?server[_-]?name",
|
||||
"ci[_-]?registry[_-]?user", "ci[_-]?project[_-]?url",
|
||||
"ci[_-]?deploy[_-]?password",
|
||||
"chrome[_-]?refresh[_-]?token", "chrome[_-]?client[_-]?secret",
|
||||
"cheverny[_-]?token", "cf[_-]?password", "certificate[_-]?password",
|
||||
"censys[_-]?secret",
|
||||
"cattle[_-]?secret[_-]?key", "cattle[_-]?agent[_-]?instance[_-]?auth",
|
||||
"cattle[_-]?access[_-]?key", "cargo[_-]?token",
|
||||
"cache[_-]?s3[_-]?secret[_-]?key",
|
||||
"bx[_-]?username", "bx[_-]?password",
|
||||
"bundlesize[_-]?github[_-]?token", "built[_-]?branch[_-]?deploy[_-]?key",
|
||||
"bucketeer[_-]?aws[_-]?secret[_-]?access[_-]?key",
|
||||
"bucketeer[_-]?aws[_-]?access[_-]?key[_-]?id",
|
||||
"browserstack[_-]?access[_-]?key", "browser[_-]?stack[_-]?access[_-]?key",
|
||||
"brackets[_-]?repo[_-]?oauth[_-]?token",
|
||||
"bluemix[_-]?username", "bluemix[_-]?pwd", "bluemix[_-]?password",
|
||||
"bluemix[_-]?pass[_-]?prod", "bluemix[_-]?pass",
|
||||
"bluemix[_-]?auth", "bluemix[_-]?api[_-]?key",
|
||||
"bintraykey", "bintray[_-]?token", "bintray[_-]?key",
|
||||
"bintray[_-]?gpg[_-]?password", "bintray[_-]?apikey", "bintray[_-]?api[_-]?key",
|
||||
"b2[_-]?bucket", "b2[_-]?app[_-]?key",
|
||||
"awssecretkey", "awscn[_-]?secret[_-]?access[_-]?key",
|
||||
"awscn[_-]?access[_-]?key[_-]?id", "awsaccesskeyid",
|
||||
"aws[_-]?ses[_-]?secret[_-]?access[_-]?key",
|
||||
"aws[_-]?ses[_-]?access[_-]?key[_-]?id", "aws[_-]?secrets",
|
||||
"aws[_-]?secret[_-]?key", "aws[_-]?secret[_-]?access[_-]?key",
|
||||
"aws[_-]?secret", "aws[_-]?key",
|
||||
"aws[_-]?config[_-]?secretaccesskey", "aws[_-]?config[_-]?accesskeyid",
|
||||
"aws[_-]?access[_-]?key[_-]?id", "aws[_-]?access[_-]?key", "aws[_-]?access",
|
||||
"author[_-]?npm[_-]?api[_-]?key", "author[_-]?email[_-]?addr",
|
||||
"auth0[_-]?client[_-]?secret", "auth0[_-]?api[_-]?clientsecret",
|
||||
"auth[_-]?token",
|
||||
"assistant[_-]?iam[_-]?apikey",
|
||||
"artifacts[_-]?secret", "artifacts[_-]?key", "artifacts[_-]?bucket",
|
||||
"artifacts[_-]?aws[_-]?secret[_-]?access[_-]?key",
|
||||
"artifacts[_-]?aws[_-]?access[_-]?key[_-]?id", "artifactory[_-]?key",
|
||||
"argos[_-]?token", "apple[_-]?id[_-]?password", "appclientsecret",
|
||||
"app[_-]?token", "app[_-]?secrete", "app[_-]?report[_-]?token[_-]?key",
|
||||
"app[_-]?bucket[_-]?perm",
|
||||
"apigw[_-]?access[_-]?token", "apiary[_-]?api[_-]?key",
|
||||
"api[_-]?secret", "api[_-]?key[_-]?sid", "api[_-]?key[_-]?secret",
|
||||
"api[_-]?key", "aos[_-]?sec", "aos[_-]?key",
|
||||
"ansible[_-]?vault[_-]?password", "android[_-]?docs[_-]?deploy[_-]?token",
|
||||
"anaconda[_-]?token",
|
||||
"amazon[_-]?secret[_-]?access[_-]?key", "amazon[_-]?bucket[_-]?name",
|
||||
"alicloud[_-]?secret[_-]?key", "alicloud[_-]?access[_-]?key",
|
||||
"alias[_-]?pass",
|
||||
"algolia[_-]?search[_-]?key[_-]?1", "algolia[_-]?search[_-]?key",
|
||||
"algolia[_-]?search[_-]?api[_-]?key",
|
||||
"algolia[_-]?api[_-]?key[_-]?search", "algolia[_-]?api[_-]?key[_-]?mcm",
|
||||
"algolia[_-]?api[_-]?key", "algolia[_-]?admin[_-]?key[_-]?mcm",
|
||||
"algolia[_-]?admin[_-]?key[_-]?2", "algolia[_-]?admin[_-]?key[_-]?1",
|
||||
"adzerk[_-]?api[_-]?key", "admin[_-]?email",
|
||||
"account[_-]?sid", "access[_-]?token", "access[_-]?secret",
|
||||
"access[_-]?key[_-]?secret",
|
||||
"account", "password", "username",
|
||||
]
|
||||
|
||||
# 通配符型规则
|
||||
_NUCLEI_WILDCARD_SOURCES = [
|
||||
r"""[\w_-]*?password[\w_-]*?""",
|
||||
r"""[\w_-]*?username[\w_-]*?""",
|
||||
r"""[\w_-]*?accesskey[\w_-]*?""",
|
||||
r"""[\w_-]*?secret[\w_-]*?""",
|
||||
r"""[\w_-]*?bucket[\w_-]*?""",
|
||||
r"""[\w_-]*?token[\w_-]*?""",
|
||||
]
|
||||
|
||||
# 特殊格式的规则 (非 key=value 型)
|
||||
_NUCLEI_SPECIAL_PATTERNS = [
|
||||
r"""[\"']?[-]+BEGIN \w+ PRIVATE KEY[-]+""",
|
||||
r"""[\"']?huawei\.oss\.(ak|sk|bucket\.name|endpoint|local\.path)[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
|
||||
r"""[\"']?private[_-]?key(?:[_-]?id)?[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
|
||||
r"""[\"']?account[_-]?(?:name|key)?[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
|
||||
r"""LTAI[A-Za-z\d]{12,30}""",
|
||||
r"""AKID[A-Za-z\d]{13,40}""",
|
||||
r"""JDC_[0-9A-Z]{25,40}""",
|
||||
r"""[\"']?(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}[\"']?""",
|
||||
r"""(?:AKLT|AKTP)[a-zA-Z0-9]{35,50}""",
|
||||
r"""AKLT[a-zA-Z0-9\-_]{16,28}""",
|
||||
r"""AIza[0-9A-Za-z_\-]{35}""",
|
||||
r"""[Bb]earer\s+[a-zA-Z0-9\-=._+/\\]{20,500}""",
|
||||
r"""[Bb]asic\s+[A-Za-z0-9+/]{18,}={0,2}""",
|
||||
r"""[\"'\[]*[Aa]uthorization[\"'\]]*\s*[:=]\s*['"]?\b(?:[Tt]oken\s+)?[a-zA-Z0-9\-_+/]{20,500}['"]?""",
|
||||
r"""(glpat-[a-zA-Z0-9\-=_]{20,22})""",
|
||||
r"""((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})""",
|
||||
r"""APID[a-zA-Z0-9]{32,42}""",
|
||||
r"""[\"'](wx[a-z0-9]{15,18})[\"']""",
|
||||
r"""[\"'](ww[a-z0-9]{15,18})[\"']""",
|
||||
r"""[\"'](gh_[a-z0-9]{11,13})[\"']""",
|
||||
r"""(?:admin_?pass|password|[a-z]{3,15}_?password|user_?pass|user_?pwd|admin_?pwd)\\?['\"]*\s*[:=]\s*\\?['"][a-z0-9!@#$%&*]{5,20}\\?['"]""",
|
||||
r"""https:\/\/qyapi\.weixin\.qq\.com\/cgi\-bin\/webhook\/send\?key=[a-zA-Z0-9\-]{25,50}""",
|
||||
r"""https:\/\/oapi\.dingtalk\.com\/robot\/send\?access_token=[a-z0-9]{50,80}""",
|
||||
r"""https:\/\/open\.feishu\.cn\/open\-apis\/bot\/v2\/hook\/[a-z0-9\-]{25,50}""",
|
||||
r"""https:\/\/hooks\.slack\.com\/services\/[a-zA-Z0-9\-_]{6,12}\/[a-zA-Z0-9\-_]{6,12}\/[a-zA-Z0-9\-_]{15,24}""",
|
||||
r"""eyJrIjoi[a-zA-Z0-9\-_+/]{50,100}={0,2}""",
|
||||
r"""glc_[A-Za-z0-9\-_+/]{32,200}={0,2}""",
|
||||
r"""glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}""",
|
||||
r"""[\"']?air[-_]?table[-_]?api[-_]?key[\"']?[=:][\"']?.+[\"']""",
|
||||
r"""[\"']?jdbc:mysql[\"']?\s*[=:]\s*[\"']?[\w-]+[\"']?""",
|
||||
]
|
||||
|
||||
|
||||
def _build_nuclei_patterns() -> List[re.Pattern]:
|
||||
"""编译所有nuclei正则为Python Pattern对象 (与heartk_fast.py完全一致)"""
|
||||
compiled = []
|
||||
all_kv_keys = list(_NUCLEI_REGEX_SOURCES) + list(_NUCLEI_WILDCARD_SOURCES)
|
||||
batch_size = 120
|
||||
for i in range(0, len(all_kv_keys), batch_size):
|
||||
batch = all_kv_keys[i:i + batch_size]
|
||||
alternation = '|'.join(batch)
|
||||
pat = r"""[\"']?(?:""" + alternation + r""")[\"']?[^\S\r\n]*[=:][^\S\r\n]*[\"']?[\w-]+[\"']?"""
|
||||
compiled.append(re.compile(pat, re.IGNORECASE))
|
||||
|
||||
for pat_str in _NUCLEI_SPECIAL_PATTERNS:
|
||||
try:
|
||||
compiled.append(re.compile(pat_str, re.IGNORECASE))
|
||||
except re.error:
|
||||
compiled.append(re.compile(pat_str))
|
||||
|
||||
return compiled
|
||||
|
||||
|
||||
NUCLEI_PATTERNS = _build_nuclei_patterns()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 2. 核心分析函数 (与 heartk_fast.py 完全一致)
|
||||
# ============================================================
|
||||
|
||||
def strip_quotes(items: List[str]) -> List[str]:
|
||||
"""移除字符串两端的引号"""
|
||||
result = []
|
||||
for item in items:
|
||||
s = 0
|
||||
e = len(item)
|
||||
if item and item[0] in ("'", '"'):
|
||||
s = 1
|
||||
if item and item[-1] in ("'", '"'):
|
||||
e -= 1
|
||||
result.append(item[s:e])
|
||||
return result
|
||||
|
||||
|
||||
def extract_info(data: str) -> Dict[str, Optional[List[str]]]:
|
||||
"""从文本内容中提取各类敏感信息 (对应JS中的 extract_info + get_secret)"""
|
||||
result: Dict[str, Optional[List[str]]] = {}
|
||||
|
||||
# 运行各分类正则
|
||||
for key, pattern in EXTRACT_PATTERNS.items():
|
||||
matches = pattern.findall(data)
|
||||
if matches:
|
||||
# findall 对于有组的正则返回元组,取第一个完整匹配或全匹配
|
||||
if isinstance(matches[0], tuple):
|
||||
# 需要完整匹配,用 finditer
|
||||
matches = [m.group(0) for m in pattern.finditer(data)]
|
||||
result[key] = matches if matches else None
|
||||
else:
|
||||
result[key] = None
|
||||
|
||||
# 从URL中额外提取IP/域名 (对应JS中 extract_info 末尾逻辑)
|
||||
if result.get('url'):
|
||||
ip_pat = re.compile(r"""['\"](([a-zA-Z0-9]+:)?\/\/)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}""")
|
||||
for url in result['url']:
|
||||
ip_matches = ip_pat.findall(url)
|
||||
if ip_matches:
|
||||
full_matches = [m.group(0) for m in ip_pat.finditer(url)]
|
||||
if result['ip'] is None:
|
||||
result['ip'] = full_matches
|
||||
else:
|
||||
result['ip'].extend(full_matches)
|
||||
|
||||
# 运行nuclei秘密检测
|
||||
secrets = []
|
||||
for pat in NUCLEI_PATTERNS:
|
||||
try:
|
||||
matches = pat.findall(data)
|
||||
if matches:
|
||||
if isinstance(matches[0], tuple):
|
||||
for m in pat.finditer(data):
|
||||
secrets.append(m.group(0))
|
||||
else:
|
||||
secrets.extend(matches)
|
||||
except Exception:
|
||||
pass
|
||||
result['secret'] = secrets if secrets else None
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def init_source(source: str) -> List[str]:
|
||||
"""从HTML中提取href和src属性值 (对应JS中的 init_source)"""
|
||||
target_list = []
|
||||
|
||||
# href="..."
|
||||
for m in re.finditer(r"""href=['\"](.+?)['\"]""", source):
|
||||
target_list.append(m.group(1))
|
||||
|
||||
# src="..."
|
||||
for m in re.finditer(r"""src=['\"](.+?)['\"]""", source):
|
||||
target_list.append(m.group(1))
|
||||
|
||||
return list(set(target_list))
|
||||
|
||||
|
||||
def collect_static(items: List[str], static_list: List[str]) -> tuple:
|
||||
"""分离静态资源"""
|
||||
remaining = []
|
||||
for item in items:
|
||||
is_static = False
|
||||
for ext in STATIC_EXTENSIONS:
|
||||
if ext in item:
|
||||
if ext == '.js' and '.jsp' in item:
|
||||
continue
|
||||
is_static = True
|
||||
if item not in static_list:
|
||||
static_list.append(item)
|
||||
break
|
||||
if not is_static:
|
||||
remaining.append(item)
|
||||
return remaining, static_list
|
||||
|
||||
|
||||
def get_info(content: str) -> Dict[str, Optional[List[str]]]:
|
||||
"""完整分析一个文件内容"""
|
||||
tmp_data = extract_info(content)
|
||||
|
||||
source_links = init_source(content)
|
||||
static_data: List[str] = []
|
||||
|
||||
if source_links:
|
||||
_, static_data = collect_static(source_links, static_data)
|
||||
|
||||
for key in ['domain', 'path', 'url']:
|
||||
if tmp_data.get(key):
|
||||
_, static_data = collect_static(tmp_data[key], static_data)
|
||||
|
||||
for key in ['domain', 'path', 'url']:
|
||||
if tmp_data.get(key):
|
||||
tmp_data[key], _ = collect_static(tmp_data[key], list(static_data))
|
||||
|
||||
tmp_data['static'] = static_data if static_data else None
|
||||
|
||||
# 去引号 (secret 保留原始)
|
||||
not_sub_keys = {'secret'}
|
||||
for key in CATEGORIES:
|
||||
if key in tmp_data and tmp_data[key] is not None and key not in not_sub_keys:
|
||||
tmp_data[key] = strip_quotes(tmp_data[key])
|
||||
|
||||
return tmp_data
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 3. 文件扫描
|
||||
# ============================================================
|
||||
|
||||
def detect_encoding(filepath: str) -> Optional[str]:
|
||||
"""检测文件编码"""
|
||||
if chardet is None:
|
||||
return "utf-8"
|
||||
try:
|
||||
with open(filepath, 'rb') as f:
|
||||
raw = f.read(65536)
|
||||
if not raw:
|
||||
return None
|
||||
result = chardet.detect(raw)
|
||||
return result.get('encoding')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def scan_single_file(filepath: str) -> Optional[Dict[str, Optional[List[str]]]]:
|
||||
"""扫描单个文件,返回分析结果"""
|
||||
encoding = detect_encoding(filepath)
|
||||
if not encoding:
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(filepath, 'r', encoding=encoding, errors='ignore') as f:
|
||||
content = f.read()
|
||||
if not content.strip():
|
||||
return None
|
||||
return get_info(content)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def scan_single_file_with_custom(args):
|
||||
"""带自定义正则的扫描 (用于进程池)"""
|
||||
filepath, custom_patterns_raw = args
|
||||
|
||||
# 基础扫描
|
||||
result = scan_single_file(filepath)
|
||||
if result is None:
|
||||
result = {}
|
||||
|
||||
# 自定义正则扫描
|
||||
if custom_patterns_raw:
|
||||
encoding = detect_encoding(filepath)
|
||||
if not encoding:
|
||||
return filepath, result if result else None
|
||||
try:
|
||||
with open(filepath, 'r', encoding=encoding, errors='ignore') as f:
|
||||
content = f.read()
|
||||
except Exception:
|
||||
return filepath, result if result else None
|
||||
|
||||
if content.strip():
|
||||
for name, pat_str in custom_patterns_raw.items():
|
||||
try:
|
||||
pat = re.compile(pat_str, re.IGNORECASE)
|
||||
matches = [m.group(0) for m in pat.finditer(content)]
|
||||
if matches:
|
||||
result[f"custom_{name}"] = matches
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return filepath, result if result else None
|
||||
|
||||
|
||||
_SCAN_EXTS = {'.js', '.html', '.htm', '.json', '.css', '.wxml', '.wxss', '.txt', '.xml', '.svg', '.ts', '.jsx', '.tsx', '.md', '.yaml', '.yml', '.cfg', '.ini', '.conf', '.env', '.properties'}
|
||||
|
||||
|
||||
def collect_files(path: str) -> List[str]:
|
||||
"""递归收集所有可扫描的文本文件"""
|
||||
files = []
|
||||
for root, dirs, filenames in os.walk(path):
|
||||
for fn in filenames:
|
||||
ext = os.path.splitext(fn)[1].lower()
|
||||
if ext in _SCAN_EXTS:
|
||||
files.append(os.path.join(root, fn))
|
||||
return files
|
||||
|
||||
|
||||
def merge_results(all_info: Dict, file_result: Dict):
|
||||
"""合并单文件结果到总结果"""
|
||||
for key, value in file_result.items():
|
||||
if value is None:
|
||||
continue
|
||||
if all_info.get(key) is None:
|
||||
all_info[key] = list(value)
|
||||
else:
|
||||
all_info[key].extend(value)
|
||||
|
||||
|
||||
def dedup_and_sort(all_info: Dict):
|
||||
"""去重并排序"""
|
||||
for key in list(all_info.keys()):
|
||||
if all_info.get(key) and isinstance(all_info[key], list):
|
||||
all_info[key] = sorted(set(all_info[key]))
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 4. Extractor 类 (GUI 接口)
|
||||
# ============================================================
|
||||
|
||||
class Extractor:
|
||||
"""敏感信息提取引擎 — GUI 接口"""
|
||||
|
||||
def __init__(self):
|
||||
self.custom_patterns: Dict[str, str] = {} # {名称: 正则}
|
||||
|
||||
def add_custom_pattern(self, name: str, regex_str: str):
|
||||
self.custom_patterns[name] = regex_str
|
||||
|
||||
def remove_custom_pattern(self, name: str):
|
||||
self.custom_patterns.pop(name, None)
|
||||
|
||||
def scan_directory(self, path: str, num_workers: int = 0,
|
||||
on_progress: Optional[Callable] = None) -> Dict[str, Any]:
|
||||
"""扫描目录,返回合并后的结果"""
|
||||
files = collect_files(path)
|
||||
if not files:
|
||||
return {"files_scanned": 0, "elapsed": 0, "results": {k: [] for k in CATEGORIES},
|
||||
"custom_results": {}}
|
||||
|
||||
if num_workers <= 0:
|
||||
num_workers = min(os.cpu_count() or 4, len(files))
|
||||
num_workers = max(1, min(num_workers, 16))
|
||||
|
||||
all_info: Dict[str, Optional[List[str]]] = {k: None for k in CATEGORIES}
|
||||
custom_results: Dict[str, List[str]] = {} # 自定义正则结果
|
||||
done_count = 0
|
||||
total = len(files)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
custom_raw = dict(self.custom_patterns) if self.custom_patterns else {}
|
||||
|
||||
if num_workers == 1 or total <= 3:
|
||||
for fpath in files:
|
||||
_, result = scan_single_file_with_custom((fpath, custom_raw))
|
||||
done_count += 1
|
||||
if result:
|
||||
# 分离自定义结果
|
||||
for key in list(result.keys()):
|
||||
if key.startswith("custom_"):
|
||||
cat_name = key[7:] # 去掉 "custom_" 前缀
|
||||
if cat_name not in custom_results:
|
||||
custom_results[cat_name] = []
|
||||
custom_results[cat_name].extend(result.pop(key))
|
||||
merge_results(all_info, result)
|
||||
if on_progress:
|
||||
on_progress(done_count, total)
|
||||
else:
|
||||
args_list = [(f, custom_raw) for f in files]
|
||||
with ProcessPoolExecutor(max_workers=num_workers) as executor:
|
||||
future_to_file = {executor.submit(scan_single_file_with_custom, a): a[0] for a in args_list}
|
||||
for future in as_completed(future_to_file):
|
||||
done_count += 1
|
||||
try:
|
||||
fpath, result = future.result()
|
||||
if result:
|
||||
for key in list(result.keys()):
|
||||
if key.startswith("custom_"):
|
||||
cat_name = key[7:]
|
||||
if cat_name not in custom_results:
|
||||
custom_results[cat_name] = []
|
||||
custom_results[cat_name].extend(result.pop(key))
|
||||
merge_results(all_info, result)
|
||||
except Exception:
|
||||
pass
|
||||
if on_progress:
|
||||
on_progress(done_count, total)
|
||||
|
||||
# domain 合并到 url
|
||||
if all_info.get('domain'):
|
||||
if all_info.get('url'):
|
||||
all_info['url'].extend(all_info['domain'])
|
||||
else:
|
||||
all_info['url'] = list(all_info['domain'])
|
||||
|
||||
# 去重排序
|
||||
dedup_and_sort(all_info)
|
||||
for k in custom_results:
|
||||
custom_results[k] = sorted(set(custom_results[k]))
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
return {
|
||||
"files_scanned": total,
|
||||
"elapsed": round(elapsed, 2),
|
||||
"results": {k: all_info.get(k) or [] for k in CATEGORIES},
|
||||
"custom_results": custom_results,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def export_json(scan_result: Dict, output_path: str):
|
||||
"""导出 JSON (包含自定义正则结果)"""
|
||||
clean = {}
|
||||
for key in CATEGORIES:
|
||||
clean[key] = scan_result.get("results", {}).get(key, [])
|
||||
# 自定义正则结果
|
||||
for name, items in scan_result.get("custom_results", {}).items():
|
||||
clean[name] = items
|
||||
clean["_meta"] = {
|
||||
"files_scanned": scan_result.get("files_scanned", 0),
|
||||
"elapsed": scan_result.get("elapsed", 0),
|
||||
}
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(clean, f, ensure_ascii=False, indent=2)
|
||||
|
||||
@staticmethod
|
||||
def export_html(scan_result: Dict, output_path: str):
|
||||
"""生成 HTML 报告 (包含自定义正则结果)"""
|
||||
data = dict(scan_result.get("results", {}))
|
||||
custom = scan_result.get("custom_results", {})
|
||||
# 合并自定义到 data 以供 JS 使用
|
||||
for name, items in custom.items():
|
||||
data[name] = items
|
||||
|
||||
data_json = json.dumps(data, ensure_ascii=False)
|
||||
|
||||
# 所有类别 key 列表 (用于 JS)
|
||||
all_keys = list(CATEGORIES) + list(custom.keys())
|
||||
keys_js = json.dumps(all_keys, ensure_ascii=False)
|
||||
|
||||
popup_js = r"""
|
||||
var key = """ + keys_js + r""";
|
||||
let messages = {"popupCopy": "复制", "popupCopyurl": "复制URL"};
|
||||
function getMessage(key) { return messages[key] || ""; }
|
||||
function init_copy() {
|
||||
var elements = document.getElementsByClassName("copy");
|
||||
if(elements){
|
||||
for (var i=0, len=elements.length|0; i<len; i=i+1|0) {
|
||||
elements[i].textContent = getMessage("popupCopy");
|
||||
let ele_name = elements[i].name;
|
||||
let ele_id = elements[i].id;
|
||||
if (ele_id == "popupCopyurl") elements[i].textContent = getMessage("popupCopyurl");
|
||||
elements[i].onclick=function () {
|
||||
var inp = document.createElement('textarea');
|
||||
document.body.appendChild(inp);
|
||||
var copytext = document.getElementById(ele_name).textContent;
|
||||
inp.value = copytext; inp.select();
|
||||
document.execCommand('copy',false); inp.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function show_info(result_data) {
|
||||
for (var k in key){
|
||||
if (result_data[key[k]]){
|
||||
let container = document.getElementById(key[k]);
|
||||
if (!container) continue;
|
||||
while((ele = container.firstChild)) ele.remove();
|
||||
container.style.whiteSpace = "pre";
|
||||
for (var i in result_data[key[k]]){
|
||||
let tips = document.createElement("div");
|
||||
tips.setAttribute("class", "tips");
|
||||
let link = document.createElement("a");
|
||||
link.appendChild(tips);
|
||||
let span = document.createElement("span");
|
||||
span.textContent = result_data[key[k]][i]+'\n';
|
||||
container.appendChild(link);
|
||||
container.appendChild(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
init_copy();
|
||||
show_info(""" + data_json + ')'
|
||||
|
||||
cat_labels = {
|
||||
'ip': 'IP', 'ip_port': 'IP:PORT', 'domain': '域名',
|
||||
'sfz': '身份证', 'mobile': '手机号', 'mail': '邮箱',
|
||||
'jwt': 'JWT', 'algorithm': '加密算法', 'secret': 'Secret/密钥',
|
||||
'path': 'Path', 'incomplete_path': 'IncompletePath',
|
||||
'url': 'URL', 'static': 'StaticUrl'
|
||||
}
|
||||
left_cats = ['ip', 'ip_port', 'domain', 'sfz', 'mobile', 'mail', 'jwt', 'algorithm', 'secret']
|
||||
right_cats = ['path', 'incomplete_path', 'url', 'static']
|
||||
|
||||
left_html = ""
|
||||
for cat in left_cats:
|
||||
label = cat_labels.get(cat, cat)
|
||||
count = len(data.get(cat, []))
|
||||
left_html += f'<div class="cat_title">{label} ({count})</div><button class="copy" name="{cat}">复制</button>\n<p id="{cat}" style="word-break:break-word;">🈚️</p>\n'
|
||||
|
||||
right_html = ""
|
||||
for cat in right_cats:
|
||||
label = cat_labels.get(cat, cat)
|
||||
count = len(data.get(cat, []))
|
||||
right_html += f'<div class="cat_title">{label} ({count})</div><button class="copy" name="{cat}">复制</button>\n<p id="{cat}">🈚️</p>\n'
|
||||
|
||||
# 自定义正则类别
|
||||
custom_html = ""
|
||||
for name, items in custom.items():
|
||||
count = len(items)
|
||||
custom_html += f'<div class="cat_title">{name} ({count})</div><button class="copy" name="{name}">复制</button>\n<p id="{name}" style="word-break:break-word;">🈚️</p>\n'
|
||||
|
||||
meta = scan_result.get("files_scanned", 0)
|
||||
elapsed = scan_result.get("elapsed", 0)
|
||||
total_findings = sum(len(data.get(k, [])) for k in all_keys)
|
||||
|
||||
report_html = f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head><meta charset="UTF-8"><title>敏感信息提取报告</title></head>
|
||||
<body style="width:860px; font-size: 14px; font-family: 'Microsoft YaHei', sans-serif; margin: 20px auto;">
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div style="display:inline-block; padding:6px 16px; background:#000; color:#fff; border-radius:4px; font-weight:bold;">
|
||||
First 敏感信息提取报告
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:16px;">
|
||||
<div style="flex:1; border-right:1px solid #e8e8e8; padding-right:16px;">
|
||||
{left_html}
|
||||
</div>
|
||||
<div style="flex:1.5;">
|
||||
{right_html}
|
||||
</div>
|
||||
</div>
|
||||
{"<div style='margin-top:16px; border-top:1px solid #e8e8e8; padding-top:12px;'><div class='cat_title' style='margin-bottom:8px;'>自定义规则结果</div>" + custom_html + "</div>" if custom_html else ""}
|
||||
<script>
|
||||
{popup_js}
|
||||
</script>
|
||||
<style>
|
||||
.copy {{ border:none; background:#fff; float:right; cursor:pointer; color:#16a34a; }}
|
||||
.cat_title {{ font-size:15px; font-weight:bold; border-left:4px solid #16a34a; padding-left:6px; margin-top:12px; }}
|
||||
.tips {{ display:inline-block; border-top:0.2px solid; border-right:0.2px solid; width:8px; height:8px; border-color:#EA6000; transform:rotate(-135deg); }}
|
||||
a {{ text-decoration:none; color:#333; }}
|
||||
p {{ word-break:break-all; white-space:pre-wrap; font-size:13px; }}
|
||||
</style>
|
||||
</html>"""
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(report_html)
|
||||
|
||||
@staticmethod
|
||||
def get_summary(scan_result: Dict) -> Dict[str, int]:
|
||||
"""返回各分类的计数 (含自定义)"""
|
||||
summary = {}
|
||||
results = scan_result.get("results", {})
|
||||
for key in CATEGORIES:
|
||||
summary[key] = len(results.get(key, []))
|
||||
for name, items in scan_result.get("custom_results", {}).items():
|
||||
summary[name] = len(items)
|
||||
return summary
|
||||
|
||||
@staticmethod
|
||||
def get_category_label(key: str) -> str:
|
||||
labels = {
|
||||
'sfz': '身份证', 'mobile': '手机号', 'mail': '邮箱',
|
||||
'ip': 'IP地址', 'ip_port': 'IP端口', 'domain': '域名',
|
||||
'path': '路径', 'incomplete_path': '不完整路径',
|
||||
'url': 'URL', 'jwt': 'JWT', 'algorithm': '加密算法',
|
||||
'secret': '密钥/凭证', 'static': '静态资源'
|
||||
}
|
||||
return labels.get(key, key)
|
||||
|
||||
@staticmethod
|
||||
def get_all_builtin_patterns() -> Dict[str, str]:
|
||||
"""获取所有内置正则的字符串表示 (供 UI 显示)"""
|
||||
result = {}
|
||||
# extract patterns
|
||||
for key, pat in EXTRACT_PATTERNS.items():
|
||||
result[f"[提取] {key}"] = pat.pattern
|
||||
# nuclei kv keys count
|
||||
result["[密钥] key=value 关键字"] = f"({len(_NUCLEI_REGEX_SOURCES)} 个关键字匹配)"
|
||||
# nuclei wildcards
|
||||
for i, pat in enumerate(_NUCLEI_WILDCARD_SOURCES):
|
||||
result[f"[密钥] 通配符 #{i+1}"] = pat
|
||||
# nuclei specials
|
||||
for i, pat in enumerate(_NUCLEI_SPECIAL_PATTERNS):
|
||||
result[f"[密钥] 特殊规则 #{i+1}"] = pat
|
||||
return result
|
||||
@@ -83,6 +83,34 @@ class MiniProgramNavigator:
|
||||
await self._ensure()
|
||||
await self.engine.evaluate_js(f"window.nav.back({delta})", timeout=5.0)
|
||||
|
||||
async def refresh_page(self):
|
||||
"""刷新当前小程序页面:获取当前路由后 reLaunch 到同一页面。"""
|
||||
await self._ensure()
|
||||
result = await self.engine.evaluate_js(
|
||||
"(function(){"
|
||||
"try{"
|
||||
"var nav=window.nav;"
|
||||
"if(!nav||!nav.wxFrame)return JSON.stringify({err:'no nav'});"
|
||||
"var p=nav.wxFrame.getCurrentPages();"
|
||||
"if(!p||!p.length)return JSON.stringify({err:'no page'});"
|
||||
"var cur=p[p.length-1];"
|
||||
"var route=cur.route||cur.__route__||'';"
|
||||
"if(!route)return JSON.stringify({err:'no route'});"
|
||||
"var url='/'+route;"
|
||||
"var opts=cur.options||{};"
|
||||
"var qs=Object.keys(opts).map(function(k){return k+'='+opts[k]}).join('&');"
|
||||
"if(qs)url+='?'+qs;"
|
||||
"nav.wxFrame.wx.reLaunch({url:url,"
|
||||
"success:function(){},fail:function(e){"
|
||||
"nav.wxFrame.wx.redirectTo({url:url,success:function(){},fail:function(){}})"
|
||||
"}});"
|
||||
"return JSON.stringify({ok:true,route:route})"
|
||||
"}catch(e){return JSON.stringify({err:e.message})}"
|
||||
"})()",
|
||||
timeout=5.0,
|
||||
)
|
||||
return self._extract_value(result) or ""
|
||||
|
||||
async def get_current_route(self):
|
||||
"""Get current page route via the detected wxFrame."""
|
||||
await self._ensure()
|
||||
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
wxapkg.py — 微信小程序 wxapkg 解密 + 解包 (WeChat 4.0+)
|
||||
纯 Python 实现,基于 KillWxapkg 算法
|
||||
|
||||
加密格式:
|
||||
- Magic: "V1MMWX" (6 bytes)
|
||||
- AES-256-CBC 加密区: file[6:1030] → 解密取前 1023 bytes
|
||||
- XOR 加密区: file[1030:] → 逐字节 XOR appID[-2]
|
||||
- Key = PBKDF2(appID, "saltiest", 1000, 32, SHA1)
|
||||
- IV = "the iv: 16 bytes"
|
||||
|
||||
解包格式:
|
||||
- 0xBE marker (1 byte)
|
||||
- info1 (4 bytes, big-endian uint32)
|
||||
- indexInfoLength (4 bytes)
|
||||
- bodyInfoLength (4 bytes)
|
||||
- 0xED marker (1 byte)
|
||||
- fileCount (4 bytes)
|
||||
- index entries: nameLen(4) + name(nameLen) + offset(4) + size(4)
|
||||
- body data
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
except ImportError:
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
AES = None
|
||||
unpad = None
|
||||
|
||||
MAGIC = b"V1MMWX"
|
||||
SALT = b"saltiest"
|
||||
IV = b"the iv: 16 bytes"
|
||||
PBKDF2_ITER = 1000
|
||||
KEY_LEN = 32
|
||||
|
||||
|
||||
def _derive_key(app_id: str) -> bytes:
|
||||
"""PBKDF2-SHA1 派生 AES-256 密钥"""
|
||||
return hashlib.pbkdf2_hmac("sha1", app_id.encode("utf-8"), SALT, PBKDF2_ITER, KEY_LEN)
|
||||
|
||||
|
||||
def decrypt_wxapkg(data: bytes, app_id: str) -> bytes:
|
||||
"""解密单个 wxapkg 文件内容,返回解密后的 bytes"""
|
||||
if AES is None:
|
||||
raise ImportError("需要安装 pycryptodome: pip install pycryptodome")
|
||||
|
||||
if len(data) < 1030:
|
||||
raise ValueError("文件太小,不是有效的加密 wxapkg")
|
||||
|
||||
# 检查 magic header
|
||||
if data[:6] != MAGIC:
|
||||
# 可能是未加密的 wxapkg,直接返回
|
||||
if data[0:1] == b'\xbe':
|
||||
return data
|
||||
raise ValueError(f"未知文件格式 (magic: {data[:6].hex()})")
|
||||
|
||||
key = _derive_key(app_id)
|
||||
|
||||
# AES-256-CBC 解密前 1024 bytes (file[6:1030])
|
||||
cipher = AES.new(key, AES.MODE_CBC, IV)
|
||||
decrypted_header = cipher.decrypt(data[6:1030])
|
||||
# 取前 1023 bytes(去掉 PKCS7 padding 的最后一个字节)
|
||||
decrypted_header = decrypted_header[:1023]
|
||||
|
||||
# XOR 解密剩余部分
|
||||
xor_key = ord(app_id[-2]) if len(app_id) >= 2 else 0
|
||||
tail = data[1030:]
|
||||
xor_decrypted = bytes(b ^ xor_key for b in tail)
|
||||
|
||||
return decrypted_header + xor_decrypted
|
||||
|
||||
|
||||
def unpack_wxapkg(data: bytes) -> List[Tuple[str, bytes]]:
|
||||
"""解包 wxapkg,返回 [(文件路径, 文件内容), ...]"""
|
||||
if len(data) < 14:
|
||||
raise ValueError("数据太短,不是有效的 wxapkg")
|
||||
|
||||
pos = 0
|
||||
|
||||
# 0xBE marker
|
||||
marker1 = data[pos]
|
||||
if marker1 != 0xBE:
|
||||
raise ValueError(f"无效的 wxapkg 格式 (marker1: 0x{marker1:02x}, 期望 0xBE)")
|
||||
pos += 1
|
||||
|
||||
# info1 (4 bytes)
|
||||
_info1 = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
|
||||
# indexInfoLength (4 bytes)
|
||||
index_info_length = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
|
||||
# bodyInfoLength (4 bytes)
|
||||
_body_info_length = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
|
||||
# 0xED marker
|
||||
marker2 = data[pos]
|
||||
if marker2 != 0xED:
|
||||
raise ValueError(f"无效的 wxapkg 格式 (marker2: 0x{marker2:02x}, 期望 0xED)")
|
||||
pos += 1
|
||||
|
||||
# fileCount (4 bytes)
|
||||
file_count = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
|
||||
files = []
|
||||
for _ in range(file_count):
|
||||
if pos + 4 > len(data):
|
||||
break
|
||||
|
||||
# nameLen (4 bytes)
|
||||
name_len = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
|
||||
if pos + name_len > len(data):
|
||||
break
|
||||
|
||||
# name (nameLen bytes)
|
||||
name = data[pos:pos + name_len].decode("utf-8", errors="replace")
|
||||
pos += name_len
|
||||
|
||||
if pos + 8 > len(data):
|
||||
break
|
||||
|
||||
# offset (4 bytes)
|
||||
offset = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
|
||||
# size (4 bytes)
|
||||
size = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
|
||||
files.append((name, offset, size))
|
||||
|
||||
# 提取文件内容
|
||||
result = []
|
||||
for name, offset, size in files:
|
||||
if offset + size <= len(data):
|
||||
content = data[offset:offset + size]
|
||||
result.append((name, content))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def extract_wxapkg(wxapkg_path: str, output_dir: str, app_id: str) -> List[str]:
|
||||
"""解密并解包 wxapkg 到指定目录,返回提取的文件路径列表"""
|
||||
with open(wxapkg_path, "rb") as f:
|
||||
raw = f.read()
|
||||
|
||||
decrypted = decrypt_wxapkg(raw, app_id)
|
||||
files = unpack_wxapkg(decrypted)
|
||||
|
||||
extracted = []
|
||||
for name, content in files:
|
||||
# 规范化路径,防止路径穿越
|
||||
name = name.lstrip("/").replace("\\", "/")
|
||||
if ".." in name:
|
||||
continue
|
||||
|
||||
out_path = os.path.join(output_dir, name)
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(content)
|
||||
extracted.append(out_path)
|
||||
|
||||
return extracted
|
||||
|
||||
|
||||
def find_wxapkg_files(packages_dir: str) -> List[dict]:
|
||||
"""扫描 packages 目录,返回所有 wxapkg 文件信息
|
||||
|
||||
目录结构: packages/{appid}/ 下有 .wxapkg 文件
|
||||
返回: [{"appid": str, "path": str, "name": str, "size": int}, ...]
|
||||
"""
|
||||
results = []
|
||||
if not os.path.isdir(packages_dir):
|
||||
return results
|
||||
|
||||
for entry in os.listdir(packages_dir):
|
||||
app_dir = os.path.join(packages_dir, entry)
|
||||
if not os.path.isdir(app_dir):
|
||||
continue
|
||||
# 检查是否像 appid (wx开头 或 纯字母数字)
|
||||
appid = entry
|
||||
|
||||
for root, dirs, filenames in os.walk(app_dir):
|
||||
for fn in filenames:
|
||||
if fn.endswith(".wxapkg"):
|
||||
fpath = os.path.join(root, fn)
|
||||
results.append({
|
||||
"appid": appid,
|
||||
"path": fpath,
|
||||
"name": fn,
|
||||
"size": os.path.getsize(fpath),
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_default_packages_dir() -> Optional[str]:
|
||||
"""获取默认的微信小程序包路径(Windows)"""
|
||||
if os.name != "nt":
|
||||
return None
|
||||
user_home = os.path.expanduser("~")
|
||||
pkg_dir = os.path.join(
|
||||
user_home, "AppData", "Roaming", "Tencent",
|
||||
"xwechat", "radium", "Applet", "packages"
|
||||
)
|
||||
if os.path.isdir(pkg_dir):
|
||||
return pkg_dir
|
||||
return None
|
||||
Reference in New Issue
Block a user