Skip to content

The Art of Deserialization Gadget Hunting [part 3]

Vậy là ngày 05/03 vừa rồi, ZDI đã publish bài phân tích chi tiết về CVE-2020–2555 (https://www.thezdi.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server).

Trước đó mình cũng định viết bài về cái bug này, mà bận quá chưa có time để ngồi viết lách gì cả. Đành để các bạn ZDI publish trước, còn mình sẽ viết về cách mà mình đã tìm ra cái bug này!

Tầm 6 tháng trước, mình có nhận job về mấy con weblogic này và bắt đầu lún sâu vào Weblogic từ đó tới giờ chưa rút được được ra :(.

(Event log về đợt đó vẫn còn tại đây: https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/t%C3%B4i-%C4%91%C3%A3-chi%E1%BA%BFm-quy%E1%BB%81n-assmin-c%E1%BB%A7a-r%E1%BA%A5t-nhi%E1%BB%81u-trang-web-nh%C6%B0-th%E1%BA%BF-n%C3%A0o-weblogic-122130-sh/2234157883364831/).

Ngày đó mình có dùng GadgetInspector (https://github.com/JackOfMostTrades/gadgetinspector) tìm ra cái gadget put file bằng Spring AOP kết hợp với CVE-2018–3245 để drop shell lên server:

Đây là động lực thúc đẩy mình tiếp tục nghiên cứu cái tool này và không lâu sau đó, mình tìm ra cái gadget mới, được đặt số là CVE-2020–2555.

Nói qua chút về cái gadgetchain này, nó được tìm thấy trong library coherence.jar của weblogic.

Chain chi tiết như sau:

GadgetChain này có source là BadAttributeValueExpException.readObject() -> toString() như trong các bài trước của series mình có đề cập.

Điều thú vị của gadgetchain này đó là nó có cách hoạt động giống hệt gadgetchain CommonsCollections trong bài trước mình đã phân tích.

Với CommonsCollections là ChainedTransformer, còn với weblogic là ChainedExtractor.

Với CommonsCollections là InvokerTransformer thì weblogic là ReflectionExtractor.

Các thành phần của chain này cứ như là được sinh ra để làm gadgetchain vậy!

Thực ra ban đầu, source chain của cái bug này không phải là LimitFilter.toString(). Để lead tới ChainedExtractor.extract() thì mình sử dụng 1 đoạn chain khác khá là dài, và cần sửa nhiều chỗ linh tinh nữa mới có thể trigger được extract(). Nhưng là vì thương đội ZDI, giảm thời gian phân tích cho họ nên mình mới lấy cái LimitFilter.toString() này để report =))).

Hiện tại thì theo như patch của oracle, họ chỉ remove đoạn call tới extract() ở LimitFilter.toString() nên cái gadgetchain mình tìm ra ban đầu vẫn chưa được fix triệt để =))) …

(Thông tin chi tiết hơn của cái bug này thì các bạn vui lòng đọc thêm tại đây: https://www.thezdi.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server ).

Còn dưới đây mình sẽ kể về quá trình tìm ra cái bug này!

Như đã nói phía trên, sau khi kiếm được cái gadget drop shell, mình đã focus 100% thời gian vào tìm kiếm gadgetchain trong weblogic.

Các bug về deserialization trên weblogic đã có từ rất lâu, và oracle chọn cách fix bằng cách đặt blacklist, chặn hết các “known chain”. Entrypoint để deserialize là T3 thì vẫn còn đó, việc tìm ra lỗ hổng mới trên weblogic chỉ phụ thuộc vào việc có gadgetchain mới nào được tìm ra hay không thôi!

Weblogic có sử dụng đến hơn 500 library khi chạy, vào thời điểm đâm đầu vào tìm lỗi, mình hoàn toàn tự tin là sẽ tìm thấy 1 hoặc nhiều gadgetchain mới trong đống này. Không có lý nào nhiều lib vậy mà lại ko có thêm 1 cái gadgetchain mới nào!

Để kiếm được gadgetchain trong weblogic, mình tìm tất cả các file jar trong folder cài đặt của weblogic và ném nó vào cùng 1 folder như này:

Sau đó chạy gadgetinspector để tìm chain thôi:

¯\_(ツ)_/¯ nhà chả có gì ngoài RAM

Cho nó chạy tầm 30p thì bắt đầu ra 1 đống kết quả tại file gadgetchains.txt.

Sau khi check từng cái một (khoảng hơn 300 gadgetchain) thì trường hợp bị false positive rất rất nhiều.

Khá là nản, mình cũng nghĩ: tool thì public, target cũng public, kiểu gì chả có người chạy thử rồi!

Mình quyết định ko chạy cái tool này nữa, mà đọc nó, học xem cách nó chạy thế nào.

Vào thời điểm đó thì chưa có bài phân tích chi tiết về cách hoạt động của cái tool này, mình phải đọc slide của tác giả, contact tác giả để hỏi mà không có được accept tin nhắn :(. Đành phải vào nước cuối cùng: đọc hiểu code vậy. (Về sau có mấy anh tàu khựa viết bài nghiên cứu về nó tại đây: https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649)

Giai đoạn đọc code này khá là tốn thời gian và cũng khá là vô vọng. Mình vừa kết hợp đọc và debug trực tiếp để hiểu cách hoạt động của nó.

Sau 1 thời gian đọc hiểu tool thì mình có thể tóm gọn lại hoạt động của nó như sau:

  • Tìm kiếm tất cả các class, method trong library
  • Tìm kiếm tất cả các mối quan hệ kế thừa, các implemented method, override …
  • Tìm các passthrough, method call. Ví dụ như method A -> method B.
  • Tìm các “known source chain”.
  • Và cuối cùng là dựa vào các dữ liệu trên để tìm ra gadgetchain với GadgetChainDiscovery!

Ý tưởng của việc tìm kiếm gadgetchain khá là đơn giản:

Bắt đầu từ source chain được push vào 1 list, tool sẽ tìm tất cả các method call có thể từ cái source chain này và so sánh:

  • Nếu method được call là sink thì sẽ show ra
  • Nếu không phải thì push lại vào stack để thực hiện travel tiếp vào lượt tới
  • Push method vừa visit vào 1 cái list khác để lần sau không lặp lại việc visit nữa.

Thuật toán này hình như được gọi là Breadth-First Search.

Debug 1 thời gian thì mình mới biết lý do tại sao tool này nó hơi ngu ngu!

Vấn đề mấu chốt là ở bước: “Push method vừa visit vào 1 cái list khác để lần sau không lặp lại việc visit nữa” của class GadgetChainDiscovery.

Ví dụ như chain A->B->C và A->B->X

Nhánh nào được visit trước thì sẽ được show ra kết quả, nhánh còn lại là A->B->X sẽ bị ignore, không được list vào nữa.

Đoạn này được code là để tránh trường hợp loop, dead end.

Nếu như không có đoạn này thì chương trình sẽ chạy rất rất rất lâu …

Mình đã từng gỡ thử nó đi và chạy, kết quả là đến tận 2 ngày vẫn chưa thấy nó có dấu hiệu dừng =)).

Vừa muốn ra nhiều kết quả thêm và cũng không muốn deadend, mình thay đổi code 1 chút.

Thay vì “push method vào và không visit nữa” thì mình push method và có note lại số lần đã visit, nếu nhiều hơn vài lần đó thì sẽ cho skip:

Và lần này khi chạy đã ra nhiều kết quả hơn, hứa hẹn hơn lần trước!

CVE-2020–2555 cũng được tìm ra sau khi sửa logic của tool như vậy.

Sample của các gadgetchain tìm được là như vậy.

Dĩ nhiên là vào thời điểm tìm ra CVE-2020–2555, tool chưa chạy tốt lắm, chưa ra được ngay gadgetchain đẹp như trên được. Rất tiếc là mình ko lưu tool gốc để lấy sample post lên đây.

Hiện tại thì tool cũng chạy khá là ổn định, không còn false positive nhiều như tool ban đầu nữa. Đó là thành quả của 1 thời gian khá là lâu trong việc chỉnh sửa logic tool, thêm các blacklist, source, sink mới.

Đợt đó mình định submit bài speak tại Trà đá hacking, nhưng do bên ZDI khác là chậm, delay case của mình tận 6 tháng nên cũng phải drop theo luôn …

Phía trên vài lời mình muốn nói về quá trình mình tìm ra CVE-2020–2555. Bài viết lần này có thể hơi nhạt nhẽo và lan man do đã là 6 tháng kể từ thời gian đó tới giờ. Mình không còn nhớ nhiều gì về dạo đó nữa.

Về phiên bản modified gadgetinspector của mình thì mình chưa có ý định sẽ public vì còn 1 số công việc vẫn còn dính líu tới!

Cảm ơn bạn đọc đã theo dõi!

__Jang__

80 lượt xem