From 2e0b74242220a97926d006719d1ac6e113918e2b Mon Sep 17 00:00:00 2001
From: seuzw <930zhaowei@163.com>
Date: Thu, 20 May 2021 20:23:30 +0800
Subject: [PATCH] add strict-scp-check for CVE-2020-15778

---
 servconf.c | 12 ++++++++++++
 servconf.h |  1 +
 session.c  | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 64 insertions(+)

diff --git a/servconf.c b/servconf.c
index 333b802..0a7cfa4 100644
--- a/servconf.c
+++ b/servconf.c
@@ -91,6 +91,7 @@ initialize_server_options(ServerOptions *options)
 {
 	memset(options, 0, sizeof(*options));
 
+	options->strict_scp_check = -1;
 	/* Portable-specific options */
 	options->use_pam = -1;
 
@@ -309,6 +310,8 @@ fill_default_server_options(ServerOptions *options)
 		    _PATH_HOST_XMSS_KEY_FILE, 0);
 #endif /* WITH_XMSS */
 	}
+	if (options->strict_scp_check == -1)
+		options->strict_scp_check = 0;
 	/* No certificates by default */
 	if (options->num_ports == 0)
 		options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
@@ -516,6 +519,7 @@ fill_default_server_options(ServerOptions *options)
 /* Keyword tokens. */
 typedef enum {
 	sBadOption,		/* == unknown option */
+	sStrictScpCheck,
 	/* Portable-specific options */
 	sUsePAM,
 	/* Standard Options */
@@ -573,6 +577,7 @@ static struct {
 #else
 	{ "usepam", sUnsupported, SSHCFG_GLOBAL },
 #endif
+	{ "strictscpcheck", sStrictScpCheck, SSHCFG_GLOBAL },
 	{ "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL },
 	/* Standard Options */
 	{ "port", sPort, SSHCFG_GLOBAL },
@@ -1391,6 +1396,11 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 	/* Standard Options */
 	case sBadOption:
 		goto out;
+
+	case sStrictScpCheck:
+		intptr = &options->strict_scp_check;
+		goto parse_flag;
+
 	case sPort:
 		/* ignore ports from configfile if cmdline specifies ports */
 		if (options->ports_from_cmdline) {
@@ -2666,6 +2676,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
 		dst->n = src->n; \
 } while (0)
 
+	M_CP_INTOPT(strict_scp_check);
 	M_CP_INTOPT(password_authentication);
 	M_CP_INTOPT(gss_authentication);
 	M_CP_INTOPT(pubkey_authentication);
@@ -2960,6 +2971,7 @@ dump_config(ServerOptions *o)
 #ifdef USE_PAM
 	dump_cfg_fmtint(sUsePAM, o->use_pam);
 #endif
+	dump_cfg_fmtint(sStrictScpCheck, o->strict_scp_check);
 	dump_cfg_int(sLoginGraceTime, o->login_grace_time);
 	dump_cfg_int(sX11DisplayOffset, o->x11_display_offset);
 	dump_cfg_int(sX11MaxDisplays, o->x11_max_displays);
diff --git a/servconf.h b/servconf.h
index cb73d2d..12c2053 100644
--- a/servconf.h
+++ b/servconf.h
@@ -203,6 +203,7 @@ typedef struct {
 					 * disconnect the session
 					 */
 
+	int     strict_scp_check;
 	u_int	num_authkeys_files;	/* Files containing public keys */
 	char   **authorized_keys_files;
 
diff --git a/session.c b/session.c
index dfbebba..1b67393 100644
--- a/session.c
+++ b/session.c
@@ -175,6 +175,51 @@ static char *auth_sock_dir = NULL;
 
 /* removes the agent forwarding socket */
 
+
+int scp_check(const char *command)
+{
+	debug("Entering scp check");
+	int check = 0;
+	if (command == NULL) {
+		debug("scp check succeeded for shell mode");
+		return check;
+	}
+	int lc = strlen(command);
+	char special_characters[] = "|;&$><`\\!\n";
+	int ls = strlen(special_characters);
+	int count_char[128] = {0};
+
+	for (int i = 0; i < ls; i++) {
+		count_char[special_characters[i]] = 1;
+	}
+
+	char scp_prefix[6] = "scp -";
+	int lp = 5;
+
+	if (lc <= lp) {
+		debug("scp check succeeded for length");
+		return check;
+	}
+
+	for (int i = 0; i < lp; i++) {
+		if (command[i] - scp_prefix[i]) {
+			debug("scp check succeeded for prefix");
+		return check;
+		}
+	}
+
+	for (int i = lp; i < lc; i++) {
+		if (command[i] > 0 && command[i] < 128) {
+			if (count_char[command[i]]) {
+				check = 1;
+				debug("scp check failed at %d: %c", i, command[i]);
+				break;
+			}
+		}
+	}
+	return check;
+}
+
 static void
 auth_sock_cleanup_proc(struct passwd *pw)
 {
@@ -692,6 +737,12 @@ do_exec(struct ssh *ssh, Session *s, const char *command)
 		command = auth_opts->force_command;
 		forced = "(key-option)";
 	}
+
+	if (options.strict_scp_check && scp_check(command)) {
+		verbose("Special characters not allowed in scp");
+		return 1;
+	}
+
 #ifdef GSSAPI
 #ifdef KRB5 /* k5users_allowed_cmds only available w/ GSSAPI+KRB5 */
 	else if (k5users_allowed_cmds) {
-- 
1.8.3.1

