Forums Bug Reports Thread

Open redirect: AdminAchievementController redirects to unvalidated $_POST['return_to']

Patrick Bass · Jun 6 · 8 · 1 Locked
[Minor] [Normal Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 6:54pm

Area: Engagement (audit p9) · Surface: /admin/achievements/grant, /admin/achievements/revoke, /admin/achievements/{slug}/update · Dimension: security · Severity: minor

The achievement grant/revoke/update admin handlers send the user to whatever `return_to` value is posted, with no allowlist check. An attacker who can get a super-admin to submit a crafted form (or via a stored/posted form value) can bounce the authenticated admin to an arbitrary off-site URL (OWASP A01: Broken Access Control / unvalidated redirect). Impact is limited because the route is role-5 + CSRF-gated, so it requires phishing a super-admin into submitting attacker-supplied form data; PHP's header() blocks CRLF so this is open-redirect only, not header injection.

Evidence

platform/src/Controllers/AdminAchievementController.php:174,181,185,195,210,216,226 all do `$this->redirect($_POST['return_to'] ?? '/admin/achievements');` — the value goes straight into BaseController::redirect() which sets a raw `Location:` header (BaseController.php:411-416) with no validation. The legit forms post same-site paths (templates/admin/achievements/index.php:134,359 and edit.php:284), but the field is fully attacker-controllable. The codebase already has the correct mitigation `\App\Services\UrlValidator::safeRedirectPath()` (used in AdminBadgeController.php:177) which it does not call here.

Suggested fix. Wrap every return_to redirect in the existing helper: `$this->redirect(\App\Services\UrlValidator::safeRedirectPath($_POST['return_to'] ?? null, '/admin/achievements'));` — mirroring AdminBadgeController::assign().

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:44am

Resolved — fixed and deployed. Commit 059d6bf29b6f, shipped dev-first then to all tenants on 2026-06-06.

Wrapped all 7 return_to redirects in grant(), revoke(), and the update/grant flow with \App\Services\UrlValidator::safeRedirectPath($_POST['return_to'] ?? null, '/admin/achievements'), mirroring AdminBadgeController to close the open-redirect on /admin/achievements/grant, /revoke, and /{slug}/update.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.