CVSS 6.5 · Mass assignment on payroll fields (CWE-915) · Fixed in Dolibarr 24.0.0
The form that updates your profile refuses to let you change your job title, but leaves “salary” wide open, then shows you a receipt with the salary line blanked out, so it looks like nothing happened.
The attack in six steps
- The profile-update route copies your fields onto your record: standard API behaviour.
- It blocks a short list of obvious secrets: but salary, bonus and cost rates aren't on it. (this is the break)
- Send a new salary for your own account: from a user with zero payroll permissions.
- The write succeeds, but the reply hides it: the response omits the field it just changed, so a quick security test looks like nothing happened.
- Read the database before and after: the only way to see the new number is really there.
- It flows into the official payroll export: an employee rewrote their own pay. → payroll tampering.
Payroll is one of the few things in a company everyone agrees must be locked down. What each person earns, salary, bonus, the internal rates used to cost their time, sits behind a permission only HR and finance are meant to hold. The interface Dolibarr shows a human respects that boundary completely.
This is the story of how, underneath that respectable interface, the route that saves your own profile would quietly write your own salary, then hand you back a receipt with that very change hidden, so a casual API security test would swear nothing had happened.
It is a mass-assignment vulnerability, the same family as the admin-takeover one, but with a nastier twist: this one covers its tracks.
The short block-list that forgot the important fields
When you save your profile, the route takes the fields you sent and writes them onto your record. It does try to be careful. It keeps out a short list of obviously dangerous fields. But a block-list is only ever as good as its imagination, and this one's imagination stopped short. Salary, bonus and the internal cost and daily rates, the four fields that actually decide what someone is paid, were not on it.
So an ordinary user, holding no payroll permission at all, edits their own profile and includes a new salary. The route writes it. There is no check that this particular field belongs to a locked-down part of the system. It is simply another field in the pile, and the pile gets written.
This is the core of the CWE-915 mass-assignment issue: the API accepts fields based on what has not been explicitly blocked rather than limiting the request to fields the caller is actually authorized to modify.
The silence that hides it
Here is what makes this one genuinely sneaky. The reply you get back is run through a “cleaner” whose job is to strip sensitive fields before sending anything out. So the response leaves the salary line blank, not because the write failed, but because the reply is not allowed to show it.
The effect is that a quick API penetration test looks like a clean rejection. You send a new salary, the answer comes back with no salary in it, and every instinct says “nothing changed.” The write succeeded; the receipt just doesn't mention it. That gap between what happened and what the response admits is exactly where a bug like this hides for a long time.
Following the number all the way through
Because the response gives nothing away, the only way to catch the full impact was to stop trusting it. We read the database directly, before and after: the new salary was really there, written by an account with zero payroll rights. Then we followed that number where it counts, into the official payroll export, the report finance actually uses, and there it was too.
That is the difference between “a field can be set” and “an employee can rewrite their own pay and have it flow into the payroll run.” The first is a curiosity. The second is the finding.
For Dolibarr security testing, that distinction matters. A penetration test cannot stop at whether an API returns 200 OK or 403 Forbidden. It has to establish what the request actually changed, what permissions the caller had, and where that changed data flows next.
What closes it
The fix in 24.0.0 stops treating payroll fields as ordinary profile fields: they now require the permission that governs pay, rather than riding in on a self-profile update, and the change is no longer hidden from the very response that would let someone notice it.
There are two lessons stacked here. First, a block-list of “dangerous fields” is the wrong shape. It fails silently the moment someone adds a new sensitive field and forgets to list it; an explicit list of the fields a caller may set fails safe instead. Second, hiding a value in the response is not the same as protecting it in the database. A write you can't see in the reply is still a write.
For developers and security teams, this is also a reminder that API authorization testing needs to cover individual fields, not just endpoints. An endpoint can have authentication and authorization checks and still expose a sensitive field through mass assignment.
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
















