Safeguard
Vulnerability Analysis

CVE-2020-14343: PyYAML arbitrary code execution via pytho...

CVE-2020-14343 lets attackers run arbitrary code via PyYAML's python/object/new tag, bypassing an earlier FullLoader fix. Versions, CVSS, and remediation inside.

Aman Khan
AppSec Engineer
7 min read

CVE-2020-14343 is a critical arbitrary code execution vulnerability in PyYAML, the near-ubiquitous Python library for parsing and emitting YAML. The flaw lives in PyYAML's FullLoader and the yaml.full_load() convenience function, both of which were supposed to be the "safe-ish" way to load YAML that might contain arbitrary Python types. Instead, an attacker who can supply a YAML document to an application using full_load() or FullLoader can construct arbitrary Python objects via the python/object/new tag and use that capability to execute arbitrary code on the host — no authentication, no user interaction beyond the application parsing the file.

What makes this CVE particularly instructive for supply chain security teams is that it isn't a fresh bug. It's a bypass. PyYAML had already been burned once by unsafe deserialization (CVE-2017-18342, and again CVE-2020-1747) and had shipped a fix specifically meant to close this class of issue. CVE-2020-14343 exists because that fix was incomplete: it blocked some dangerous YAML tags but missed python/object/new, leaving a functionally identical path to code execution wide open. For anyone tracking dependency risk, this is the textbook case of "patched" not meaning "safe" — the CVE ID changed, the vulnerable behavior didn't.

What's Affected

PyYAML versions prior to 5.4 are affected. The vulnerability specifically requires the vulnerable application to load untrusted YAML using one of the constructs that PyYAML documented as its "full" loading mode:

  • yaml.full_load(untrusted_input)
  • yaml.load(untrusted_input, Loader=yaml.FullLoader)

The python/object/new YAML tag lets a YAML document instruct the loader to instantiate an arbitrary Python class and pass it arbitrary constructor arguments. Combined with Python's rich object model, this is enough to reach code execution — the same underlying primitive that made the original yaml.load() default loader dangerous before PyYAML started requiring an explicit Loader argument.

Because PyYAML is a foundational dependency across the Python ecosystem — pulled in transitively by tools like Ansible, AWS CLI/boto-adjacent tooling, Kubernetes clients, CI/CD systems, data pipelines, and countless internal YAML-config loaders — the practical blast radius of this bug is much larger than the direct PyYAML dependency graph suggests. Any application, library, or automation script that parses external or semi-trusted YAML (config files from users, webhook payloads, CI job definitions, plugin manifests) with full_load() or FullLoader inherits this risk. Note that yaml.safe_load() and yaml.SafeLoader were never affected by CVE-2020-14343 — the vulnerability is specific to the "full" loading path, which was explicitly designed to be less restrictive than SafeLoader while still being marketed as suitable for most use cases.

The fix landed in PyYAML 5.4, which tightened what FullLoader (and the other non-UnsafeLoader loaders) will construct, removing the python/object/new bypass along with the rest of the incomplete restriction from the CVE-2020-1747 patch.

CVSS, EPSS, and KEV Context

CVE-2020-14343 is rated Critical, with a CVSS v3.1 base score of 9.8 (vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) in NVD scoring — the maximum practical impact across confidentiality, integrity, and availability, with no privileges or user interaction required for exploitation once untrusted YAML reaches the vulnerable code path. It's worth reading that score with the right context: the "network" attack vector reflects that the vulnerable input can arrive over a network channel (an API payload, an uploaded config file, a webhook body), not that the vulnerability is remotely exploitable against PyYAML in isolation. Exploitability is entirely conditional on an application choosing to run full_load() or FullLoader against attacker-influenced YAML — which is exactly the trap, since both were documented as reasonable choices for handling "almost trusted" input.

As of this writing, CVE-2020-14343 does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog, and it hasn't attracted the kind of mass-exploitation attention that, say, Log4Shell did. That's consistent with how these deserialization bugs tend to play out in practice: exploitation requires a specific, somewhat less common application pattern (parsing untrusted YAML with the "full" loader rather than safe_load), so real-world weaponization tends to be targeted rather than internet-scale scanning. That doesn't make it low priority — it makes it the kind of vulnerability that sits quietly in a dependency tree until a security review, an SCA scan, or an incident response finds it, at which point the fix is trivial and the cost of not having found it earlier is not.

Timeline

  • Earlier history: PyYAML's default yaml.load() was long known to be unsafe for untrusted input (tracked as CVE-2017-18342), which led the maintainers to require callers to pass an explicit Loader and to introduce FullLoader and SafeLoader as clearer options.
  • CVE-2020-1747: A related vulnerability was identified in FullLoader/full_load(), showing that PyYAML's "full" loading mode could still be abused for code execution. PyYAML shipped 5.3.1 with restrictions intended to close that gap.
  • CVE-2020-14343 discovered: Security researchers found that the CVE-2020-1747 fix was incomplete — the python/object/new construction path had been overlooked, preserving the same code execution capability under a new tag.
  • Fix released: PyYAML 5.4 shipped a more thorough restriction of what FullLoader and SafeLoader are permitted to construct, closing the python/object/new bypass along with tightening loader defaults more broadly.

The pattern across this history — unsafe default, partial fix, bypass, more thorough fix — is common for deserialization vulnerabilities in general-purpose parsing libraries, and it's a strong argument for tracking not just "is this CVE patched" but "does this library still expose this class of primitive at all."

Remediation

  1. Upgrade PyYAML. Move to PyYAML 5.4 or later (current releases are well past 6.0) in every environment — direct dependencies, virtual environments, container base images, and any vendored or bundled copies.
  2. Stop using full_load()/FullLoader on untrusted input, even after patching. Use yaml.safe_load() or yaml.SafeLoader unless you have a specific, reviewed reason to construct arbitrary Python objects from YAML. Defense in depth matters here: the safest fix is not relying on "full" loading of anything that isn't fully trusted.
  3. Audit transitive dependencies. PyYAML is frequently pulled in indirectly. Check pip freeze, lockfiles, and container image manifests for pinned versions below 5.4 introduced by other packages, not just direct requirements.
  4. Grep your codebase for the vulnerable call sites. Search for yaml.load(, full_load(, and FullLoader across your Python codebase and any internal tooling, scripts, or notebooks — these calls are easy to miss outside of the primary application repo.
  5. Regenerate SBOMs and re-run SCA scans after upgrading, to confirm the vulnerable version is actually gone from every build artifact, not just the source manifest.
  6. Apply least-privilege and sandboxing around any process that parses external YAML as a defense-in-depth measure, since new bypasses of "safe-ish" loading modes have precedent in this exact library.

How Safeguard Helps

CVE-2020-14343 is a good illustration of why point-in-time dependency scanning isn't enough on its own. Safeguard continuously inventories software components across your build pipelines and running environments — generating accurate SBOMs, tracking exact PyYAML versions across every repo, container image, and transitive dependency chain, and flagging when a vulnerable version like pre-5.4 PyYAML is present anywhere in your estate.

Beyond presence detection, Safeguard correlates vulnerability data — CVSS severity, EPSS exploitation likelihood, and CISA KEV status — so your team can prioritize remediation based on actual risk rather than raw CVE counts. For a bug like this one, where exploitability hinges on how the library is used rather than merely whether it's present, Safeguard's supply chain visibility helps surface the difference between "PyYAML is in the dependency tree" and "PyYAML's dangerous loading path is actually reachable from untrusted input" — the distinction that determines whether this CVE is a real incident risk or a housekeeping item.

Safeguard also enforces policy gates in CI/CD so newly introduced or reintroduced vulnerable versions of libraries like PyYAML are caught before merge, not after deployment, and provides audit-ready evidence of remediation timelines for compliance frameworks like SOC 2 that require demonstrable vulnerability management. For known-bypass histories like the CVE-2020-1747 → CVE-2020-14343 sequence, that continuous re-verification is exactly what prevents "we already patched that" from becoming a false sense of security.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.