Controlled vulnerability research and reproduction lab for CVE-2020-14343 in PyYAML
This project demonstrates CVE-2020-14343 in a controlled and isolated Docker environment.
The lab contains two environments:
yaml.safe_load()The lab demonstrates the complete vulnerability lifecycle:
The reproduction payload used in this lab is intentionally harmless and only prints a test marker.
| Property | Details |
|---|
| CVE | CVE-2020-14343 |
| Product | PyYAML |
| Affected versions | Versions before 5.4 |
| Fixed version | 5.4 |
| Vulnerability type | Improper Input Validation (CWE-20) |
| Severity | Critical |
| Attack surface | YAML input processed using vulnerable loading functionality |
The vulnerability occurs when untrusted YAML input is processed using vulnerable PyYAML loading functionality.
PyYAML versions before 5.4 allowed specially crafted YAML tags, including Python-specific tags such as:
!!python/object/new
to reach object construction functionality when FullLoader was used.
A malicious YAML document can therefore cause unintended Python code execution.
The issue was related to an incomplete fix for an earlier PyYAML vulnerability.
PyYAML 5.4 fixed CVE-2020-14343 by moving arbitrary Python tags to UnsafeLoader.
For applications processing untrusted YAML, this lab also uses yaml.safe_load() as a secure loading approach.
The lab contains two isolated Docker services:
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
| Service | Host Port | Container Port | PyYAML | Purpose |
|---|---|---|---|---|
| vulnerable | 5000 | 5000 | 5.3.1 | Vulnerable environment |
| patched | 5001 | 5000 | 5.4 | Remediated environment |
cve-2020-14343-lab/
│
├── vulnerable/
│ ├── app.py
│ ├── Dockerfile
│ └── requirements.txt
│
├── patched/
│ ├── app.py
│ ├── Dockerfile
│ └── requirements.txt
│
├── exploit/
│ └── reproduce.py
│
├── detection/
│ └── detect.py
│
├── docker-compose.yml
└── README.md
| File/Directory | Purpose |
|---|---|
vulnerable/app.py | Flask application using vulnerable YAML loading |
vulnerable/Dockerfile | Builds the vulnerable Docker image |
vulnerable/requirements.txt | Pins PyYAML to 5.3.1 |
patched/app.py | Flask application using secure YAML loading |
patched/Dockerfile | Builds the patched Docker image |
patched/requirements.txt | Pins PyYAML to 5.4 |
exploit/reproduce.py | Reproduces the vulnerability using a controlled payload |
detection/detect.py | Checks the PyYAML version inside a Docker container |
docker-compose.yml | Builds and runs both environments |
The following software is required:
Docker Desktop must be running before starting the lab.
The vulnerable application is intentionally exposed only on the local machine through Docker port mappings.
From the project root directory, run:
docker compose up --build -d
Run:
docker compose ps
Both services should show Up.
Open:
http://127.0.0.1:5000
Expected response:
{
"message": "CVE-2020-14343 vulnerable YAML parser"
}
Open:
http://127.0.0.1:5001
Expected response:
{
"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
The vulnerable application exposes a /parse endpoint that accepts YAML input.
The vulnerable environment uses:
yaml.FullLoaderThe reproduction script is located at:
exploit/reproduce.py
The vulnerable application is available at:
http://127.0.0.1:5000/parse
Run:
python exploit/reproduce.py
The script sends a controlled YAML payload containing a Python-specific YAML tag.
In the vulnerable environment, the payload is accepted and the application returns an HTTP 200 response.
The controlled test marker:
CVE-2020-14343-TEST
is executed inside the vulnerable application container.
The execution can be verified using:
docker logs cve-vulnerable
Expected log output includes:
CVE-2020-14343-TEST
This demonstrates code execution through the vulnerable YAML loading behavior.
The reproduction payload is intentionally harmless. It only prints a test marker and does not modify the host system, access credentials, access sensitive data, interact with external systems, or perform destructive actions.
The detection script is located at:
detection/detect.py
The script checks the PyYAML version installed inside the specified Docker container.
The fixed version is:
5.4
Run:
python detection/detect.py cve-vulnerable
Expected result:
PyYAML installed version: 5.3.1
Fixed version: 5.4
STATUS: VULNERABLE
Reason: PyYAML version is older than 5.4.
Run:
python detection/detect.py cve-patched
Expected result:
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
The script queries the installed PyYAML version from the target container.
The vulnerable environment uses:
PyYAML==5.3.1
The patched environment uses:
PyYAML==5.4
The patched application also uses:
yaml.safe_load(yaml_input)
instead of:
yaml.load(yaml_input, Loader=yaml.FullLoader)
yaml.safe_load() when the application only requires standard YAML data types.The same exploit payload is tested against the patched application.
The patched application runs on:
http://127.0.0.1:5001/parse
Run:
python exploit/reproduce.py http://127.0.0.1:5001/parse
Observed result in this lab:
HTTP Status: 400
Server Response:
{"message":"could not determine a constructor for the tag 'tag:yaml.org,2002:python/object/new:tuple' ...","status":"error"}
The Python-specific YAML tag is rejected by the patched application.
The controlled test marker is not executed in the patched application.
| Test | Vulnerable Environment | Patched Environment |
|---|---|---|
| PyYAML version | 5.3.1 | 5.4 |
| Detection | VULNERABLE | PATCHED |
| Exploit request | HTTP 200 | HTTP 400 |
| Python-specific YAML tag | Accepted | Rejected |
| Test marker execution | Observed in container logs | Not executed |
Make sure Docker Desktop is running and verify:
docker version
Check:
docker ps -a
Remove old containers if required:
docker rm cve-vulnerable cve-patched
Then:
docker compose up --build -d
docker compose ps
docker logs cve-vulnerable
docker logs cve-patched
docker compose up --build -d
For a clean rebuild:
docker compose build --no-cache
docker compose up -d
This project is a controlled proof-of-concept lab and does not represent a production deployment.
Limitations include:
The following evidence should be captured for the project submission:
STATUS: VULNERABLE.CVE-2020-14343-TEST marker in Docker logs.STATUS: PATCHED.Screenshots can be stored in:
screenshots/
All reproduction code and documentation in this repository were developed specifically for this lab.
This lab demonstrates CVE-2020-14343 from identification through remediation.
The vulnerable environment uses PyYAML 5.3.1 and demonstrates controlled code execution through a crafted YAML payload.
The detection script identifies the vulnerable dependency version.
The remediated environment upgrades PyYAML to 5.4 and uses yaml.safe_load() for processing untrusted YAML.
The same reproduction payload is then rejected by the patched application, providing before-and-after evidence of remediation.
The entire environment can be reproduced using Docker Compose.