A single call to yaml.load() on attacker-influenced input is enough to hand over a Python process — no sandbox escape, no memory corruption, just YAML doing exactly what its default loader was built to do. That is the core of CVE-2017-18342, a critical deserialization flaw in PyYAML, the ubiquitous Python library used to parse YAML across CI pipelines, configuration loaders, Kubernetes tooling, and countless internal services. Any application that passes untrusted or semi-trusted YAML to PyYAML's default Loader can be coerced into instantiating arbitrary Python objects, and with the right constructor, into executing arbitrary shell commands.
What's actually vulnerable
PyYAML's yaml.load() function was, for years, unsafe by design rather than by accident. Unlike yaml.safe_load(), which restricts deserialization to a fixed set of primitive types (strings, numbers, lists, dicts), the default Loader class supports YAML tags that map directly onto Python's object model — including !!python/object/apply and !!python/object/new. Those tags let a YAML document instruct the parser to call an arbitrary Python callable with attacker-supplied arguments at parse time.
The canonical proof-of-concept looks like this:
!!python/object/apply:subprocess.check_output [['id']]
or the classic:
!!python/object/apply:os.system ["id"]
Feed either string into yaml.load(data) (with no Loader argument, or with Loader=yaml.Loader/Loader=yaml.FullLoader) and PyYAML will happily import os or subprocess, resolve the named callable, and invoke it with the supplied arguments — before your application ever sees a parsed dictionary. There is no need for the caller to opt into anything unsafe; yaml.load() was simply unsafe by default.
Affected versions and components
- PyYAML < 5.1 is squarely in scope for CVE-2017-18342, where
yaml.load()had no way to select a safer loader without the caller explicitly opting in. - PyYAML 5.1 began emitting a
YAMLLoadWarningwhenyaml.load()was called without an explicitLoader=argument, nudging developers towardSafeLoader, but did not remove the dangerous default outright — it also introducedFullLoader, which is safer than the original defaultLoaderbut is not equivalent toSafeLoader. - A related, easily confused issue is CVE-2020-14343, which affects
FullLoaderandUnsafeLoaderin PyYAML before 5.4 and closes a separate gap whereFullLoadercould still be abused via Python'sobject.__reduce__machinery. Teams remediating CVE-2017-18342 should upgrade past both fixes (PyYAML 5.4+) rather than stopping at 5.1. - Because PyYAML is a transitive dependency of an enormous swath of the Python ecosystem — Ansible, AWS CLI, Docker Compose, Salt, various Django and Flask extensions, and thousands of internal tools — the practical blast radius extends well beyond code that directly imports
yaml. Any service that ingests YAML-formatted configuration, webhook payloads, CI job definitions, or Kubernetes manifests through one of these dependencies inherits the same exposure.
Severity, exploit prediction, and known exploitation
CVE-2017-18342 carries a CVSS v3.1 base score of 9.8 (Critical) under the vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — network-exploitable, no privileges or user interaction required, and full compromise of confidentiality, integrity, and availability once triggered. That score reflects the worst-case scenario: an internet-facing service that deserializes attacker-controlled YAML with the default loader.
EPSS (Exploit Prediction Scoring System) context is worth reading carefully rather than at face value. Because this is a library-level deserialization primitive rather than a single exploitable endpoint, EPSS — which models the probability of near-term, in-the-wild exploitation of a specific CVE ID — tends to under-represent real-world risk here. The actual exploitation surface depends entirely on how a downstream application feeds data into yaml.load(), which EPSS's internet-scan-driven model isn't built to capture for library CVEs. Treat CVSS as the ceiling on impact and use reachability analysis, not EPSS alone, to judge whether your specific deployment is exploitable.
As of this writing, CVE-2017-18342 is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog. That should not be read as a signal of low risk — YAML deserialization gadgets of this shape (mirrored in Ruby's Psych, Java's SnakeYAML, and other ecosystems) have a long history of being weaponized in red-team tooling, CTFs, and real intrusions once a vulnerable code path is identified, even without a dedicated KEV entry for this specific library.
Timeline
- Long-standing, pre-disclosure: PyYAML's documentation had, for years, informally acknowledged that
yaml.load()behaves likepickle.load()for a subset of tags and should not be used on untrusted input — but this was buried in docs rather than enforced in code. - 2017: The behavior was formally catalogued and assigned as part of a batch of retrospective CVEs covering long-known but previously unticketed issues in widely used Python libraries.
- 2018: CVE-2017-18342 was published in the NVD, bringing the issue into mainstream vulnerability-management tooling and dependency scanners for the first time under a trackable identifier.
- May 2019 — PyYAML 5.1 released: Introduced
FullLoaderand began warning whenLoaderwas omitted, marking the first concrete step toward a safer default. - 2020: CVE-2020-14343 was filed against
FullLoader/UnsafeLoader, showing that the 5.1 mitigation was incomplete. - March 2021 — PyYAML 5.4 / 5.4.1 released: Closed the
FullLoadergap and madeSafeLoaderthe unambiguous recommendation for any untrusted input, effectively completing the remediation lineage that started with 5.1.
Remediation steps
- Inventory your PyYAML footprint. Run
pip show pyyamland checkrequirements.txt,poetry.lock,Pipfile.lock, orpip freezeoutput across every service — including containers and CI runners. Don't forget transitive pulls via Ansible, AWS CLI, Docker Compose, and similar tools. - Upgrade to PyYAML 5.4.1 or later. This covers both CVE-2017-18342 and CVE-2020-14343 in one move.
pip install --upgrade pyyamlat minimum; pin the floor in your dependency manifest so it can't silently regress. - Grep your codebase for unsafe calls. Search for
yaml.load(without an explicitLoader=yaml.SafeLoader(orCSafeLoader) argument, and for any use ofyaml.load(..., Loader=yaml.Loader)orLoader=yaml.FullLoaderon data that isn't fully trusted and internally generated. - Standardize on
yaml.safe_load()/yaml.safe_load_all(). These wrapSafeLoaderand are the correct default for parsing configuration files, webhook bodies, user uploads, or anything crossing a trust boundary. ReserveFullLoaderorUnsafeLoaderfor cases where you control both ends of the serialization and have a specific reason to need Python object round-tripping. - Add a lint/CI gate. A simple pre-commit hook or CI grep step (
yaml\.load\([^,)]*\)withoutLoader=) catches regressions before they reach production, since a single newyaml.load()call without a loader argument can reintroduce the vulnerability even after the library upgrade. - Validate and constrain YAML inputs at the boundary. Where possible, apply a schema check (e.g.,
jsonschemaagainst the loaded structure, orcfn-lint/kubeval-style validators for domain-specific YAML) after safe-loading, so malformed or unexpected structures are rejected regardless of the loader in use. - Monitor for anomalous process behavior. For services that must parse less-trusted YAML, runtime monitoring for unexpected child-process spawning from Python worker processes (e.g., a config-parsing service suddenly forking
/bin/sh) provides a defense-in-depth signal if a bypass or misconfiguration slips through.
How Safeguard Helps
Safeguard's SBOM generation and ingestion pipeline identifies every instance of PyYAML — direct or transitive — across your services and containers, flagging exactly which versions predate the 5.4.1 fix line for CVE-2017-18342 and CVE-2020-14343. Rather than stopping at "PyYAML is present," Safeguard's reachability analysis traces whether yaml.load() or an unsafe Loader is actually called on a code path that touches untrusted input, so security teams can triage the handful of genuinely exploitable services instead of chasing every vendored copy of the library. Griffin AI, Safeguard's contextual reasoning engine, reviews the surrounding code to distinguish a safe internal deserialization use case from a real attack surface, cutting through the noise that pure SCA scanners generate on old, high-CVSS library findings like this one. When a fix is warranted, Safeguard opens an auto-fix pull request that bumps PyYAML to a patched version and, where it detects unsafe yaml.load() calls, proposes the safe_load() swap directly in the diff — turning a multi-day remediation sprint into a reviewable PR.