From 18f3167bbcbec3bc746f62db72e016aa99144efc Mon Sep 17 00:00:00 2001
From: Peter Knut <peter@pematon.com>
Date: Fri, 19 Jan 2024 00:29:25 +0100
Subject: [PATCH] Validate server input
Applied-Upstream: commit:27132d1175ad931d591d40286b1c9864483ca13b

- Allow only scheme, host and port in the server field.
- Use proper default host and port in Elasticsearch and ClickHouse driver.
---
 adminer/drivers/elastic.inc.php | 13 ++++---
 adminer/include/auth.inc.php    | 62 +++++++++++++++++++++++++++++----
 plugins/drivers/clickhouse.php  | 10 ++++--
 3 files changed, 72 insertions(+), 13 deletions(-)

--- adminer.git.orig/adminer/drivers/elastic.inc.php	2024-07-03 12:06:13.960713367 +0200
+++ adminer.git/adminer/drivers/elastic.inc.php	2024-07-03 12:06:13.956713285 +0200
@@ -58,9 +58,15 @@
 				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) {
-				preg_match('~^(https?://)?(.*)~', $server, $match);
-				$this->_url = ($match[1] ? $match[1] : "http://") . "$username:$password@$match[2]";
+				$this->_url = build_http_url($server, $username, $password, "localhost", 9200);
+
 				$return = $this->query('');
 				if ($return) {
 					$this->server_info = $return['version']['number'];
--- adminer.git.orig/adminer/include/auth.inc.php	2024-07-03 12:06:13.960713367 +0200
+++ adminer.git/adminer/include/auth.inc.php	2024-07-03 12:09:58.593148739 +0200
@@ -15,6 +15,58 @@
 	}
 }
 
+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() {
 	global $adminer;
 	$fp = file_open_lock(get_temp_dir() . "/adminer.invalid");
@@ -52,7 +104,7 @@
 if ($auth) {
 	session_regenerate_id(); // defense against session fixation
 	$vendor = $auth["driver"];
-	$server = $auth["server"];
+	$server = trim($auth["server"]);
 	$username = $auth["username"];
 	$password = (string) $auth["password"];
 	$db = $auth["db"];
@@ -155,11 +207,9 @@
 stop_session(true);
 
 if (isset($_GET["username"]) && is_string(get_password())) {
-	list($host, $port) = explode(":", SERVER, 2);
-	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.'));
-	}
+	validate_server_input();
 	check_invalid_login();
+
 	$connection = connect();
 	$driver = new Min_Driver($connection);
 }
--- adminer.git.orig/plugins/drivers/clickhouse.php	2024-07-03 12:06:13.960713367 +0200
+++ adminer.git/plugins/drivers/clickhouse.php	2024-07-03 12:06:13.956713285 +0200
@@ -55,9 +55,15 @@
 			return $this->rootQuery($this->_db, $query);
 		}
 
+		/**
+		 * @param string $server
+		 * @param string $username
+		 * @param string $password
+		 * @return bool
+		 */
 		function connect($server, $username, $password) {
-			preg_match('~^(https?://)?(.*)~', $server, $match);
-			$this->_url = ($match[1] ? $match[1] : "http://") . "$username:$password@$match[2]";
+			$this->_url = build_http_url($server, $username, $password, "localhost", 8123);
+
 			$return = $this->query('SELECT 1');
 			return (bool) $return;
 		}
