← Back to Feed CACHED · 2026-10-08 16:12:42 · CACHE_KEY CVE-2022-43680
CVE-2022-43680 · CWE-416 · Disclosed 2022-10-24

libexpat Use-After-Free via Shared DTD in XML_ExternalEntityParserCreate During OOM

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Like a fire alarm that only malfunctions during a power outage you're already dealing with

CVE-2022-43680 is a use-after-free in libexpat through version 2.4.9. When XML_ExternalEntityParserCreate creates a child parser that shares the parent's DTD, and a subsequent memory allocation inside parserInit fails (OOM), XML_ParserFree incorrectly destroys the shared DTD because the m_isParamEntity flag hasn't been set yet. The parent parser is left holding a dangling pointer. Exploiting this yields a crash — denial of service only — with no path to code execution or data disclosure per the CVSS vector (C:N/I:N/A:H). Affected: libexpat ≤ 2.4.9. Fixed in libexpat 2.5.0. Distro backports: Debian Buster 2.2.6-2+deb10u6, Bullseye 2.2.10-2+deb11u5, Bookworm 2.5.0-1+deb12u4; RHEL/CentOS patches shipped via expat package updates in RHSA advisories.

NVD rates this HIGH / 7.5, but that score is misleading for prioritization. The CVSS vector (AV:N/AC:L) treats attack complexity as Low, yet the actual trigger — an out-of-memory condition on the target — is something the attacker almost never controls remotely. You can't just send a crafted XML document and trigger this; you need the host to be memory-starved *at the moment* the external entity parser is created. Red Hat correctly downgraded this to Moderate, noting it "is not possible to exploit on every possible system." The impact ceiling is a crash, not RCE. With no PoC, no KEV listing, no in-the-wild exploitation, and an EPSS of ~2.5%, this is a backlog-priority patch, not an emergency.

"DoS-only bug gated behind an out-of-memory condition that attackers rarely control"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify XML-parsing service using libexpat

The attacker identifies a network-accessible service that parses attacker-supplied XML using libexpat ≤ 2.4.9. This could be a web application, API endpoint, WebDAV server (e.g., Apache mod_dav), or any service accepting XML input. The service must specifically use XML_ExternalEntityParserCreate, meaning the application must be configured to process external entities — many hardened deployments disable XXE processing entirely.
Conditions required:
  • Network-reachable service accepting XML input
  • Service uses libexpat ≤ 2.4.9
  • Application processes external entities (XXE not disabled)
Where this breaks in practice:
  • Most modern XML configurations disable external entity processing by default
  • Many services use libxml2 or other XML parsers, not libexpat
  • Service may not be network-facing
Detection/coverage: Version scanning via dpkg -l libexpat1 or rpm -q expat. Qualys QID 354821, Nessus plugin for libexpat version check. No network-level signature because exploitation requires OOM coincidence.
STEP 02

Send XML with external entity reference

The attacker sends a crafted XML document containing an external entity declaration to force the target to call XML_ExternalEntityParserCreate. This creates a child parser that shares the parent's DTD. The XML itself is not malicious in a traditional sense — it's a legitimate-looking document that exercises the external entity path.
Conditions required:
  • Ability to send arbitrary XML to the service
  • Service resolves external entities
Where this breaks in practice:
  • WAFs and XML gateways commonly block or strip external entity declarations
  • Application-level XML processing may reject DTDs entirely
Detection/coverage: WAF rules for XXE patterns will flag external entity declarations. OWASP ModSecurity CRS rules 200007, 200008.
STEP 03

Target must enter out-of-memory state

The vulnerability only triggers when parserInit fails a memory allocation — specifically copyString(encodingName, ...). This requires the host to be in or near an out-of-memory condition at the precise moment the child parser is being initialized. The attacker does not control this condition via the XML payload alone. They would need a separate memory exhaustion vector or hope the system is already resource-starved.
Conditions required:
  • Target host is in or near OOM condition during parser creation
  • Attacker has separate memory exhaustion capability OR system is already degraded
Where this breaks in practice:
  • OOM conditions are rare on properly provisioned servers
  • Linux OOM killer typically terminates processes before reaching this state
  • An attacker with memory exhaustion capability already has a DoS vector, making this bug redundant
  • Timing the OOM with the exact parser initialization is extremely unreliable
Detection/coverage: Host-level memory monitoring (Prometheus node_exporter, Datadog agent) will detect OOM pressure long before this bug becomes reachable.
STEP 04

Crash via use-after-free

If the OOM condition coincides with parser creation, XML_ParserFree destroys the shared DTD. The parent parser subsequently accesses the freed DTD memory, causing a crash. The impact is process termination — a denial of service. There is no known technique to leverage this use-after-free for code execution; the freed structure is a DTD, and the dangling pointer dereference leads to a segfault, not a controllable write.
Conditions required:
  • Steps 1-3 all succeed simultaneously
Where this breaks in practice:
  • Process restart mechanisms (systemd, supervisord, container orchestrators) typically recover the service within seconds
  • No evidence this can be escalated beyond DoS
Detection/coverage: Process crash monitoring, core dump analysis. SIGSEGV in libexpat stack traces.
03 · Compensating Control

1
MEDIUM 5.3→LOW 2.5
SEVERITY REDUCED
Disable external entity processing in XML parsers — Configure applications to reject DTDs and external entities. In Python: defusedxml or set XML_SetExternalEntityRefHandler to a no-op. In Apache: ensure mod_dav is not exposed or disable entity expansion. This eliminates the code path to XML_ExternalEntityParserCreate entirely. No mitigation SLA for MEDIUM — go straight to the 365-day remediation window for the actual patch.
2
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Apply vendor patch (libexpat ≥ 2.5.0 or distro backport) — Update the expat / libexpat1 package to the fixed version. This is the definitive remediation. Per the noisgate remediation SLA for MEDIUM, complete within 365 days. Given that patches have been available since late 2022, most environments should already be patched — verify and close. apt list --installed 2>/dev/null | grep -i expat or rpm -qa | grep expat to audit.
3
MEDIUM 5.3→LOW 3.0
SEVERITY REDUCED
Ensure process supervisors auto-restart crashed services — Verify that any service linked against libexpat runs under a process supervisor (systemd with Restart=always, Kubernetes with restart policy, Docker with --restart=unless-stopped). This doesn't prevent the crash but limits the DoS window to seconds. This is defense-in-depth, not a primary control.
What doesn't work
  • WAF XXE rules — While WAF rules for XXE (OWASP CRS 200007/200008) block many external entity payloads, they don't address this CVE specifically because the vulnerability requires an OOM state, not a specially crafted XML payload. The XML itself can be benign-looking.
  • Memory limits / cgroups — Setting memory limits on containers or cgroups might seem relevant, but lower memory limits actually *increase* the likelihood of OOM conditions, potentially making the bug *easier* to reach, not harder.
  • Network segmentation — Reducing attack surface is always good, but libexpat is embedded in so many tools (Git, Python, PHP) that internal services are equally likely to link against it. Segmentation doesn't address the library-level defect.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not listed in CISA KEV. No campaigns, threat actor TTPs, or incident reports reference this CVE. Zero GreyNoise or Shadowserver hits related to this specific bug.
Proof-of-conceptNo public PoC exists. No results on pocindex.io (404), no dedicated GitHub repos (e.g., CVE-2022-43680), no ExploitDB or Metasploit modules. The upstream GitHub issue #649 contains a technical description but no weaponized reproduction script. The OOM prerequisite makes PoC construction impractical.
EPSS0.02457 (2.46%) — roughly 80th percentile. Indicates low predicted exploitation probability over the next 30 days. Consistent with the OOM gate making real-world exploitation unreliable.
KEV statusNot listed. CISA has not added this CVE to the Known Exploited Vulnerabilities catalog as of October 2026.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — Network vector, low complexity (contested — OOM prerequisite is not captured), no privileges, no user interaction, availability-only impact. The vector overstates practical risk by encoding attack complexity as Low.
Affected versionslibexpat ≤ 2.4.9. Embedded in CPython's pyexpat, Apache mod_dav, D-Bus, CMake, Git, PHP, and many other packages that bundle or link against expat.
Fixed versionslibexpat 2.5.0 (released 2022-10-25). Distro backports: Debian Buster 2.2.6-2+deb10u6, Bullseye 2.2.10-2+deb11u5, Bookworm 2.5.0-1+deb12u4. RHEL 8/9 patched via expat package errata. Ubuntu fixed across Bionic, Focal, Jammy, Kinetic. Amazon Linux patched via ALAS advisories.
Disclosure timelineReported 2022-10-24. Fix committed same day (commit 5290462a). libexpat 2.5.0 released 2022-10-25. Distro patches rolled out through late 2022 and early 2023.
ReporterRhodri James, maintainer of libexpat. Discovered during code review of DTD lifetime management.
Scanning / exposureNo Shodan/Censys/FOFA dork specifically identifies libexpat-exposed services. The library is a dependency, not a directly exposed service. Vulnerability scanners detect it via package version checks on authenticated scans only.

Sources.

  1. NVD — CVE-2022-43680 Detail
  2. libexpat GitHub Issue #649
  3. Red Hat CVE-2022-43680
  4. Debian Security Tracker — CVE-2022-43680
  5. GHSA-4hjv-8mmr-jxwv (GitHub Advisory)
  6. libexpat 2.5.0 Release Announcement
  7. Fix Commit 5290462a
05 · The Call

Final Verdict
↓ DOWNGRADED to MEDIUM (5.3/10)

Why this verdict

  • OOM prerequisite collapses attack reliability. The CVSS vector marks Attack Complexity as Low, but the actual trigger requires the target host to be in an out-of-memory state at the exact moment a child parser is initialized. This is not attacker-controlled via the XML payload. Effective AC is High, which alone drops the base score from 7.5 to ~5.9.
  • Impact ceiling is DoS only — no RCE, no data leak. The use-after-free dereferences a DTD structure that segfaults the process. There is no known write-what-where primitive, no heap spray technique documented, and the CVSS vector itself confirms C:N/I:N. For a library-level crash, process supervisors (systemd, container restart policies) restore service in seconds.
  • No exploitation evidence whatsoever. No KEV listing, no PoC, no in-the-wild campaigns, EPSS at 2.46%. Four years post-disclosure with zero weaponization strongly suggests the OOM gate is a genuine barrier, not merely an unexploited opportunity.
  • Role multiplier: libexpat is ubiquitous — it ships in nearly every Linux distribution and is embedded in Python, Apache, Git, PHP, and more. High-value roles include identity providers parsing SAML XML, API gateways, CI/CD pipelines, and database management consoles. However, even on these high-value targets, the chain outcome is *process crash with automatic restart*, not domain takeover, fleet compromise, or data exfiltration. A transient DoS on an IdP is disruptive but not catastrophic, and the OOM prerequisite means it cannot be triggered on demand. The blast radius is *host-level, transient* — not fleet-scale or identity-scale. The DoS-only outcome does not meet the high-value-role floor threshold of 'domain takeover / fleet compromise / mass data egress / supply-chain pivot.'
  • Red Hat's Moderate rating is the right call. Red Hat explicitly noted this 'is not possible to exploit on every possible system' and rated Moderate. Their analyst judgment aligns with the friction analysis: the NVD's 7.5 is a mechanical score that doesn't capture the OOM precondition.

Why not higher?

The vulnerability does affect a near-universally-deployed library and the CVSS attack vector is unauthenticated/remote. If the OOM gate were removed — if an attacker could trigger this with a crafted XML document alone — this would be a clear HIGH. But the OOM prerequisite is load-bearing: it makes exploitation probabilistic and uncontrollable, which is the entire difference between a theoretical and a practical risk.

Why not lower?

libexpat's install base is enormous and includes security-critical infrastructure. A use-after-free in a C library *could* theoretically be chained with a heap-shaping technique for code execution, even though none has been demonstrated in four years. Dropping to LOW would imply the bug is purely cosmetic; it isn't — it's a real memory-safety defect in a foundational parsing library. MEDIUM correctly captures 'real bug, impractical to exploit, low-impact outcome.'

06 · Verification

Crowdsourced verification payload.

Run this script on each target host as any user with read access to the package database. No root required. Invoke: bash check_cve_2022_43680.sh. Works on Debian/Ubuntu (dpkg) and RHEL/CentOS/Fedora (rpm) systems.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2022_43680.sh — Detect libexpat vulnerable to CVE-2022-43680
# Target: Linux hosts with dpkg or rpm package managers
# Privileges: none (read-only package query)
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -euo pipefail

VULN_MAJOR=2
VULN_MINOR=4
VULN_PATCH=9

compare_version() {
  local ver="$1"
  local major minor patch
  major=$(echo "$ver" | cut -d. -f1)
  minor=$(echo "$ver" | cut -d. -f2)
  patch=$(echo "$ver" | cut -d. -f3 | sed 's/[^0-9].*//')
  patch=${patch:-0}

  if (( major > VULN_MAJOR )); then
    return 0  # patched
  elif (( major == VULN_MAJOR && minor > VULN_MINOR )); then
    return 0
  elif (( major == VULN_MAJOR && minor == VULN_MINOR && patch > VULN_PATCH )); then
    return 0
  fi
  return 1  # vulnerable
}

VERSION=""

# Try dpkg first (Debian/Ubuntu)
if command -v dpkg-query &>/dev/null; then
  VERSION=$(dpkg-query -W -f='${Version}' libexpat1 2>/dev/null || true)
  if [[ -n "$VERSION" ]]; then
    # Extract upstream version (strip epoch and debian revision)
    UPSTREAM=$(echo "$VERSION" | sed 's/^[0-9]*://;s/-[^-]*$//')
    # Check for distro backport indicators (deb10u6+, deb11u5+, etc.)
    if echo "$VERSION" | grep -qE 'deb10u([6-9]|[1-9][0-9])'; then
      echo "PATCHED — libexpat1 $VERSION (Debian Buster backport)"
      exit 0
    elif echo "$VERSION" | grep -qE 'deb11u([5-9]|[1-9][0-9])'; then
      echo "PATCHED — libexpat1 $VERSION (Debian Bullseye backport)"
      exit 0
    fi
    if compare_version "$UPSTREAM"; then
      echo "PATCHED — libexpat1 $VERSION (upstream >= 2.5.0)"
      exit 0
    else
      echo "VULNERABLE — libexpat1 $VERSION (affected by CVE-2022-43680)"
      exit 1
    fi
  fi
fi

# Try rpm (RHEL/CentOS/Fedora)
if command -v rpm &>/dev/null; then
  VERSION=$(rpm -q --queryformat '%{VERSION}' expat 2>/dev/null || true)
  if [[ -n "$VERSION" && "$VERSION" != *"not installed"* ]]; then
    RELEASE=$(rpm -q --queryformat '%{RELEASE}' expat 2>/dev/null || true)
    if compare_version "$VERSION"; then
      echo "PATCHED — expat $VERSION-$RELEASE (>= 2.5.0)"
      exit 0
    else
      # RHEL backports: check if release string indicates patched
      if echo "$RELEASE" | grep -qE 'el[89]'; then
        echo "UNKNOWN — expat $VERSION-$RELEASE (RHEL backport; verify via 'rpm -q --changelog expat | grep CVE-2022-43680')"
        exit 2
      fi
      echo "VULNERABLE — expat $VERSION-$RELEASE (affected by CVE-2022-43680)"
      exit 1
    fi
  fi
fi

echo "UNKNOWN — could not detect libexpat/expat package on this system"
exit 2