Skip to content

Missing Authorization on Admin Write Endpoints Allows RBAC Bypass

High
MrWeez published GHSA-pxmw-gj52-9p68 May 8, 2026

Package

composer CtrlPanel-gg/panel (Composer)

Affected versions

<= 1.1.1

Patched versions

1.2.0

Description

Summary

Multiple admin controllers implement permission checks on form display methods
(create(), edit()) but omit equivalent checks on the corresponding write
action methods (store(), update()). Any authenticated user can bypass
role-based access control (RBAC) by sending direct POST/PATCH requests to
these endpoints, skipping the form entirely.

Details

The affected controllers follow a pattern where the GET methods that render
forms correctly verify the caller's permissions, but the POST/PATCH methods
that process submissions do not. Because the permission gate is only applied
to the UI entry point and not to the underlying action, it can be bypassed
by crafting a direct HTTP request.

Missing permission checks - store() and update():

Controller Required permission
ApplicationApiController admin.api.write
CouponController admin.coupons.write
PartnerController admin.partners.write
ShopProductController admin.store.write
UsefulLinkController admin.useful_links.write
VoucherController admin.voucher.write

Missing permission check - update() only:

Controller Required permission(s)
ProductController admin.products.edit
ServerController Any of: write, change_owner, change_identifier
UserController Any of: write, change_email, change_credits, change_username, change_password, change_role, change_referral, change_ptero, change_serverlimit

Additionally, ActivityLogController exposed stub store() and update()
methods with empty bodies that accepted any request silently; these have been
patched to return 403 unconditionally.

A separate but related issue was identified in UserController.logBackIn():
the method that restores a previous admin session after impersonating a user
lacked a permission check for admin.users.login_as, allowing any
authenticated user to trigger session restoration.

PoC

  1. Authenticate as any user without admin write permissions.
  2. Send a direct POST request to any of the affected endpoints, bypassing
    the form UI entirely:
POST /admin/coupons HTTP/1.1
Host: <panel_url>
Cookie: <valid_session_cookie>
Content-Type: application/x-www-form-urlencoded

_token=<csrf_token>&code=FREECREDITS&type=percentage&value=100&uses=9999
  1. Result: the coupon is created successfully despite the caller lacking
    admin.coupons.write permission. The same technique applies to all
    endpoints listed in the tables above.

Impact

An authenticated attacker without admin write privileges can:

  • Create and modify API credentials - issue application API keys with
    arbitrary scopes, enabling persistent unauthorized API access
  • Create and modify discount coupons and vouchers - generate unlimited
    discount codes or vouchers, causing direct financial loss
  • Create and modify partner relationships - assign arbitrary commission
    and discount rates to any user account
  • Create and modify shop products and pricing - alter product prices,
    resource limits, and billing periods
  • Modify user accounts - update roles, credits, passwords, and linked
    Pterodactyl IDs for any user, enabling full privilege escalation
  • Modify server records - reassign server ownership or change server
    identifiers
  • Abuse session restoration - trigger logBackIn() without the
    login_as permission, potentially interfering with active admin
    impersonation sessions

Remediation

Add permission checks at the beginning of each affected write action method,
mirroring the checks already present in the corresponding create() and
edit() methods. The fix has been applied as follows:

For controllers with a single write permission - add checkPermission()
at the top of store() and update():

 public function store(Request $request)
 {
+    $this->checkPermission(self::WRITE_PERMISSION);
+
     $request->validate([...]);

For controllers where update requires any one of several permissions
(e.g. ServerController, UserController) - use checkAnyPermission():

 public function update(Request $request, User $user)
 {
+    $this->checkAnyPermission([
+        self::WRITE_PERMISSION,
+        self::CHANGE_ROLE_PERMISSION,
+        // ... other applicable permissions
+    ]);
+
     $data = $request->validate([...]);

For ActivityLogController - stub methods now explicitly abort with 403:

 public function store(Request $request)
 {
-    //
+    abort(403, __('User does not have the right permissions.'));
 }

Ensure that every action method that modifies state has a server-side
permission check independent of the UI flow that leads to it.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N

CVE ID

CVE-2026-34358

Weaknesses

Improper Access Control

The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor. Learn more on MITRE.

Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action. Learn more on MITRE.

Credits