Skip to content

Liferay deserialization (JSON Deserialization) [part 4] (CVE-2019-16891)

Câu chuyện về liferay tưởng chừng như đã đi đến một happy ending và đi vào dĩ vãng,

Mình cũng đã khá là mệt mỏi khi follow thằng này gần 1 năm rồi, …

Câu chuyện lại tiếp diễn khi vào một ngày nọ, đồng nghiệp có nói bâng quơ với mình: “A nghe thanh niên KV ở V* khoe mới tìm đc cái deserialize mới trên liferay kìa …”.

Nghĩ thầm trong bụng: “Cay ko, chẳng lẽ bên V* có PoC RCE liferay mà mình mang tiếng chọc liferay gần 1 năm lại ko có!”

Thế là mình bắt đầu start lại cái project cũ và đâm đầu vào tìm cái 1day này!

Kể đôi chút về cái project cũ kia, nó được gọi là GadgetInspector (https://github.com/JackOfMostTrades/gadgetinspector), mình dùng nó để tìm ra mấy cái gadget trên 1 target khác mà có dịp mình cũng sẽ nói về target này.Cái lúc mình thực hiện trace đống source này thì chưa biết tới Semmle nên việc sử dụng tool này khá hiệu quả (sau này ms biết Semmle cũng ko ăn thua với đống này…).

Tool này thực hiện trace, tìm các CallGraph, TaintTracking, Passthrough …. ở mức BYTECODE (Semmle chưa có cái này) nên rất tiện để analyze nguyên 1 đống library của java mà không cần source, đảm bảo kết quả trả về vẫn rất chính xác (mặc dù thời gian thực thi khá lâu và tài nguyên nó ngốn cũng không kém phần long trọng!!!).

Đợt trà đá hacking vừa rồi mình có apply bài nói nhưng do dính quả bom từ một bên trung gian nào đó nên buộc phải cancel vào phút cuối … Bạn đọc quan tâm có thể tham khảo thêm thông tin chi tiết, cách hoạt động của tool này tại đây (https://paper.seebug.org/1034/).

Ròi đó là câu chuyện của cái tool kia, còn đây là thông tin về cái bug…

Thực ra ban đầu mình hơi nhầm hướng đi một chút, cứ nghĩ deserialize trên java thì mặc định phải là deserialize của java như bao trường hợp bình thường khác …

Điều đó dẫn đến 2 tháng lãng phí của mình, chỉ đâm đầu vào tìm các Sink có gọi đến ObjectInputStream.readObject(), XStream.fromXML() …

Sửa đi sửa lại cái tool không biết bao nhiêu lần mà vẫn chỉ tìm được những cái Chain deserialize đã biết (/api/spring, /api/liferay …).

Sau đó mình quyết định dừng nghiên cứu cái bug quần què này … Mất thời gian .

.

.

.

Và tuần vừa rồi, đồng nghiệp mình có nhận khá nhiều target về liferay, chuyện cũ lại được khơi ra … Vẫn có gì đó cay cay, quyết định tìm cho bằng được cái bug khỉ gió này!

The entrypoints

JSONFactoryImpl là class handle hầu hết các hành động lquan tới json của liferay
Set breakpoint tại đây và fuzz các entrypoint có thể truy cập của LifeRay,
Sau một hồi loại trừ các junk request, cuối cùng cũng tìm được cái Chain từ HttpServlet qua tới đoạn call deserialize JSON
Hơi thất vọng 1 chút là để access được Chain này thì lại cần authenticated!
Thôi ksao, dù sao thì các target bọn mình cần xử lý bây giờ đều có account hết cả.

JSON Deserialize

Nói lằng nhằng bên trên mà quên mất điều tối quan trọng là chưa nói lý do tại sao cái JSON Deserialize này lại khác, và có thể lead to RCE được!
Method thực hiện deserialize ở đây là JSONFactoryImpl.deserialize(String),
Từ đây call tới org.jabsorb.JSONSerializer.fromJSON(), ban đầu mình có search qua về tiền án tiền sự của thằng này thì ko có chút info gì cả, ¯\_(ツ)_/¯ ko có thì mình phải tự xét tội cho nó thôi!
JSONSerializer.fromJSON() call tới JSONSerializer.unmarshall() để tiếp tục thực hiện deserialize object.
Method JSONSerializer.unmarshall() có content như sau:
 
Đọc sơ sơ qua thì có thể thấy method này thực hiện lấy một loại serializer thích hợp cho đoạn JSON String và thực hiện deserialize nó theo cái serializer đó
Set breakpoint tại method getSerializer để tìm các class thực hiện việc unmarshall:
 
Đây là những list những class có thể sẽ unmarshall object JSON truyền vào, được xét từ trên xuống dưới: Locale, Liferay, Primitive, Boolean, …
Trong các target này thì có org.jabsorb.serializer.impl.BeanSerializer là đáng nghi nhất!
Method unmarshall() có invoke Setter Method của cái object được khởi tạo từ JSON String ra:
public Object unmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException {        JSONObject jso = (JSONObject)o;
...
instance = clazz.newInstance();
Method setMethod = (Method)bd.writableProps.get(field);
setMethod.invoke(instance, invokeArgs); ... }
Cái này nghe hơi giống với cách hoạt động của 1 số trường hợp về JSON Deserialize lead to RCE đã xuất hiện trước đó (Jackson, fasterxml …).
Những target này đều gọi tới Getter/Setter method và sau đó trigger tới các chain khác!
Các gadgetchain về setter method hầu như là hơi hiếm, mình có search qua mà ko được nhiều thông tin gì nữa. Và quan trọng hơn: đối với jackson, fastxml … đều làm việc với các Serializable class.
Đối với trường hợp của liferay thì mọi thứ hơi phức tạp thêm 1 chút xíu!
Để trigger được unmarshall() bằng BeanSerializer thì class để trigger setter method không được Serializable, nếu class này Serializable thì nó sẽ dừng ngay ở LiferaySerializer khi gọi method getSerialize(), method này không invoke setter method!
Tóm tắt lại về cái Source class có thể trigger được BeanSerializer này là 1 class có dạng:
  • Có public constructor không có arg
  • Không implement Serializable
  • Có setter method có thể lợi dụng được!
¯\_(ツ)_/¯ Vậy là đã có đủ dữ kiện lắp vào tool GadgetInspector customized kia rồi.
Sau khi chạy 1 hồi, check hơn 1k trường hợp false positive, cuối cùng cũng đã tìm thấy cái gadget đầu tiên có thể sử dụng được, lead to RCE!
Ngay khi RCE được thì mình confirm ngay vs người ae xã hội, và nhận lại câu trả lời như này:
 
 
Hy vọng lần sau sẽ ko vì cay cú mà bị lừa nữa …
Về chi tiết của gadget có lẽ mình sẽ ko cung cấp thêm nhiều vì target của bọn mình vẫn đang trong thời gian kiểm tra lại và fix.
Dưới đây là PoC thêm cho có gì đó gọi là màu mè thôi )):
https://www.youtube.com/watch?v=DjMEfQW3bf0&feature=youtu.be
  • Version affect: Liferay <7.x
  • Fix: Không dùng liferay 6.x nữa
Link tham khảo:
Update: bug đã được gán CVE-2019-16891
_Jang from VNPT ISC_
Ké tuyển dụng:
  • CV xin gởi về: security@vnpt.vn
610 lượt xem