CodeAnt AI Security Research

CVE-2026-71505: Dolibarr API Flaw Enables Account Takeover

Amartya | CodeAnt AI Code Review Platform
Amartya Jha

CEO, CodeAnt AI

TL;DR

CVE-2026-71505 is a broken-object-level-authorization flaw (BOLA, CWE-639) in Dolibarr ERP CRM's Third Parties REST API, scored CVSS 8.1. A non-admin API key holding only the create-third-parties right overwrites the customer-portal password of a company the same API refuses to let it read, then signs in as that company and reads its invoices. The read route runs the per-object ownership check and the write route skips it. It is fixed in Dolibarr 24.0.0.

Detail

CVE-2026-71505

Product

Dolibarr ERP/CRM

Component

Third Parties REST API

Vulnerability

Broken Object Level Authorization (BOLA)

CWE

CWE-639 — Authorization Bypass Through User-Controlled Key

Severity

High

CVSS

8.1

Attack requirement

Low-privilege API account with societe.creer

Impact

Customer portal account takeover and unauthorized invoice access

Affected build

Develop-branch build at commit ab7e6040

Fixed version

Dolibarr 24.0.0

Recommended action

Upgrade to Dolibarr 24.0.0 or later

CVE-2026-71505 is a broken-object-level-authorization flaw in the Third Parties REST API of Dolibarr ERP CRM. It is a BOLA vulnerability example straight out of the textbook, filed formally as CWE-639. Dolibarr is the open-source PHP suite that runs invoicing, accounting, and a customer-facing portal for small and mid-size businesses.

A low-privilege API user who holds nothing but the create-third-parties right can overwrite the customer-portal password of any company in the instance, including companies the very same API refuses to let them read, then sign into that company's portal and page through its invoices.

It rates 8.1 High, vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N. Dolibarr is carried by volunteers with no funded security function and a single small commercial sponsor behind it, and that team had this shut in the 24.0.0 release before a word of this went public. Patched installs are not exposed.

What is CVE-2026-71505?

CVE-2026-71505 is a broken object-level authorization vulnerability (CWE-639) in the Third Parties REST API of Dolibarr ERP CRM, scored CVSS 8.1 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N). The write route that sets a company's customer-portal password checks only that the caller holds the create-third-parties right, then fetches the account keyed on the caller-supplied company id with no per-object ownership check.

The read route on the same nested resource runs that ownership check and returns 403, so a low-privilege key is refused a read on a company yet allowed to reset its portal password one request later. The attacker then signs into the WebPortal as that company and reads its invoices. It is fixed in Dolibarr 24.0.0.

CVE-2026-71505 Impact at a Glance

Detail

CVE-2026-71505

Severity

High

CVSS Score

8.1

CWE

CWE-639 — Authorization Bypass Through User-Controlled Key

Vulnerability

Broken Object Level Authorization (BOLA)

Affected Component

Dolibarr Third Parties REST API

Privileges Required

Low-privilege API account with societe.creer

User Interaction

None

Attack Vector

Network

Primary Impact

Customer portal account takeover

Additional Impact

Unauthorized invoice access and password-verifier disclosure

Fixed Version

Dolibarr 24.0.0

Who Is Affected by CVE-2026-71505?

CVE-2026-71505 affects Dolibarr deployments where the vulnerable Third Parties REST API write routes are exposed. Exploitation requires an authenticated, non-admin API user with the societe.creer permission and a valid DOLAPIKEY. The attacker does not need access to the target company's account or any interaction from the victim.

The vulnerability was confirmed on the develop-branch build at commit ab7e6040 and is fixed in Dolibarr 24.0.0. Organizations running affected versions should upgrade to 24.0.0 or later.

Affected Detail

Status

Product

Dolibarr ERP/CRM

Component

Third Parties REST API

Required Permission

societe.creer

Authentication

Valid Dolibarr API key

Privilege Level

Low

Confirmed Build

Develop branch, commit ab7e6040

Fixed In

Dolibarr 24.0.0

What Can an Attacker Do With CVE-2026-71505?

An attacker with a low-privilege Dolibarr API key can target another company's portal account by supplying its company ID to the vulnerable write route.

The attack works because the API checks whether the attacker has permission to create or update third parties, but does not verify whether they are authorized to modify the specific company identified in the request.

In a successful attack, the attacker can:

  1. Target another company's ID through the Third Parties REST API.

  2. Change that company's customer-portal password.

  3. Log into the Dolibarr WebPortal using the new password.

  4. Access information available to that customer, including invoices.

  5. Potentially create, modify, or delete portal accounts belonging to other companies.

  6. Receive password verifiers in the API response for accounts they were not authorized to read.

No administrator privileges, CSRF token, password cracking, or victim interaction are required.

The most important proof of the authorization failure is the difference between the read and write requests: the same API key receives 403 Forbidden when attempting to access the target company, but 200 OK when modifying its portal account.

CVE-2026-71505 Exploit: The 403 Read and 200 Write

Ask the API to read company number 2 and it returns 403 Forbidden. Ask it, with the same key one request later, to replace company number 2's portal password, and it returns 200 OK. Nothing moved between those two calls except the HTTP verb and the tail of the URL.

From there the attacker logs into Dolibarr's WebPortal as that company and reads the invoice list the portal renders for it, references, amounts, and paid status.

How the Dolibarr BOLA Vulnerability Works

Dolibarr's REST layer authorizes in two stages. A right answers whether this user may perform this kind of action, here societe.creer, the create-and-update-companies permission. A separate call, DolibarrApi::_checkAccessToResource(), answers the harder question of whether this user may act on this specific company.

The first is a role check. The second is the object check, and the object check is exactly the thing a BOLA leaves out. That split, one door that enforces both and another door that enforces only the first, is one architectural mistake Dolibarr repeated across nine findings, and this is its cleanest single expression.

The read route for these nested portal accounts does it correctly. getSocieteAccounts() (in htdocs/societe/class/api_thirdparties.class.php, lines 2235-2243) runs both stages back to back:




The object check on line 2241 is the whole ballgame. It is why the GET returns 403. The write route, putSocieteAccount(), lives about three hundred lines down in the same class. It runs the first block and never runs the second.

Why Dolibarr's Write Route Allows the BOLA

putSocieteAccount() (lines 2527-2569) opens with a single permission check, then goes straight to the database (lines 2529-2533):




$id is the {id} path segment, the third-party number supplied by the caller. The SELECT is keyed on that attacker-controlled fk_soc with no ownership reconciliation afterward. That is the distinction between a generic missing-authorization bug and a CWE-639 example. The object is fetched by the user-controlled key, so whatever number you put in the URL is the row you get.

If you have ever chased the idor vs bola distinction, they are the same defect here, a per-object access decision that the code simply never makes on this path.

It gets worse at the assignment step (lines 2550-2561), where the request body is copied field-by-field with no allowlist:




A pass field in the body rides straight through this loop onto the object. CommonObject::updateCommon() (htdocs/core/class/commonobject.class.php, lines 11157-11195) then spots the plaintext password and hashes it into pass_crypted, the exact bcrypt verifier the portal login checks against.

So a broken object level authorization on a REST API, combined with an unfiltered mass-assignment, turns "create companies" into "set any customer's portal password."

CVE-2026-71505 Proof of Concept

Every request below hit a Dolibarr container on my own laptop, a develop-branch build at commit ab7e6040, hardened to a production profile (install locked, production mode on, HTTPS forced, CSRF at its strictest), seeded with fixture companies I created. No live deployment and no real customer's portal was ever touched.

The attacking principal is a non-admin user carrying exactly two rights, societe.lire and societe.creer, plus its own DOLAPIKEY. First, the control. The same key asks to read the victim company and its portal-account collection:




Both come back Forbidden: Access not allowed for login audit_account_attacker on this thirdparty. The API is unambiguous. This user may not see company 2.

Now change only the method and hand it a new password:




The write succeeds against the object the same key was refused a request earlier. The 200 body even hands back "login":"portal_customer_2" and the account's pre-update pass_crypted, a stored verifier the caller had no read access to, because the routine serializes the object before the new hash lands on it.

Reading the row afterward confirms the verifier changed and that fk_user_modif now records user id 8, the attacker. A password_verify() of the attacker's chosen plaintext against the new verifier returns true. The victim's original returns false.

The takeover then needs no cracking and no victim action. Log into the WebPortal as portal_customer_2 with the password just set, request ?controller=invoicelist, and the portal renders DOL02-CONFIDENTIAL-260804, the marker stamped onto the victim's invoice, at 100 EUR, status Paid.

The invoice controller (htdocs/webportal/controllers/invoicelist.controller.class.php, lines 77-85) filters on the logged-in company's id, so the session sees exactly that company's books.

What an Attacker Gains From CVE-2026-71505

societe.creer is Dolibarr's Create and update third parties permission (llx_rights_def.id = 122). In a real deployment that is the salesperson onboarding a new client, the partner account wiring up a customer, or the integration user syncing companies in from a CRM.

None of those roles was ever meant to imply that the holder may read, and rewrite the credentials of, every other company in the tenant. On this route it does. One junior sales login, or one API key baked into a middling integration, is the whole prerequisite.

What that principal gains is a full customer-portal account takeover with no interaction from the customer, no CSRF token, and no reset flow to intercept. They just set the password. With the session in hand they read another company's commercial records, and the same 200 response leaks stored bcrypt verifiers for accounts they cannot read.

Be clear about the ceiling. Those leaked hashes are disclosed, not cracked. They are offline-cracking and credential-reuse candidates, an aggravating factor rather than the basis of the Confidentiality rating, which rests on the portal invoice read that was demonstrated.

The same unguarded path also forges and deletes portal accounts on other people's companies. Because Dolibarr ships no self-service portal reset and fires no notification on the change, a locked-out customer has no signal and no recovery short of a back-office admin.

CVE-2026-71505 Fix and Dolibarr 24.0.0 Patch

The 24.0.0 patch is the missing line put back. _checkAccessToResource('societe', $id) goes immediately after the right check in putSocieteAccount(), matching the read route, and the same guard was added to the four sibling write routes, create, replace-or-create, and both delete paths, that shared the omission.

The lesson generalizes past this one function. When two surfaces write the same rows and each author has to remember to re-type the object check, one of them eventually will not.

The durable fix is structural. Resolve and authorize the owning third party in the API base class for every nested /thirdparties/{id}/... route before the handler body runs, so a brand-new write route fails closed by default. And a credential change should be its own permissioned operation, not a side effect a generic mass-assignment loop can trip into. Reject pass_crypted on the way in, and never serialize it on the way out.

The Bigger Security Lesson

CVE-2026-71505 is a good example of why API authorization cannot stop at checking whether a user has permission to perform an action. The API also needs to verify whether that user is authorized to perform the action on the specific object identified in the request.

Here, the read route already enforced that distinction. The write route did not. That small inconsistency turned a routine societe.creer permission into a path for customer-portal account takeover.

For security teams, this is exactly the kind of authorization gap that can be missed when API endpoints are reviewed individually. Read and write routes for the same resource need to be tested against the same object-level authorization rules, including cases where a user can write to an object they cannot read.

This is where CodeAnt AI can help security teams identify authorization gaps and insecure data flows during code and security analysis, giving developers another layer of protection against vulnerabilities that can otherwise reach production.

The broader lesson is simple: if a user can modify an object they are not authorized to access, endpoint-level permissions are not enough. Object-level authorization has to be enforced consistently on every path that touches that object.

The API Could Change What the User Couldn’t See

CVE-2026-71505 turned a low-privilege API permission into customer-portal account takeover. The read route blocked access to another company, but the write route skipped the same object-level check, letting an attacker change that company's portal password and access its invoices. CodeAnt AI Penetration Testing identified the gap by testing what the attacker could read versus what they could modify, then validating the takeover path end to end. The lesson is simple: API authorization isn't just about who can call a route. It's about who can act on every object that route exposes.

Related research: This finding is one node in the split-brain authorization pattern, the pillar that walks through why the same read-guarded, write-unguarded asymmetry recurs across Dolibarr's REST surface. For the sibling that follows the identical missing object check on a Third Parties write route but ends with money rather than data, see CVE-2026-71507, where the unchecked write redirects an outbound SEPA payment file.

FAQs

What is CVE-2026-71505?

What privileges are required to exploit CVE-2026-71505?

Which Dolibarr versions are affected, and how is CVE-2026-71505 fixed?

What is the difference between IDOR and BOLA in this finding?

What is a BOLA vulnerability, and how does CVE-2026-71505 illustrate it?

Are the password hashes leaked by CVE-2026-71505 crackable?

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
AI Native SAST and SCA

Get Pentest Report

NO CC REQUIRED