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 và 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 configurations và add 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/ và 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.entity là data 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
là ""
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
là ""
com/fasterxml/jackson/databind/deser/BeanDeserializer.class#_deserializeUsingPropertyBased
Hình 18: Khi propName
là ""
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