Why yaml.load() is Unsafe Without SafeLoader in Python
Deserializing untrusted data with Python's PyYAML library using
yaml.load() without specifying SafeLoader
exposes applications to arbitrary code execution vulnerabilities. This
article explains the technical mechanics behind how the default PyYAML
loader handles custom Python tags, how an attacker can leverage this
behavior to execute arbitrary commands, and how using
SafeLoader or yaml.safe_load() completely
eliminates the risk.
The Mechanics of Arbitrary Code Execution
YAML is not just a data-serialization format like JSON; the full YAML specification supports custom data types and tags that instruct the parser to reconstruct native objects. PyYAML natively implements this feature to serialize and deserialize complex Python objects, including class instances, module functions, and system calls.
When yaml.load() runs without
Loader=yaml.SafeLoader, it uses loaders capable of
interpreting Python-specific tags, such as
!!python/object/apply:. When the parser encounters this
tag, it resolves the specified Python module or callable and passes the
provided arguments directly to it during parsing.
For example, an attacker can provide the following YAML payload:
!!python/object/apply:os.system ["id"]When processed with an unsafe loader, the PyYAML parser imports the
os module and executes os.system("id").
Because execution occurs during the parsing phase, any untrusted input
passed to yaml.load() can run shell commands, alter files,
or open reverse shells with the same permissions as the running
application process.
The Function of SafeLoader
The yaml.SafeLoader class disables the resolution of
dynamic Python tags. Instead of constructing arbitrary Python objects or
invoking callables, SafeLoader strictly parses basic YAML
types, converting documents only into native, non-executable data
structures:
- Dictionaries (mappings)
- Lists (sequences)
- Strings, integers, floats, and booleans (scalars)
None(null values)
If a payload containing custom constructor tags like
!!python/object or !!python/object/apply is
fed to SafeLoader, the parser raises a
ConstructorError and immediately halts execution,
preventing malicious code from running.
How to Secure PyYAML Deserialization
To prevent Remote Code Execution (RCE) vulnerabilities, avoid raw
calls to yaml.load() without an explicit safe loader.
The standard and recommended approach is to use
yaml.safe_load():
import yaml
# Safe: parses standard types and rejects custom executable tags
data = yaml.safe_load(untrusted_yaml_string)Alternatively, pass SafeLoader explicitly if using
yaml.load():
import yaml
# Safe: explicitly defines SafeLoader
data = yaml.load(untrusted_yaml_string, Loader=yaml.SafeLoader)Any YAML data received over a network, uploaded by users, or
retrieved from untrusted storage should always be deserialized using
yaml.safe_load() to maintain application security.