Medium. Authenticated open redirect (state-changing table/edit POSTs), plus unauthenticated control of the session-cookie path attribute and of self-referential URLs, from a single spoofable request header. The header is honoured even when Adminer is not behind a reverse proxy.
if ($_SERVER["HTTP_X_FORWARDED_PREFIX"]) {
$_SERVER["REQUEST_URI"] = $_SERVER["HTTP_X_FORWARDED_PREFIX"] . $_SERVER["REQUEST_URI"];
}
There is no check that the request actually came through a trusted proxy and no restriction on the prefix value. The poisoned REQUEST_URI then reaches:
<?php
// Adminer 5.4.5-dev — X-Forwarded-Prefix poisons REQUEST_URI. Models bootstrap.inc.php:40-42
// and the two sinks (redirect Location + cookie_path) with the exact source lines.
// --- attacker input ---
$_SERVER["REQUEST_URI"] = "/adminer/?server=db&username=root&db=x";
$_SERVER["HTTP_X_FORWARDED_PREFIX"] = "https://evil.example"; // spoofable request header
// --- bootstrap.inc.php:40-42 (verbatim) ---
if ($_SERVER["HTTP_X_FORWARDED_PREFIX"]) {
$_SERVER["REQUEST_URI"] = $_SERVER["HTTP_X_FORWARDED_PREFIX"] . $_SERVER["REQUEST_URI"];
}
echo "poisoned REQUEST_URI = " . $_SERVER["REQUEST_URI"] . "\n";
// --- sink 1: redirect() Location (functions.inc.php:441, e.g. db.inc.php:52 passes REQUEST_URI) ---
$location = $_SERVER["REQUEST_URI"];
echo "Location: $location <-- open redirect off-site\n";
// --- sink 2: cookie_path() (functions.inc.php:324), used for the session cookie path ---
$cookie_path = strtr(preg_replace('~\?.*~', '', $_SERVER["REQUEST_URI"]), array(";" => "%3B", "," => "%2C"));
echo "Set-Cookie: adminer_sid=...; path=$cookie_path <-- attacker-set cookie path\n";
Summary
The new reverse-proxy support prepends the client-supplied
X-Forwarded-Prefixheader to$_SERVER["REQUEST_URI"]on every request, with no configuration gate and no validation. BecauseREQUEST_URIalways begins with/, an attacker header likeX-Forwarded-Prefix: https://evil.exampleyields a fully attacker-controlled absolute URL that then flows intoLocation:redirects, theSet-Cookiepath attribute, and every self-referential link Adminer emits.Severity
Medium. Authenticated open redirect (state-changing table/edit POSTs), plus unauthenticated control of the session-cookie
pathattribute and of self-referential URLs, from a single spoofable request header. The header is honoured even when Adminer is not behind a reverse proxy.Root Cause
adminer/include/bootstrap.inc.php:40-42:There is no check that the request actually came through a trusted proxy and no restriction on the prefix value. The poisoned
REQUEST_URIthen reaches:redirect()/queries_redirect()—header("Location: $location")where$locationis$_SERVER["REQUEST_URI"](e.g.adminer/db.inc.php:52,adminer/edit.inc.php:20).cookie_path()—adminer/include/functions.inc.php:324, used for the session cookie (bootstrap.inc.php:49) and allSet-Cookiepaths.Impact
db.inc.php) returnsLocation: https://evil.example/..., sending the operator off-site after the action.path=attribute ofadminer_sid, enabling cookie-scoping tricks / session fixation on shared hosts.header()strips them), so this is not header splitting or XSS.Reproduction Steps
Environment
Steps
X-Forwarded-Prefix: https://evil.example,redirect()emitsLocation: https://evil.example/adminer/?...andcookie_path()returnshttps://evil.example/adminer/.X-Forwarded-Prefix: https://evil.example; observe self-links and theSet-Cookie: adminer_sid=...; path=reflect the prefix. For the redirect, submit an authenticated table op POST and inspect theLocationresponse header.Proof of Concept Code
Patch Recommendation
X-Forwarded-Prefixwhen the request originates from a configured trusted proxy (explicit opt-in setting), matching how frameworks gateX-Forwarded-*./, no scheme/authority) and reject anything containing://or a leading//.Affected Files
adminer/include/bootstrap.inc.php:40-42adminer/include/functions.inc.php:324(cookie_path),:441-451(redirect)adminer/db.inc.php:52,adminer/edit.inc.php:20