{"id":"GHSA-3h6j-9x8m-rg3g","aliases":[],"url":"https://o3.security/vulnerability/GHSA-3h6j-9x8m-rg3g","summary":"Graby has stored XSS via iframe srcdoc Attribute in htmLawed Sanitization Config","details":"## Summary\n\nGraby's `cleanupXss()` function configures htmLawed with conflicting settings: `safe=1` (which removes `<iframe>`) combined with `'elements' => '*+iframe-meta'` (which re-enables `<iframe>`). htmLawed does not sanitize the `srcdoc` attribute, allowing injection of arbitrary JavaScript that executes when the content is rendered via `|raw` in templates.\n\n## Root Cause\n\n**`src/Graby.php` lines 1038-1048:**\n```php\nhtmLawed($html, [\n    'safe' => 1,                    // removes <iframe>\n    'elements' => '*+iframe-meta',  // re-adds <iframe>, overrides safe=1\n    'deny_attribute' => 'style',    // srcdoc is NOT denied\n]);\n```\n\nThe `safe=1` and `+iframe` combination is a conflict: `safe` mode is designed to strip dangerous elements, but the elements override re-enables `<iframe>` without also blocking the dangerous `srcdoc` attribute.\n\n## Proof of Concept\n\nInput to `cleanupXss()`:\n```html\n<iframe srcdoc=\"&lt;script&gt;alert(document.domain)&lt;/script&gt;\"></iframe>\n```\n\nOutput (unchanged — htmLawed passes it through):\n```html\n<iframe srcdoc=\"&lt;script&gt;alert(document.domain)&lt;/script&gt;\"></iframe>\n```\n\nWhen rendered via `{{ content|raw }}` in a template, `srcdoc` executes in an `about:srcdoc` frame with the same origin as the page. **Confirmed via Puppeteer/Chromium headless: `alert(document.domain)` fires.**\n\nValidated on Wallabag (which uses Graby) via Docker: entry created via API with iframe-only content body triggers Readability failure → falls through to `cleanupXss()` path.\n\n## Impact\n\n- Stored XSS in any application rendering Graby-sanitized content via `|raw`\n- In Wallabag: affects both authenticated views and public share pages (unauthenticated)\n- No CSP headers in default Wallabag config — no secondary mitigation\n\n## Suggested Fix\n\nEither remove `+iframe` from the elements config to keep iframes blocked:\n```php\n'elements' => '*-iframe-meta',\n```\n\nOr explicitly deny the `srcdoc` attribute:\n```php\n'deny_attribute' => 'style srcdoc',\n```\n\n## Credit\n\nDiscovered by @tikket1, 2026-03-25. Redirected from wallabag/wallabag advisory by @j0k3r.","published":"2026-03-31T23:12:36Z","modified":"2026-03-31T23:32:26.906898Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"j0k3r/graby","fixedVersion":"2.5.1"}],"fix":{"url":"https://github.com/j0k3r/graby/commit/0295d828822f7a59c5751a8199973a4f965a99b0","label":"j0k3r/graby@0295d82"},"references":[{"type":"WEB","url":"https://github.com/j0k3r/graby/security/advisories/GHSA-3h6j-9x8m-rg3g"},{"type":"WEB","url":"https://github.com/j0k3r/graby/commit/0295d828822f7a59c5751a8199973a4f965a99b0"},{"type":"PACKAGE","url":"https://github.com/j0k3r/graby"},{"type":"WEB","url":"https://github.com/j0k3r/graby/releases/tag/2.5.1"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-03-31T23:32:26.906898Z"}}