AI Pentesting

CVE-2026-71503: How a Single Reflected XSS Bug Mints a Rogue Admin in Dolibarr

Amartya | CodeAnt AI Code Review Platform
Sonali Sood

Founding GTM, CodeAnt AI

CVSS 9.3 · Reflected XSS (CWE-79), chained to admin creation · Fixed in Dolibarr 24.0.0

Picture a visitor badge that quietly prints itself a duplicate at the front desk, and the guard waves it through because the request never left the building.


The attack in six steps

  1. A link that looks like ordinary work: A URL pointing at a routine admin setup page, with a tiny instruction hidden in the web address.

  2. An admin opens it: It arrives by email, ticket or chat and looks like an internal settings page; nothing on screen changes.

  3. The page runs the hidden instruction: It drops that address value straight into its own scripting, missing one safety step, so the attacker's snippet runs as code. (this is the break)

  4. The script reads the page's own security token: The anti-forgery token Dolibarr prints into every page for itself; the injected script just reads it, no theft required.

  5. It asks Dolibarr to create an admin: Using that token and the admin's own logged-in session, a create-user request that looks completely legitimate.

  6. A second administrator now exists: With a password and API key the attacker chose, surviving after the browser closes. → full admin.

Dolibarr runs the back office for a lot of small and mid-sized businesses: invoices, customer records, supplier bank details, payroll, the works. Whoever administers it can touch all of it, which makes the admin account the single highest-value target in the entire deployment.

This is what happens when that account gets duplicated without anyone noticing. One link, opened once, by an admin doing nothing more than their normal Tuesday, and Dolibarr ends up with a second administrator it never meant to create, complete with a password and API key the attacker picked in advance.

The link itself gives nothing away. It points at a routine internal setup screen, the kind an admin opens several times a week to add a custom field. The page loads, nothing appears to happen, and the admin moves on. In that pause, a fresh account is written into the database.

What makes this one worth walking through is that the obvious version of the attack, the one most reflected XSS vulnerability reports lead with, failed outright. That failure is exactly what pushed the exploit toward something more interesting.

The reflex that didn't work

When you find a bug that lets you run a snippet of code on someone else's page, the first instinct is boring and predictable: steal the login cookie, become that user. We tried. It came back empty.

Dolibarr, sensibly, hides its session cookie from scripts, a setting that tells the browser "code running on this page may not read this." So the textbook move was dead on arrival. On most days, that is where the story ends and the bug gets filed as low severity: yes, you can run a script, but there is nothing here worth taking.

From an application security testing perspective, this is an important distinction. Finding reflected XSS does not automatically tell you what the vulnerability can achieve. The real question is what security-sensitive actions the injected code can perform within the victim's authenticated session.

The moment it turns

Except there was something worth using, not stealing.

To protect its own forms, Dolibarr prints a small security token into every page. It is an anti-forgery measure: the app's own buttons read that token and send it back, so the server can tell a genuine click from a forged one. It is a good defence. But a token printed into the page for the page's own scripts to read is, by definition, readable by any script on the page, including the one the attacker just slipped in.

So the injected script does not need to steal anything. It reads the token sitting right there in the page, and then it does the single thing that token unlocks: it asks Dolibarr to create a new user, as the admin, from the admin's own logged-in browser. To the server it looks like the administrator calmly filling in the "add user" form. The browser attaches the admin's session automatically, because the request is going to the very same site it came from. The request came from inside the building, so the guard waves it through.

And no alarm sounds, for a mundane reason: a fresh Dolibarr install does not switch on the browser-level protection that would have refused to run the injected script in the first place. It is off by default, so nothing stands in the way.

What the attacker walks away with

The important thing about that new account is what it isn't. It is not a hijacked browser tab that vanishes when the admin logs out. It is a real administrator, a real row in the database, with a password the attacker chose and an API key the attacker chose. It stays there after the browser closes, after the laptop sleeps, after the admin goes home.

That one detail changes the entire clean-up. Picture the team noticing something odd and doing the sensible thing: reset the admin's password. It does not help. The attacker was never using the admin's password, they made their own account. And the API key on that account keeps working quietly in the background, reading the whole user directory through Dolibarr's own interface, long after everyone assumes the incident is closed.

There is a limit worth being honest about: some records, specific invoices, customers, payroll, stay behind Dolibarr's normal permission checks, which have no special "admins skip this" bypass. But a full administrator can grant itself any permission it likes with one more request, because simply being marked an administrator is enough to unlock that. So the limit is a speed bump, not a wall.

And it is not about one page or one feature. The vulnerable screen is built from a template Dolibarr reuses across 80 different pages. We confirmed the same trick on the Products, Agenda and Third-parties setup screens. Turning off one module only moves the unlocked door somewhere else.

What actually closes this

The frustrating part is that Dolibarr already had the fix sitting in its own codebase. One function exists purely to make a value safe to print into a script, and it is used correctly on plenty of other pages. This particular screen just never called it.

The value pulled from the URL was filtered for a handful of bad characters on the way in, which is not the same thing as escaping it properly on the way out, and that gap sat behind eighty near-identical pages.

Dolibarr 24.0.0 closes it. The value now runs through the correct escaper before it reaches the page, and because the fix lives in the shared template, all eighty pages get protected in one patch. If you are on an affected build, upgrading is the entire remediation, there is no workaround or config flag to reach for in the meantime.

This finding sits a little apart from the rest of our Dolibarr research, which we reported to the Dolibarr Foundation through the same coordinated vulnerability disclosure process as the other eight findings. Most of that work traces missing permission checks between two different doors into the same room, like the portal account takeover via broken object-level authorization we found elsewhere in this series. This one is a plain encoding mistake, but it earns its place in the writeup for the same reason: a safe function already existed one line away, and a single template skipped it.

It is also a reminder of what separates a source-code review from real API penetration testing. Reading the code tells you a sink is unsafe.

Actually chaining the reflected XSS through a live session is what tells you it can hand an attacker a persistent admin account, and that is the gap between a low-severity ticket and a CVSS 9.3.

The fix: upgrade to Dolibarr 24.0.0. Curious where your own application quietly disagrees with itself the same way? 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