CVSS 8.1 · Broken object-level authorization (CWE-639) · Fixed in Dolibarr 24.0.0
The clerk who shows you a file first checks it's yours. The clerk who resets the password on that file never checks, so you can change the lock on a company you're not even allowed to look at.
The attack in six steps
- Try to read a company you don't own. Your low-privilege key is correctly refused. (403 Forbidden)
- Call the write route instead. The path that sets that company's customer-portal password. (this is the break)
- It only asks: can you create companies? Never "is this company yours?"
- Set the portal password to one you choose. The write lands on a company you can't even see. (200 OK)
- Sign into the customer portal as them with the password you just set.
- Read their invoices. Records you were forbidden to read directly. → another company's private billing.
Dolibarr can hand each of a company's customers a small self-service portal, a place to log in and see their own invoices. Which means Dolibarr is holding, on behalf of every one of those customers, the keys to a private account. The entire point of that arrangement is that one customer can never see another customer's invoices.
This is the story of a low-privilege account that could not so much as look at a company's record, and used a second, unguarded door to reset that company's portal password, log in as them, and read their invoices anyway.
The account in question is deliberately weak. It can create companies; it cannot read the ones it does not own. When it tries to read the victim's record, Dolibarr does exactly the right thing and refuses. On the read side, the walls are solid. The mistake is on the write side.
The door that checks, and the door that doesn't
Before this finding we had already gone over the customer-portal read paths, the ones that hand information back, and found them careful. Ask to read a company you do not own and you are turned away, per company, correctly. It would have been easy to tick the box and call the whole area safe.
The discipline that paid off was refusing to assume the write paths behaved the same way. Reading and writing are different doors, even when they lead to the same room, and they have to be tested separately. That distinction is central to good API security testing: an authorization control on a read endpoint does not prove that the corresponding write endpoint enforces the same boundary.
The write path in question sets a company's portal password. And it asks a much lazier question at the entrance. Instead of “are you allowed to touch this company?”, it asks only “are you the kind of account that can create companies at all?” The attacker's account can create companies, so it sails through. It never has to prove the company is its own.
Setting the lock on a door you can't see
So the attacker takes a company it was just told it may not read, and writes a brand-new portal password onto it. Dolibarr accepts the write. Then the attacker walks around to the customer portal, signs in as that company with the password it just set, and the invoices, the ones meant to be private to that customer forever, open up on screen.
The read wall was never breached. It was simply walked around, through a write path that forgot to ask the same question.
That is the defining shape of a broken object-level authorization (BOLA) vulnerability. The API knows that access should be restricted at the object level, but one route checks only a broad permission instead of whether the caller is authorized to access the specific company identified in the request.
Why this shape keeps happening
This is one of the cleanest examples of the pattern that runs through most of our Dolibarr security research. Enforcement on the way in tells you nothing about the way out. A team hardens the read paths, watches them hold, and reasonably assumes the feature is safe, while a sibling write path, added at a different time by a different hand, checks a weaker condition, or none at all.
The impact is concrete and it compounds. Every company with a portal account is exposed, not just one. An attacker who can create companies, a low bar in many deployments, can reset portal passwords across the whole customer base and read each customer's private billing in turn. For a business whose customers trust it to keep their invoices to themselves, that is exactly the kind of quiet authorization vulnerability that does lasting damage.
This is also why BOLA belongs in API penetration testing rather than being treated as a purely theoretical code issue. The important question is not only whether an endpoint contains an authorization check, but whether the check actually protects the specific object the request is trying to modify.
What closes it
The fix in 24.0.0 makes the write path ask the same per-company ownership question its read sibling already asked, the same check, applied to both doors instead of one.
The takeaway we wrote down afterwards is worth repeating: test read and write as separate doors, always. A boundary that is enforced in the place you happen to look first is not proof of a boundary. It is proof of one door.
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



Top comments (0)