Post

CVE-2024-36401 Analysis

CVE-2024-36401

Theo mô tả của NVD:

GeoServer là một máy chủ mã nguồn mở cho phép người dùng chia sẻ và chỉnh sửa dữ liệu không gian địa lý. Các phiên bản GeoServer trước 2.23.6, 2.24.4 và 2.25.2 có lỗ hổng thực thi mã từ xa (RCE), nguyên nhân là do việc sử dụng thư viện commons-jxpath để xử lí XPath mà không được làm sạch, dẫn đến người dùng có thể kiểm soát các XPath tạp và tiêm mã độc bằng cách gửi các yêu cầu OGC khác nhau.

Trước khi đi vào phần chính thì ta hãy tìm hiểu những kiến thức cơ bản và root cause của cái CVE này.

XML and XPATH

XML

XML giống với HTML nhưng nó có mục đích khác nhau. Nhiều bạn hay lẫn lộn giữa HTML và XML. XML (eXtensible Markup Language) được thiết kế để truyền dữ liệu trong khi HTML được thiết kế để hiện thị dữ liệu.

Ví dụ sử dụng XML dễ hình dung nhất là việc giao tiếp giữa front end và back end thông qua các API. Thưởng thì ở các RESTful API, mỗi endpoint sẽ đảm nhiệm 1 nhiệm vụ nhất định. Ví dụ: /api/login sẽ quản lí việc đăng nhập của user, /api/register sẽ quản lí việc đăng kí tài khoản, v.v

Nếu mà các ứng dụng nhỏ thì việc chia từng endpoint cho từng chức năng là hợp lí. Nhưng đối với các ứng dụng lớn và có rất nhiều chức năng thì số lượng endpoint nó sẽ rất lớn và sẽ khó theo dõi.

Có một phương án giải quyết là chỉ sử dụng 1 endpoint duy nhất là /api và xác định chức năng muốn sử dụng thông qua XML. Ví dụ:

1
2
3
4
5
6
7
8
9
POST /api HTTP/1.1
Host: example.com
Content-Type: application/xml
Content-Length: 82

<Login>
  <username>user</username>
  <password>password123</password>
</Login>

Khi gửi request này về phía server, server sẽ đọc xml và hiểu rằng cần phải sử dụng chức năng Login với 2 tham số usernameuserpasswordpassword123.

XPATH

XML sẽ được biểu diễn theo dạng cây, giống như các cái folder trong máy tính vậy. Và để có thể truy cập vào một file nào đó thì ta cần có một đường dẫn, giống như địa chỉ nhà của cái file đó vây. Ví dụ như C:/Users/dat/Desktop/note.txt

XPath cũng giống như đường dẫn đến file vậy, nhưng được sử dụng cho XML. Ví dụ để truy cập được tới thuộc tính password trong cái XML trên. Ta sẽ sử dụng XPath là /Login/password.

Đó là chức năng cơ bản nhất của XPath. Ngoài ra nó còn có cho sử dụng các hàm hỗ trợ cho việc truy vấn cây XML. Ví dụ có XML sau:

1
2
3
4
5
6
7
8
9
10
<bookstore>
  <book>
    <title lang="en">Harry Potter</title>
    <price>29.99</price>
  </book>
  <book>
    <title lang="en">Learning XML</title>
    <price>39.95</price>
  </book>
</bookstore>

Này là một cây XML cơ bản thể hiện dữ liệu các cuốn sách có trong một nhà sách.

Nếu mình muốn truy vấn cuốn sách cuối cùng thì có thể sử dụng XPath như sau: /bookstore/book[last()]

Hàm last() sẽ trả về vị trí cuối cùng của cuốn sách có trong nhà sách và câu truy vấn sẽ trả về:

1
2
3
4
<book>
  <title lang="en">Learning XML</title>
  <price>39.95</price>
</book>

Mặc định thì những hàm có sẵn được hỗ trợ bởi XPATH được cho là “an toàn” bởi vì nó chỉ là những hàm cơ bản và đã được kiểm chứng rất là kĩ.

Nhưng nếu các bạn “thêm mắm thêm muối”, chế thêm các hàm để cho XPATH sử dụng thì liệu nó còn an toàn hay không? Đó là câu chuyện của thư viện commons-jxpath

JXPath

Khác XPath, JXPath sẽ làm việc với cây object ở trong Java thay vì cây xml mà thường gặp.

Ví dụ cây object sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Employee {
  public Address getHomeAddress(){
      ...
  }
  public Vehicle getVehicle(){
      ...
  }
}

public class Address {
  public String getStreetNumber(){
      ...
  }
}

public class Vehicle {
  public String getPlate(){
      ...
  }
  public String getModel(){
      ...
  }
}

Đây là một cây object của Employee, các Employee (nhân viên) sẽ có thuộc tính homeAddress (địa chỉ nhà) và vehicle (thông tin xe). Trong homeAddress sẽ có streetNumber (số nhà), trong vehicle sẽ có plate (số xe) và model (mẫu xe).

Để dễ hiểu hơn thì ta có thể chuyển cây object trên thành dạng xml sau:

1
2
3
4
5
6
7
8
9
<Employee>
  <homeAddress>
    <streetNumber>436 Nguyễn Thị Minh Khai</streetNumber>
  </homeAddress>
  <vehicle>
    <plate>59GA-44.444</plate>
    <model>Yamaha</model>
  </vehicle>
</Employee>

Và để truy vấn thì cũng sử dụng syntax giống XPath thôi, ví dụ như homeAddress/streetNumber để truy vấn thông tin số nhà.

Dưới dạng code java thì có thể tóm tắt như sau:

1
2
3
4
5
Employee emp = new Employee();
JXPathContext context = JXPathContext.newContext(emp);
String sNumber = (String)context.getValue("homeAddress/streetNumber");
String sPlate = (String)context.getValue("vehicle/plate");
Address addr = (Address)context.getValue("homeAddress");

Sẽ không có chuyện gì xảy ra nếu thư viện này chỉ hỗ trợ các XPath đơn giản như ví dụ trên. Nhưng như đã nói ở trên, “nếu các bạn chế thêm các hàm để cho XPATH sử dụng thì liệu nó còn an toàn hay không?”.

Thì trong trường hợp này, thư viện commons-jxpath đã chế thêm tính năng gọi là Extension Functions để sử dụng trong XPath khi truy vấn.

Trích từ documentation, ta có:

JXPath supports standard XPath functions right out of the box. It also supports “standard” extension functions, which are basically a bridge to Java, as well as entirely custom extension functions.

Cụm từ quan trọng ở đây là bridge to Java, có nghĩa là nó cho sử dụng các hàm của java trong XPath khi truy vấn cây object.

Ví dụ như:

1
2
3
4
5
6
Employee emp = new Employee();
JXPathContext context = JXPathContext.newContext(emp);
// Create a new object through XPath
Employee emp2 = (Employee)context.getValue("com.example.employees.Employee.new('ABC')");
// Call any regular functions
String firstName = (String)context.getValue("getEmployeeFirstName($employee)");

Có điều đặc biệt là nó không có giới hạn hay kiểm tra rằng hàm mình truyền vô có “an toàn” hay không. Và trong java có một hàm gọi là exec cho thực thi bất kì câu lệnh nào trên hệ điều hành.

Nên ta có thể cho một cái XPath như sau: exec(java.lang.Runtime.getRuntime(),"calc") và nó sẽ chạy câu lênh calc trên hệ điều hành và mở ứng dụng calculator lên.

Hoặc nếu trên linux: exec(java.lang.Runtime.getRuntime(),"touch /tmp/pwned") sẽ tạo một file tên là pwned trong thư mục /tmp

Nhiều người đã phát hiện ra điều này và đã report cho phía bên apache, ban đầu “lỗi” này đã được đánh số là CVE-2022-41852 và đã có nhiều người đưa ra phương án để sửa. Nhưng sau này thì CVE này đã bị REJECTED và các phương án sửa đều không được triển khai. https://github.com/apache/commons-jxpath/pull/25

Có vẻ đây là “tính năng” và không phải là “bug” của thư viện này rồi.

Điều đó cũng có nghĩa là các ứng dụng hoặc thư viện khác mà có xài thư viện commons-jxpath này phải làm sạch dữ liệu truyền vào của người dùng trước khi sử dụng nó. Nếu không thì khả năng cao sẽ bị dính lỗ hổng thực thi mã độc từ xa (RCE).

Thường thì khi một lập trình viên sử dụng một thư viện của bên thứ 3 thì họ sẽ chủ quan rằng thư viện đó đã bảo mật và không có lỗ hổng nào, và họ cũng không thường xuyên đọc kĩ documentation nên là dẫn đến việc thường xuyên bị dính lỗ hổng bảo mật.

Đây cũng là trường hợp của thư viện geotools, thư viện này đã sử dụng commons-jxpath và cho người dùng truyền vào một XPath bất kì mà không làm sạch nó, dẫn đến việc bị dính RCE.

Và dẫn đến các sản phẩm, dự án mà sử dụng thư viên geotools này cũng dính theo. Lớn nhất là GeoServer - Một máy chủ mã nguồn mở với mục đích kết nối và chia sẻ những thông tin địa lý tới các trang web. Lỗ hổng này đã được đánh số là CVE-2024-36401.

-> Root cause của lỗi này chính là do “tính năng” extension function của commons-jxpath và sử chủ quan của lập trình viên khi sử dụng thư viện này mà không nghiên cứu kĩ về nó trước.

GeoServer và GeoTools (CVE-2024-36401)

Phần này ta sẽ phân tích workflow của thằng GeoServer nó sử dụng GeoTool như thế nào mà dẫn đến việc thực thi mã độc thông qua commons-jxpath. Ta sẽ tập trung 1 trong các chức năng bị dính lỗi đó chính là WFS GetPropertyValue

WFS GetPropertyValue

WFS (Web Feature Service) là một dịch vụ cung cấp người dùng những dữ liệu không gian vector (điểm, đường, đa giác) thông qua giao thức HTTP, ví dụ nếu một trang web nếu cần vẽ ra bản đồ của nước Việt Nam thì cần những thông tin vector trả về bơi dịch vụ này.

Một POST request ví dụ để lấy thông tin the_geom (các dữ liệu không gian vector) của các bang của nước Mỹ:

1
2
3
4
5
6
7
<wfs:GetPropertyValue service='WFS' version='2.0.0'
 xmlns:topp='http://www.openplans.org/topp'
 xmlns:fes='http://www.opengis.net/fes/2.0'
 xmlns:wfs='http://www.opengis.net/wfs/2.0'
 valueReference='the_geom'>
  <wfs:Query typeNames='topp:states'/>
</wfs:GetPropertyValue>

Thông tin trả về là các vector để hỗ trợ việc vẽ ra bản đồ của các bang:

1
<?xml version="1.0" encoding="UTF-8"?><wfs:ValueCollection xmlns:ws2="ws2" xmlns:xs="http://www.w3.org/2001/XMLSchema" ..><wfs:member><ws2:the_geom><gml:MultiSurface srsDimension="2" srsName="urn:ogc:def:crs:EPSG::4326"><gml:surfaceMember><gml:Polygon srsDimension="2"><gml:exterior><gml:LinearRing><gml:posList>37.51099 -88.071564 37.476273 -88.087883 37.442852 -88.311707 37.409309 -88.359177 37.420292 -88.419853 37.400757 -88.467644 37.296852 -88.511322 37.257782 -88.501427 37.205669 -88.450699 37.15691 -88.422516 37.098671 -88.45047 37.072144 -88.476799 37.06818 -88.4907 37.06477 -88.517273 37.072815 -88.559273 37.109047 -88.61422 37.13541 -88.68837 37.141182 -88.739113 37.152107 -88.746506 37.202194 -88.863289 37.218407 -88.932503 37.220036 -88.993172 37.18586 -89.065033 37.112137 -89.116821 37.093185 -89.146347 37.064236 -89.169548 37.025711 -89.174332 36.99844 -89.150246 36.988113 -89.12986 36.986771 -89.193512 37.028973 -89.210052 37.041733 -89.237679 37.087124 -89.264053 37.091244 -89.284233 37.085384 -89.303291 37.060909 -89.3097 37.027733 -89.264244 37.008686 -89.262001 36.999207

Theo mô tả của CVE và các POC đã có thì phần valueReference mà người dùng truyền vô chính là XPath mà không được làm sạch, dẫn đến RCE thông qua commons-jxpath.

Vậy nếu để valueReferenceexec(java.lang.Runtime.getRuntime(),"calc") thì ta đã RCE thành công.

1
2
3
4
5
6
7
<wfs:GetPropertyValue service='WFS' version='2.0.0'
 xmlns:topp='http://www.openplans.org/topp'
 xmlns:fes='http://www.opengis.net/fes/2.0'
 xmlns:wfs='http://www.opengis.net/wfs/2.0'
 valueReference='exec(java.lang.Runtime.getRuntime(),"calc")'>
  <wfs:Query typeNames='topp:states'/>
</wfs:GetPropertyValue>

Tại sao lại như vậy? hãy cùng vào phân tích workflow của nó

Khi gửi POST request đến /geoserver/wfs, đầu tiền nó sẽ đọc tag ngoài cùng đó là wfs:GetPropertyValue để biết được cần thực hiện chức năng nào. Sau đó nó sẽ tìm đến hàm tương ứng trong class wfs và thực thi với các tham số người dùng truyền vô. Ở đây operation chính là GetPropertyValue

alt text

Hàm GetPropertyValue ở trong class DefaultWebFeatureService20 (wfs), hàm này sẽ chuyển tiếp qua class cùng tên để xử lí GetPropertyValue

alt text

Trong hàm run của class GetPropertyValue, nó sẽ lấy giá trị của referenceValue mình chuyền vào và chạy hàm evaluate. Đây cũng là dòng lệnh sẽ gây ra lỗi RCE. alt text

Hãy cùng xem hàm evaluate này nó làm gì

Để giải thích cho nhưng gì hàm này làm, hãy quay lại với request ví dụ của use case bình thường:

1
2
3
4
5
6
7
<wfs:GetPropertyValue service='WFS' version='2.0.0'
 xmlns:topp='http://www.openplans.org/topp'
 xmlns:fes='http://www.opengis.net/fes/2.0'
 xmlns:wfs='http://www.opengis.net/wfs/2.0'
 valueReference='the_geom'>
  <wfs:Query typeNames='topp:states'/>
</wfs:GetPropertyValue>

Mục đích của request này là lấy tập dữ liệu vector địa lý the_geom của layer topp:states (dữ liệu địa lí của các bang tại Mỹ). alt text

Để mà có thể lấy the_geom từ topp:states thì ta cần phải thông qua một PropertyAccessor. Đối với những thuộc tính đã có sẵn như trong hình (the_geom, STATE_NAME, STATE_FIPS,…) thì PropertyAccessor được sử dụng là SimpleFeaturePropertyAccessor, hiểu đơn giản là cái SimpleFeaturePropertyAccessor này sẽ lấy thông tin có sẵn trong object topp:states mà không cần phải truy vấn phức tạp.

Nếu mà valueReference không nằm trong cái list the_geom, STATE_NAME, STATE_FIPS, … thì valueReference được đối xử như 1 XPath và PropertyAccessor lúc này sẽ là FeaturePropertyAccessor.

Quay lại với ví dụ RCE valueReference='exec(java.lang.Runtime.getRuntime(),"calc")' lúc này PropertyAccessor sẽ là FeaturePropertyAccessor và lúc truy xuất sẽ truy xuất XPath thông qua thư viện commons-jxpath.

alt text

Đây chính là nơi truy vấn XPath thông qua thư viện commons-jxpath. Ta có thể thấy rằng valueReference của người dùng được truyền vô thằng tham số xpath mà không có thông qua bất kì validation nào. alt text

Và như đã tìm hiểu ở trên, JXPath của commons-jxpath có hộ trợ thêm “tính năng” đó là Extension Functions, nên vì thế ta có thể dễ dàng thực thi mã độc trên hệ điều hành thông qua hàm exec.

Sau khi đã đọc xpath xong thì đoạn code sau sẽ truy vấn xpath, lúc này đoạn mã độc sẽ thực thi và chạy ứng dụng calculator

1
Iterator it = context.iteratePointers(xpath);

alt text

This post is licensed under CC BY 4.0 by the author.