
Akaunting 3.3.0 - Stored XSS through imported invoice numbers
7
High
Detected by

Fluid Attacks AI SAST Scanner
Disclosed by
Miguel Gómez
Abstract
Fluid Attacks' AI SAST detected CVE-2026-119275, a high-severity stored cross-site scripting vulnerability (CWE-79) in Akaunting 3.3.0. An authenticated Manager can execute JavaScript in a recurring-invoice reader's browser. It has a CVSS v4.0 base score of 8.3. Fluid Attacks assigned this CVE as a CNA, and this advisory includes a proof of concept.
Summary
Full name
Akaunting 3.3.0 - Stored XSS through imported recurring-invoice children
Code name
State
Public
Release date
Affected product
Akaunting
Vendor
Akaunting
Affected version(s)
3.3.0
Vulnerability name
Stored cross-site scripting (XSS)
Vulnerability type
Remotely exploitable
Yes
CVSS v4.0 vector string
CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:A/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N
CVSS v4.0 base score
7.0
Exploit available
Yes
CVE ID(s)
CWE
CAPEC
Description
Akaunting 3.3.0 contains a stored XSS vulnerability in the XLSX invoice-import workflow. A Manager can import an ordinary invoice whose invoice_number contains HTML, associate it with a recurring invoice, and cause that value to be rendered as HTML when a victim opens the recurring invoice's child section.
Vulnerability
Root cause
Imports\\Sales\\Invoices\\Sheets\\Invoices::map() casts the spreadsheet invoice_number to a string and assigns it to document_number. The import-specific request rule requires a string but does not neutralize HTML. The importer also accepts a supplied parent_id when parent_number is nonempty, allowing an imported ordinary invoice to be linked to an attacker-created recurring invoice.
resources/views/components/documents/show/children.blade.php then builds an anchor as a PHP string with $child->document_number directly inside it. That string is passed as the url placeholder to the recurring.child translation and emitted with Blade's raw {!! !!} syntax. The normal {{ }} escaping is bypassed while constructing the anchor and while rendering the translation, so browser parsing interprets markup stored in the invoice number.
Source-to-sink path
The normal-invoice import route is registered in
routes/admin.php;Sales\\Invoices::import()invokes the invoice importer forPOST /{company}/sales/invoices/import.Invoices::map()castsinvoice_number, maps it todocument_number, and setsparent_idfrom the import row (app/Imports/Sales/Invoices/Sheets/Invoices.php:33-57). Its validation is derived fromDocumentand acceptsrequired|string|uniquerather than neutralizing HTML (app/Http/Requests/Document/Document.php:48-68).App\\Traits\\Import::getParentId()retains a provided parent ID whenparent_numberis nonempty (app/Traits/Import.php:200-220), creating the parent-child relationship.A victim opens the associated recurring invoice. The child view concatenates
document_numberinside an anchor (resources/views/components/documents/show/children.blade.php:15-19) and raw-renders the translated entry (:21-23). This is the executable HTML sink.
Impact
When an authenticated victim opens the affected recurring-invoice detail page, attacker-controlled JavaScript executes in that victim's authenticated Akaunting session. It can read data exposed to the browser and submit same-origin actions that the victim is authorized to perform. The impact is session compromise within the victim's existing permissions: it is greatest for an administrator and does not independently grant privileges to a lower-privileged victim.
PoC
Preconditions
Use Akaunting 3.3.0, company ID 1, a predefined Manager account, and a separate victim allowed to read recurring invoices. This public proof uses an ordinary invoice-import workbook created from the standard template; it contains no private companion material and no state-changing payload.
Step-by-step
Install the released
3.3.0version, complete normal setup, create the first company (ID1), and retain the initial administrator for verification and cleanup.Create or invite a user in that company with the predefined Manager role only. Complete the invitation flow and sign in as that Manager; no administrator permission, custom role, API token, cache change, or server-side command is part of the proof.
Confirm that Importer, Sales → Invoices, and Sales → Recurring Invoices are available. The stock role has
common-import: candsales-invoices: c,r,u,d, but no user-management permission.Create or import a recurring invoice parent through Importer → Import Recurring Invoices. Open it under Sales → Recurring Invoices and record its numeric ID from
/1/sales/recurring-invoices/<parent-id>.Create an ordinary-invoice XLSX using the standard invoice-import template. In its
invoicessheet, setparent_idto the ID from step 4 and keepparent_numbernonempty. Put this exact harmless value in theinvoice_numbercell:Import the workbook through Importer → Import Invoices (
POST /1/sales/invoices/import), not the recurring-invoice import page. Confirm that the ordinary child invoice exists and is linked to the recurring parent.In a separate victim session, open the recurring parent and its child-invoice section.
In browser developer tools, evaluate:
The value is
SODA_RECURRING_CHILD_XSS. It is set during page rendering; no child-link click, request, or state-changing action is involved.
Evidence of Exploitation
Video of exploitation:
Static evidence:

Our security policy
We have reserved the ID CVE-2026-119275 to refer to this issue from now on.
System information
Product: Akaunting
Version: 3.3.0
Operating System: Any supported Akaunting deployment
References
GitHub Repository: https://github.com/akaunting/akaunting
Security: https://github.com/akaunting/akaunting/blob/master/SECURITY.md
Release: https://github.com/akaunting/akaunting/releases#release-3.3.0
Solution and Workaround
Solution
Escape document_number when constructing the child link and avoid passing a prebuilt HTML string to a raw translation sink. In addition, reject or normalize markup in document numbers on every write path, including XLSX imports, and add regression coverage for imported child invoices rendered by recurring invoices.
Workaround and temporary mitigation
Restrict invoice-import permission to trusted administrators, inspect existing document numbers for markup, and avoid opening recurring invoices with children imported from untrusted workbooks. These measures reduce exposure but do not remove the vulnerable sink.
Credits
The vulnerability was detected by AI SAST Scanner and disclosed by Miguel Gómez from Fluid Attacks' Offensive Team.
Timeline
Vulnerability discovered
Vendor contacted
Public disclosure
Does your application use this vulnerable software?
During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.














