+13

Phân tích CVE-2021-26295 Apache OFBIZ

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 có 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:

Sau khi build thành công thì các bạn làm tiếp như sau để setup remote debug

  • ./gradle ofbizDebug: đối với người dùng linux
  • .\gradlew.bat ofbizDebug: đố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 thành 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.

Cảm ơn

Cảm ơn anh @minhtuan.nguy đã giúp đỡ em trong quá trình xây dựng môi trường và phân tích lại con cve này. Hy vọng sẽ cùng anh có những bài viết phân tích chất lượng khác


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.