11.Json.Net反序列化点(前篇)以及XamlImageInfo、GetterSecurityException相关链

2024-10-27 20:13   广东  

1.Json.Net的使用

首先还是一个没有序列化特性的类

然后进行序列化,注意这里的两个序列化操作,一个就是完全普通的序列化,还有一个在序列化时SerializeObject方法的第二个参数传入JsonSerializerSettings类的对象,对TypeNameHandling属性赋值为TypeNameHandling.None

运行一下序列化

可以看到,就是普通的json数据,没什么特别的


2.Json.Net反序列化安全问题

根据Y4er师傅的演示代码可以看到还有TypeNameHandling.All之类的值,看看序列化的时候配置TypeNameHandling.All会怎样

这里有意思的一点是

官方叫我们慎用这个功能,除了使用None的情况下,都需要用SerializationBinder进行验证,照这么说,除了None的情况,其他情况都有安全问题。我们先来玩玩

{"$type":"Json.NetSerializer.Person, ConsoleApp3","Name":"jack"}

注意到这个$type指定全类名,非常的经典,这样的操作在Fastjson里见过太多,甚至上篇文章也是类似的。代码底层会用反射调用目标类的构造函数创建对象。而且注意,这里如果对面的构造方法需要传参,我们直接在Json数据里添加形参名字并赋值即可,就可以调用目标构造方法并传值。那我们写Payload就非常简单非常便利了,和上篇文章学习的JavaScriptSerializer反序列化点相关payload类似,最主要的不同其实就是这个数组的写法,其它都没啥区别

然后注意这个反序列化点

首先注意到DeserializeObject()方法,这就是反序列化点,第一个传参就是反序列化的数据,得保证这个可控,然后第二个关键点就是第二个参数,运行下面这个代码的时候

new JsonSerializerSettings()

TypeNameHandling只要不等于None就有漏洞,比方说这里为All的时候

触发RCE

为Objects,也可以RCE

Arrays,也可以

唯独None不行

此外第二个参数不传的话,也相当于None,也不行

因此实战中的审计除了关注DeserializeObject()方法的第一个传参是否可控,还要关注第二个参数的信息,只要第二个参数不是None(或者干脆没有第二个参数),那么就有戏,除了上面这种格式的调用方法,还有下面这种格式:

也是大同小异,只不过这时候调用的是Deserialize()方法,并且这里就不接受String类型的传参了,得用JsonReader获取json数据。其他的倒是没啥变化,运行也是一样的效果


3.Type不可控的情况

在调用反序列化方法的时候

DeserializeObject()

除了可以传入JsonSerializerSettings,还可以指定type,比如下面的案例:

注意这里的typeof(),指定为Person,此时运行我们上述Payload是无法成功RCE的:

这时候怎么破局呢?答案是在Json里把之前的Payload内容赋值给Person类中的Test属性:

此时又可以成功RCE,这个可以参考

breeze CVE-2017-9424

总之,如果Type指定的目标类中有Object类型的属性,可以借助这个属性存放序列化payload


4.XamlImageInfo链

Json.Net自己专门的链子,很有牌面。可以看到有两种模式,

LazyFileStream

ReadOnlyStreamFormStrings

我们先来看看XamlImageInfo类吧,它是下面这个类的内部类

System.Activities.Presentation.Internal.ManifestImages

其构造方法接受Stream类型的传参,并进一步传给XamlReader.Load()方法,这个方法可以执行任意Xaml内容,可以直接RCE。

Json.Net反序列化确实可以调用XamlImageInfo的构造方法,不过这个stream该怎么选用呢?第一条链路就是LazyFileStream,这个类是

Microsoft.Build.Tasks.Windows.ResourcesGenerator

类的内部类,其构造方法接受一个string类型的path,并且给本类的_sourcePath赋值

而后续会用FileStream读取指定的文件并转化为FileStream类型的数据,所以可以利用。

第一种payload如下:

注意XamlImageInfo的stream形参是LazyFileStream类的对象,这里指定testxaml.txt文件,文件内容如下

对json内容进行反序列化即触发计算器

这种利用模式下,实战里得设法落地一个包含恶意内容的文件到目标服务器上,并且最好是知道绝对路径,然后可以用这个链子打。不过LazyFileStream也支持从smb加载文件内容,.NET知识安全矩阵关于这条链子的演示就是通过smb加载的恶意文件

还有一个链子的起始点是

ReadOnlyStreamFromStrings

这也是一个实现了Stream的类

其构造方法接受一个IEnumerator<string>类型的enumerator传参和一个string类型的stringSuffix传参,并传入StringAsBufferEnumerator()

这个方法分别对本类的_enumerator和_stringSuffix方法进行赋值

后续如果触发

object IEnumerator.Current

则调用本类Current属性并返回

而本类的Current属性的get,会拿到

this._enumerator.Current

的值和_stringSuffix做拼接,然后直接拿到字符串的byte[]

后续XamlReader.Load()在解析我们传入的

ReadOnlyStreamFromStrings

类的对象的时候,是会经过各种流程后加载_currentBuffer的内容的。Ysoserial生成一个XamlImageInfo+ReadOnlyStreamFromStrings的链子:

ysoserial.exe -f Json.Net -g XamlImageInfo -c calc -o raw --var 2

还是比较好理解,stringSuffix的值就是Xaml的payload,这样可以实现不出网利用,也不需要落地文件,美中不足的是

ReadOnlyStreamFromStrings

所处的

Microsoft.Web.Deployment

并不是默认就有的程序集,除非目标加载了这个程序集,不然这个链子没法用

最后这里运行一下上述payload,成功RCE

5.SecurityException链

(这个链包括上面提到的链子都来自whitepaper-net-deser,后续还会再学到里面的各种内容)

这个类的构造方法要传入一堆值,并且完成类中属性的赋值,其中最关键的是Method

在它的set里,把用户传入的value作为

RuntimMethodInfo

传入

ObjectToByteArray()

这个方法的执行结果赋值给m_serializedMethodInfo属性

这个方法里把传入的obj进行一次binaryFormatter序列化,并且把结果转为byte[]

而在Method属性的get中,有一个getMethod()方法的调用

将上文提到的m_serializedMethodInfo传入其中

而这里面又有一个反序列化操作

So,理论上说只要先利用set设置好payload,再触发get就可以实现攻击了。

①PropertyGrid

这里第一种方法是结合PropertyGrid的

public object[] SelectedObjects

属性进行利用,这个属性类型是object数组,该属性的set会调用一个Refresh方法

再进入RefreshProperties()

其中又调用UpdateSelection()

又调用一个Refresh()

这个Refresh方法里调用CreateChildren()方法,CreateChildren里又调用GetPropEntries()

而这个方法里有GetValue的操作

当然这中间有一大堆代码要你分析,人力分析累死了,我们直接来看看使用效果是怎么样的,就一目了然了

可以看到,当我们把对象装到object[]里,然后赋值给SelectedObjects属性的时候,就会把对象里所有属性的get触发一遍

而我们前面分析Method属性的时候就说过,只要能触发它的get方法,就能实现RCE,因此我们可以先搞一个object[],在里面放一个SecurityException类的对象,当我们把这个object[]赋值给SelectedObjects属性的时候,就会触发SecurityException类中所有属性的get,包括我们的Method,自然就完成攻击了。因此Ysoserial给出的payload如下:

可以看到主要就是

PropertyGrid.SelectedObjects

属性和

SecurityException.Method

属性

运行上述payload成功RCE

这样我们就可以把思路打开,只要某个属性的set方法能触发指定对象的指定属性的get方法,就可以用于延续链子,ysoserial这里还有好几种

②ComboBox

第二种ComboBox也是类似的原理,其流程比上面的PropertyGrid要ez一些,他有几个关键的属性,分别是Items、Text、DisplayMember,先看Items

乍一看没有set好像连赋值都搞不了,注意他是一个ObjectCollection,实际上这玩意用Add()添加值就行,例如

comboBox.Items.Add("Item 1");

接下来是DisplayMember,这个属性最后的用途是指定你要调用目标类中哪个属性的get方法,把值先给到BindingMemberInfo

BindingMemberInfo又把值给到dataField

接着进入SetDataConnection()方法

这个方法又把BindingMemberInfo的对象传给displayMember

最后是Text属性

会拿到我们前面传入的Items,并传入GetItemText()方法

这个方法里注意,把item和displayMember.BindingField一起传入FilterItemOnProperty方法

这里先看看

displayMember.BindingField

其实就是我们一开始对DisplayMember属性赋值时传入的值,dataField

进入FilterItemOnProperty,意图很明显,从我们传入的item对象中,搜索field字段,field的值也就是我们前面给DisplayMember传入的值

然后获取对应字段的值,这会触发其get操作。简单来说,我们写出如下demo:

这样就很容易和前面的sink点串起来了。写出payload:

这里的Items、DisplayMember、Text的作用我们上面介绍过了,DisplayMember指定目标类中的Method属性即可

③ListBox与CheckedListBox

它和ComboBox在同一个命名空间。这玩意和上面的ComboBox链子除了类名不一样,其他基本上可以说完全一样的,比方说几个关键的属性Text

Items

然后它父类也是ListControl,DisplayMember也是一样的,这个就没啥好说的了,ysoserial生成出来的ListBox的payload和ComboBox内容是一样的,就类名变了一下而已。CheckedListBox也是同理,还是上面这几个属性,类名变了一下

这几个类PropertyGrid、ComboBox、ListBox、CheckedListBox还是有必要记一下的,如果有一些恶意操作在某个属性的get里,可以靠这几个类中关键属性的set去触发


6.总结

审计中主要关注三个点,首先关注DeserializeObject()接受的内容是否可控,其次关注TypeNameHandling不为None,最后关注type是否可控,如果type不可控,则观察type指定的目标类中有没有Object类型的属性,可以用于存放payload内容。


7.参考

https://xz.aliyun.com/t/9603






HW专项行动小组
大师!教我打攻防
 最新文章