The SvgSanitizer::decodeAllEntities() method limits recursive entity decoding to 5 iterations. By wrapping each character of javascript in an href attribute value with 5 levels of & encoding around numeric HTML entities (e.g., j for j), an attacker can bypass both isSafe() detection and sanitize() removal. The uploaded SVG is served from the application origin with image/svg+xml content type, and the browser's XML parser fully decodes the remaining &#NNN; entities, resulting in a clickable javascript: link that executes arbitrary JavaScript.
Root cause: decodeAllEntities() at phpmyfaq/src/phpMyFAQ/Helper/SvgSanitizer.php:223-249 limits entity decoding to maxIterations=5. Each iteration: (1) decodes &#NNN; numeric entities, (2) decodes &#xHH; hex entities, (3) calls html_entity_decode() which resolves one level of & → &. With 5 levels of & wrapping, all 5 iterations are consumed unwinding the & nesting, leaving the final &#NNN; numeric entities unresolved.
Code path:
FAQ_EDIT permission uploads SVG via POST /admin/api/content/images (ImageController::upload() at line 39)svg → SvgSanitizer::isSafe() called (line 114)isSafe() calls decodeAllEntities() — 5 iterations resolve & nesting but leave ja... (numeric entities for javascript)/href\s*=\s*["\'][\s]*javascript\s*:/i) does not match ja...isSafe() returns true — file saved without any sanitizationcontent/user/images/ with image/svg+xml MIME typej → j, a → a, etc., reconstructing javascript:alert(document.domain)The bypass is even simpler than initially described — no <script>...
4.1.24.1.2Exploitability
AV:NAC:LPR:LUI:RScope
S:CImpact
C:LI:LA:N5.4/CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N