[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1105888: bookworm-pu: package dropbear/2022.83-1+deb12u3



Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: dropbear@packages.debian.org
Control: affects -1 + src:dropbear
User: release.debian.org@packages.debian.org
Usertags: pu

[ Reason ]

Fix CVE-2025-47203 (shell injection vulnerability in multihop handling).

[ Impact ]

dbclient(1) hostnames could result in running arbitrary shell commands
locally during multihop handling.

[ Tests ]

Manual tests for the injection itself and also to ensure that multihop
still works.

[ Risks ]

Low risk: backported patches from upstream's 2022.84 to 2025.88 release.

[ Checklist ]

  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in stable
  [x] the issue is verified as fixed in unstable

[ Changes ]

Backport upstream changes to fix CVE-2025-47203.

-- 
Guilhem.
diffstat for dropbear-2022.83 dropbear-2022.83

 changelog                                                               |    6 
 patches/CVE-2025-47203.patch                                            |  365 ++++++++++
 patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch |   99 ++
 patches/series                                                          |    2 
 4 files changed, 472 insertions(+)

diff -Nru dropbear-2022.83/debian/changelog dropbear-2022.83/debian/changelog
--- dropbear-2022.83/debian/changelog	2024-07-09 14:22:02.000000000 +0200
+++ dropbear-2022.83/debian/changelog	2025-05-16 15:01:36.000000000 +0200
@@ -1,3 +1,9 @@
+dropbear (2022.83-1+deb12u3) bookworm; urgency=high
+
+  * Fix CVE-2025-47203: Shell injection vulnerability in multihop handling.
+
+ -- Guilhem Moulin <guilhem@debian.org>  Fri, 16 May 2025 15:01:36 +0200
+
 dropbear (2022.83-1+deb12u2) bookworm; urgency=medium
 
   * Fix noremotetcp behavior.  Keepalive packets were being ignored when the
diff -Nru dropbear-2022.83/debian/patches/CVE-2025-47203.patch dropbear-2022.83/debian/patches/CVE-2025-47203.patch
--- dropbear-2022.83/debian/patches/CVE-2025-47203.patch	1970-01-01 01:00:00.000000000 +0100
+++ dropbear-2022.83/debian/patches/CVE-2025-47203.patch	2025-05-16 15:01:36.000000000 +0200
@@ -0,0 +1,365 @@
+From: Matt Johnston <matt@ucc.asn.au>
+Date: Mon, 5 May 2025 23:14:19 +0800
+Subject: Execute multihop commands directly, no shell
+
+This avoids problems with shell escaping if arguments contain special
+characters.
+
+Origin: https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b
+---
+ cli-main.c    |  61 +++++++++++++++++++++++------------
+ cli-runopts.c | 100 ++++++++++++++++++++++++++++++++++------------------------
+ dbutil.c      |   9 ++++--
+ dbutil.h      |   1 +
+ runopts.h     |   5 +++
+ 5 files changed, 113 insertions(+), 63 deletions(-)
+
+diff --git a/cli-main.c b/cli-main.c
+index 065fd76..2fafa88 100644
+--- a/cli-main.c
++++ b/cli-main.c
+@@ -77,9 +77,8 @@ int main(int argc, char ** argv) {
+ 	}
+ 
+ #if DROPBEAR_CLI_PROXYCMD
+-	if (cli_opts.proxycmd) {
++	if (cli_opts.proxycmd || cli_opts.proxyexec) {
+ 		cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid);
+-		m_free(cli_opts.proxycmd);
+ 		if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR ||
+ 			signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR ||
+ 			signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) {
+@@ -101,7 +100,8 @@ int main(int argc, char ** argv) {
+ }
+ #endif /* DBMULTI stuff */
+ 
+-static void exec_proxy_cmd(const void *user_data_cmd) {
++#if DROPBEAR_CLI_PROXYCMD
++static void shell_proxy_cmd(const void *user_data_cmd) {
+ 	const char *cmd = user_data_cmd;
+ 	char *usershell;
+ 
+@@ -110,41 +110,62 @@ static void exec_proxy_cmd(const void *user_data_cmd) {
+ 	dropbear_exit("Failed to run '%s'\n", cmd);
+ }
+ 
+-#if DROPBEAR_CLI_PROXYCMD
++static void exec_proxy_cmd(const void *unused) {
++	(void)unused;
++	run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd);
++	dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]);
++}
++
+ static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) {
+-	char * ex_cmd = NULL;
+-	size_t ex_cmdlen;
++	char * cmd_arg = NULL;
++	void (*exec_fn)(const void *user_data) = NULL;
+ 	int ret;
+ 
++	/* exactly one of cli_opts.proxycmd or cli_opts.proxyexec should be set */
++
+ 	/* File descriptor "-j &3" */
+-	if (*cli_opts.proxycmd == '&') {
++	if (cli_opts.proxycmd && *cli_opts.proxycmd == '&') {
+ 		char *p = cli_opts.proxycmd + 1;
+ 		int sock = strtoul(p, &p, 10);
+ 		/* must be a single number, and not stdin/stdout/stderr */
+ 		if (sock > 2 && sock < 1024 && *p == '\0') {
+ 			*sock_in = sock;
+ 			*sock_out = sock;
+-			return;
++			goto cleanup;
+ 		}
+ 	}
+ 
+-	/* Normal proxycommand */
+-
+-	/* So that spawn_command knows which shell to run */
+-	fill_passwd(cli_opts.own_user);
+-
+-	ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
+-	ex_cmd = m_malloc(ex_cmdlen);
+-	snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd);
++	if (cli_opts.proxycmd) {
++		/* Normal proxycommand */
++		size_t shell_cmdlen;
++		/* So that spawn_command knows which shell to run */
++		fill_passwd(cli_opts.own_user);
++
++		shell_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
++		cmd_arg = m_malloc(shell_cmdlen);
++		snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd);
++		exec_fn = shell_proxy_cmd;
++	} else {
++		/* No shell */
++		exec_fn = exec_proxy_cmd;
++	}
+ 
+-	ret = spawn_command(exec_proxy_cmd, ex_cmd,
+-			sock_out, sock_in, NULL, pid_out);
+-	DEBUG1(("cmd: %s  pid=%d", ex_cmd,*pid_out))
+-	m_free(ex_cmd);
++	ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out);
+ 	if (ret == DROPBEAR_FAILURE) {
+ 		dropbear_exit("Failed running proxy command");
+ 		*sock_in = *sock_out = -1;
+ 	}
++
++cleanup:
++	m_free(cli_opts.proxycmd);
++	m_free(cmd_arg);
++	if (cli_opts.proxyexec) {
++		char **a = NULL;
++		for (a = cli_opts.proxyexec; *a; a++) {
++			m_free_direct(*a);
++		}
++		m_free(cli_opts.proxyexec);
++	}
+ }
+ 
+ static void kill_proxy_sighandler(int UNUSED(signo)) {
+diff --git a/cli-runopts.c b/cli-runopts.c
+index 632d097..06b686a 100644
+--- a/cli-runopts.c
++++ b/cli-runopts.c
+@@ -530,58 +530,81 @@ static void loadidentityfile(const char* filename, int warnfail) {
+ 
+ /* Fill out -i, -y, -W options that make sense for all
+  * the intermediate processes */
+-static char* multihop_passthrough_args(void) {
+-	char *args = NULL;
+-	unsigned int len, total;
++static char** multihop_args(const char* argv0, const char* prior_hops) {
++	/* null terminated array */
++	char **args = NULL;
++	size_t max_args = 14, pos = 0, len;
+ #if DROPBEAR_CLI_PUBKEY_AUTH
+ 	m_list_elem *iter;
+ #endif
+-	/* Sufficient space for non-string args */
+-	len = 100;
+ 
+-	/* String arguments have arbitrary length, so determine space required */
+-	if (cli_opts.proxycmd) {
+-		len += strlen(cli_opts.proxycmd);
+-	}
+ #if DROPBEAR_CLI_PUBKEY_AUTH
+ 	for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
+ 	{
+-		sign_key * key = (sign_key*)iter->item;
+-		len += 4 + strlen(key->filename);
++		/* "-i file" for each */
++		max_args += 2;
+ 	}
+ #endif
+ 
+-	args = m_malloc(len);
+-	total = 0;
++	args = m_malloc(sizeof(char*) * max_args);
++	pos = 0;
+ 
+-	/* Create new argument string */
++	args[pos] = m_strdup(argv0);
++	pos++;
+ 
+ 	if (cli_opts.quiet) {
+-		total += m_snprintf(args+total, len-total, "-q ");
++		args[pos] = m_strdup("-q");
++		pos++;
+ 	}
+ 
+ 	if (cli_opts.no_hostkey_check) {
+-		total += m_snprintf(args+total, len-total, "-y -y ");
++		args[pos] = m_strdup("-y");
++		pos++;
++		args[pos] = m_strdup("-y");
++		pos++;
+ 	} else if (cli_opts.always_accept_key) {
+-		total += m_snprintf(args+total, len-total, "-y ");
++		args[pos] = m_strdup("-y");
++		pos++;
+ 	}
+ 
+ 	if (cli_opts.proxycmd) {
+-		total += m_snprintf(args+total, len-total, "-J '%s' ", cli_opts.proxycmd);
++		args[pos] = m_strdup("-J");
++		pos++;
++		args[pos] = m_strdup(cli_opts.proxycmd);
++		pos++;
+ 	}
+ 
+ 	if (opts.recv_window != DEFAULT_RECV_WINDOW) {
+-		total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window);
++		args[pos] = m_strdup("-W");
++		pos++;
++		args[pos] = m_malloc(11);
++		m_snprintf(args[pos], 11, "%u", opts.recv_window);
++		pos++;
+ 	}
+ 
+ #if DROPBEAR_CLI_PUBKEY_AUTH
+ 	for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
+ 	{
+ 		sign_key * key = (sign_key*)iter->item;
+-		total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
++		args[pos] = m_strdup("-i");
++		pos++;
++		args[pos] = m_strdup(key->filename);
++		pos++;
+ 	}
+ #endif /* DROPBEAR_CLI_PUBKEY_AUTH */
+ 
++	/* last hop */
++	args[pos] = m_strdup("-B");
++	pos++;
++	len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2;
++	args[pos] = m_malloc(len);
++	snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport);
++	pos++;
++
++	/* hostnames of prior hops */
++	args[pos] = m_strdup(prior_hops);
++	pos++;
++
+ 	return args;
+ }
+ 
+@@ -596,7 +619,7 @@ static char* multihop_passthrough_args(void) {
+  * etc for as many hosts as we want.
+  *
+  * Note that "-J" arguments aren't actually used, instead
+- * below sets cli_opts.proxycmd directly.
++ * below sets cli_opts.proxyexec directly.
+  *
+  * Ports for hosts can be specified as host/port.
+  */
+@@ -604,7 +627,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
+ 	char *userhostarg = NULL;
+ 	char *hostbuf = NULL;
+ 	char *last_hop = NULL;
+-	char *remainder = NULL;
++	char *prior_hops = NULL;
+ 
+ 	/* both scp and rsync parse a user@host argument
+ 	 * and turn it into "-l user host". This breaks
+@@ -622,6 +645,8 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
+ 	}
+ 	userhostarg = hostbuf;
+ 
++	/* Split off any last hostname and use that as remotehost/remoteport.
++	 * That is used for authorized_keys checking etc */
+ 	last_hop = strrchr(userhostarg, ',');
+ 	if (last_hop) {
+ 		if (last_hop == userhostarg) {
+@@ -629,35 +654,28 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
+ 		}
+ 		*last_hop = '\0';
+ 		last_hop++;
+-		remainder = userhostarg;
++		prior_hops = userhostarg;
+ 		userhostarg = last_hop;
+ 	}
+ 
++	/* Update cli_opts.remotehost and cli_opts.remoteport */
+ 	parse_hostname(userhostarg);
+ 
+-	if (last_hop) {
+-		/* Set up the proxycmd */
+-		unsigned int cmd_len = 0;
+-		char *passthrough_args = multihop_passthrough_args();
+-		if (cli_opts.remoteport == NULL) {
+-			cli_opts.remoteport = "22";
++	/* Construct any multihop proxy command. Use proxyexec to
++	 * avoid worrying about shell escaping. */
++	if (prior_hops) {
++		cli_opts.proxyexec = multihop_args(argv0, prior_hops);
++		/* Any -J argument has been copied to proxyexec */
++		if (cli_opts.proxycmd) {
++			m_free(cli_opts.proxycmd);
+ 		}
+-		cmd_len = strlen(argv0) + strlen(remainder)
+-			+ strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport)
+-			+ strlen(passthrough_args)
+-			+ 30;
+-		/* replace proxycmd. old -J arguments have been copied
+-		   to passthrough_args */
+-		cli_opts.proxycmd = m_realloc(cli_opts.proxycmd, cmd_len);
+-		m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
+-				argv0, cli_opts.remotehost, cli_opts.remoteport,
+-				passthrough_args, remainder);
++
+ #ifndef DISABLE_ZLIB
+-		/* The stream will be incompressible since it's encrypted. */
++		/* This outer stream will be incompressible since it's encrypted. */
+ 		opts.compress_mode = DROPBEAR_COMPRESS_OFF;
+ #endif
+-		m_free(passthrough_args);
+ 	}
++
+ 	m_free(hostbuf);
+ }
+ #endif /* !DROPBEAR_CLI_MULTIHOP */
+diff --git a/dbutil.c b/dbutil.c
+index bd66454..910fa27 100644
+--- a/dbutil.c
++++ b/dbutil.c
+@@ -371,7 +371,6 @@ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
+ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
+ 	char * argv[4];
+ 	char * baseshell = NULL;
+-	unsigned int i;
+ 
+ 	baseshell = basename(usershell);
+ 
+@@ -393,6 +392,12 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
+ 		argv[1] = NULL;
+ 	}
+ 
++	run_command(usershell, argv, maxfd);
++}
++
++void run_command(const char* argv0, char** args, unsigned int maxfd) {
++	unsigned int i;
++
+ 	/* Re-enable SIGPIPE for the executed process */
+ 	if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
+ 		dropbear_exit("signal() error");
+@@ -404,7 +409,7 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
+ 		m_close(i);
+ 	}
+ 
+-	execv(usershell, argv);
++	execv(argv0, args);
+ }
+ 
+ #if DEBUG_TRACE
+diff --git a/dbutil.h b/dbutil.h
+index 64af170..bfc1f1f 100644
+--- a/dbutil.h
++++ b/dbutil.h
+@@ -63,6 +63,7 @@ char * stripcontrol(const char * text);
+ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
+ 		int *writefd, int *readfd, int *errfd, pid_t *pid);
+ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell);
++void run_command(const char* argv0, char** args, unsigned int maxfd);
+ #if ENABLE_CONNECT_UNIX
+ int connect_unix(const char* addr);
+ #endif
+diff --git a/runopts.h b/runopts.h
+index 1675836..11c3ef2 100644
+--- a/runopts.h
++++ b/runopts.h
+@@ -188,7 +188,12 @@ typedef struct cli_runopts {
+ 	unsigned int netcat_port;
+ #endif
+ #if DROPBEAR_CLI_PROXYCMD
++	/* A proxy command to run via the user's shell */
+ 	char *proxycmd;
++#endif
++#if DROPBEAR_CLI_MULTIHOP
++	/* Similar to proxycmd, but is arguments for execve(), not shell */
++	char **proxyexec;
+ #endif
+ 	char *bind_address;
+ 	char *bind_port;
diff -Nru dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch
--- dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch	1970-01-01 01:00:00.000000000 +0100
+++ dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch	2025-05-16 15:01:36.000000000 +0200
@@ -0,0 +1,99 @@
+From: Matt Johnston <matt@ucc.asn.au>
+Date: Mon, 1 Apr 2024 11:50:26 +0800
+Subject: Handle arbitrary length paths and commands in
+ multihop_passthrough_args()
+
+Origin: https://github.com/mkj/dropbear/commit/2f1177e55f33afd676e08c9449ab7ab517fc3b30
+Origin: https://github.com/mkj/dropbear/commit/697b1f86c0b2b0caf12e9e32bab29161093ab5d4
+Origin: https://github.com/mkj/dropbear/commit/dd03da772bfad6174425066ff9752b60e25ed183
+Origin: https://github.com/mkj/dropbear/commit/d59436a4d56de58b856142a5d489a4a8fc7382ed
+---
+ cli-runopts.c | 45 +++++++++++++++++++++++++--------------------
+ 1 file changed, 25 insertions(+), 20 deletions(-)
+
+diff --git a/cli-runopts.c b/cli-runopts.c
+index 38a73f7..632d097 100644
+--- a/cli-runopts.c
++++ b/cli-runopts.c
+@@ -528,56 +528,61 @@ static void loadidentityfile(const char* filename, int warnfail) {
+ 
+ #if DROPBEAR_CLI_MULTIHOP
+ 
+-static char*
+-multihop_passthrough_args() {
+-	char *ret;
++/* Fill out -i, -y, -W options that make sense for all
++ * the intermediate processes */
++static char* multihop_passthrough_args(void) {
++	char *args = NULL;
+ 	unsigned int len, total;
++#if DROPBEAR_CLI_PUBKEY_AUTH
+ 	m_list_elem *iter;
+-	/* Fill out -i, -y, -W options that make sense for all
+-	 * the intermediate processes */
+-	len = 30; /* space for "-q -y -y -W <size>\0" */
++#endif
++	/* Sufficient space for non-string args */
++	len = 100;
++
++	/* String arguments have arbitrary length, so determine space required */
++	if (cli_opts.proxycmd) {
++		len += strlen(cli_opts.proxycmd);
++	}
+ #if DROPBEAR_CLI_PUBKEY_AUTH
+ 	for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
+ 	{
+ 		sign_key * key = (sign_key*)iter->item;
+-		len += 3 + strlen(key->filename);
+-	}
+-#endif /* DROPBEAR_CLI_PUBKEY_AUTH */
+-	if (cli_opts.proxycmd) {
+-		/* "-J 'cmd'" */
+-		len += 6 + strlen(cli_opts.proxycmd);
++		len += 4 + strlen(key->filename);
+ 	}
++#endif
+ 
+-	ret = m_malloc(len);
++	args = m_malloc(len);
+ 	total = 0;
+ 
++	/* Create new argument string */
++
+ 	if (cli_opts.quiet) {
+-		total += m_snprintf(ret+total, len-total, "-q ");
++		total += m_snprintf(args+total, len-total, "-q ");
+ 	}
+ 
+ 	if (cli_opts.no_hostkey_check) {
+-		total += m_snprintf(ret+total, len-total, "-y -y ");
++		total += m_snprintf(args+total, len-total, "-y -y ");
+ 	} else if (cli_opts.always_accept_key) {
+-		total += m_snprintf(ret+total, len-total, "-y ");
++		total += m_snprintf(args+total, len-total, "-y ");
+ 	}
+ 
+ 	if (cli_opts.proxycmd) {
+-		total += m_snprintf(ret+total, len-total, "-J '%s' ", cli_opts.proxycmd);
++		total += m_snprintf(args+total, len-total, "-J '%s' ", cli_opts.proxycmd);
+ 	}
+ 
+ 	if (opts.recv_window != DEFAULT_RECV_WINDOW) {
+-		total += m_snprintf(ret+total, len-total, "-W %u ", opts.recv_window);
++		total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window);
+ 	}
+ 
+ #if DROPBEAR_CLI_PUBKEY_AUTH
+ 	for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
+ 	{
+ 		sign_key * key = (sign_key*)iter->item;
+-		total += m_snprintf(ret+total, len-total, "-i %s ", key->filename);
++		total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
+ 	}
+ #endif /* DROPBEAR_CLI_PUBKEY_AUTH */
+ 
+-	return ret;
++	return args;
+ }
+ 
+ /* Sets up 'onion-forwarding' connections. This will spawn
diff -Nru dropbear-2022.83/debian/patches/series dropbear-2022.83/debian/patches/series
--- dropbear-2022.83/debian/patches/series	2024-07-09 14:22:02.000000000 +0200
+++ dropbear-2022.83/debian/patches/series	2025-05-16 15:01:36.000000000 +0200
@@ -3,3 +3,5 @@
 raise-connection-delay-in-tests.patch
 CVE-2023-48795.patch
 fix-noremotetcp-behavior.patch
+Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch
+CVE-2025-47203.patch

Attachment: signature.asc
Description: PGP signature


Reply to: