INTRO

Ngày 25/3 vừa qua, ZDI có xuất bản bài đăng cve-2021-25646-getting-code-execution-on-apache-druid , cũng nhân tiện trong quá trình nghiên cứu về java thì mình cũng mày mò tìm hiểu cái bug này xem sao. Về apache nói chung thì nó có rất nhiều sản phẩm như zookeeper,dubbo,… theo đó là các plugin, extension hỗ trợ cho các ứng dụng web kèm theo. Theo mình có tìm hiểu về lịch sử của apache dạo gần đây, thì từ khoảng năm 2018, tầm cái dạo lỗi JAVA DESERIALIZE nổi lên, thì các sản phẩm của apache cũng bị chọc ngoáy vô số kể ví dụ như Apache Dubbo decodeBody Deserialization of Untrusted Data Remote Code Execution Apache Dubbo readUTF Deserialization of Untrusted Data Remote Code Execution Vulnerability . Quay trở lại với APACHE DRUID , nói đơn giản thì sản phẩm này của apache gồm chức năng chính là để phần tích database. Có thể tải nó ở đây.

CẤU HÌNH MÔI TRƯỜNG DEBUG:

  • Version: 0.19.0
  • [LOCAL] Có thể cấu hình local từ source code, cần chỉnh sửa lại một số lib io.druid.math.expr.antlr.*
  • [REMOTE] Có thể dễ dàng hơn khi debug remote bằng cách tải hẵn tệp binary về ( Mình sẽ hướng dẫn cho phần này)

###Thiết lập Debug Remote:

Sau khi tải apache druid về và giải nén, xem sơ qua thì có khá nhiều service được chạy kèm với ứng dụng này. Vì thế mà mình ngốn khá nhiều thời gian để tìm ra được tệp cấu hình jvm cho service tồn tại lỗ hỏng này.

Hình 1: Các cổng và dịch vụ đang lắng nghe

Và cuối cùng thì cần chỉnh sửa tại tệp /conf/druid/single-server/micro-quickstart/coordinator-overlord/jvm.config và thêm dòng sau vào tệp jvm.config.

agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=<ip-listen>:<port-listen>

Hình 2: Các option enviroment của jvm

Sau đó thực hiện copy tất cả tệp jar (external libraries) của ứng dụng vào một folder.

find . -iname '*.jar' -exec cp {} /home/libs/druid/ \;

Sau đó trên IDE debug ( ở đây mình dùng Intellij ) tạo một project rỗng và thực hiện Debug configurationsadd modules .

Hình 3: Debug configurations chọn template remote debug và thiết lập các option như trên

Hình 4: Add modules thực hiện thêm các libs đã trích xuất ở bước trên vào project.
File > Project Structure > Modules

Sau đó vào bin/run start-micro-quickstart.

Hình 5: Chạy ứng dụng apache adruid

PHÂN TÍCH:

Từ source đến sink !

com/sun/jersey/spi/container/ContainerRequest.class#271

Hình 5: Bóc tách các entity(thực thể) ở dạng raw từ request

Dòng 271, sau khi thực hiện bóc tách các entity thì chuyển tiếp đến phương thức readFrom() với tham số this.entitydata json gửi từ client.

com/fasterxml/jackson/jaxrs/base/ProviderBase.class#436

Hình 6: Phương thức readFrom()

Dòng 439, lúc này bắt đầu parse data ở dạng raw về dạng object thông qua việc khởi tạo ObjectReader ở dòng 438. Từ dòng 440 đến 458 thực hiện kiểm tra lần lượt các properties data json có null hay không, và thực hiện kiểm tra các giá trị của context ứng dựng. Đến dòng 460 kiểm tra nếu data json là multiValue như ở dòng 446 đã so sánh thì sẽ thực hiện readValues() hoặc readValue().

? Giải thích multiValue

com/fasterxml/jackson/databind/ObjectReader.class#readValues

Hình 7: Phương thức readValues

com/fasterxml/jackson/databind/ObjectReader.class#readValue

Hình 8: Phương thức readValue

com/fasterxml/jackson/databind/ObjectReader.class#_bind

Hình 9: Phức thức _bind()

Từ dòng 743 đến 755 bao gồm các bước tạo context cho quá trình deserialize các object, ngoài ra là các bước kiểm tra định dạng của jsonToken

com/fasterxml/jackson/core/JsonToken.class#jsonToken

Hình 10: Các define của JsonToken

Dòng 757 thực hiện deserialize các object với p là object đã được parse ra ở bước trên, ctxt là context ( bao gồm _factory,_config,..) . Gía trị valueToUpdate kia được bao gồm trong context của ứng dụng.

?? Giải thích valueToUpdate

Tiếp theo là stacktrace( ngăn xếp ) cho luồng thực thi tiếp theo.

\->com/fasterxml/jackson/databind/deser/impl/TypeWrappedDeserializer.class#deserialize(JsonParser p, DeserializationContext ctxt)
-->com/fasterxml/jackson/databind/deser/AbstractDeserializer.class#deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
--->com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.class#deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt)
---->com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.class#\_deserializeTypedForId(JsonParser p, DeserializationContext ctxt, TokenBuffer tb)

Hình 11: Stacktrace ở bước _deserializeTypedForId()

Thấy rằng trên stacktrace khá nhiều phương thức trong luồng thực thi lặp đi lặp lại nhiều lần. Lý do là vì dữ liệu json gửi lên server gồm nhiều cấu trức giống nhau . Dẫn đến các để đọc, phân tích, deserialize toàn bộ dữ liệu này cần phải lặp lại nhiều lần. Đây là đoạn json gửi lên server:

https://gist.github.com/dianguc38/1417e9d923afedabe76120d5d8b33a97

com/fasterxml/jackson/databind/deser/BeanDeserializer.class#_deserializeUsingPropertyBased()

Hình 12: Phương thức _deserializeUsingPropertyBased()

Ở bước (1) thực hiện tạo một contructor đến PropertyValueBuffer()

com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.class#PropertyValueBuffer()

Hình 13: Contructor PropertyValueBuffer()

Dòng 299 (2) là một vòng lặp để thực hiện deserialize lần lượt các propertie của Json.

Bước 3 thực hiện findCreatorProperty() với cá propName( PropName ở đây là các key của json, không phải key mặc định)

Hình 14: Các key ( propname)

findCreatorProperty() gọi đến là một hashmap _propertyLookup() để tìm thuộc tính tương ứng với khóa.

Hình 15: _propertyLookup được khai báo dưới dạng hashMap

Bước (4) lại tiếp tục là một chuỗi deserialize object từ json với creatorProp chỉ dùng để ném ra cùng exeption

Hình 16: lifecycle deserialize ở bước(4)

Và đến propName"" thì thật sự khó hiểu tại sao ứng dụng web lại có thể mặc định cho nó là option javaScriptConfig{enable=true}

Hình 17: PropName""

com/fasterxml/jackson/databind/deser/BeanDeserializer.class#_deserializeUsingPropertyBased

Hình 18: Khi propName"" thì các giá trị khởi tạo của creatorProp sẽ như bảng bên phải

com/fasterxml/jackson/databind/deser/BeanDeserializer.class#_deserializeUsingPropertyBased

Hình 19: Với propName là enable

org/apache/druid/query/filter/JavaScriptDimFilter.class#JavaScriptDimFilter

Hình 20: Chuyển đến contructor JavaScriptDimFilter định nghĩa lần lượt các biến địa phương

Bước này lần lượt định nghĩa các biến địa phương như this.function để chuẩn bị cho bước compilerFunction .

org/apache/druid/query/filter/JavaScriptDimFilter.class#getPredicateFactory

Hình 21: checkState kiểm tra trạng thái của option javascript

Nếu this.config[enable] != true thì sẽ ném ra một thông báo lỗi JavaScript is disabled. Nhưng với dữ liệu json được gửi lên đã định nghĩ cho nó là true , dẫn việc checkState này được vượt qua.

org/apache/druid/query/filter/JavaScriptDimFilter.class#JavaScriptPredicateFactory

Hình 22: Phương thức JavaScriptPredicateFactory

Dòng 157 là sink để thực thi các mã script đọc hại từ this.script đã được khai báo.

Ref:

https://github.com/fupinglee/Struts2_Bugs

https://www.freebuf.com/vuls/263276.html