+2

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

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

Step into:

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

Parse expression:

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. Demo

6. 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
 ____________________
< Thanks for reading >
 --------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     |

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí