+6

JDBC Exploit

1. Giới thiệu về JDBC

JDBC (viết tắt của Java Database Connectivity) là một API để thực hiện việc kết nối tới database, thực thi các câu lệnh SQL,...

Nó là một phần của JavaSE (Standard Edition). JDBC API sử dụng JDBC Driver để connect tới database và có 4 loại driver:

  • JDBC-ODBC Bridge Driver
  • Native Driver
  • Network Protocol Driver
  • Thin Driver

image.png

Lập trình viên Java có lẽ cũng khá quen thuộc với cách sử dụng JDBC API

image.png

Trên thực tế, JDBC là một interface chuẩn và mỗi CSDL quan hệ riêng sẽ có từng implement riêng.

image.png

2. JDBC Exploit

Để khai thác những lỗ hổng liên quan đến JDBC, việc quan trọng nhất là phải kiểm soát được URL của JDBC Connection.

image.png

image.png

JDBC Driver là thư viện được cài ở trên client, không phải trên server (với client ở trong trường hợp này là nơi thực hiện gọi API JDBC - tức server web, còn server là database). Nó sẽ convert từ một request từ Java program thành protocol mà Database Management System (DBMS) có thể hiểu được và thực thi. Sau khi thực thi, DBMS sẽ trả lại kết quả tương ứng.

Như vậy kịch bản tấn công theo mô hình trên sẽ là:

  • Kẻ tấn công control được giá trị URL của JDBC Connection, trỏ vào server Database fake do kẻ tấn công kiểm soát
  • Server web mục tiêu sẽ thực hiện gọi tới JDBC API connect tới server giả mạo bằng JDBC Driver
  • Kẻ tấn công lợi dụng các lỗ hổng bảo mật hoặc một số tính năng cụ thể của JDBC Driver đó để trigger lỗ hổng.

3. Khai thác JDBC trong Mysql

Phần này mình sẽ trình bày 2 lỗ hổng liên quan đến JDBC trong Mysql.

3.1. Đọc file bất kỳ

Mysql có một câu lệnh là LOAD DATA LOCAL cho phép đọc file của client sau đó gửi lên Mysql Server. Link chi tiết tại đây.

Như vậy với một câu lệnh load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; thì nó sẽ đọc file /etc/passwd của client sau đó gửi lên server.

image.png

Chính trang chủ của Mysql cũng đã nêu về sự nguy hiểm của nó và cảnh báo người dùng không được kết nối tới Mysql Server không đáng tin cậy.

image.png

Sau khi hiểu sơ qua về lỗ hổng, điều tiếp theo cần làm là làm sao dựng được một Mysql Server độc hại. Trước khi làm điều này, chúng ta cần nghiên cứu về cấu trúc gói tin mà Mysql thường thực hiện liên kết và truy vấn.

Để tìm hiểu, mình sử dụng Mysql Shell để connect tới localhost:3306 với username là root, database là test và chạy load data local infile "c:\\windows\\win.ini" into table test.test FIELDS TERMINATED BY '\n';. (Lưu ý cần sử dụng cờ useSSL=false để có thể đọc được các package một cách rõ ràng)

1. Greeting package, server trả lại banner với version của Mysql

image.png

2. Client login request

image.png

3. Khởi tạo truy vấn. Sẽ có rất nhiều câu truy vấn

Ngoài những câu truy vấn để lấy các thông tin cần thiết, sẽ có câu truy vấn ta thực hiện.

image.png

image.png

Server Mysql trả về thông tin file cần load lên.

image.png

Client (Web server) đọc file và gửi lên cho Server Mysql.

image.png

Server trả lại thông báo đã insert 7 records.

image.png

4. Kết thúc

Kết quả sẽ xuất hiện data của win.ini file trong table test:

image.png

Lưu ý, tùy thuộc loại Mysql DBMS mà các gói tin gửi và nhận sẽ khác nhau đôi chút. Trên đây là mình thử với Mysql Workbench với Mysql Server ver 5.7 trên windows.

Quá trình truy vấn ban đầu là:

Client: Yêu cầu insert file win.ini vào table test
Server: Yêu cầu win.ini content
Client: Gửi win.ini content

Kịch bản tấn công khi Server do attacker kiểm soát:

Client: Yêu cầu truy vấn bất kì
Server: Yêu cầu win.ini content
Client: Có gửi win.ini content hay không?

Theo document của Mysql:

image.png

Do vậy, theo lý thuyết hoàn toàn có thể trực tiếp gửi về Client (là Webserver) yêu cầu đọc file bất kỳ vào gửi lại server (Mysql Server). Sau khi xem các gói tin gửi và nhận, ta thấy vấn đề này nằm ở việc cấu hình trong Mysql Server, không phải do Client. Sau khi gửi gói tin Greeting, Mysql Server sẽ trả về cấu hình cài đặt của mình.

image.png

Cấu hình trả về có thể sẽ không đầy đủ. Mình viết 1 poc nhỏ cho lỗ hổng này tại đây. Lưu ý là poc này chỉ hoạt động với local của mình trong trường hợp cụ thể này, không chắc nó sẽ hoạt động với trường hợp khác. Chạy poc và dùng jdbc connection API tới jdbc:mysql://localhost:33060/test với user, pass bất kì ta được kết quả:

        Connection from: ('127.0.0.1', 40608)
        [*] Payload send!
        Data received: ; for 16-bit app support
        [fonts]
        [extensions]
        [mci extensions]
        [files]
        [Mail]
        MAPI=1

3.2. MySQL JDBC Client Deserialization Vulnerability

Lỗ hổng đầu tiên được biết tới liên quan đến Deserialization của Mysql JDBC là CVE-2017-3523

image.png

Đến năm 2019, một hacker người Tàu là ZhangYang đã hoàn thiện và trình bày <New Exploit Technique In Java Deserialization Attack> tại BlackHat 2019.

Điểm kích hoạt lỗ hổng là com.mysql.jdbc.ResultSetImpl#getObject (Phân tích trên mysql-connector-java-5.1.8.jar file)

    public Object getObject(int columnIndex) throws SQLException {
        ...
                case -4:
                case -3:
                case -2:
                    if (field.getMysqlType() == 255) {
                        return this.getBytes(columnIndex);
                    } else if (!field.isBinary() && !field.isBlob()) {
                        return this.getBytes(columnIndex);
                    } else {
                        byte[] data = this.getBytes(columnIndex);
                        if (!this.connection.getAutoDeserialize())
                            return data;
                        } else {
                            Object obj = data;
                            if (data != null && data.length >= 2) {
                                if (data[0] != -84 || data[1] != -19) {
                                    return this.getString(columnIndex);
                                }

                                try {
                                    ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
                                    ObjectInputStream objIn = new ObjectInputStream(bytesIn);
                                    obj = objIn.readObject();
                                    objIn.close();
                                    bytesIn.close();
                                } catch (ClassNotFoundException var11) {
                                    throw SQLError.createSQLException(Messages.getString("ResultSet.Class_not_found___91") + var11.toString() + Messages.getString("ResultSet._while_reading_serialized_object_92"), this.getExceptionInterceptor());
                                } catch (IOException var12) {
                                    obj = data;
                                }
                            }

                            return obj;
                        }
                    }

Hàm này sẽ check xem 2 byte đầu tiên có phải là 0xAC 0xED (magic byte của object Java) (-84 và -19) sau đó thực hiện readObject() từ data là giá trị đọc được từ cột đầu vào. (Lưu ý cột đầu vào phải có type BLOB hoặc dạng Binary và có cờ autoDeserialize được bật để vượt qua một số lệnh if-else phía trên).

image.png

Như vậy, để đạt được deserialize, chúng ta cần gọi đến hàmgetObject và đặt autoDeserialize=true.

Để gọi đến getObject, ta sẽ lợi dụng một khái niệm là queryInterceptors. Theo mô tả của Mysql, queryInterceptors có thể mang giá trị một triển khai của com.mysql.cj.interceptors.QueryInterceptor interface. Interface này có tác dụng tùy biến quá trình xử lý theo nhu cầu thực tế, như tự động kiểm tra data trong memcache server, viết lại các truy vấn chậm, logging, v.v... (Link). Các lớp sẽ thực hiện implement 2 hàm là preProcesspostProcess, là quá trình trước và sau khi nhận được data truy vấn.

Trong số các lớp kế thừa interface trên, ta có com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor với hàm preProcess:

image.png

image.png

image.png

Và như vậy, payload cuối cùng sẽ tương tự dạng jdbc:mysql://localhost:33060/test?autoDeserialize=true&queryInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor

Nhiệm vụ của server attacker sẽ là lưu trữ một Object trong bảng với column type là BLOB rồi đợi Client connect với giá trị như trên. Tùy vào ngữ cảnh cụ thể của Client, ta có thể sử dụng gadget tương ứng - điều kiện cuối cùng để có thể RCE thông qua deserialization. Note là tên lớp và thuộc tính của QueryInterceptor sẽ khác nhau đôi chút tùy từng phiên bản của JDBC Driver.

Trong lỗ hổng này, có thể không cần kiểm soát giá trị URL của JDBC. Nếu Mysql Server internal thiết kế không đúng định dạng, có cột dữ liệu dạng BLOB, hoặc trong server web có lỗ hổng SQL Injection để attacker lợi dụng chuyển type thành BLOB thì cũng có thể khai thác.

Nguồn

https://pyn3rd.github.io/2022/06/06/Make-JDBC-Attacks-Brillian-Again-I/

https://w00tsec.blogspot.com/2018/04/abusing-mysql-local-infile-to-read.html

https://i.blackhat.com/eu-19/Thursday/eu-19-Zhang-New-Exploit-Technique-In-Java-Deserialization-Attack.pdf


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí