Bug report (security-relevant)
/panel/upgrade executes upgrade scripts without authentication — only panel page missing handlePanelPageLoad
Affected: modules/Core/pages/panel/upgrade.php:12-17, route registered at modules/Core/module.php:111
// upgrade.php — zero authentication checks
$update_needed = DB::getInstance()->query('SELECT `value` FROM nl2_settings WHERE `name` = \'version_update\'')->first();
if (!$update_needed || ($update_needed->value !== 'true' && $update_needed->value !== 'urgent')) {
Redirect::to(URL::build('/panel/update'));
}
Every other StaffCP page calls $user->handlePanelPageLoad(...) (e.g. update.php:22) — upgrade.php is the lone outlier, and standalone routes bypass the gated panel index entirely.
The single guard — the version_update setting — is set automatically and persistently by the hourly update check whenever any staff member views any StaffCP page (core/classes/Core/Util.php:150-157, TTL 3600s, value stored in DB until consumed).
Exploit chain (guest-level, no auth)
- Install runs any release older than latest upstream (extremely common).
- Any staff views any StaffCP page once →
version_update = 'true' persists indefinitely.
- Guest sends
GET /panel/upgrade → UpgradeScript::get($version)->run() executes:
- Phinx database schema migrations against code still on the old version (schema/app mismatch),
- destructive helpers exist in the base class (
deleteFiles, UpgradeScript.php:126-146),
- cache→settings migrations rewrite live configuration,
- sets
nameless_version forward and clears the pending-update banner (setVersion(), :162-166) — silently consuming the admin's update state.
Integrity + availability impact on all outdated installs; no privileges needed beyond network access.
Suggested fix
Mirror update.php:22-25 at the top of the file:
if (!$user->handlePanelPageLoad('admincp.update')) {
require_once ROOT_PATH . '/403.php';
die();
}
Bug report (security-relevant)
/panel/upgradeexecutes upgrade scripts without authentication — only panel page missinghandlePanelPageLoadAffected:
modules/Core/pages/panel/upgrade.php:12-17, route registered atmodules/Core/module.php:111Every other StaffCP page calls
$user->handlePanelPageLoad(...)(e.g.update.php:22) —upgrade.phpis the lone outlier, and standalone routes bypass the gated panel index entirely.The single guard — the
version_updatesetting — is set automatically and persistently by the hourly update check whenever any staff member views any StaffCP page (core/classes/Core/Util.php:150-157, TTL 3600s, value stored in DB until consumed).Exploit chain (guest-level, no auth)
version_update = 'true'persists indefinitely.GET /panel/upgrade→UpgradeScript::get($version)->run()executes:deleteFiles,UpgradeScript.php:126-146),nameless_versionforward and clears the pending-update banner (setVersion(), :162-166) — silently consuming the admin's update state.Integrity + availability impact on all outdated installs; no privileges needed beyond network access.
Suggested fix
Mirror
update.php:22-25at the top of the file: