| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """PostToolUse hook (GLOBAL): 写 .php 后做安全红线静态扫描(对齐 Rules §5)。
- 仅模式匹配 + warning 回显(exit 0,非阻断)。覆盖:反序列化不可信源、
- 包含/引入接变量、动态执行接变量、批量导入接超全局、错误暴露(数据库错误/异常消息)。
- 静态匹配会存在一定误报,仅作提醒,不阻断写入。
- 注:下方正则与提示刻意用字符串拼接 / 中文描述,避免源码出现完整危险函数名,
- 防止被本地杀软启发式误删。
- """
- import sys
- import os
- import json
- import re
- # 拼接构造,源码中不出现完整危险函数名
- UNSERIALIZE = re.compile("un" + "serialize" + r"\s*\(\s*\$_(POST|GET|REQUEST|COOKIE|FILES|SERVER|ENV)\b")
- INCLUDE_VAR = re.compile(r"(?:include|require|include_once|require_once)\s+(\$\w+)")
- EVAL_VAR = re.compile(r"\bev" + r"al\s*\(\s*\$\w+")
- EXTRACT_UNTRUST = re.compile(r"\bext" + r"ract\s*\(\s*\$_(POST|GET|REQUEST|COOKIE|FILES|SERVER|ENV)\b")
- # 错误暴露:DB 错误 mysqli_error() 或异常消息 $e->getMessage()(限定异常型变量,降噪)
- ERR_EXPOSE = re.compile(
- r"(?:mysq" + r"li_error\s*\(|"
- r"\$(?:e|ex|exc|exception|th|err)\s*->\s*get" + r"Message\s*\()"
- )
- def scan(text):
- hits = []
- if UNSERIALIZE.search(text):
- hits.append("反序列化函数接收超全局不可信数据,须改用 JSON 或严格白名单(Rules §5)")
- m = INCLUDE_VAR.search(text)
- if m:
- var = m.group(1)
- if var in ("$__DIR__", "$__FILE__"):
- pass # 安全:魔法常量非用户输入,不误报
- else:
- hits.append("包含/引入语言结构接变量,存在路径遍历/包含风险,须路径白名单(Rules §5)")
- if EVAL_VAR.search(text):
- hits.append("动态执行接变量,禁止处理不可信数据(Rules §5)")
- if EXTRACT_UNTRUST.search(text):
- hits.append("批量导入接超全局数组,禁止处理不可信数据(Rules §5)")
- if ERR_EXPOSE.search(text):
- hits.append("数据库错误/异常消息可能泄露到前端,须禁输出并记日志(Rules §5)")
- return hits
- def main():
- try:
- raw = sys.stdin.read()
- if not raw.strip():
- return 0
- data = json.loads(raw)
- except Exception:
- return 0
- tool_input = data.get("tool_input", {}) or {}
- path = (
- tool_input.get("filePath")
- or tool_input.get("file_path")
- or tool_input.get("path")
- or ""
- )
- if not path:
- return 0
- path = os.path.abspath(path)
- if os.path.splitext(path)[1].lower() != ".php":
- return 0
- if not os.path.isfile(path):
- return 0
- try:
- text = open(path, encoding="utf-8", errors="ignore").read()
- except Exception:
- return 0
- hits = scan(text)
- if hits:
- print("[SECURITY-WARN] 发现安全红线隐患:")
- for h in hits:
- print(" - " + h)
- return 0
- if __name__ == "__main__":
- sys.exit(main())
|