CodeAnt AI Security Research
CVE-2026-71504: Dolibarr API Flaw Enables Admin Takeover

Amartya Jha
CEO, CodeAnt AI
TL;DR
CVE-2026-71504 is a mass-assignment vulnerability (CWE-915) in Dolibarr ERP CRM's Members REST API, scored CVSS 8.3. A key holding only the two membership-desk rights, read-member and create-member, sends a user_id and a pass in a member create or update body, and Dolibarr copies both onto the linked user account and overwrites the administrator's password with no permission check. The User API refuses the same write. The Members API never copied the User API's guard. It is fixed in Dolibarr 24.0.0.
Send Dolibarr's User API a request to rewrite the administrator's password and it answers 403 Forbidden: User update not allowed. Send the same password, keyed to the same administrator row, through the Members API instead, and it answers 200 OK. One instance, one API key, one target account, and two verdicts.
The credit here belongs to Dolibarr's volunteer maintainers, who run this project on community time with a small commercial sponsor and no funded security team, and who had shipped the fix in the 24.0.0 release before this post existed. The gap is not that Dolibarr forgot how to check permissions. It is that the check lives in one REST class and was never copied into its sibling.
What is CVE-2026-71504?
CVE-2026-71504 is a mass-assignment vulnerability (CWE-915) in the Members REST API of Dolibarr ERP CRM, the open-source PHP business suite used for accounting, invoicing, and membership management. The Members::put() and Members::post() endpoints authorise a request with one coarse check for adherent.creer, then blindly copy every remaining JSON body key onto the member object.
Two of those keys, user_id (which login account the member links to) and pass (a new plaintext password), are not member attributes the caller is entitled to control. Dolibarr synchronises them onto the linked llx_user account, rewriting that user's password with no permission test, so a caller with membership-desk rights gains full instance takeover.
It carries a CVSS 3.1 base score of 8.3 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L) and was patched in the 24.0.0 release. This is one case of a broader pattern we saw across Dolibarr's REST layer, two write surfaces disagreeing about who may touch the same rows, which we document as split-brain authorization.
CVE-2026-71504 Impact at a Glance
CVE-2026-71504 is a high-severity mass-assignment vulnerability (CWE-915) in Dolibarr's Members REST API. A low-privilege membership account can abuse attacker-controlled fields such as user_id and pass to overwrite the password of a privileged Dolibarr user, including the administrator, leading to full instance takeover. CWE-915 specifically covers cases where externally supplied attributes are not properly restricted to the fields an attacker should be allowed to modify.
Attribute | Details |
|---|---|
CVE | CVE-2026-71504 |
Severity | High |
CVSS Score | 8.3 |
CWE | CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes |
Vulnerability Type | Mass Assignment |
Affected Component | Dolibarr Members REST API |
Required Privileges | Low |
Required Permissions |
|
Authentication | Valid Dolibarr API key |
Attack Vector | Network |
User Interaction | None |
Primary Impact | Administrator account takeover |
Confidentiality Impact | High |
Integrity Impact | High |
Availability Impact | Low |
Affected Build Tested | Dolibarr develop branch, commit |
Fixed Version | Dolibarr 24.0.0 |
The key takeaway: the attacker does not need administrator privileges or a user permission. A legitimate membership-level API key is enough to reach the vulnerable write path and cross the boundary into administrator account control.
Who Is Affected by CVE-2026-71504?
adherent.creer is the permission you grant the person at the membership desk, the registrar at an association, club, union, or nonprofit who adds and edits member records all day. Dolibarr's permission matrix presents Members and Users as unrelated modules, and adherent.creer is grantable with no user right at all, so an administrator handing out that box reasonably believes they granted membership access and nothing more.
This bug converts that role into full administrator authority. The legitimate administrator is locked out the instant the write lands, and if no second admin exists, recovery means an out-of-band database or CLI reset.
The score's A:L rather than A:H reflects exactly that ceiling, one account's availability is disrupted and recoverable by an operator with shell access. Confidentiality and integrity are both H, because the attacker now owns the instance. Anyone holding an API key for such an account, a leaked member-desk key included, can do this.
What Can an Attacker Do With CVE-2026-71504?
A Dolibarr account holding only the two Members permissions, read a member (adherent.lire) and create or edit a member (adherent.creer), can overwrite the installer-created administrator's stored password with a string of its choosing in one authenticated API call.
The real administrator is locked out at once, and the attacker's password logs in with full admin authority. Dolibarr 24.0.0 fixed it.
The 403 and the 200, One Request Apart
The clearest way to see CVE-2026-71504 is to send the same password-change intent through two different Dolibarr API routes.
The User API correctly blocks the request because the attacker does not have permission to modify user credentials:
The response is:
The same attacker then sends the password change through the Members API, targeting the same administrator account:
This time, the request succeeds.
The difference is not the API key, the target user, or the password. The difference is the route. The User API enforces credential-specific authorization, while the Members API accepts user_id and pass as ordinary request fields and synchronizes them to the linked user account.
That turns a membership-level permission into an administrator password reset:
403 through Users → 200 through Members → administrator password changed → full admin takeover.
How CVE-2026-71504 Works
The right way to handle a pass field in a Dolibarr REST endpoint is already written and working, in the User API. In htdocs/user/class/api_users.class.php, the update path (lines 441-456) does two things the Members path never does.
First, it refuses to accept credential columns at all. pass_crypted, pass_indatabase, pass_indatabase_crypted, pass_temp, and api_key are met with a 405. Second, it treats a plaintext pass as privileged, gating it on user.user.password (or user.self.password when you are editing your own row) before it will write.
That is why the control request in the opening snippet dies at api_users.class.php:429 with Forbidden: User update not allowed. The key holding only Members rights never clears the User API's front door.
None of that discipline reaches htdocs/adherents/class/api_members.class.php. Same codebase, same request, same target row, and a different file that simply never made the call. The allow-and-deny logic is not missing from Dolibarr. It is missing from this endpoint.
Inside the Members Write Path
Members::put() makes exactly one authorisation decision, at api_members.class.php:412:
Line 412 checks that you can create members. Line 422 scopes which member row you may touch, not which of its fields. Then the loop at 426-463 assigns every request-body key straight onto the object, so user_id and pass land as if they were ordinary member data. Line 467 is the detonator. update() is called with no sync flags, and the defaults do the damage.
Follow it into the backing model. The signature at adherent.class.php:794 reads update($user, $notrigger = 0, $nosyncuser = 0, $nosyncuserpass = 0, ...), and both sync flags default to 0, meaning yes, push these changes onto the linked user account, password included.
A non-empty $this->pass then flows through lines 909-921 into setPassword(), and at 1403-1417 that routine fetches the User named by the attacker-supplied $this->user_id and calls $luser->setPassword(...) on it with no permission test on the acting user. The block at 923-944 repoints llx_user.fk_member onto the attacker's member in the same pass.
Members::post() carries the identical defect at lines 373-390, and its create() path hits :731, where the fourth argument to update() is 0, so a freshly created member syncs its password too.
CVE-2026-71504 Proof of Concept
Everything below ran on an isolated Dolibarr 25.0.0-alpha build (commit ab7e604, develop branch) in Docker, configured the way a careful operator would ship it, with install locked, production mode on, HTTPS forced, and CSRF at its strictest. None of that hardening touched the outcome. No live or third-party install was ever contacted.
The control comes first, to prove the key genuinely cannot do this the honest way. PUT /api/index.php/users/1 with body {"pass":"F002-Owned-2026!"} returns 403 Forbidden, {"error":{"code":403,"message":"Forbidden: User update not allowed"}}. Good.
Now the same intent through Members. POST /api/index.php/members with {"typeid":1,"morphy":"phy","lastname":"PoC","statut":1,"user_id":1,"pass":"F002-Owned-2026!"} returns 200 OK and a new member id, with no pre-existing member record required. The PUT /members/{id} form does it too, returning 200 with "user_login":"admin" in the body.
In the database, the administrator's pass_crypted is now a bcrypt hash of the attacker's string and fk_member has moved from NULL to the attacker's member. The old password fails login with Bad value for login. The attacker's string logs in 302 -> /admin/index.php, and /admin/modules.php, gated on $user->admin at admin/modules.php:104-106, renders the setup console at 200 with 81 module links.
An anonymous request to that URL also returns 200 but serves the login form with zero module links, proof the session, not the status code, is what changed.
One quieter detail rides along in that 200 body. The response serialises pass_indatabase_crypted, handing back the very hash it just wrote. That separate exposure is the seam our password-hash disclosure finding, CVE-2026-71511, pulls on from the read side.
Impact of CVE-2026-71504
The impact of CVE-2026-71504 goes far beyond changing a password. A low-privilege membership account can cross the application's intended privilege boundary and take control of the Dolibarr administrator account.
Once the administrator's password is overwritten, the attacker can authenticate with full administrative privileges and access the instance's configuration and business data. The legitimate administrator is immediately locked out, while recovery may require an out-of-band database or CLI password reset if no second administrator exists.
The CVSS 8.3 score reflects this impact:
Confidentiality: High, the attacker gains access to data available to the administrator.
Integrity: High, the attacker can modify administrative settings, users, and other application data.
Availability: Low, the legitimate administrator can be locked out, but the instance can be recovered by an operator with appropriate system access.
The vulnerability also exposes the administrator's newly generated password hash through the API response. While the hash itself is not the basis for the CVSS confidentiality rating, it creates an additional credential-disclosure risk.
Impact | Result |
|---|---|
Initial privilege | Low-privilege membership account |
Compromised account | Dolibarr administrator |
Privilege escalation | Membership permissions → full administrator access |
Account takeover | Administrator password can be replaced |
Data access | Administrator-level access to the Dolibarr instance |
Integrity | Attacker can modify administrative and business data |
Availability | Legitimate administrator can be locked out |
User interaction | None required |
Password cracking | Not required |
Who Actually Holds This Permission?
The vulnerable adherent.creer permission is not an administrator permission. It is a routine Members permission intended for users who create or edit member records.
In a typical Dolibarr deployment, this could belong to a membership-desk employee, registrar, association administrator, club staff member, or another user responsible for maintaining member information.
The important distinction is that adherent.creer does not imply permission to manage Dolibarr user accounts or change administrator credentials. Dolibarr's permission model treats Members and Users as separate areas of access.
CVE-2026-71504 breaks that separation.
A user who is legitimately allowed to create or edit members can supply a different user_id and a new pass value. The Members API then synchronizes those fields to the linked user account without checking whether that user is authorized to modify the target account.
CVE-2026-71504 Fix: Dolibarr 24.0.0
Dolibarr 24.0.0 brings the Members write path under the same discipline the User API already enforced. Credential columns are rejected before the assignment loop rather than copied through it, a plaintext pass is gated on the password right rather than accepted from anyone with adherent.creer, and the linked-user sync no longer fires on wide-open default flags. The serialised pass_indatabase_crypted is cleaned from the response as well.
The general lesson outlives this endpoint. Blind mass assignment, foreach ($request_data as $field => $value) { $obj->$field = $value; }, is a loaded weapon, because the set of writable fields silently becomes whatever the client sends, privilege-crossing columns included.
The durable fix is an explicit per-endpoint allow-list of the fields a caller may set, not a per-field patch chased after each new column proves dangerous. And when one REST class already gates a sensitive field correctly, the next one that touches the same table is not a fresh design problem. It is a copy-paste you owe yourself.
The Bigger Security Lesson
CVE-2026-71504 is not just a password-reset bug. It demonstrates how mass assignment can turn a legitimate low-privilege API permission into a privilege-escalation path when sensitive fields are not explicitly controlled.
The Members API correctly checks whether the caller has permission to create or edit members. The problem comes next: it treats every field in the request body as writable. Fields such as user_id and pass are not ordinary member attributes—they control a linked user account and its credentials.
That creates a dangerous chain:
Membership permission → unrestricted fields → linked user account → administrator password change → full instance takeover.
The safer approach is to use an explicit allow-list of writable fields for every API endpoint. Sensitive operations such as password changes, account linking, API-key management, and role changes should have their own authorization checks rather than being exposed through generic object updates.
CVE-2026-71504 also highlights why testing only endpoint-level permissions is not enough. An API may correctly reject a direct request to modify a user while another endpoint touching the same underlying user record quietly allows the same operation.
For security teams, the key questions are:
Can this API field modify another security-sensitive object?
Can a low-privilege role control fields belonging to a higher-privilege account?
Are linked-object updates protected by the same authorization rules as direct updates?
Does every endpoint enforce an allow-list rather than accepting arbitrary request fields?
Can a seemingly harmless permission be chained into privilege escalation?
CodeAnt AI's penetration testing identified CVE-2026-71504 by testing this boundary in the Members API and validating that a membership-level account could use attacker-controlled fields to modify a privileged administrator account. This is precisely the type of API privilege-escalation path that can remain invisible when security testing checks individual endpoints but does not test how permissions, fields, and linked objects behave together.
The broader lesson is simple: authorization must control not only which endpoint a user can call, but also which fields they can change and which security boundary those changes can cross.
One Member Permission, Full Admin Takeover
CVE-2026-71504 turned a routine membership permission into full Dolibarr administrator takeover. The Members API allowed low-privilege users to control sensitive fields like user_id and pass, making an admin password reset possible through an API they were never meant to control. CodeAnt AI found the flaw by testing whether a membership-level API key could cross into privileged account control. The lesson: if an API lets users write fields they shouldn't control, one ordinary permission can become an admin takeover.
Related reading:
The pattern this belongs to, two doors into the same rows, one that checks and one that does not, is laid out in our pillar on split-brain authorization in Dolibarr's REST API.
Its mirror image lives in the same Members API. This finding writes credentials in, while CVE-2026-71511 reads the stored password hash out through the very field this endpoint hands back in its response.
FAQs
What is CVE-2026-71504?
What permissions are required to exploit CVE-2026-71504?
What is a mass assignment vulnerability, and how does CVE-2026-71504 work?
Why does the User API block this write but the Members API allow it?
Can the administrator recover access after CVE-2026-71504 is exploited?
What permissions does an attacker need to exploit CVE-2026-71504?

Get Pentest Report
