CVE-2021-3156: Heap-Based Buffer Overflow in Sudo (Baron Samedit)

Animesh Jain

Update Feb 3, 2021: It has been reported that macOS, AIX, and
Solaris are also vulnerable to CVE-2021-3156, and that others may also
still be vulnerable. Qualys has not independently verified the exploit.

Original Post: The Qualys Research Team has discovered a heap overflow vulnerability in sudo, a near-ubiquitous utility available on major Unix-like operating systems. Any unprivileged user can gain root privileges on a vulnerable host using a default sudo configuration by exploiting this vulnerability.

Sudo is a powerful utility that’s included in most if not all Unix- and Linux-based OSes. It allows users to run programs with the security privileges of another user. The vulnerability itself has been hiding in plain sight for nearly 10 years. It was introduced in July 2011 (commit 8255ed69) and affects all legacy versions from 1.8.2 to 1.8.31p2 and all stable versions from 1.9.0 to 1.9.5p1 in their default configuration.

Successful exploitation of this vulnerability allows any unprivileged user to gain root privileges on the vulnerable host. Qualys security researchers have been able to independently verify the vulnerability and develop multiple variants of exploit and obtain full root privileges on Ubuntu 20.04 (Sudo 1.8.31), Debian 10 (Sudo 1.8.27), and Fedora 33 (Sudo 1.9.2). Other operating systems and distributions are also likely to be exploitable.

As soon as the Qualys research team confirmed the vulnerability, Qualys engaged in responsible vulnerability disclosure and coordinated with sudo’s author and open source distributions to announce the vulnerability.

Disclosure Timeline

  • 2021-01-13: Advisory sent to Todd.Miller@sudo
  • 2021-01-19: Advisory and patches sent to distros@openwall
  • 2021-01-26: Coordinated Release Date (6:00 PM UTC)

Proof of Concept Video 

Technical Details

If Sudo is executed to run a command in “shell” mode (shell -c command):

  • either through the -s option, which sets Sudo’s MODE_SHELL flag; OR
  • through the -i option, which sets Sudo’s MODE_SHELL and MODE_LOGIN_SHELL flags; then, at the beginning of Sudo’s main(), parse_args() rewrites argv (lines 609-617), by concatenating all command-line arguments (lines 587-595) and by escaping all meta-characters with backslashes (lines 590-591):
-------------------------------------------------------------------- 
571     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { 
572         char **av, *cmnd = NULL; 
573         int ac = 1; 
... 
581             cmnd = dst = reallocarray(NULL, cmnd_size, 2); 
... 
587             for (av = argv; *av != NULL; av++) { 
588                 for (src = *av; *src != '\0'; src++) { 
589                     /* quote potential meta characters */ 
590                     if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$') 
591                         *dst++ = '\\'; 
592                     *dst++ = *src; 
593                 } 
594                 *dst++ = ' '; 
595             } 
... 
600             ac += 2; /* -c cmnd */ 
... 
603         av = reallocarray(NULL, ac + 1, sizeof(char *)); 
... 
609         av[0] = (char *)user_details.shell; /* plugin may override shell */ 
610         if (cmnd != NULL) { 
611             av[1] = "-c"; 
612             av[2] = cmnd; 
613         } 
614         av[ac] = NULL; 
615  
616         argv = av; 
617         argc = ac; 
618     } 
--------------------------------------------------------------------- 

Later, in sudoers_policy_main(), set_cmnd() concatenates the command-line arguments into a heap-based buffer “user_args” (lines 864-871) and unescapes the meta-characters (lines 866-867), “for sudoers matching and logging purposes”:

--------------------------------------------------------------  
819     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { 
... 
852             for (size = 0, av = NewArgv + 1; *av; av++) 
853                 size += strlen(*av) + 1; 
854             if (size == 0 || (user_args = malloc(size)) == NULL) { 
... 
857             } 
858             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { 
... 
864                 for (to = user_args, av = NewArgv + 1; (from = *av); av++) { 
865                     while (*from) { 
866                         if (from[0] == '\\' && !isspace((unsigned char)from[1])) 
867                             from++; 
868                         *to++ = *from++; 
869                     } 
870                     *to++ = ' '; 
871                 } 
... 
884             } 
... 
886     } 
--------------------------------------------------------------------- 

Unfortunately, if a command-line argument ends with a single backslash character, then:

  • at line 866, “from[0]” is the backslash character, and “from[1]” is the argument’s null terminator (i.e., not a space character);
  • at line 867, “from” is incremented and points to the null terminator;
  • at line 868, the null terminator is copied to the “user_args” buffer, and “from” is incremented again and points to the first character after the null terminator (i.e., out of the argument’s bounds);
  • the “while” loop at lines 865-869 reads and copies out-of-bounds characters to the “user_args” buffer.

In other words, set_cmnd() is vulnerable to a heap-based buffer overflow, because the out-of-bounds characters that are copied to the “user_args” buffer were not included in its size (calculated at lines852-853).

In theory, however, no command-line argument can end with a single backslash character: if MODE_SHELL or MODE_LOGIN_SHELL is set (line 858, a necessary condition for reaching the vulnerable code), then MODE_SHELL is set (line 571) and parse_args() already escaped all meta-characters, including backslashes (i.e., it escaped every single backslash with a second backslash).

In practice, however, the vulnerable code in set_cmnd() and the escape code in parse_args() are surrounded by slightly different conditions:

--------------------------------------------------------------------- 
819     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { 
... 
858             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { 
--------------------------------------------------------------------- 

versus:

--------------------------------------------------------------------- 
571     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { 
--------------------------------------------------------------------- 

Our question is: can we set MODE_SHELL and either MODE_EDIT or MODE_CHECK (to reach the vulnerable code) but not the default MODE_RUN (to avoid the escape code)?

The answer, it seems, is no: if we set MODE_EDIT (-e option, line 361) or MODE_CHECK (-l option, lines 423 and 519), then parse_args() removes MODE_SHELL from the “valid_flags” (lines 363 and 424) and exits with an error if we specify an invalid flag such as MODE_SHELL (lines 532-533):

--------------------------------------------------------------------- 
358                 case 'e': 
... 
361                     mode = MODE_EDIT; 
362                     sudo_settings[ARG_SUDOEDIT].value = "true"; 
363                     valid_flags = MODE_NONINTERACTIVE; 
364                     break; 
... 
416                 case 'l': 
... 
423                     mode = MODE_LIST; 
424                     valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST; 
425                     break; 
... 
518     if (argc > 0 && mode == MODE_LIST) 
519         mode = MODE_CHECK; 
... 
532     if ((flags & valid_flags) != flags) 
533         usage(1); 
--------------------------------------------------------------------- 

But we found a loophole: if we execute Sudo as “sudoedit” instead of “sudo”, then parse_args() automatically sets MODE_EDIT (line 270) but does not reset “valid_flags”, and the “valid_flags” include MODE_SHELL by default (lines 127 and 249):

--------------------------------------------------------------------- 
127 #define DEFAULT_VALID_FLAGS     (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL) 
... 
249     int valid_flags = DEFAULT_VALID_FLAGS; 
... 
267     proglen = strlen(progname); 
268     if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) { 
269         progname = "sudoedit"; 
270         mode = MODE_EDIT; 
271         sudo_settings[ARG_SUDOEDIT].value = "true"; 
272     } 
------------------------------------------------------------------------ 

Consequently, if we execute “sudoedit -s”, then we set both MODE_EDIT and MODE_SHELL (but not MODE_RUN), we avoid the escape code, reach the vulnerable code, and overflow the heap-based buffer “user_args” through a command-line argument that ends with a single backslash character:

--------------------------------------------------------------------- 
sudoedit -s '\' `perl -e 'print "A" x 65536'` 
malloc(): corrupted top size 
Aborted (core dumped) 
---------------------------------------------------------------------

From an attacker’s point of view, this buffer overflow is ideal due to following reasons:

1) The attacker controls the size of the “user_args” buffer that can be overflowed (the size of our concatenated command-line arguments, at lines 852-854);

2) The attacker independently controls the size and contents of the overflow itself (our last command-line argument is conveniently followed by our first environment variables, which are not included in the size calculation at lines 852-853);

3) The attacker can even write null bytes to the buffer that was overflowed (every command-line argument or environment variable that ends with a single backslash writes a null byte to “user_args”, at lines 866-868).

For example, on an amd64 Linux, the following command allocates a 24-byte “user_args” buffer (a 32-byte heap chunk) and overwrites the next chunk’s size field with “A=a\0B=b\0” (0x00623d4200613d41), its fd field with “C=c\0D=d\0” (0x00643d4400633d43), and its bk field with “E=e\0F=f\0” (0x00663d4600653d45):

--------------------------------------------------------------------- 
env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\' 
--------------------------------------------------------------------- 

--|--------+--------+--------+--------|--------+--------+--------+--------+-- 
  |        |        |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.| 
--|--------+--------+--------+--------|--------+--------+--------+--------+-- 

              size  <---- user_args buffer ---->  size      fd       bk 

Solution

Given the breadth of the attack surface for this vulnerability, Qualys recommends users apply patches for this vulnerability immediately.

Qualys customers can search the vulnerability knowledgebase for CVE-2021-3156 to identify all the QIDs and assets vulnerable for this vulnerability.

If you are not a customer, start your free Qualys VMDR trial to get full access to the QIDs (detections) for CVE-2021-3156, so you can identify your vulnerable assets.

Qualys Coverage

Qualys is releasing the QIDs in the table below as they become available starting with vulnsigs version VULNSIGS-2.5.90-4 and in Linux Cloud Agent manifest version lx_manifest-2.5.90.4-3.

QID Title O/S Advisory Version*
352207  Amazon Linux Security Advisory for sudo: ALAS2-2021-1590 (Baron Samedit)) Amazon Linux  ALAS2-2021-1590 VULNSIGS-2.5.94-2 / 2.5.94.4-3
352208 Amazon Linux Security Advisory for sudo: ALAS-2021-1478 (Baron Samedit) Amazon Linux  ALAS-2021-1478 VULNSIGS-2.5.94-2 / 2.5.94.4-3
174570 SUSE Enterprise Linux Security Update for sudo (SUSE-SU-2021:0226-1) (Baron Samedit) SUSE Enterprise Linux SUSE-SU-2021:0226-1 VULNSIGS-2.5.94-2 / 2.5.94.4-3
174571 SUSE Enterprise Linux Security Update for sudo (SUSE-SU-2021:0227-1) (Baron Samedit) SUSE Enterprise Linux SUSE-SU-2021:0227-1 VULNSIGS-2.5.94-2 / 2.5.94.4-3
174572 SUSE Enterprise Linux Security Update for sudo (SUSE-SU-2021:0225-1) (Baron Samedit) SUSE Enterprise Linux SUSE-SU-2021:0225-1 VULNSIGS-2.5.94-2 / 2.5.94.4-3
178379 Debian Security Update for sudo (DSA 4839-1) (Baron Samedit) Debian DSA 4839-1 VULNSIGS-2.5.94-2 / 2.5.94.4-3
198231 Ubuntu Security Notification for Sudo Vulnerabilities : USN-4705-1(Baron Samedit) Ubuntu USN-4705-1 VULNSIGS-2.5.94-2 / 2.5.94.4-3
374915 Gentoo Linux Sudo Multiple Vulnerabilities (GLSA 202101-33) (Baron Samedit) Gentoo GLSA 202101-33 VULNSIGS-2.5.94-2 / 2.5.94.4-3
158992 Oracle Enterprise Linux Security Update for sudo (ELSA-2021-0221)(Baron Samedit) Oracle Enterprise Linux ELSA-2021-0221 VULNSIGS-2.5.94-4 / 2.5.94.4-3
158993 Oracle Enterprise Linux Security Update for sudo (ELSA-2021-0218)(Baron Samedit) Oracle Enterprise Linux ELSA-2021-0218 VULNSIGS-2.5.94-4 / 2.5.94.4-3
158994 Oracle Enterprise Linux Security Update for sudo (ELSA-2021-9019)(Baron Samedit) Oracle Enterprise Linux ELSA-2021-9019 VULNSIGS-2.5.94-4 / 2.5.94.4-3
178383 Debian Security Update for sudo (DLA 2534-1) (Baron Samedit) Debian DLA 2534-1 VULNSIGS-2.5.94-4 / 2.5.94.4-3
239026 Red Hat Update for sudo (RHSA-2021:0218) (Baron Samedit) Red Hat RHSA-2021:0218 VULNSIGS-2.5.94-4 / 2.5.94.4-3
239027 Red Hat Update for sudo (RHSA-2021:0219) (Baron Samedit) Red Hat RHSA-2021:0219 VULNSIGS-2.5.94-4 / 2.5.94.4-3
239028 Red Hat Update for sudo (RHSA-2021:0220) (Baron Samedit) Red Hat RHSA-2021:0220 VULNSIGS-2.5.94-4 / 2.5.94.4-3
239029 Red Hat Update for sudo (RHSA-2021:0221) (Baron Samedit) Red Hat RHSA-2021:0221 VULNSIGS-2.5.94-4 / 2.5.94.4-3
239030 Red Hat Update for sudo (RHSA-2021:0222) (Baron Samedit) Red Hat RHSA-2021:0222 VULNSIGS-2.5.94-4 / 2.5.94.4-3
239031 Red Hat Update for sudo (RHSA-2021:0223) (Baron Samedit) Red Hat RHSA-2021:0223 VULNSIGS-2.5.94-4 / 2.5.94.4-3
239032 Red Hat Update for sudo (RHSA-2021:0227) (Baron Samedit) Red Hat RHSA-2021:0227 VULNSIGS-2.5.94-4 / 2.5.94.4-3
257056 CentOS Security Update for sudo Security Update (CESA-2021:0221) (Baron Samedit) CentOS CESA-2021:0221 VULNSIGS-2.5.94-4 / 2.5.94.4-3
280866 Fedora Security Update for sudo (FEDORA-2021-8840cbdccd) (Baron Samedit) Fedora FEDORA-2021-8840cbdccd VULNSIGS-2.5.94-4 / 2.5.94.4-3
280868 Fedora Security Update for sudo (FEDORA-2021-2cb63d912a) (Baron Samedit) Fedora FEDORA-2021-2cb63d912a VULNSIGS-2.5.94-4 / 2.5.94.4-3
352217 Amazon Linux Security Advisory for sudo: AL2012-2021-335 (Baron Samedit) Amazon Linux AL2012-2021-335 VULNSIGS-2.5.95-2 / 2.5.95.2-1
374891 Sudo Heap-based Buffer Overflow Vulnerability (Baron Samedit) Local Sudo Security Alerts VULNSIGS-2.5.90-4 / 2.5.90.4-3

* Version is the signature version followed by the Linux manifest version.

Dashboard

With VMDR Dashboard, you can track this vulnerability, their impacted hosts, their status and overall management in real time. With trending enabled for dashboard widgets, you can keep track of these vulnerabilities trends in your environment using the “Baron Samedit | Heap-based buffer overflow Sudo” Dashboard.

View and download the Baron Samedit dashboard.

Baron Samedit dashboard

Vendor References

Frequently Asked Questions (FAQs)

What versions are vulnerable?

The following versions of sudo are vulnerable:

  • All legacy versions from 1.8.2 to 1.8.31p2
  • All stable versions from 1.9.0 to 1.9.5p1
How can I test if I have vulnerable version?

To test if a system is vulnerable or not, login to the system as a non-root user.

Run command “sudoedit -s /”

If the system is vulnerable, it will respond with an error that starts with “sudoedit:”

If the system is patched, it will respond with an error that starts with “usage:”

Are versions before 1.8.2 vulnerable?

No. See explanation above.

I’m running a vulnerable version of sudo from the list mentioned above. Why does the test show my host is not vulnerable?

Operating system vendors don’t always update the software’s version number after introducing a patch to the software, particularly when they backport the patch. For example, Ubuntu released a patch (USN-4705-1) for Ubuntu 20.04, but the patched version is still 1.8.31 after the patch is installed.

Why am I seeing multiple QIDs for the same CVE-2021-3156 on the same host?

Qualys released a generic QID at the time of release which detected the vulnerability based on the output of the command sudoedit. Later on, as OS vendors released patches, we released QIDs based on package versions as well. So, it is possible for a vulnerable asset to have the vulnerability reported for QID 374891, and OS specific checks that were released later on.

What are the possible reasons for QID 374891 not triggering against vulnerable systems?

QID 374891 was released with vulnsigs version VULNSIGS-2.5.90-4 and in Linux Cloud Agent manifest version lx_manifest-2.5.90.4-3. For the vulnerabilities to be reported these signature and manifest versions need to be available on the platform (the deployment schedule varies), and agents have completed a scan with the latest versions of the signatures.

Also, Qualys released more QIDs based on the security advisories and patches released by various Linux distributions. So certain Linux distributions had coverage before the rest. For e.g., Amazon Linux security advisory was released first, and CentOS advisories, and patches were released couple of days later. Support for CentOS linux was added to later versions vulnsigs and manifests. See table above for details.

For CVE-2021-3156, what is the difference in the detection logic between QID 374891 and the rest of the QIDs?

QID 374891 attempts to confirm the vulnerability based on output of command sudoedit. The rest of the QIDs confirm the vulnerability based on version comparison based on the versions disclosed by the OS vendor in their respective security advisories.

Is a local user required to exploit the vulnerability?

Yes. However, this user does not need to be a privileged user or be a part of sudoers list. For example, even account ‘nobody’ can exploit the issue.

Why name the vulnerability “Baron Samedit”?

It’s a play on Baron Samedi and sudoedit.

Will Qualys Research Team publish exploit code for this vulnerability? 

No. 

Show Comments (2)

Leave a Reply to davidhcefx Cancel reply

Your email address will not be published. Required fields are marked *