某云的反序列漏洞及绕过思路分析
漏洞分析
最近看到Kingdee星空反序列化漏洞各种各样的接口,本文将分析其漏洞原理及绕过思路,入门学习.NET程序的调试过程。
某云的反序列漏洞及绕过思路分析 =============== 0x00 漏洞前言 --------- 最近看到Kingdee星空反序列化漏洞各种各样的接口,本文将分析其漏洞原理及绕过思路,入门学习.NET程序的调试过程。 0x01 调试环境 --------- 参照网络上Kingdee星空安装教程搭建即可,调试工具使用dnspy: 1、搜索dll文件定位exe程序  2、附加进程到dnSpy中 调试-->附加到进程(P)-->附加 3、搜索要调试的程序集 调试-->窗口-->模块(如果搜不到说明dll文件还没加载进来,先触发使其加载后再搜索)  4、断点调试 注意:如果调试时dnSpy报错:`无法获取局部变量或参数的值,因为它在此时不可用,可能是被优化了`  根据 [dnSpy的wiki描述](https://github.com/dnSpy/dnSpy/wiki/Making-an-Image-Easier-to-Debug):创建ini文件禁用编译优化(创建完后需重启iis服务器);配置环境变量。 0x02 漏洞分析 --------- 根据网络上的POC可以发现漏洞的url路径都是以`.kdsvc`结尾的,我们在web.config文件中搜索可以发现handlers对Http Request请求做了处理:  ```php path="*kdsvc" 处理程序要处理的请求路径,即当应用程序收到路径以kdsvc结尾的请求时进行处理 verb="*" 处理程序要处理的HTTP动作,在这里*表示该处理程序将处理所有类型的HTTP请求包括GET、POST等 type="xxx,xxx" 处理程序的类型,这里的类型由两部分组成,处理程序类型的完全限定名,命令空间(dll文件) ``` 断点入口找到dll文件展开后定位到具体类:`Kingdee.BOS.ServiceFacade.KDServiceFx.KDServiceHandler`,该类是对传入的url参数进行处理后返回程序实例KDSVCHandler:  查看该处理器,第一个方法为`ProcessRequest`:  其中方法`RequestExtractor#Creat`是对HTTP Request请求做处理,判断请求方法、是否为json格式:  我们可以看到方法`IsContainsJson`根据ContentType是否包含json返回flag:  然后通过`requestExtractor = new JQueryRequestExtractor(request, isGet)`将request传递的值进行属性的赋值:  继续跟进该实例的方法`RequestExcuteRuntime#StartRequest`,该方法用于处理HTTP请求并根据请求的情况执行相应的逻辑包含返回相应状态码等:  方法`RequestExcuteRuntime#StartRequest`里调用了方法`RequestExcuteRuntime#BeginRquest`,方法在处理完会话信息后,继续执行到69行我们可以发现这里的path为我们传递的url ,通过`webCtx.Context.Server.MapPath(path)` 生成一个localFile,接着调用了`ServiceTypeManager#BuidServiceType`方法:  跟进该方法可以发现,首先会判断localFile是否包含`common.kdsvc`,如果包含会进入`ServiceTypeManager.ReflectServiceType`方法,该方法会通过查找缓存或在程序集中搜索提取出类名和方法名等:  接着`RequestExcuteRuntime#BeginRquest`方法会走到`RequestExcuteRuntime.pipeline.ExcuteRequest(kdserviceContext)`处:  跟进方法可以看到,对Modules的遍历会进入不同的OnProcess方法(是对request请求校验:分别为是否为https、session信息、version信息以及API鉴权):  最后一次循环会进入`ExecuteServiceModule#OnProcess`方法中:  方法`RequestExtractor#GetServiceParameters`用于处理参数的提取和验证(即payload位置这里重点关注下),对传入的参数进行判断,首先判断参数是否包含`parameters`,如果包含则创建一个JSON数组;当payload传入的参数为ap0时,会进入else里遍历所有的ap+数字:  所以这里我们猜想payload的参数位置应该可以为两个: ```php {"parameters": "[\"payload\"]"} {"ap*": "payload"} ``` 对参数处理完后方法进入如下: ```php SerializerProxy serializeProxy = new SerializerProxy(requestExtractor.Format, contentEncoding, requestExtractor.Compressed, requestExtractor.UserAgent, requestExtractor.UrlForm_Encode); ``` this.Format=format对传入的参数format定义,我们传入的format=3会匹配到Binary(直接给format赋值为 Binary也是可以的):  接着会到`ServiceExecutor#Execute`方法:  该方法首先需要用构造函数参数来实例化一个特定类型的对象obj且传参只能为context即`KDServiceContext`,然后调用不同的`serializer.Deserializer`进行反序列化处理,所以如果任意一个类型的构造函数支持传递该参数都可以进入反序列化:  进入的第一个反序列化方法为`SerializerProxy#Deserialize`,该方法用来对参数类型做判断方法如下,参数类型不能为(string、int、byte、float...): ```asp public object Deserialize(string content, Type type) { if (string.IsNullOrEmpty(content)) { if (type.IsValueType) { return Activator.CreateInstance(type); } if (type.Equals(typeof(string))) { return content; } return null; } else if (type == typeof(string)) { if (this.proxy.RequireEncoding) { byte[] array = this.proxy.Encoder.Decoding(content); return this.encoding.GetString(array, 0, array.Length); } return content; } else { if (type.IsEnum) { return Enum.Parse(type, content, true); } if (type == typeof(int)) { return int.Parse(content); } if (type == typeof(byte)) { return byte.Parse(content); } if (type == typeof(float)) { return float.Parse(content); } if (type == typeof(double)) { return double.Parse(content); } if (type == typeof(long)) { return long.Parse(content); } if (type == typeof(DateTime)) { return DateTime.Parse(content); } if (type == typeof(decimal)) { return decimal.Parse(content); } if (type == typeof(bool)) { return bool.Parse(content); } return this.proxy.Deserialize(content, type); } } ``` 最终进入`BinaryFormatterProxy#Deserialize`方法中通过`BinaryFormatter#Deserialize`方法实现反序列化(根据微软描述:[将BinaryFormatter.Deserialize方法用于不受信任的输入时,该方法永远都不安全](https://learn.microsoft.com/zh-cn/dotnet/standard/serialization/binaryformatter-security-guide?source=recommendations#binaryformatter-security-vulnerabilities)):  0x03 漏洞总结 --------- 1、Url路径:`程序集.满足条件的类.common.kdsvc` 满足构造函数支持传递KDServiceContext类型,且传递参数不为上述`SerializerProxy#Deserialize`方法中的类型即可,我们可以看到在程序集`Kingdee.BOS.ServiceFacade.ServicesStub`中满足条件的类非常多,这里我们就找第一个类说明下:  再找一个满足传参的类,如: ```asp SaveBizTipsInfos(string bizHost, List<IBizTipsInfos> tips) ``` 2、参数 1. 使用ap作为参数 由于第一个参数为string所以我们需要用第二个参数来传递payload,这里我们用ysoserial.exe生成: ```php ysoserial.exe -c "a.cs;System.Windows.Forms.dll;System.Web.dll;System.dll" -f BinaryFormatter -o base64 -g ActivitySurrogateSelectorFromFile ```  2. 使用parameters作为参数 parameters根据上述`RequestExtractor#GetServiceParameters`方法可以得知,需要传递一个数组对象,同样由于第一个参数为string不能放payload,所以将paylaod放置在数组的第二个位置即可:  0x04 修复&绕过 -------------- 漏洞修复:启用金蝶数据签名校验格式KigndeeXml格式并禁用了二进制序列化: ```php <add key="KDSVCDefaultFormat" value="4"/> <add key="EnabledKDSVCBinary" value="false"/> ``` 绕过思路:修改format为4,即赋值为KingdeeXml,我们可以看到代码最终还是会通过`BinaryFormatter#Deserialize`反序列化为KingdeeXMLPack对象: ```asp KingdeeXMLPack kingdeeXMLPack = obj as KingdeeXMLPack; if (kingdeeXMLPack != null) { BinaryFormatterProxy binaryFormatterProxy = new BinaryFormatterProxy(this.encoding, false); obj = binaryFormatterProxy.Deserialize(kingdeeXMLPack.Data, type); } ``` 所以我们可以将参数修改为KingdeeXMLPack形式: ```php {"ap0": "<KingdeeXMLPack>BinaryPayload</KingdeeXMLPack>", "format": "4"} ``` 我们发现也是可以成功执行的: 
发表于 2023-12-28 10:00:01
阅读 ( 18596 )
分类:
漏洞分析
5 推荐
收藏
1 条评论
中铁13层打工人
79 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!