mnbplus/PyAegis
GitHub: mnbplus/PyAegis
一款基于 AST 和污点追踪的 Python SAST 工具,用于检测代码注入、命令注入、SSRF 等安全漏洞,具备低误报率和多格式报告输出能力。
Stars: 2 | Forks: 0
```
██████╗ ██╗ ██╗ █████╗ ███████╗ ██████╗ ██╗███████╗
██╔══██╗╚██╗ ██╔╝██╔══██╗██╔════╝██╔════╝ ██║██╔════╝
██████╔╝ ╚████╔╝ ███████║█████╗ ██║ ███╗██║███████╗
██╔═══╝ ╚██╔╝ ██╔══██║██╔══╝ ██║ ██║██║╚════██║
██║ ██║ ██║ ██║███████╗╚██████╔╝██║███████║
╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝
```
**面向 Python 的下一代静态应用程序安全测试 (SAST) 引擎。**
**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
为 Python 安全社区 ❤️ 构建。
标签:DevSecOps, odt, Redis利用, SAST, 上游代理, 云安全监控, 代码安全, 域环境探测, 安全扫描, 抽象语法树, 时序注入, 漏洞枚举, 盲注攻击, 自动化payload嵌入, 逆向工具, 静态分析