Back to list
CTFVery Easy

Debug TryHackMe Walkthrough — PHP Deserialization to Root

ducky
2026-05-22
27 views
7 min read

Debug TryHackMe Walkthrough — PHP Deserialization to Root

Difficulty: Medium · OS: Linux · Skills: Web enumeration, PHP object injection, hash cracking, Linux privilege escalation

Debug is a medium-rated Linux machine on TryHackMe built around one core idea: insecure PHP deserialization. We start with a tiny attack surface (just SSH and a web server), find a leaked source-code backup, abuse an unserialize() call to get a webshell, then chain a cracked password and a writable MOTD script all the way to root.

This writeup keeps it short and practical so you can follow the logic, not just copy commands. Flags and passwords are intentionally redacted — go earn your own.


Attack Chain at a Glance

  1. Recon → Only ports 22 (SSH) and 80 (HTTP) are open.
  2. Web enumeration → A /backup directory leaks index.php.bak (the site's source code).
  3. Initial access → The source passes user input straight into unserialize(). We craft a PHP object that abuses the __destruct() magic method to drop a webshell on the live index.php → RCE as www-data.
  4. Lateral movement → A .htpasswd hash is cracked; the password is reused by user james.
  5. Privilege escalationjames can edit MOTD scripts in /etc/update-motd.d/, which run as root on SSH login → root shell.

1. Enumeration

Port Scan

Start with a full Nmap scan to see what's exposed.

Prefer speed? RustScan wraps Nmap and is much faster for the initial sweep:

Result — a very small footprint:

22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.10
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))

What this tells us: SSH alone is rarely the way in, so the web server on port 80 is almost certainly our entry point.

A Note on SSH (a deliberate rabbit hole)

OpenSSH 7.2p2 is affected by a well-known username enumeration issue (CVE-2018-15473), and you may see tools report users like root. It's worth knowing this exists, but on Debug it's a dead end — the passwords aren't guessable and SSH isn't the intended foothold. Note it, then move on. Recognising rabbit holes quickly is half the skill.

Web Directory Brute-Force

The homepage is just the default Apache page, so we hunt for hidden content.

Interesting hits:

/index.php       (the LIVE page — remember this)
/readme.md
/backup          (directory listing enabled)

readme.md reveals the site uses the "Base" front-end framework — useful context, but it's only styling, not the vulnerability. The real prize is /backup.

Tip: Directories named backup, dev, old, or bak are gold mines — they often hold source code or credentials never meant to be public.


2. Finding the Vulnerability

Browsing to /backup shows directory listing is enabled, with several .bak files. The HTML backup is just the default Apache page, but index.php.bak is the backup of the live page's source. Download it:

The critical logic:

Why this is exploitable, in plain English

  • The debug parameter is taken from the URL and passed directly into unserialize() with no validation — a textbook insecure deserialization bug.
  • unserialize() rebuilds whatever PHP object we describe, including its property values.
  • When that object is destroyed at the end of the script, PHP automatically runs the __destruct() magic method.
  • __destruct() writes to a file whose name (form_file) and contents (message) we fully control.

So we can instruct the server: create a file called shell.php containing PHP of our choosing. That's remote code execution.

Important: the .bak file can't run — target the live index.php

A common beginner trap: you can't exploit index.php.bak, because the server serves it as plain text (it doesn't execute .bak as PHP). The .bak is source disclosure — it just tells us how the code works. The vulnerable unserialize() actually runs on the live /index.php we found during enumeration. That's where we send the payload.


3. Initial Access — Crafting the Payload

Write a small PHP script locally to build the malicious serialized object. Use the same class and property names as the source, set form_file to a .php filename, and set message to a one-line command webshell.

Generate the payload:

Anatomy of the serialized string

Understanding the format helps you debug payloads by hand:

TokenMeaning
O:10:"FormSubmit"an Object whose class name is 10 characters long
:2:the object has 2 properties
s:9:"form_file"a string property, 9 characters long, named form_file
s:9:"shell.php"its value: a 9-character string

If you ever change the filename or payload, the length numbers must match exactly, or unserialize() rejects it.

Deliver the payload

URL-encode the serialized output and send it through the debug parameter on the live page:

The destructor fires and writes the webshell. Confirm code execution:

We have RCE as www-data.

Upgrade to a Reverse Shell

Start a listener locally:

Trigger a reverse shell through the webshell (URL-encode the command). Because the box has no internet access, host any payload script locally and pull it in. Then stabilise the shell:


4. Lateral Movement — Cracking and Reusing a Password

In the web root there's a hidden Apache credentials file:

The $apr1$ prefix means Apache MD5 (APR1 / md5crypt). Crack it with either tool:

It cracks almost instantly to a common rockyou password. Now check /etc/passwd for real login users, then test whether james reused that password for his system account — a very common real-world mistake:

Password reuse pays off. Grab the user flag:


5. Privilege Escalation — Writable MOTD Scripts

Manual enumeration first

Before reaching for LinPEAS, do the quick manual checks — they often reveal the path instantly:

The .bash_history is the giveaway: it shows previous activity in /etc/update-motd.d/ and an SSH session. That's our breadcrumb.

The note

"root" asks james to make the SSH welcome message prettier and notes he was given write access to "all these files." That welcome banner is the MOTD (Message Of The Day).

The vulnerability

On Ubuntu, scripts in /etc/update-motd.d/ execute as root every time someone logs in over SSH. Check the permissions:

james owns the group and can write to a script that root runs on login. Append a couple of lines to 00-header that copy bash and set the SUID bit (copying to /tmp is cleaner than modifying the system /bin/bash):

Trigger it

Log in over SSH as james — this fires the MOTD scripts as root:

Now drop into a root shell using the SUID binary with the privileged flag:

Rooted.


Key Takeaways

  • Never pass untrusted input to unserialize(). If you must deserialize, use a safe format like JSON, or restrict classes with unserialize($data, ['allowed_classes' => false]).
  • Source-code backups are a real leak. A stray .bak exposed the whole vulnerability. Keep backups out of the web root and disable directory listing.
  • .bak files don't execute — they disclose. Find the live endpoint to actually trigger the bug.
  • Don't reuse passwords across services. One cracked Apache hash gave full SSH access.
  • MOTD scripts run as root. Any user who can write to /etc/update-motd.d/ effectively owns the box. Audit file ownership and group permissions carefully.

FAQ

What vulnerability does the Debug machine teach? Insecure PHP deserialization — passing attacker-controlled input to unserialize(), abused via the __destruct() magic method to write a webshell and gain RCE.

Why can't I exploit index.php.bak directly? The server returns .bak files as plain text, so the PHP never executes. The backup is only for reading the source; you exploit the live index.php.

Which Hashcat mode cracks the .htpasswd hash? The $apr1$ (Apache MD5 / md5crypt) hash uses Hashcat mode 1600. John the Ripper auto-detects it as md5crypt.

How does the privilege escalation work? User james can write to scripts in /etc/update-motd.d/, which run as root on every SSH login. Adding a SUID-bash command and then logging in via SSH yields a root shell.


Tools Used

nmap / rustscan · gobuster / dirsearch / ffuf · php · curl · netcat · hashcat / john · ssh


Educational walkthrough for the Debug room on TryHackMe. Practise responsibly — only test systems you're authorised to attack.

Tags

#Debug TryHackMe#Debug walkthrough#PHP deserialization"#insecure unserialize#MOTD privilege escalation#TryHackMe writeup

Keep Reading

Related writeups