CVE-2022-22980 – Spring Data MongoDB remote code execution.

1. Prerequisites Ubuntu GNOME Spring Boot MongoDB 2. Overview CVE-2022-22980 là một lỗ hổng bảo mật của thư viện spring-data-mongodb có thể khiến attacker chạy bất kì câu lệnh nào trên máy chủ bằng việc truyền một đoạn mã độc vào user input để trigger thư viện thực thi nếu như ứng dụng Spring Boot

1. Prerequisites

image.png

  • Ubuntu GNOME
  • Spring Boot
  • MongoDB

2. Overview

image.png

CVE-2022-22980 là một lỗ hổng bảo mật của thư viện spring-data-mongodb có thể khiến attacker chạy bất kì câu lệnh nào trên máy chủ bằng việc truyền một đoạn mã độc vào user input để trigger thư viện thực thi nếu như ứng dụng Spring Boot + MongoDB sử dụng JSON based query methods với @Query, @Aggregation annotaion có chứa parameter expression SpEL – Spring Expression Language tương tự như sau:

	@Query("{ 'firstName' : ?#{?0} }")
	Customer findByFirstName(String firstName);

Đến đây bạn có thể thực hiện mở ứng dụng gnome-calculator trên Ubuntu GNOME bằng cách thực thi câu lệnh truy vấn như thế này:

    repository.findByFirstName("T(java.lang.Runtime).getRuntime().exec("gnome-calculator")");

Do lổ hổng bảo mật này đã được report từ hơn 1 tháng trước (13/06/2022) nên nếu bạn sử dụng spring-data-mongodb depedency mới release gần đây (v.3.3.5 hay v3.4.1 trở lên) thì sẽ không khai thác được lỗ hổng này.

spring-data-mongodb-impacted-version.png

3. Exploitation Explain

Trước khi đi vào chi tiết, các bạn có thể xem toàn bộ source code ở đây

Vì lỗ hổng này liên quan trực tiếp đến cách Spring Boot framework xử lý SpEL query parameter của MongoDB interface repository, nên để hiểu chúng ta cần phải biết quá trình một câu truy vấn được xử lý ra sao.

  1. Khai báo CustomerRepository

image.png

  1. Tiến hành gọi câu truy vấn

image.png

Bạn có thể thấy, CustomerRepository là một proxy class (JdkDynamicAopProxy), có nghĩa rằng tại runtime (dynamic), Spring Boot framework đã tiến hành proxy CustomerRepository bean và khi bạn bạn tiến hành gọi câu truy vấn thì bạn phải gọi qua proxy class trước.

image.png

Khi tiến hành debug, nhìn qua callstack bạn sẽ thấy khá loạn vì Spring Boot framework gọi qua nhiều proxy class khác nhau, nhưng thực sự để mà nói thì source code quá lớn và phức tạp để chúng ta có thể hiểu một cách tường tận một workflows hoàn chỉnh nhưng có thể hiểu nôm na như sau (theo cách hiểu của mình):

Khi gọi đến phương thức findByFirstName() trong CustomerRepository, thì tất cả mọi lời gọi phương thức đều phải đi qua một handler duy nhất – đó chính là phương thức invoke().

image.png

image.png

Bên trong phương thức invoke() này, chúng ta sẽ thực thi các bước tiền xử lý trước khi tiến hành chọc thẳng vào database truy vấn dữ liệu, đơn cử như là StringBasedMongoQuery#createQuery(). Tuy nhiên trước khi gọi #createQuery() thì Spring Boot framework cần binding paramter vào câu query trước thông qua phương thức getBindingContext():

image.png

Tiếp theo là bước phân tích câu query và gán giá trị từ tham số.

image.png

image.png

Bạn có thể thấy, với parameter expression đầu vào là T(java.lang.Runtime).getRuntime().exec("gnome-calculator") thì sau khi được xử lý giá trị của biến expression vẫn như thế, điều đó có nghĩa là phương thức ParameterBindingJsonReader#bindableValueFor() không phát hiện ra được đây là một đoạn mã được truyền vào thay vì một giá trị thông thường.

image.png

Và lỗ hổng bảo mật xảy ra ở đây chính là khi gọi phương thức ParameterBindingJsonReader#evaluateExpression(), expression truyền vào được covert thành một UUNIXProcess và tiến hành việc mở ứng dụng gnome-calculator ngay sau đó.

image.png

image.png

Debug callstack:

bindableValueFor:389, ParameterBindingJsonReader (org.springframework.data.mongodb.util.json)
readBsonType:304, ParameterBindingJsonReader (org.springframework.data.mongodb.util.json)
decode:227, ParameterBindingDocumentCodec (org.springframework.data.mongodb.util.json)
decode:180, ParameterBindingDocumentCodec (org.springframework.data.mongodb.util.json)
createQuery:121, StringBasedMongoQuery (org.springframework.data.mongodb.repository.query)
doExecute:122, AbstractMongoQuery (org.springframework.data.mongodb.repository.query)
execute:107, AbstractMongoQuery (org.springframework.data.mongodb.repository.query)
invoke:-1, 1601756706 (org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryQueryMethodInvoker$$Lambda$580)
doInvoke:137, RepositoryMethodInvoker (org.springframework.data.repository.core.support)
invoke:121, RepositoryMethodInvoker (org.springframework.data.repository.core.support)
doInvoke:159, QueryExecutorMethodInterceptor (org.springframework.data.repository.core.support)
invoke:138, QueryExecutorMethodInterceptor (org.springframework.data.repository.core.support)
proceed:186, ReflectiveMethodInvocation (org.springframework.aop.framework)
invoke:80, DefaultMethodInvokingMethodInterceptor (org.springframework.data.projection)
proceed:186, ReflectiveMethodInvocation (org.springframework.aop.framework)
invoke:97, ExposeInvocationInterceptor (org.springframework.aop.interceptor)
proceed:186, ReflectiveMethodInvocation (org.springframework.aop.framework)
invoke:215, JdkDynamicAopProxy (org.springframework.aop.framework)
findByFirstName:-1, $Proxy44 (com.sun.proxy)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeJoinpointUsingReflection:344, AopUtils (org.springframework.aop.support)
invokeJoinpoint:198, ReflectiveMethodInvocation (org.springframework.aop.framework)
proceed:163, ReflectiveMethodInvocation (org.springframework.aop.framework)
invoke:137, PersistenceExceptionTranslationInterceptor (org.springframework.dao.support)
proceed:186, ReflectiveMethodInvocation (org.springframework.aop.framework)
invoke:215, JdkDynamicAopProxy (org.springframework.aop.framework)
findByFirstName:-1, $Proxy44 (com.sun.proxy)
run:65, AccessingDataMongodbApplication (com.logbasex.accessingdatamongodb)
callRunner:777, SpringApplication (org.springframework.boot)
callRunners:761, SpringApplication (org.springframework.boot)
run:310, SpringApplication (org.springframework.boot)
run:1312, SpringApplication (org.springframework.boot)
run:1301, SpringApplication (org.springframework.boot)
main:34, AccessingDataMongodbApplication (com.logbasex.accessingdatamongodb)

Vào một tuần sau khi lỗ hổng được report (20/06/2022), Spring Boot framework tung ra hai bản vá mới cho spring-data-mongodb dependency là v3.3.6v3.4.2.
Vậy chúng ta hãy cùng xem source code đã thay đổi như thế nào nhé.

image.png

Biến expression không còn mang giá trị cũ nữa: #_QVar0, qua đó thì lỗ hổng đã được vá.

image.png

Fixed commit:
image.png

image.png

image.png

4. Workaround

  1. Sử dụng parameter array syntax:
    @Query("{ 'firstName' : ?#{?[0]} }")
        Customer findByFirstName(String firstName);
    
  2. Sử dụng custom repository.

5. References

  1. Spring Data MongoDB SpEL Expression Injection Vulnerability (CVE-2022-22980)
  2. CVE-2022-22980: Spring Data MongoDB SpEL Expression injection vulnerability through annotated repository query methods
  3. PoC CVE-2022-22980
  4. [CVE-2022-22980] Spring Data MongoDB SpEL Expression injection
  5. Code difference between vulnerable version and fixed version

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ