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