Cũng đã khá lâu kể từ lần cuối viết lách,

Phần là vì công việc khá nhiều, phần nữa là dạo này bị mất cảm hứng để viết :(, nên là ko còn viết gì nữa từ lúc xong đồ án tới giờ.

Tuần vừa rồi có 1 bug Java Deser của IBM Qradar SIEM được release — CVE-2020–4280 (chi tiết tại: https://www.securify.nl/advisory/java-deserialization-vulnerability-in-qradar-remotejavascript-servlet). Lại đúng vào product mấy tháng nay mình hay dùng nên tiện tay mổ xẻ xem sao!

#SETUP

Trước đây, mình đã tưởng lầm rằng Qradar chỉ có Enterprise version, tới khi đọc blog chi tiết về bug này thì mới biết thực ra nó còn có 1 Community Edition nữa.

Bản CE các bạn có thể download tại: https://developer.ibm.com/qradar/ce/

Nếu như yêu cầu login, các bạn có thể sử dụng các shared credential tại: http://bugmenot.com/view/idaas.iam.ibm.com (mình cũng dùng với oracle.com)

Chỉ có phiên bản 7.3.3 cho Community Edition, theo như mình trải nghiệm và được biết thì Enterprise Edition hiện đang sử dụng version 7.4.1.

Sau khi login vào thì IBM sẽ cho down 1 file ova để về import vào VMWare/Virtual Box.

Việc install cũng khá đơn giản, chỉ hơi tốn thời gian chút, có thể tham khảo setup guide tại: https://kifarunix.com/how-to-install-ibm-qradar-ce-v7-3-1-on-virtualbox/

  • Trong quá trình setup đôi khi cũng sẽ xảy ra 1 số lỗi vặt, nếu các bạn stuck thì có thể inbox mình để trao đổi!

Tương tự như đã làm với weblogic or liferay, để debug Qradar thì mình tìm tất cả các library có trong WEB-INF và import vào IntellIJ.

Có 1 lưu ý chết người đó là Qradar đang chạy trên IBM Java, hoàn toàn khác với Oracle Java thông thường vẫn sử dụng.

Để việc debug được suôn sẻ, trong setting của project đang debug phải chọn đúng Project SDK là IBM Java 1.8 (có thể download tại đây: https://www.ibm.com/support/pages/java-sdk-downloads).

(Nếu không chọn đúng SDK thì khi debug sẽ bị nhảy lung tung mà không hiểu tại sao =))) )

#Root Cause

Dựa vào blog post của tác giả, ta có thể xác định lỗ hổng nằm tại servlet RemoteJavaScript với servlet-name là remoteMethod

Trong qradar, các url-pattern sau được handle bởi remoteMethod:

  • /remoteJavaScript
  • /remoteMethod
  • /JSON-RPC
  • /JSON-RPC/*

Tính năng remoteMethod này hoạt động như sau:

  • Từ method body hoặc query param, entrypoint này nhận vào các tham số “method”, “params”, “QRadarCSRF”, ….
  • Từ tham số “method”, appName và methodName được tách ra như sau: appName + “.” + methodName = method, ví dụ: Qradar.getSavedSearch
  • Nếu methodName của appName tồn tại method sẽ được gọi!

Nói thì dài, 1 request mẫu có dạng như sau:

Method được xác định tại đây:

Tại dòng 309/311, method được call với các param đã truyền vào

Tại ExportedMethod.call(), các param tiếp tục được truyền vào ReflectionUtils.stringsToObjects() để xử lý,

ExportedMethod.call()

Chain phía sau ReflectionUtils.stringsToObjects() còn dài, nhưng có thể nói ngắn gọn đây chính là nơi dữ liệu được deserialize!

Chốt lại là Chain từ source tới sink như sau (T_T chữ xấu, các bạn thông cảm):

Tuy nhiên, không phải param nào cũng tới được SerializationUtils.deserialize().

Đối với các param có type đơn giản như: String, integer, long, … thì sẽ có các phần xử lý riêng, không đi vào nhánh deserialize().

Chỉ với các param có type phức tạp, ví dụ: Map<HostInfo>, mới được truyền vào deserialize() để xử lý tiếp.

Các exported-method này được khai báo trong /opt/qradar/conf/appconfig/<app>-exported_methods.xml kèm với các class xử lý method đó, method name, params name. Ví dụ như với method mà tác giả đã sử dụng để attack trong PoC của CVE-2020-xxxx là:

<exportedMethod exposeToJMX="false" readOnly="true" parameterNames="changedSettings" className="com.q1labs.assetprofilerconfiguration.ui.util.AssetProfilerConfig" methodCode="974337442" methodName="validateChangesAssetConfiguration" appName="qradar"/>

Mò vào class xử lý, có thể thấy method Qradar.validateChangesAssetConfiguration() có param là changedSettings. Param này có kiểu List<AssetProfilerChanges>:

Trong PoC gốc của tác giả thì có xử lý hơi cồng kềnh:

Dùng gadget Jython1 để enable property console.enableExecuteCommand -> sau đó gọi Qradar.executeCommand().

Mình có cách khác xử lý ngắn gọn hơn đó là sử dụng custom lại gadget ROME để execute và response luôn ra body, kết quả PoC được như sau:

Và đương nhiên là PoC cũng sẽ ko đc cung cấp theo bài này ¯\_(ツ)_/¯, có làm mới có ăn …

#The Patch

Sau khi thử nghiệm xong PoC thì mình cũng bắt đầu thử vá luôn xem sao.

Bản vá có thể down tại đây:

Apply patch thì cũng khá đơn giản, IBM đã có sẵn guide tại đây, chỉ việc làm theo là xong, tuy hơi có lâu chút.

Đúng là sau khi patch, exploit không còn hoạt động nữa, ¯\_(ツ)_/¯ đương nhiên rồi.

Kéo lib về để diff patch thì thấy có 1 số thay đổi lớn như sau, ReflectionUtils.stringToObject() đã có thay đổi, thêm 1 vài dòng để kiểm tra dữ liệu được deserialize,

ReflectionUtils.stringToObject() cũ:

https://gist.github.com/testanull/86a214d3db3ea6be33796b8e74fb8da4

ReflectionUtils.stringToObject() mới:

https://gist.github.com/testanull/6d52769c6704770c76359f3d56946ba2

Có thể thay đổi khá rõ ràng, đã có thêm class ValidatingObjectInputStream để filter các class được phép deserialize, với whitelist là:

  • org.postgresql.util.*
  • com.q1labs.*
  • java.lang.*
  • java.util.*
  • gnu.trove.*

Tuy nhiên nhánh này chỉ xử lý các param có kiểu không phải là Map:

Trong nhánh else cuối cùng của đoạn code xử lý, dữ liệu vẫn tiếp tục được decode base64 và deserialize như bình thường:

Điều đó có nghĩa là, nếu như tìm được 1 method có param type = Map, thì tiếp tục có thể bypass bản vá này và deserialize to RCE! (1)

Tạm thời dừng ở đó, tiếp tục soi bản vá của IBM, có thể thấy sự thay đổi lớn trong /opt/qradar/appconfig/qradar-exported_methods.xml.

Một vài exported methods đã bị xóa bỏ trong file config mới:

Các exported methods đã bị xóa là:

  • qradar.validateChangesAssetConfiguration() -> trong PoC gốc của tác giả
  • qradar.getSecurityState()
  • qradar.getVulnerabilityState()
  • qradar.getNetworkSecuritySummary()
  • qradar.getRssFeedItem()
  • qradar.getDataPoints()
  • qradar.executeCommand()

Sau khi check lại 1 lượt thì đa số các method này đều có param có thể bị lợi dụng để deserialize,

Điều đó lý giải tại sao khi sử dụng PoC cũ để exploit, kết quả trả về sẽ là 1 trang trắng với response = 200, nghĩa là method không còn tồn tại nữa:

Đặt breakpoint tại dòng 195 của RemoteJavaScript để thấy rõ hơn, method lúc này đã trả về null do không còn được khai báo trong file config nữa:

Đó là những gì IBM đã làm để vá lại lỗ hổng này, tuy nhiên vẫn còn tới 439 exported methods trong file config

Kết hợp với điều kiện cần (1) thì chỉ cần tìm ra 1 method nào đó mới,

  • Được khai báo trong qradar-exported_methods.xml
  • Không yêu cầu cấp quyền cao
  • Có method param là dạng Map<>

=> Bypass CVE-2020–4280

Mình đã tìm ra 1 vài method thỏa mãn điều kiện và bypass thành công bản vá:

Hôm trước mình có report cho vendor nhưng có vẻ như họ không quan tâm lắm, do vậy mình quyết định public bài này với dạng nửa vời.

Phần PoC bypass CVE-2020–4280 này dành lại cho các bạn researcher quan tâm có thể tự tìm ra, mình tin là với những dữ liệu đã cung cấp thì việc tìm ra nó chỉ trong vài giờ đồng hồ setup lab thôi.

Cảm ơn các bạn đã đón đọc!

_Jang_

157 lượt xem