# REPRO-2026-00256: Pagekit CMS privilege escalation leading to RCE ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00256 CVE: CVE-2026-57518 ## Package Name: pagekit/pagekit Ecosystem: github Affected: <= 1.0.18 Fixed: None available; project archived ## Root Cause # RCA Report — CVE-2026-57518 ## Summary Pagekit CMS 1.0.18 contains a privilege escalation vulnerability in `UserApiController::saveAction()` that allows authenticated users holding the `user: manage users` permission to self-assign arbitrary custom roles. The authorization check only prevents assignment of the built-in Administrator role (hard-coded ID=3); any custom role (ID ≥ 4) — including one with elevated permissions such as `system: manage packages` — passes through unchecked. An attacker can exploit this to gain the `system: manage packages` permission, then upload and install a malicious PHP extension package containing a webshell through the admin package installer, achieving remote code execution on the server. ## Impact - **Package/component affected:** `pagekit/pagekit` — `app/system/modules/user/src/Controller/UserApiController.php` (`saveAction`, lines 178–183) - **Affected versions:** Pagekit CMS ≤ 1.0.18 (project archived 2023-12-01; no patched release exists) - **Risk level:** High (CVSS 8.8 — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) - **Consequences:** Full server compromise. An authenticated low-privileged user with `user: manage users` can escalate to arbitrary code execution as the web server user (`www-data`). ## Impact Parity - **Disclosed/claimed maximum impact:** Privilege escalation leading to remote code execution (code_execution) - **Reproduced impact from this run:** Full privilege escalation to `system: manage packages`, followed by upload and installation of a malicious PHP package, followed by confirmed remote code execution (`uid=33(www-data)`) - **Parity:** `full` - **Not demonstrated:** N/A — the full chain from low-privilege user to arbitrary command execution was demonstrated end-to-end against the real running Pagekit service via its HTTP API. ## Root Cause The vulnerability is in `UserApiController::saveAction()` at `app/system/modules/user/src/Controller/UserApiController.php`, lines 178–183: ```php $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.'); } ``` `Role::ROLE_ADMINISTRATOR` is a constant equal to `3` (defined in `app/system/modules/user/src/Model/Role.php`). The authorization check **only** tests whether the Administrator role (ID=3) is being added or removed. Custom roles created by an administrator (which receive IDs 4, 5, 6, …) are never subjected to any authorization check. Consequently, any user who has the `user: manage users` permission (required by the `@Access("user: manage users")` annotation on `UserApiController`) can call `POST /api/user/{own_id}` with a `roles` array that includes a custom role with elevated permissions — for example, `system: manage packages` — and the server will silently accept and persist the change. Once the attacker holds `system: manage packages` (and `system: access admin area`), the admin package installer at `POST /admin/system/package/upload` and `POST /admin/system/package/install` becomes available. The attacker uploads a ZIP archive containing a `composer.json` (type `pagekit-extension`) and a PHP webshell (`index.php`). The installer extracts the package into the web-accessible `packages/` directory, and the webshell is immediately reachable at `/packages/pagekit/shell/index.php?cmd=`, executing arbitrary commands as the web server user. **Fix commit:** None — the Pagekit project was archived on 2023-12-01 and no patched version has been released. ## Reproduction Steps 1. **Script:** `bundle/repro/reproduction_steps.sh` 2. **What the script does:** - Downloads (or reuses a cached copy of) the official Pagekit 1.0.18 release ZIP (which includes all PHP dependencies in `app/vendor/`). - Builds a Docker image based on `php:7.4-apache` with the required PHP extensions (gd, zip, pdo_sqlite, opcache) and deploys Pagekit with Apache + mod_rewrite. - Starts the container and installs Pagekit via its installer API (`POST /installer/install`) using a SQLite database, creating an administrator account. - As administrator, creates two custom roles: - **Package Manager** (ID 4): `system: manage packages` + `system: access admin area` - **User Manager** (ID 5): `user: manage users` - As administrator, creates a low-privileged **editor** user (ID 2) with the User Manager role. - As editor, calls `POST /api/user/2` with `{"user":{"id":2,"roles":[2,4]}}` — self-assigning the Package Manager role. The `saveAction` only blocks role ID 3 (Administrator); the custom role ID 4 passes through unchecked. - As the now-escalated editor, uploads a malicious extension ZIP (containing a PHP webshell) via `POST /admin/system/package/upload` and installs it via `POST /admin/system/package/install`. - Accesses `/packages/pagekit/shell/index.php?cmd=id` and verifies the output contains `uid=`, confirming remote code execution. 3. **Expected evidence of reproduction:** - `bundle/logs/reproduction_steps.log` — full execution log - `bundle/logs/exploit_evidence.txt` — structured evidence summary - `bundle/logs/rce_id_output.txt` — output of `id` command via webshell (`uid=33(www-data) gid=33(www-data) groups=33(www-data)`) - `bundle/logs/rce_whoami_output.txt` — output of `whoami` (`www-data`) - `bundle/repro/runtime_manifest.json` — runtime evidence manifest ## Evidence ### Key excerpts **Privilege escalation (Step 3):** ``` Editor BEFORE escalation: {"id":2,...,"roles":[2,5],...} Escalation response: {"message":"success","user":{"id":2,...,"roles":[2,4],...}} PRIVILEGE ESCALATION CONFIRMED — editor now has 'system: manage packages' ``` **RCE (Step 6):** ``` RCE output (id): uid=33(www-data) gid=33(www-data) groups=33(www-data) RCE output (whoami): www-data === RCE CONFIRMED === ``` ### Environment details - **OS:** Ubuntu 26.04 LTS (host); Debian Bullseye (container) - **PHP:** 7.4.33 (Apache, inside Docker container) - **Database:** SQLite (`pagekit.db`) - **Pagekit:** 1.0.18 (official release from GitHub) - **Web server:** Apache 2.4.54 with mod_rewrite ### Sandbox adaptations (non-security-relevant) 1. **Docker networking:** The sandbox restricts host-to-container port forwarding, so all HTTP requests to the running Pagekit service are made via `docker exec` (curl inside the container). This does not alter the product's request handling — the same HTTP requests reach the same Apache + PHP + Pagekit stack. 2. **Session bootstrap:** A PHP helper (`create_session.php`) creates a valid authenticated session directly in the database (session + auth tables) to bypass a PHP 7.4 / Symfony 3.0 session-storage interaction issue that prevents normal web login via curl. The auth state created is identical to a successful web login (same cookies, same database records, same CSRF token derivation). 3. **Composer marketplace repository:** The package installer's Composer blueprint includes a remote `composer` repository pointing to `https://pagekit.com`. The sandbox has no outbound network, so this repository entry is removed from the blueprint, leaving only the local `artifact` repository. This does not change any security-relevant code path — it only avoids a network fetch for the marketplace index that is irrelevant to the exploit. ## Recommendations / Next Steps ### Suggested fix The authorization check in `saveAction` should validate **all** role assignments, not just the Administrator role. A correct implementation would verify that the calling user is permitted to assign each role in the `$data['roles']` array — for example, by checking that the calling user either is an administrator or holds a permission that authorizes assigning each specific role. At minimum, the check should prevent any non-administrator from assigning roles with `trusted` permissions (such as `system: manage packages`) to themselves. ### Upgrade guidance Pagekit is archived and unmaintained. Users should migrate to an actively maintained CMS. If migration is not immediately feasible, a mitigation is to never grant the `user: manage users` permission to non-administrator accounts, and to avoid creating custom roles with `trusted` permissions. ### Testing recommendations - Add a unit test that verifies a non-administrator user with `user: manage users` cannot self-assign a custom role with `trusted` permissions. - Add an integration test that verifies the package installer rejects uploads from users without `system: manage packages`. ## Additional Notes ### Idempotency confirmation The script was run twice consecutively, both times producing the same result: privilege escalation confirmed and RCE confirmed (`uid=33(www-data)`). Each run creates a fresh Docker container with a clean Pagekit installation, ensuring no state carries over between runs. ### Limitations - The project is archived; no fixed version exists for a negative control. - The webshell executes as `www-data` (the Apache user), not as root. Full root compromise would require a separate local privilege escalation, which is out of scope for this CVE. - The `user: manage users` precondition is realistic: Pagekit supports delegating user management to non-owner accounts (e.g., a "User Manager" or "Editor" role), which is the exact scenario this vulnerability targets. ## Reproduction Details Reproduced: 2026-07-06T09:34:54.172Z Duration: 1395 seconds Tool calls: 291 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00256 pruva-verify CVE-2026-57518 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00256&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00256/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-57518 - Source: https://github.com/pagekit/pagekit ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 20608 bytes) - bundle/repro/rca_report.md (analysis, 9688 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 26900 bytes) - bundle/vuln_variant/rca_report.md (analysis, 14583 bytes) - bundle/artifact_promotion_manifest.json (other, 7444 bytes) - bundle/vuln_variant/source_identity.json (other, 1919 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 4683 bytes) - bundle/repro/validation_verdict.json (other, 932 bytes) - bundle/repro/runtime_manifest.json (other, 744 bytes) - bundle/logs/rce_id_output.txt (other, 54 bytes) - bundle/logs/exploit_evidence.txt (other, 397 bytes) - bundle/logs/reproduction_steps.log (log, 3711 bytes) - bundle/logs/rce_whoami_output.txt (other, 9 bytes) - bundle/logs/vuln_variant/rce_id_output.txt (other, 54 bytes) - bundle/logs/vuln_variant/exploit_evidence.txt (other, 1030 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 4455 bytes) - bundle/vuln_variant/variant_manifest.json (other, 4270 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3306 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 8803 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 965 bytes) - bundle/logs/vuln_variant/rce_whoami_output.txt (other, 9 bytes) - bundle/logs/vuln_variant/fixed_version.txt (other, 74 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00256 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00256/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00256 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev