Jump to content
Nytro

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

Recommended Posts

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

Animesh Jain
Animesh Jain, Vulnerability Signatures Product Manager, Qualys
January 26, 2021 - 8 min read
3

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

QID 374891: Sudo Heap-Based Buffer Overflow Vulnerability

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-1070x594.png 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.

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. 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...