AI Pentesting

CVE-2026-71506: Wrong-Permission Bug Deletes Payments

Amartya | CodeAnt AI Code Review Platform
Sonali Sood

Founding GTM, CodeAnt AI

CVSS 6.5 · Wrong-permission authorization (CWE-863) · Fixed in Dolibarr 24.0.0

A button labelled to delete a draft invoice is actually wired straight into the vault holding recorded payments. Press it, and money that was genuinely paid reappears on the books as owed.

The attack in six steps

  1. Deleting a payment should need the "manage payments" right: A specific permission for a specific role.

  2. But the route checks the "delete invoices" right instead: A different key for a different job. (this is the break)

  3. The attacker holds only "delete invoices": We dumped its real rights first to prove it.

  4. Delete a real, recorded customer payment: With no check on which invoice it belongs to.

  5. The payment row simply vanishes: An invoice's "paid" total is just the sum of its payments.

  6. A fully-paid invoice flips back to "owed": Phantom debt on the books. → corrupted accounts.

In any accounting system, a recorded payment isn't something you casually erase. It's a line in the ledger that says this invoice was settled, this money actually arrived. Deleting one should be a careful, tightly gated act, reserved for the people who reconcile the books, not a side effect of an unrelated permission.

This is what happens when the route guarding that ledger checks the wrong key. An account holding nothing more than the right to delete invoices ends up deleting recorded payments outright, and quietly writes phantom debt onto books that were already settled.

The two rights involved sound close enough that the mix-up is easy to make on paper. "Delete invoices" and "manage payments" are different permissions, meant for different roles doing different jobs. The route responsible for deleting a payment checks the first when it should check the second, a swapped lock on a door that leads somewhere it was never supposed to. It's a textbook case of broken function level authorization: the check exists, it just isn't the one the action actually requires.

Proving the account wasn't secretly privileged

Deleting money is destructive, so before anything else we did the unglamorous, essential step: we dumped the test account's actual rights and wrote them down. That matters, because the easiest way for a finding like this to be waved away is "well, your account probably had extra permissions." It did not. It held the right to read and delete invoices, and nothing to do with payments at all.

Then we pointed that account at a real, recorded customer payment and asked Dolibarr to delete it. Two questions should have stopped the request. The first, "do you hold the right to manage payments?", was never asked; the route checked the invoice-deletion right instead. The second, "are you even allowed to touch the specific invoice this payment belongs to?", was not asked either. The payment simply vanished.

That distinction is at the heart of effective authorization testing. It is not enough to confirm that an API checks a permission. Security testing has to establish that the application checks the permission that actually governs the requested action.

Where the phantom debt comes from

Here is the quietly nasty part. In Dolibarr, an invoice's "paid" figure is not stored as a fact of its own. It is the sum of its payments. Remove one, and the sum drops, automatically, silently, with no separate record that anything was deleted.

So a fully-paid invoice, the moment its payment is destroyed, flips back to showing an outstanding balance. On the screen, and in every report that reads from it, money that genuinely arrived now looks owed. Delete the payments across a batch of invoices and you have manufactured debt that never existed, on books that will be trusted by whoever reads them next.

Why it lands harder than it looks

On paper this is a medium-severity bug, and in isolation that is fair. It does not hand anyone the keys to the system. But sit with what it does to a set of real books. Payments disappear with no audit trail. Paid invoices reopen. Reconciliation, the monthly ritual of matching what the bank says against what the system says, stops adding up, and the people doing it have no deleted-payment record to point at, because there isn't one.

The proof we kept coming back to was almost mundane, and all the more convincing for it: watching an outstanding balance climb on an invoice that had been paid in full, driven entirely by an account that was never supposed to be anywhere near the payment ledger.

This is exactly the kind of financial data integrity issue that can look harmless when reviewed only at the endpoint level. The permission check appears to be there, but it governs the wrong operation. A penetration test that exercises the action with deliberately mismatched privileges can expose that gap before it reaches production.

What actually closes this

The fix in 24.0.0 does exactly what the finding calls for: the delete-payment route now checks the permission that actually governs payments, not the one that happened to be sitting nearby, and it verifies the caller has access to the specific record before it acts.

The lesson generalizes past this one route. When a destructive action is gated behind a permission check, the question isn't whether a check exists, it's whether it's checking the right thing. A guard that enforces a plausible but wrong permission feels like security and is arguably worse than having none, because it passes every casual review. We saw the same shape play out with expense approvals elsewhere in this Dolibarr assessment, where users could sign off on their own expense reports because the check governing approval wasn't the one actually enforced.

Catching this kind of gap is what good penetration testing is for. It rarely shows up in a code read alone, since the permission decorator is right there on the route. It shows up when someone deliberately exercises the action with a mismatched, lower-privilege key and watches what the response actually allows.

The fix

Upgrade to Dolibarr 24.0.0. That closes this finding. Want to know where your own app disagrees with itself? Start with a free CodeAnt pentest.

Back to all nine findings in the Dolibarr research series

FAQs

Start Your 14-Day Free Trial

AI code reviews, security and quality trusted by modern engineering teams.

Table of Content
No headings found on page
Ship clean & secure code faster

Get Pentest Report

NO CC REQUIRED