Validate server input

- Allow only scheme, host and port in the server field.
- Use proper default host and port in Elasticsearch and ClickHouse driver.
This commit is contained in:
Peter Knut 2024-01-19 00:29:25 +01:00 committed by Lionel Laffineur
parent 55d40f8563
commit 27132d1175
3 changed files with 72 additions and 13 deletions

View file

@ -58,9 +58,15 @@ if (isset($_GET["elastic"])) {
return $this->rootQuery(($this->_db != "" ? "$this->_db/" : "/") . ltrim($path, '/'), $content, $method); return $this->rootQuery(($this->_db != "" ? "$this->_db/" : "/") . ltrim($path, '/'), $content, $method);
} }
/**
* @param string $server
* @param string $username
* @param string $password
* @return bool
*/
function connect($server, $username, $password) { function connect($server, $username, $password) {
preg_match('~^(https?://)?(.*)~', $server, $match); $this->_url = build_http_url($server, $username, $password, "localhost", 9200);
$this->_url = ($match[1] ? $match[1] : "http://") . "$username:$password@$match[2]";
$return = $this->query(''); $return = $this->query('');
if ($return) { if ($return) {
$this->server_info = $return['version']['number']; $this->server_info = $return['version']['number'];
@ -227,9 +233,6 @@ if (isset($_GET["elastic"])) {
global $adminer; global $adminer;
$connection = new Min_DB; $connection = new Min_DB;
list($server, $username, $password) = $adminer->credentials(); list($server, $username, $password) = $adminer->credentials();
if (strpos($server, '/') !== false || strpos($server, ':') !== false) {
return lang('Only hostname or IP address');
}
if ($password != "" && $connection->connect($server, $username, "")) { if ($password != "" && $connection->connect($server, $username, "")) {
return lang('Database does not support password.'); return lang('Database does not support password.');
} }

View file

@ -15,6 +15,58 @@ if ($_COOKIE["adminer_permanent"]) {
} }
} }
function validate_server_input() {
if (SERVER == "") {
return;
}
$parts = parse_url(SERVER);
if (!$parts) {
auth_error(lang('Invalid credentials.'));
}
// Check proper URL parts.
if (isset($parts['user']) || isset($parts['pass']) || isset($parts['query']) || isset($parts['fragment'])) {
auth_error(lang('Invalid credentials.'));
}
// Allow only HTTP/S scheme.
if (isset($parts['scheme']) && !preg_match('~^(https?)$~i', $parts['scheme'])) {
auth_error(lang('Invalid credentials.'));
}
// Allow only host without a path. Note that "localhost" is parsed as path.
$host = (isset($parts['host']) ? $parts['host'] : '') . (isset($parts['path']) ? $parts['path'] : '');
if (strpos(rtrim($host, '/'), '/') !== false) {
auth_error(lang('Invalid credentials.'));
}
// Check privileged ports.
if (isset($parts['port']) && ($parts['port'] < 1024 || $parts['port'] > 65535)) {
auth_error(lang('Connecting to privileged ports is not allowed.'));
}
}
/**
* @param string $server
* @param string $username
* @param string $password
* @param string $defaultServer
* @param int|null $defaultPort
* @return string
*/
function build_http_url($server, $username, $password, $defaultServer, $defaultPort = null) {
if (!preg_match('~^(https?://)?([^:]*)(:\d+)?$~', rtrim($server, '/'), $matches)) {
$this->error = lang('Invalid credentials.');
return false;
}
return ($matches[1] ?: "http://") .
($username !== "" || $password !== "" ? "$username:$password@" : "") .
($matches[2] !== "" ? $matches[2] : $defaultServer) .
(isset($matches[3]) ? $matches[3] : ($defaultPort ? ":$defaultPort" : ""));
}
function add_invalid_login() { function add_invalid_login() {
global $adminer; global $adminer;
$fp = file_open_lock(get_temp_dir() . "/adminer.invalid"); $fp = file_open_lock(get_temp_dir() . "/adminer.invalid");
@ -55,7 +107,7 @@ $auth = $_POST["auth"];
if ($auth) { if ($auth) {
session_regenerate_id(); // defense against session fixation session_regenerate_id(); // defense against session fixation
$vendor = $auth["driver"]; $vendor = $auth["driver"];
$server = $auth["server"]; $server = trim($auth["server"]);
$username = $auth["username"]; $username = $auth["username"];
$password = (string) $auth["password"]; $password = (string) $auth["password"];
$db = $auth["db"]; $db = $auth["db"];
@ -81,7 +133,7 @@ if ($auth) {
set_session($key, null); set_session($key, null);
} }
unset_permanent(); unset_permanent();
redirect(substr(preg_replace('~\b(username|db|ns)=[^&]*&~', '', ME), 0, -1), lang('Logout successful.') . '.'); redirect(substr(preg_replace('~\b(username|db|ns)=[^&]*&~', '', ME), 0, -1), lang('Logout successful.'));
} elseif ($permanent && !$_SESSION["pwds"]) { } elseif ($permanent && !$_SESSION["pwds"]) {
session_regenerate_id(); session_regenerate_id();
@ -158,11 +210,9 @@ if (isset($_GET["username"]) && !class_exists("Min_DB")) {
stop_session(true); stop_session(true);
if (isset($_GET["username"]) && is_string(get_password())) { if (isset($_GET["username"]) && is_string(get_password())) {
list($host, $port) = explode(":", SERVER, 2); validate_server_input();
if (preg_match('~^\s*([-+]?\d+)~', $port, $match) && ($match[1] < 1024 || $match[1] > 65535)) { // is_numeric('80#') would still connect to port 80
auth_error(lang('Connecting to privileged ports is not allowed.'));
}
check_invalid_login(); check_invalid_login();
$connection = connect(); $connection = connect();
$driver = new Min_Driver($connection); $driver = new Min_Driver($connection);
} }

View file

@ -55,9 +55,15 @@ if (isset($_GET["clickhouse"])) {
return $this->rootQuery($this->_db, $query); return $this->rootQuery($this->_db, $query);
} }
/**
* @param string $server
* @param string $username
* @param string $password
* @return bool
*/
function connect($server, $username, $password) { function connect($server, $username, $password) {
preg_match('~^(https?://)?(.*)~', $server, $match); $this->_url = build_http_url($server, $username, $password, "localhost", 8123);
$this->_url = ($match[1] ? $match[1] : "http://") . "$username:$password@$match[2]";
$return = $this->query('SELECT 1'); $return = $this->query('SELECT 1');
return (bool) $return; return (bool) $return;
} }