mnbplus/PyAegis

GitHub: mnbplus/PyAegis

一款基于 AST 和污点追踪的 Python SAST 工具,用于检测代码注入、命令注入、SSRF 等安全漏洞,具备低误报率和多格式报告输出能力。

Stars: 2 | Forks: 0

``` ██████╗ ██╗ ██╗ █████╗ ███████╗ ██████╗ ██╗███████╗ ██╔══██╗╚██╗ ██╔╝██╔══██╗██╔════╝██╔════╝ ██║██╔════╝ ██████╔╝ ╚████╔╝ ███████║█████╗ ██║ ███╗██║███████╗ ██╔═══╝ ╚██╔╝ ██╔══██║██╔══╝ ██║ ██║██║╚════██║ ██║ ██║ ██║ ██║███████╗╚██████╔╝██║███████║ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ``` **面向 Python 的下一代静态应用程序安全测试 (SAST) 引擎。**

PyPI Python Build Coverage License Code Style Powered by PyAegis

English · 简体中文 · 日本語

**PyAegis** 是一款 Python 优先的 SAST 工具,它超越了简单的正则匹配。它将代码解析为 AST,构建轻量级的控制流模型,并执行 **污点式源点 → 汇点分析** 以查找真实的注入路径——而不仅仅是可疑的模式。 ## 目录 - [工作原理](#how-it-works) - [检测能力](#detects) - [快速开始](#quick-start) - [实时示例](#live-example) - [误报率](#false-positive-rate) - [使用方法](#usage) - [编写自定义规则](#writing-custom-rules) - [CI/CD 集成](#cicd-integration) - [工具对比](#comparison-with-other-tools) - [路线图](#roadmap) - [贡献指南](#contributing) - [展示支持](#show-your-style) ## 工作原理 ``` .py files │ ▼ ┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ AST Parser │ ───▶ │ Taint Tracker │ ───▶ │ Reporter │ │ (parallel) │ │ source → sink │ │ text/json/sarif │ └─────────────┘ └──────────────────┘ └─────────────────┘ │ │ │ ├── propagates through assignments │ ├── follows f-strings & concatenation │ ├── tracks across local function calls │ └── stops at known sanitizers │ └── multiprocessing pool for large repos ``` 1. **收集** — 发现目标路径下的所有 `.py` 文件。 2. **解析** — 并行为每个文件构建 AST。 3. **建模** — 提取函数体、参数和调用图。 4. **污点分析** — 植入源点,在函数内部传播,检测污点何时到达汇点。 5. **报告** — 以 `text`、`json`、`csv`、`html` 或 `sarif` 格式输出结果。 ## 检测能力 PyAegis 附带一套全面的默认规则集,涵盖最关键的 Python 漏洞类型: ### 代码注入 | 汇点 | 风险 | 示例 | |------|------|---------| | `eval()` | Critical | 任意代码执行 | | `exec()` | Critical | 任意代码执行 | | `compile()` | Critical | 动态代码编译 | | `runpy.run_module()` | Critical | 动态模块执行 | | `runpy.run_path()` | Critical | 动态路径执行 | ### 操作系统命令注入 | 汇点 | 风险 | 示例 | |------|------|---------| | `os.system()` | Critical | Shell 命令注入 | | `os.popen()` | Critical | Shell 命令注入 | | `subprocess.call()` | Critical | 进程注入 | | `subprocess.run()` | Critical | 进程注入 | | `subprocess.Popen()` | Critical | 进程注入 | | `os.spawn*` | Critical | 进程生成 | ### 不安全的反序列化 | 汇点 | 风险 | 示例 | |------|------|---------| | `pickle.loads()` | Critical | 任意对象实例化 | | `pickle.load()` | Critical | 任意对象实例化 | | `dill.loads()` | Critical | 任意对象实例化 | | `marshal.loads()` | Critical | 字节码反序列化 | | `yaml.load()` | High | 任意 Python 执行 | | `yaml.unsafe_load()` | Critical | 任意 Python 执行 | | `jsonpickle.decode()` | Critical | 任意对象实例化 | ### 服务端请求伪造 (SSRF) | 汇点 | 风险 | 示例 | |------|------|---------| | `requests.get/post/request()` | High | 针对内部服务的 SSRF | | `httpx.get/post/request()` | High | 针对内部服务的 SSRF | | `urllib.request.urlopen()` | High | SSRF | | `aiohttp.ClientSession.*()` | High | 异步 SSRF | | `socket.create_connection()` | High | 原始套接字 SSRF | ### 路径遍历 / 不安全文件操作 | 汇点 | 风险 | 示例 | |------|------|---------| | `open()` | High | 读/写任意文件 | | `pathlib.Path()` | High | 路径遍历 | | `shutil.copy/move/rmtree()` | High | 任意文件操作 | | `os.remove/unlink/rmdir()` | High | 任意文件删除 | | `tempfile.NamedTemporaryFile()` | Medium | 可预测的临时路径 | ### SQL 注入 | 汇点 | 风险 | 示例 | |------|------|---------| | `sqlite3.Cursor.execute()` | Critical | SQL 注入 | | `psycopg2.cursor.execute()` | Critical | SQL 注入 | | `pymysql.connect()` | High | SQL 注入 | | `MySQLdb.connect()` | High | SQL 注入 | | `sqlalchemy.text()` | High | 原生 SQL 注入 | ### 模板注入 (SSTI) | 汇点 | 风险 | 示例 | |------|------|---------| | `jinja2.Template()` | Critical | 服务端模板注入 | | `jinja2.Environment.from_string()` | Critical | SSTI | | `mako.template.Template()` | Critical | SSTI | ### XML / XXE | 汇点 | 风险 | 示例 | |------|------|---------| | `xml.etree.ElementTree.parse()` | High | XXE 实体扩展 | | `xml.etree.ElementTree.fromstring()` | High | XXE | | `lxml.etree.parse()` | High | XXE | | `xml.dom.minidom.parse()` | High | XXE | ### ReDoS | 汇点 | 风险 | 示例 | |------|------|---------| | `re.compile()` | Medium | 正则表达式拒绝服务 | | `re.match/search()` | Medium | 正则表达式拒绝服务 | **追踪的源点:** | 类别 | 示例 | |----------|----------| | 内置函数 | `input()`, `sys.argv` | | 环境变量 | `os.getenv()`, `os.environ.get()` | | Flask/Werkzeug | `request.args`, `request.form`, `request.json`, `request.cookies`, `request.headers`, `request.files` | | Django | `request.GET`, `request.POST`, `request.COOKIES`, `request.body`, `request.META` | | FastAPI/Starlette | `request.query_params`, `request.path_params`, `request.form`, `request.body` | | 解析 | `json.loads()`, `ujson.loads()`, `xmltodict.parse()` | **已知净化器** (停止污点传播): `html.escape`, `markupsafe.escape`, `bleach.clean`, `django.utils.html.escape`, `os.path.abspath`, `os.path.normpath`, `pathlib.Path.resolve`, `urllib.parse.urlparse` ## 快速开始 **安装:** ``` pip install pyaegis ``` **扫描当前目录 (推荐):** ``` pyaegis scan . ``` **向后兼容 (仍然有效):** ``` pyaegis . ``` **仅显示高/严重级别发现:** ``` pyaegis scan . --severity HIGH,CRITICAL ``` **解释规则 / 修复指南:** ``` pyaegis explain PYA-001 ``` **列出内置规则:** ``` pyaegis list-rules ``` **创建项目配置文件:** ``` pyaegis init ``` **导出 SARIF 用于 GitHub Advanced Security:** ``` pyaegis scan . --format sarif --output results.sarif ``` **导出 JSON:** ``` pyaegis scan . --format json --output results.json ``` **导出 CSV:** ``` pyaegis scan . --format csv --output results.csv ``` **导出 HTML 报告:** ``` pyaegis scan . --format html --output report.html ``` ## 实时示例 给定此存在漏洞的 Python 文件: ``` # vuln_example.py import os import subprocess import pickle from flask import request def run_command(): cmd = request.args.get("cmd") # <-- tainted source os.system(cmd) # <-- SINK: OS command injection def deserialize_data(): raw = request.get_data() # <-- tainted source obj = pickle.loads(raw) # <-- SINK: insecure deserialization return obj def eval_expr(): expr = request.form.get("expr") # <-- tainted source result = eval(expr) # <-- SINK: code injection return result ``` 运行 PyAegis: ``` $ pyaegis vuln_example.py ``` ``` [-] Detected 3 Potential Vulnerabilities: -> [CRITICAL] Tainted data reaches sink: os.system (PYA-TAINT) File: vuln_example.py:8 | Context: run_command -> [CRITICAL] Tainted data reaches sink: pickle.loads (PYA-TAINT) File: vuln_example.py:13 | Context: deserialize_data -> [CRITICAL] Tainted data reaches sink: eval (PYA-TAINT) File: vuln_example.py:18 | Context: eval_expr ``` 使用净化器 — PyAegis 正确地停止了污点传播: ``` import html from flask import request def safe_render(): user_input = request.args.get("name") safe = html.escape(user_input) # <-- sanitizer: taint stops here return f"Hello {safe}" ``` ``` $ pyaegis safe_example.py [+] No vulnerabilities detected. Subsystems secure. ``` ## 误报率 PyAegis 旨在最大限度地减少噪音。污点引擎仅在能够追踪到函数内(或跨局部函数边界)从源点到汇点的连续数据流路径时,才会报告发现。净化器调用会打断该链条。 | 工具 | 方法 | 估计误报率¹ | 备注 | |------|----------|--------------------------|-------| | **PyAegis** | AST 污点流 (源点→汇点) | **~8–12%** | 感知净化器;通过赋值、f-strings、局部调用传播 | | Bandit | AST 模式匹配 | ~25–35% | 无论数据来源如何,都会标记危险调用 | | Semgrep (模式模式) | 语法模式匹配 | ~20–40% | 很大程度上取决于规则质量;污点模式可降低误报 | | Semgrep (污点模式) | 污点分析 | ~10–18% | 与 PyAegis 相当;多语言开销 | | 基于正则的扫描器 | 文本/正则 | ~40–60% | 高噪音,无语义理解 | **为什么 PyAegis 具有较低的误报率:** - 仅标记在函数体中 **实际可达** 的污点路径 - 通过赋值、f-strings、字符串拼接和容器字面量追踪污点 - 识别 **净化器调用** — 当数据通过 `html.escape`、`bleach.clean`、`os.path.abspath` 等传递时,污点会被清除 - 过程间:**跨局部函数边界** 追踪污点 - **不会** 仅因函数名看起来危险就标记调用 ## 使用方法 ``` pyaegis [options] ``` | 参数 | 描述 | 默认值 | |------|-------------|---------| | `target` | 要扫描的文件或目录 | — | | `--rules` | YAML 规则文件的路径 | `pyaegis/rules/default.yml` | | `--format` | 输出格式: `text`, `json`, `csv`, `html`, `sarif` | `text` | | `--output` | 输出文件 (省略则输出到 stdout) | stdout | | `--debug` | 详细日志记录 | off | **退出码:** - `0` — 无发现 - `1` — 发现检测结果或致命扫描错误 ## 编写自定义规则 规则是包含三个可选键的普通 YAML 文件: ``` # my_rules.yml inputs: - input - os.getenv - request.args sinks: - eval - exec - os.system - subprocess.* - my_custom_exec_fn sanitizers: - html.escape - my_project.utils.sanitize ``` 使用您的规则运行: ``` pyaegis ./src --rules my_rules.yml ``` 模式支持 `fnmatch` 通配符:`subprocess.*` 匹配 `subprocess.call`、`subprocess.Popen` 等。 查看完整文档:[docs/detectors.md](docs/detectors.md) ## CI/CD 集成 ### GitHub Actions ``` - name: Run PyAegis SAST run: | pip install pyaegis pyaegis . --format sarif --output pyaegis.sarif - name: Upload SARIF to GitHub Advanced Security uses: github/codeql-action/upload-sarif@v3 with: sarif_file: pyaegis.sarif ``` ### GitLab CI ``` sast: stage: test script: - pip install pyaegis - pyaegis . --format json --output gl-sast-report.json artifacts: reports: sast: gl-sast-report.json ``` 完整示例:[docs/ci-integration.md](docs/ci-integration.md) ## 与其他工具的对比 | 特性 | PyAegis | Bandit | Semgrep | |---------|---------|--------|---------| | 语言重心 | Python 优先 | Python | 多语言 | | 分析方法 | AST 污点流 | AST 模式 | 模式 + 污点 | | 源点→汇点追踪 | ✅ 是 | ⚠️ 有限 | ✅ 污点模式 | | 净化器感知 | ✅ 是 | ❌ 否 | ✅ 是 | | 过程间分析 | ✅ 局部函数 | ❌ 否 | ✅ 是 | | SARIF 输出 | ✅ 原生 | ❌ 外部转换器 | ✅ 原生 | | 自定义规则格式 | YAML | Python 插件 | YAML | | 规则生态 | 小 (增长中) | 大 | 非常大 | | 典型误报率 | ~8–12% | ~25–35% | ~10–40% | | 安装大小 | 最小 | 适中 | 大 | | CI 集成 | 简单 | 简单 | 适中 | ## 路线图 - [ ] 更多内置源点/汇点 - [ ] 更好的跨模块边界过程间追踪 - [ ] 感知框架的建模 (Flask 路由装饰器、FastAPI 依赖) - [ ] 基线/抑制支持 (忽略已知发现,专注于回归) - [ ] 针对大型单体仓库的增量扫描 - [ ] IDE 插件 - [ ] 用于发现分类的 Web UI ## 贡献指南 欢迎贡献!请阅读: - [CONTRIBUTING.md](CONTRIBUTING.md) - [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - [SECURITY.md](SECURITY.md) 文档站点:参见 `docs/` 和 `mkdocs.yml` (使用 [MkDocs Material](https://squidfunk.github.io/mkdocs-material/) 构建)。 ## 展示支持 在您的项目中使用 PyAegis?添加一个徽章: ``` [![security: powered by PyAegis](https://img.shields.io/badge/security-powered%20by%20PyAegis-blueviolet?style=flat-square&logo=shield)](https://github.com/mnbplus/PyAegis) ``` [![security: powered by PyAegis](https://img.shields.io/badge/security-powered%20by%20PyAegis-blueviolet?style=flat-square&logo=shield)](https://github.com/mnbplus/PyAegis)
为 Python 安全社区 ❤️ 构建。
标签:DevSecOps, odt, Redis利用, SAST, 上游代理, 云安全监控, 代码安全, 域环境探测, 安全扫描, 抽象语法树, 时序注入, 漏洞枚举, 盲注攻击, 自动化payload嵌入, 逆向工具, 静态分析