Post

CVE-2019-19544 - CA Dollar Universe 5.3.3 'uxdqmsrv' - Privilege Escalation via a Vulnerable SUID Binary

A vulnerability was discovered in the uxdqmsrv binary. It consists in an arbitrary file write as root that can be leveraged by any local user to gain full root privileges on the host (UNIX/Linux only).

Indeed, the program tries to write to a log file that can be specified using the U_LOG_FILE environment variable. When uxdqmsrv is owned by root and the SUID bit is set (default setup), this file will be created with root privileges if it doesn’t exist. Using a UNIX/Linux feature called umask, a local user can also control the permissions of the created file and make it world-writable, thus controlling the content of the file.

Vulnerability analysis

On a default UNIX/Linux setup, the binary uxdqmsrv is owned by root and is configured with the SUID bit. In other words, when executed by a user other than root, the system will set the EUID to zero so that it can execute code as root.

When the program is executed without any arguments, two error messages are displayed. Apparently, it tries to open two files, which cannot be found if the appropriate options are not set.

The file and ldd commands will give some basic information about the file. Here, we notice two things:

  • Although the host is running a 64-bit OS, the file is a 32-bit executable.
  • The file is not stripped, i.e. it was compiled with debugging information. This may ease the reverse engineering process.

Using IDA, we will try to identify the code responsible for the two error messages. First, we list all the strings that are present in the binary and search for U_LOG_FILE. This string is located in the .rodata section at the address 0x0806A8FF. Then, we can list all the references to this address (using Xrefs to). In the present case, there is only one reference, so we jump directly to this one.

The string U_LOG_FILE is indeed used in the instruction at the address 0x08061BCC (.text section).

A pointer to this string is loaded into EAX. Then the content of the register is pushed onto the stack and finally getenv() is called. This is equivalent to the following C code:

1
getenv("U_LOG_FILE");

This means that the second error message is somehow related to a missing environment variable. So, without further investigation, we can try to set this environment variable and observe the behavior of the program.

As it seems a file is expected, we can try to set the value of the U_LOG_FILE variable to a dummy file path. After executing the program, we notice that the second error message has now disappeared.

Using ls, we can see that the file foo123.log was created and is owned by root, which means that the program created it without dropping the privileges. However, it can only be modified by root, so we get a Permission denied error message if we try to modify it.

To work around this issue, we can take advantage of a UNIX/Linux feature, which is called umask. umask is used to set the default permissions of newly created files. On the screenshot below, we can see that the current umask is set to 022, i.e. new files are created with rw-r--r-- permissions (and new folders are created with rwxr-xr-x permissions).

Therefore, if we set the current umask to 0111 (or 0000, which yields the same result for files), we could theoretically control the permissions of the new file and set them to rw-rw-rw-, unless the program sets its own umask.

To do so, we use the command umask 111 (or umask 000) and then repeat the previous steps.

This time, the file is still owned by root but the permissions are set to rw-rw-rw-, which means that we can now modify it. This arbitrary file creation as root can be used as a primitive to gain full root privileges on the host. This will be explained in the next section.

Exploit development

When an arbitrary file creation vulnerability is found in a SUID binary, a common trick to gain full root privileges is to take advantage of another UNIX/Linux feature: Shared Object preloading.

Shared object preloading can be used to specify libraries that will be loaded by a program before any other library. This can be achieved in two ways: either by setting the LD_PRELOAD environment variable or by using the /etc/ld.so.preload file, which requires root privileges.

According to the manual, /etc/ld.so.preload is a file containing a whitespace-separated list of ELF shared objects to be loaded before the program. Unlike LD_PRELOAD, Shared Objects listed in /etc/ld.so.preload are loaded even if the program has the SUID bit.

The exploit will consist in using the vulnerable binary to create the /etc/ld.so.preload file and using umask to make it writable by everyone. This way, we will be able to reference a custom library that will be loaded by any program. Especially, we will use an arbitrary built-in SUID binary to trigger the execution of some malicious code as root.

As a summary, the following steps will be implemented in the final exploit:

1) Create a root shell binary

  • It must invoke setuid(0) and setgid(0) to be able to impersonate root.
  • It will then call system('/bin/sh') to get a shell as root.

2) Create a custom Shared Object

  • We will overwrite the function geteuid() (for example).
  • The malicious code will set the owner of the root shell binary to root, set the SUID bit and finally return the result of the legitimate geteuid() function.
  • The execution of the code will be triggered by a call to /usr/bin/sudo (which is also a SUID binary owned by root).

3) Trigger the vulnerability

  • Set the UMASK to 111 to make new files writable by everyone in the current context.
  • Set the environment variable U_LOG_FILE to /etc/ld.so.preload.
  • Execute the vulnerable binary. This way, /etc/ld.so.preload will be created and will be writable by the current user.
  • Clear the file’s content and reference our custom shared object.
  • Finally call /usr/bin/sudo to trigger the execution of the malicious code.

4) Run the root shell

  • At this stage, the root shell binary should be owned by root and should have the SUID and SGID bits enabled.
  • Running the file should pop a shell as root.

Side note

The machine on which the vulnerability was initially discovered was properly hardened. The /tmp/ folder was mounted in a separate partition with the option nosuid. It means that although the exploit was successful and the root shell was created, it didn’t grant root privileges. Therefore, some additional code was added to search for a world-writable directory in /opt/. The global variable USE_TMP is used in the script to specify whether the exploit should use /tmp/ as a working directory or recursively search for a suitable one in /opt/.

Remediation

At the time of writing, Dollar Universe 5.3.3 is reaching its end of life. Therefore, no patch has been developped on this version.

However, a workaround exists:

  • Remove the SUID bit.
  • Create a new entry in /etc/sudoers to enable a specific user to run it as root.

Alternatively, upgrade to Dollar Universe 6.

Credits

The shared object was taken from the following exploit: https://www.exploit-db.com/exploits/40768/.

Disclosure timeline

2018-06-06 - Vulnerability discovery
2018-06-07 - Being redirected to the Product Manager
2018-06-26 - Report (+demonstration video) sent to vendor
2018-07-11 - Reminder sent to vendor
2018-07-12 - Vendor acknowledges vulnerability
2018-07-12 - Suggested a workaround
2018-08-02 - Reminder sent to vendor
2018-08-03 - Workaround accepted by vendor
2018-08-31 - Vulnerability disclosed

This post is licensed under CC BY 4.0 by the author.