The Profile Page That Handed Out Admin: CVE-2026-16772 in Akaunting

Louis Sanchez August 14, 2026 8 min read

Most privilege-escalation bugs make you work for it. You chain a file write into code execution, or you abuse a token that was never meant to leave one screen, or you find the one admin endpoint that forgot its middleware. The one we found in Akaunting asks for none of that. You open your own profile, you add the admin role to the form, and you save. The server makes you an administrator because you told it to.

Akaunting is a widely used open-source accounting and invoicing platform. Small businesses run their books on it, usually with a handful of staff who each hold a limited role: an employee here, an accountant there, a manager over the top. The whole reason those roles exist is so that a limited user cannot do administrator things. This bug lets a limited user do the most administrator thing there is, which is to stop being limited.

The short version

CVE-2026-16772 is a privilege escalation (CWE-862/CWE-269) in Akaunting. When a user saves their own profile, the update job reads the list of roles out of the submitted form and applies it directly, with no check on whether that user is allowed to assign roles. A user who holds only the default "update your own profile" permission — which the seeded manager and accountant roles both carry — can add the administrator role to their own account and take full control of the installation. CVSS 8.8 High. There is no patch.

No fix available

This is being published after CERT/CC coordination (VU#737420) because the vendor did not respond. If you self-host Akaunting, the mitigations near the end of this post are what you have to work with right now: audit who holds the admin role, and watch for profile updates that add one.

One call that trusts the form

Updating your profile in Akaunting dispatches a job that saves the submitted fields. Your name, your email, your landing page — and, if the request contains it, your roles. The job applies the roles with a single call that synchronizes your account to exactly the list the form carried:

The problem is not that the code is complicated. It is that the code is trusting. There is no check, anywhere on that path, that asks whether the person saving the form is allowed to hand out roles. The job's own authorization method guards two narrow edge cases — it stops you from disabling yourself, and it handles an orphaned-company situation — and then it takes the role list at its word.

The controller in front of the job is the part that makes this reachable. It is written to let you edit your own record, which is entirely reasonable: you should be able to change your own name and email. The trouble is that "edit your own record" and "assign your own roles" went through the same door. The permission to do the first was quietly treated as permission to do the second. They are not the same permission, and they should never have shared a code path.

The sibling that got it right

Here is the part that always makes these worth writing up. Akaunting's JSON API manages users too, and on the API the same operation is gated correctly. The API user controller inherits the update-auth-users permission from its base controller, so an ordinary account cannot use the API to touch roles it has no business touching. The knowledge of how to check was already in the codebase. It was already applied, correctly, one controller over.

It just was not applied on the web self-update path — the one an ordinary logged-in user actually reaches by clicking "Edit profile." The right check existed. It guarded the door most attackers would not bother with and left open the one every user walks through.

That gap between the two paths is also why we scoped the finding carefully. Early on it looked like it might reach further — a cross-tenant variant through the API, where you could edit other companies' users. We chased that and it did not hold, precisely because the API path does its permission check. So the advisory says what we could prove and nothing more: a self-service privilege escalation on the web path. Scoping a finding down to what is actually true is less satisfying than claiming the bigger version, and it is the difference between a report someone can act on and a report someone has to fact-check.

For the full technical breakdown — the vulnerable call, the exact request, the CVSS rationale, and the code-level fix — see the advisory.
Read the advisory

Why "just an accountant" is the whole point

It is worth being clear about who can do this, because the severity lives entirely in the answer. This is not an outside attacker who first has to break in. It is not an existing admin abusing access they already have. It is any account with the most basic self-service permission, which nearly every user in an Akaunting company is given by default.

So the threat model is not "a hacker on the internet." It is the contractor you onboarded for one project. The bookkeeper you gave read-mostly access. The employee who is leaving next month. Any of them, from the profile page they use to change their own password, can become the owner of the company's entire financial system: every invoice, every bill, every bank and payment detail, every customer and vendor record. Once they are admin they can alter or delete those records, create more admin accounts to keep the access after their own login is disabled, and change settings like SMTP and module installation that open a road toward the server itself.

What operators can do right now

There is no patched release to move to, so the honest guidance is about shrinking exposure and watching for abuse rather than closing the hole.

Audit who is an admin today. Before anything else, look at the list of accounts that currently hold the administrator role and confirm every one of them should. This bug leaves no dramatic trace, so an unexpected admin is the clearest signal you will get.

Watch for role changes on profile updates. Any profile save that adds a role — especially the admin role — to an account is worth an alert. Under normal use, ordinary users do not change their own roles at all, so any such event is either an administrator doing something deliberate or this bug being used.

Keep the account list small. Every login on the instance is a potential administrator until this is fixed. That is a reason to remove accounts that are no longer needed and to be deliberate about who gets one in the first place.

The disclosure

The broader lesson

The thing to take from this one is not "check your authorization," which everyone already believes they do. It is that the same operation can travel more than one path, and each path needs the check on its own. Akaunting's team knew a user's roles are sensitive — the API proves it. The check was written, by the same people, for the same data. It simply was not repeated on the second route to the same action.

That is why we read features as a set of paths, not a single function. Profile update on the web and user update on the API are two doors into the same room. A permission guard on one of them tells you the developers understood the risk. It does not tell you the other door is locked. The fastest question you can ask of any sensitive operation is: how many ways can a user reach this, and does every one of them check?

A dependency scanner does not ask that. A profile form that quietly accepts a roles field is not a known-vulnerable library or an unpatched CVE — it is the application working as written, and only a person who reads the update path and asks which submitted fields anyone actually verified will find it.

For the full technical detail — the vulnerable call, the exact profile-update request, the CVSS vector rationale, and the one-gate fix — see the full advisory.

The check on one path proves nothing about the other

The most common authorization gaps are not missing checks — they are checks that guard one route to an action and miss the second. They surface when someone maps every path to a sensitive operation and asks whether each one validates on its own. That is how we approach web application and API testing for clients across the Charlotte, NC area and nationwide.

Get a Quote