Vài ngày vừa rồi, khi lượn lờ trên 1 group bảo mật, mình có đọc được bài báo nói về HPE vừa release bản vá cho 0day nào đó có CVSS 9.8/10.
Có thể với những người làm về java đủ lâu sẽ nhận ra CVSS 9.8/10 thì chỉ có Deserialization chứ còn gì nữa ¯_(ツ)_/¯.
Product bị ảnh hưởng ở đây là một enterprise product có tên: HPE System Insight Manager (SIM).
Product này có thể dùng ở dạng trial, link down tại đây: Document Display | HPE Support Center Edit descriptionsupport.hpe.com
Và tình cờ khi lướt qua ZDI, mình cũng thấy bug này xuất hiện trên published advisory, bug này được mô tả như sau:
Rõ ràng trường hợp này chính là AMF -> Deserialization,
Về mảng AMF Deser này mình không nghiên cứu nhiều lắm, có thằng em xã hội PeterJson cũng đã làm khá nhiều, một bài viết khá hay về AMF Deser của Đức tại đây . Trong quá trình viết PoC đã phải tham khảo rất nhiều thông tin từ thanh niên này …
Bài viết dưới đây là chút phân tích về lỗ hổng CVE-2020–7200, cũng như cách nhìn của mình về AMF Deserialization.
#SETUP & DEBUG các thứ
Môi trường mình sử dụng là Windows Server 2016, mặc dù HPE SIM hỗ trợ cả linux nhưng mình vẫn quen với các công cụ hỗ trợ về process trên windows hơn là trên linux, và lý do nữa là nổ calc trên win phê hơn 😎.
Một chút lưu ý khi setup trên môi trường windows, đó là cần phải enable Feature SNMP Service của windows lên trước và cài dotNet 3.5, sau đó việc cài đặt sẽ diễn ra suôn sẻ:
.
.
Quay trở lại với document về cách vá tạm thời của HPE có đề cập như sau:
- more detail: https://support.hpe.com/hpesc/public/docDisplay?docLocale=en_US&docId=hpesbgn04068en_us
HPE không cung cấp bản vá mà chỉ cho cách xử lý tạm thời chỉ đơn giản là xóa simsearch.war trong “C:\Program Files\HP\Systems Insight Manager\jboss\server\hpsim\deploy\”, vậy có nghĩa đây chính là nguyên nhân chính gây ra lỗ hổng này!
Theo suy luận là như thế nhưng để viết được PoC thì cần phải setup debug cho nó trước đã.
Việc setup debug trên target này khá là loằn ngoằn, mình đã mất nguyên buổi chiều để ngồi nghiên cứu tìm chỗ nó startup servlet.
Sau khi đánh giá dựa trên các thông tin mà HPE cung cấp cũng như bằng các biện pháp tâm link thì mình đã biết được entrypoint của bug này nằm tại:
[https://<target>:50000/simsearch/messagebroker/amfsecure](https://192.168.139.217:50000/simsearch/messagebroker/amfsecure)
web.xml simsearch
service-config.xml simsearch
Và đúng như CVSS 9.8/10, đây là 1 entrypoint pre-auth
entrypoint amf
.
.
Truy tìm process theo listen port 50000 được 1 process khá là không liên quan lắm:
Hiện tại mình đang muốn tìm 1 process java or jboss gì đó, trông như vậy thì không giống lắm. Có thể các product của HPE về sau đều được gói gọn cả java vào thành dạng portable mất rồi.
Check các Handles của process mxdomainmgr.exe có thể thấy nó vẫn load các java library như 1 process java thông thường (trò này mình mới học được, dùng để kiểm tra chính xác các library nào có sẵn trong classpath của target).
Dò ngược về process cha của mxdomainmgr, chính là hpsimsvc.exe,
Cũng may thay, process này là service nhưng nó cũng cho phép chạy dưới dạng console cho phép mở interactive console để trực tiếp đọc các log của chương trình đẩy ra bằng options “-console”:
Trong quá trình nhìn ngắm từng dòng của log của chương trình đẩy ra, mình có để ý 1 dòng error log được log lại nhiều lần:
Giác quan thứ 7, chủ nhật của mình mách bảo rằng file “C:\Program Files\HP\Systems Insight Manager\config\globalsettings.props” chính là thứ cần tìm lúc này.
Và sau khi kiểm chứng thì mình có thể khẳng định file này là file chứa các setting cũng như quyết định environment của chương trình sẽ chạy. Biến môi trường JAVA_OPTS cũng được khai báo tại đây, để setup remote debug được thì mình đã thêm options debug tại dòng 214 của globalsettings.props:
#THE BUG
Tiếp theo là công cuộc tìm lib để debug, thì bây giờ đã nhẹ nhàng hơn với trò mới này.
Chỉ việc dùng Process Explorer hoặc Process Hacker, check phần Handles sẽ show ra các file đang được tác động bởi process này. Trong đó bao gồm cả các file library của java được load lên (gián tiếp hay trực tiếp):
Kéo hết cả đám này ra 1 folder riêng và ném vào IntellIJ để debug thôi!
Theo config của web.xml, flex.messaging.MessageBrokerServlet sẽ handle entrypoint /simsearch/messagebroker/*
Class này sẽ nhận vào request và kiểm tra endpoint có tồn tại trong config hay không, và sau đó sẽ tiếp tục invoke class được define tương đương với endpoint đó:
Trong config của HPE SIM chỉ có duy nhất endpoint /amfsecure:
Trong đó class xử lý cho endpoint /amfsecure là flex.messaging.endpoints.SecureAMFEndpoint
SecureAMFEndpoint thừa kế AMFEndpoint
AMFEndpoint thừa kế BasePollingHTTPEndpoint
BasePollingHTTPEndpoint thừa kếBaseHTTPEndpoint
Các class SecureAMFEndpoint, AMFEndpoint, BasePollingHTTPEndpoint không có override method HttpServlet.service(), do đó khi MessageBrokerServlet invoke endpoint service, nó sẽ invoke 1 override method gần nhất, đó là BaseHTTPEndpoint.service()
Tuy nhiên this.filterChain lại được init tại AMFEndpoint.createFilterChain():
Method AMFEndpoint.createFilterChain() sẽ khởi tạo 1 instance của SerializationFilter. Lỗ hổng của AMF bị tận dụng trước đó da số cũng đều dựa vào SerializationFilter này để deserialize object.
Tiếp sau đó, body request sẽ được đưa vào AmfMessageDeserializer.readMessage() để xử lý thêm.
Tại đây để debug tiếp thì cần có 1 request theo đúng chuẩn của AMF, mình có sử dụng mẩu code example của codewhitesec amf, gói bừa 1 object vào để gen payload gửi tiếp. Code mình sử dụng được push lên tại đây.
Sau khi có 1 payload hợp lệ, tiếp tục debug vào AmfMessageDeserializer.readBody(), method này tiếp tục gọi AmfMessageDeserializer.readObject() để reconstruct object.
Từ AmfMessageDeserializer.readObject() sẽ gọi tiếp tới Amf0Input.readObject() -> Amf0Input.readObjectValue().
Amf0Input.readObjectValue() lại gọi tiếp tới Amf3Input.readObject() -> Amf3Input.readObjectValue() ->Amf3Input.readScriptObject() .
Mình ko rõ tại sao đoạn xử lý này lại hơi cồng kềnh như vậy nữa. Chỉ biết là tại Amf3Input thì các Object mới thực sự được xử lý.
Method Amf3Input.readScriptObject() xác định class đang được xử lý có thể được Externalizable (check implement java.io.Externalizable) hay không.
Nếu có implement Externalizable thì sẽ invoke method readExternal() của object đang được xử lý, như trong ví dụ mình sử dụng class LRUMap của CommonsCollections, có implement Externalizable và override method readExternal():
LRUMap
Debugger dừng tại LRUMap.readExternal()**
Đây là 1 hướng có thể lợi dụng để tiếp tục tìm các gadget chain trigger RCE sau đó, hoặc tìm 1 gadget chain từ readExternal() -> readObject() như trong bài của Đức có đề cập, chi tiết tại đây.
Tuy nhiên trong quá trình debug, Đức có suggest mình là từ AMF có thể trigger cả setter method nữa,
Check lại trong code, nếu như class không thể Externalizable, sẽ chuyển sang 1 nhánh khác để xử lý. Nhánh này sẽ lấy các property của class và sử dụng BeanProxy.setValue() để set lại các giá trị này.
Khi set giá trị cho các property này, BeanProxy sẽ cố gắng tìm các setter method của class và invoke.
Từ đó giải thích tại sao lại có thể chuyển từ AMF deserialization sang invoke setter method để trigger RCE.
Có thể tóm tắt chain attack bằng hình sau:
Đối với trường hợp trong bài viết của @peterjson, do trong classpath có tồn tại khá nhiều library nên việc tìm ra 1 chain để lead từ readExternal() sang readObject() không phải chuyện quá khó.
Còn trong trường hợp hiện tại thì library khá là hạn chế nên việc tìm ra chain như vậy khá là khó khăn, mình đành sử dụng cách thứ 2 đó là dùng setter method.
Loay hoay một hồi thì thanh niên Đức tìm ra 1 chain có sẵn trong classpath có thể lợi dụng được, đó là: org.jgroups.blocks.ReplicatedTree.setState()
Chain này thực tế đã được tìm ra trước đó và đề cập tại: https://codewhitesec.blogspot.com/2018/03/exploiting-adobe-coldfusion.html
Xem xét lại method setState() có thể thấy method này nhận vào mảng byte, sau đó gọi tiếp Util.objectFromByteBuffer().
Đúng như tên gọi của method, Util.objectFromByteBuffer() nhận vào mảng byte và sau đó khởi tạo ObjectInputStream và gọi readObject() từ đó:
Như vậy hiện tại chúng ta đã có thể lead từ AMF Deser -> Setter method invoke -> Java Deserialization.
Sau một hồi loằn ngoằn mỳ tôm, cuối cùng cũng đã quay trở lại vấn đề muôn thuở: Java Deserialization.
Vấn đề bây giờ là deser cái gì, dùng gadget gì mới được …
Ban đầu khi viết PoC thì mình có nghĩ là product này khá mới, CommonsCollections đều 3.2.2 hết cả rồi, khó mà dùng lại các chain cũ.
Tuy nhiên, vô tình khi tìm class thì mình có phát hiện trong classpath của HPE SIM có tới 3 library CommonsCollections,
Trong đó lại may mắn thay, CommonsCollections được sử dụng trong classpath của JBoss chỉ có version là 3.1 😂
Theo mình nghĩ, có thể là đang gửi request từ webapp, jboss lại đang xử lý trực tiếp nên nó sẽ sử dụng luôn CommonsCollections tìm thấy trong thư mục library thay vì sử dụng lib nó tìm thấy tại global library. ¯_(ツ)_/¯ Need more information!
Tiếp tục lại có 1 vấn đề mới nảy sinh: Làm sao để set cái prop “state” của ReplicatedTree?
Thông thường thì các prop sẽ có 1 private field đi kèm, dạng như vậy:
Property “foobyte” sẽ được có field foobyte, và các setter + getter method đi kèm.
Đối với ReplicatedTree thì không phải vậy:
Ngày trước mình xử lý các trường hợp này bằng cách viết lại và fake cái class này cho phù hợp với mục đích. Cách này cũng vẫn xử lý được nhưng cảm giác không được chuyên nghiệp lắm =))).
Tình cờ trong 1 lần nghiên cứu 1 tool về deserialization (GadgetProbe), mình biết được javassist có thể được dùng để tạo mới hoặc chỉnh sửa các thành phần của 1 class nào đó, ví dụ như thêm field, method, …
Quay trở lại với phần PoC, mình sử dụng javassist vào để thêm 1 field “state” cho ReplicatedTree, và thay đổi method getState() cho phép return giá trị của field “state” này. Đoạn code xử lý ngắn gọn như sau (có kèm theo bài viết):
Như vậy các vấn đề đều đã được xử lý, chỉ cần gói serialized object vào field state, ròi serialize tiếp bằng AMF là xong:
PoC video tại đây: https://youtu.be/XLAqBqToXW0
PoC github: https://github.com/testanull/ProjectSIM
Tham khảo thêm:
Cảm ơn các bạn đã đón đọc,
Jang with Luv ❤