Skip to content

Instantly share code, notes, and snippets.

@junaire
Created March 25, 2026 10:43
Show Gist options
  • Select an option

  • Save junaire/c5f8a6c7d965fc6072c9827f6a2f1cc8 to your computer and use it in GitHub Desktop.

Select an option

Save junaire/c5f8a6c7d965fc6072c9827f6a2f1cc8 to your computer and use it in GitHub Desktop.
Apifox Self-Check for Malicious Code
#!/usr/bin/env python3
import datetime
import hashlib
import os
import struct
import sys
import zlib
from pathlib import Path
TARGET_URL = "https://cdn.apifox.com/www/assets/js/apifox-app-event-tracking.min.js"
TARGET_BYTES = TARGET_URL.encode("utf-8")
SUSPICIOUS_PATTERNS = [
"REMOTE_JS_URL",
"rsaDecrypt",
"loadAndExecute",
"scheduleNext",
"eval(",
]
def unique(items):
seen = set()
result = []
for item in items:
if not item:
continue
if item in seen:
continue
seen.add(item)
result.append(item)
return result
def unique_real_paths(items):
seen = set()
result = []
for item in items:
try:
stat = os.stat(item)
key = (stat.st_dev, stat.st_ino)
except Exception:
key = str(Path(item).resolve())
if key in seen:
continue
seen.add(key)
result.append(str(Path(item).resolve()))
return result
def candidate_roots():
home = Path.home()
if sys.platform == "darwin":
return unique(
[
str(home / "Library" / "Application Support" / "apifox" / "Cache" / "Cache_Data"),
str(home / "Library" / "Application Support" / "Apifox" / "Cache" / "Cache_Data"),
]
)
if os.name == "nt":
appdata = os.environ.get("APPDATA")
local_appdata = os.environ.get("LOCALAPPDATA")
return unique(
[
appdata and str(Path(appdata) / "apifox" / "Cache" / "Cache_Data"),
appdata and str(Path(appdata) / "Apifox" / "Cache" / "Cache_Data"),
local_appdata and str(Path(local_appdata) / "apifox" / "Cache" / "Cache_Data"),
local_appdata and str(Path(local_appdata) / "Apifox" / "Cache" / "Cache_Data"),
]
)
return unique(
[
str(home / ".config" / "apifox" / "Cache" / "Cache_Data"),
str(home / ".config" / "Apifox" / "Cache" / "Cache_Data"),
str(home / ".cache" / "apifox" / "Cache" / "Cache_Data"),
str(home / ".cache" / "Apifox" / "Cache" / "Cache_Data"),
]
)
def walk_files(root):
for base, _, files in os.walk(root):
for name in files:
yield os.path.join(base, name)
def read_null_terminated(data, start):
pos = start
size = len(data)
while pos < size and data[pos] != 0:
pos += 1
return pos + 1
def try_inflate_gzip_member(data, gzip_offset):
try:
if data[gzip_offset : gzip_offset + 3] != b"\x1f\x8b\x08":
return None
pos = gzip_offset
flg = data[pos + 3]
pos += 10
if flg & 0x04:
if pos + 2 > len(data):
return None
xlen = struct.unpack_from("<H", data, pos)[0]
pos += 2 + xlen
if flg & 0x08:
pos = read_null_terminated(data, pos)
if flg & 0x10:
pos = read_null_terminated(data, pos)
if flg & 0x02:
pos += 2
if pos >= len(data):
return None
return zlib.decompress(data[pos:], -zlib.MAX_WBITS)
except Exception:
return None
def extract_payload(data, url_offset):
gzip_offset = data.find(b"\x1f\x8b\x08", url_offset)
if gzip_offset != -1:
inflated = try_inflate_gzip_member(data, gzip_offset)
if inflated:
return "gzip", inflated
for marker in (b"/*!", b"!function", b"(function", b"(()=>"):
marker_offset = data.find(marker, url_offset)
if marker_offset != -1:
return "plain", data[marker_offset:]
return "unknown", b""
def sha256_hex(data):
return hashlib.sha256(data).hexdigest()
def main():
roots = candidate_roots()
existing_roots = unique_real_paths([root for root in roots if os.path.exists(root)])
print(f"目标 URL: {TARGET_URL}")
if existing_roots:
print(f"检查目录: {' | '.join(existing_roots)}")
else:
print("检查目录: (未找到)")
print("结论: 未找到 Apifox 缓存目录 (NO_APIFOX_CACHE_FOUND)")
print(
"说明: 这不代表机器安全,可能是没有安装 Apifox 桌面端,或者缓存已经被清理。"
)
return 1
findings = []
for root in existing_roots:
for file_path in walk_files(root):
try:
data = Path(file_path).read_bytes()
except Exception:
continue
url_offset = data.find(TARGET_BYTES)
if url_offset == -1:
continue
extracted_as, payload = extract_payload(data, url_offset)
matched = [pattern for pattern in SUSPICIOUS_PATTERNS if pattern.encode("utf-8") in payload]
try:
modified = os.stat(file_path).st_mtime
modified_iso = datetime.datetime.fromtimestamp(
modified, tz=datetime.timezone.utc
).isoformat().replace("+00:00", "Z")
except Exception:
modified_iso = "(unknown)"
findings.append(
{
"file_path": file_path,
"cache_size": len(data),
"payload_size": len(payload),
"payload_sha256": sha256_hex(payload) if payload else None,
"extracted_as": extracted_as,
"matched": matched,
"modified": modified_iso,
}
)
if not findings:
print("结论: 缓存中未发现证据 (NO_EVIDENCE_IN_CACHE)")
print("未在本机缓存中找到该可疑 Apifox 脚本 URL 的历史响应。")
print(
"说明: 这可能是假阴性,例如用户未触发相关页面、缓存路径不同,或缓存已经被清空。"
)
return 2
confirmed = [item for item in findings if len(item["matched"]) >= 2]
if confirmed:
print("结论: 很可能已中招 (LIKELY_HIT)")
print("原因: 本机缓存中的脚本包含已知恶意执行链特征。")
else:
print("结论: 命中过该 URL,但未发现已知恶意链 (URL_CACHED_BUT_MALICIOUS_CHAIN_NOT_FOUND)")
print(
"原因: 本机缓存中过该可疑 URL,但脚本中未发现目前已知的恶意模式。"
)
for item in findings:
print("---")
print(f"文件: {item['file_path']}")
print(f"修改时间: {item['modified']}")
print(f"缓存文件大小: {item['cache_size']} 字节")
print(f"解包后脚本大小: {item['payload_size']} 字节")
print(f"提取方式: {item['extracted_as']}")
if item["payload_sha256"]:
print(f"脚本 SHA-256: {item['payload_sha256']}")
if item["matched"]:
print(f"命中特征: {', '.join(item['matched'])}")
else:
print("命中特征: (无)")
if confirmed:
print("---")
print("处置建议: 按已中招机器处理,并轮换这台机器上使用过的 token、会话和密钥。")
return 10
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment