针对 PyYAML 中 CVE-2020-14343 的受控漏洞研究与复现实验环境
本项目在受控且隔离的 Docker 环境中演示 CVE-2020-14343。
该实验包含两个环境:
yaml.safe_load() 的已修补环境该实验演示了完整的漏洞生命周期:
本实验中使用的复现载荷有意设计为无害,仅打印一个测试标记。
| 属性 | 详情 |
|---|---|
| CVE | CVE-2020-14343 |
| 产品 | PyYAML |
| 受影响版本 | 5.4 之前的版本 |
| 修复版本 | 5.4 |
| 漏洞类型 | 输入验证不当 (CWE-20) |
| 严重性 | 严重 |
| 攻击面 | 使用易受攻击的加载功能处理的 YAML 输入 |
| CVSS | 9.3 (CVSS v4, GitHub Advisory) |
当使用易受攻击的 PyYAML 加载功能处理不受信任的 YAML 输入时,就会出现该漏洞。
5.4 之前的 PyYAML 版本允许特制的 YAML 标签,包括 Python 特定的标签,例如:
!!python/object/new
以在使用了 FullLoader 时达到对象构造功能。
因此,一个恶意的 YAML 文档可能导致非预期的 Python 代码执行。
该问题与对早期 PyYAML 漏洞的不完整修复有关。
PyYAML 5.4 通过将任意 Python 标签移至 UnsafeLoader 修复了 CVE-2020-14343。
对于处理不受信任 YAML 的应用程序,本实验还使用 yaml.safe_load() 作为一种安全的加载方式。
本实验包含两个隔离的 Docker 服务:
Host Machine
|
+------------+------------+
| |
v v
Vulnerable App Patched App
localhost:5000 localhost:5001
| |
PyYAML 5.3.1 PyYAML 5.4
FullLoader SafeLoader
| |
v v
Payload executes Payload rejected
| 服务 | 主机端口 | 容器端口 | PyYAML | 用途 |
|---|---|---|---|---|
| vulnerable | 5000 | 5000 | 5.3.1 | 存在漏洞的环境 |
| patched | 5001 | 5000 | 5.4 | 已修复的环境 |
cve-2020-14343-lab/
├── vulnerable/
│ ├── app.py
│ ├── Dockerfile
│ └── requirements.txt
├── patched/
│ ├── app.py
│ ├── Dockerfile
│ └── requirements.txt
├── exploit/
│ └── reproduce.py
├── detection/
│ └── detect.py
├── screenshots/
│ ├── 01-docker-compose-running.png
│ ├── 02-vulnerable-detection.png
│ ├── 03-patched-detection.png
│ ├── 04a-vulnerable-request.png
│ ├── 04b-vulnerable-execution-log.png
│ └── 05-patched-exploit-blocked.png
├── blog/
│ └── CVE-2020-14343-Technical-Blog.pdf
├── docker-compose.yml
├── README.md
└── .gitignore
| 文件/目录 | 用途 |
|---|---|
vulnerable/app.py | 使用存在漏洞的 YAML 加载方式的 Flask 应用 |
vulnerable/Dockerfile | 构建存在漏洞的 Docker 镜像 |
vulnerable/requirements.txt | 将 PyYAML 固定为 5.3.1 |
patched/app.py | 使用安全 YAML 加载方式的 Flask 应用 |
patched/Dockerfile | 构建已修补的 Docker 镜像 |
patched/requirements.txt | 将 PyYAML 固定为 5.4 |
exploit/reproduce.py | 使用受控载荷复现该漏洞 |
detection/detect.py | 检查 Docker 容器内的 PyYAML 版本 |
docker-compose.yml | 构建并运行两个环境 |
screenshots/ | 包含实验过程中捕获的证据 |
blog/ | 包含 800–1200 字的技术博客 PDF |
需要以下软件:
启动实验前,Docker Desktop 必须正在运行。
存在漏洞的应用通过 Docker 端口映射,仅在本机上有意暴露。
在项目根目录下,运行:
docker compose up --build -d
运行:
docker compose ps
两个服务都应显示 Up。
打开:
http://127.0.0.1:5000
I'm ready to translate chunk 13, but the actual content to translate wasn't included in your message — only the instructions and the "INPUT:" label with nothing after it.
Please paste the Markdown content for chunk 13, and I'll return the translated version following all the rules you specified.
{
"message": "CVE-2020-14343 vulnerable YAML parser"
}
打开:
http://127.0.0.1:5001
I'm ready to translate chunk 13, but the actual content to translate wasn't included in your message — only the instructions and the "INPUT:" label with nothing after it.
Please paste the Markdown content for chunk 13, and I'll return the translated version following all the rules you specified.
{
"message": "CVE-2020-14343 patched YAML parser"
}
docker compose up -d
docker compose down
docker compose up --build -d
docker compose ps
docker logs cve-vulnerable
docker logs cve-patched
该存在漏洞的应用程序暴露了一个 /parse 端点,用于接收 YAML 输入。
该漏洞环境使用:
yaml.FullLoader复现脚本位于:
exploit/reproduce.py
存在漏洞的应用程序位于:
http://127.0.0.1:5000/parse
运行:
python exploit/reproduce.py
该脚本发送一个包含 Python 专用 YAML 标签的受控 YAML 载荷。
在存在漏洞的环境中,该载荷会被接受,应用程序返回 HTTP 200 响应。
受控测试标记:
CVE-2020-14343-TEST
在易受攻击的应用程序容器内执行。
可以使用以下方式验证执行情况:
docker logs cve-vulnerable
预期的日志输出包括:
CVE-2020-14343-TEST
这演示了通过存在漏洞的 YAML 加载行为实现代码执行。
复现载荷有意设计为无害。它仅打印一个测试标记,不会修改主机系统、访问凭据、访问敏感数据、与外部系统交互或执行破坏性操作。
检测脚本位于:
detection/detect.py
脚本会检查指定 Docker 容器内安装的 PyYAML 版本。
修复后的版本为:
5.4
运行:
python detection/detect.py cve-vulnerable
预期结果:
PyYAML installed version: 5.3.1
Fixed version: 5.4
STATUS: VULNERABLE
Reason: PyYAML version is older than 5.4.
运行:
python detection/detect.py cve-patched
预期结果:
PyYAML installed version: 5.4
Fixed version: 5.4
STATUS: PATCHED
Reason: PyYAML version is 5.4 or newer.
Docker Container
|
v
docker exec
|
v
Import PyYAML
|
v
Read yaml.__version__
|
v
Compare with 5.4
|
+----------------------+
| |
< 5.4 >= 5.4
| |
v v
VULNERABLE PATCHED
脚本从目标容器中查询已安装的 PyYAML 版本。
该易受攻击的环境使用:
PyYAML==5.3.1
修补后的环境使用:
PyYAML==5.4
修补后的应用程序还使用了:
yaml.safe_load(yaml_input)
而不是:
yaml.load(yaml_input, Loader=yaml.FullLoader)
yaml.safe_load()。使用相同的漏洞利用载荷对已修补的应用程序进行测试。
已修补的应用程序运行于:
http://127.0.0.1:5001/parse
运行:
python exploit/reproduce.py http://127.0.0.1:5001/parse
本实验中观察到的结果:
HTTP Status: 400
Server Response:
{"message":"could not determine a constructor for the tag 'tag:yaml.org,2002:python/object/new:tuple' ...","status":"error"}
Python 专用的 YAML 标签被修补后的应用程序拒绝。
受控测试标记在修补后的应用程序中未被执行。
| 测试项 | 存在漏洞的环境 | 修补后的环境 |
|---|---|---|
| PyYAML 版本 | 5.3.1 | 5.4 |
| 检测结果 | VULNERABLE | PATCHED |
| 漏洞利用请求 | HTTP 200 | HTTP 400 |
| Python 专用的 YAML 标签 | 被接受 | 被拒绝 |
| 测试标记执行 | 在容器日志中观察到 | 未执行 |
请确保 Docker Desktop 正在运行,并验证:
docker version
检查:
docker ps -a
如果必要,请删除旧容器:
docker rm cve-vulnerable cve-patched
然后:
docker compose up --build -d
docker compose ps
docker logs cve-vulnerable
docker logs cve-patched
docker compose up --build -d
用于干净重建:
docker compose build --no-cache
docker compose up -d
本项目是一个受控的概念验证实验环境,并不代表生产部署。
局限性包括:
以下证据是在实验过程中捕获的: