×

某云的反序列漏洞及绕过思路分析

hqy hqy 发表于2025-01-17 00:33:41 浏览6 评论0

抢沙发发表评论

mportant;">某云的反序列漏洞及绕过思路分析

0x00 漏洞前言

最近看到Kingdee星空反序列化漏洞各种各样的接口,本文将分析其漏洞原理及绕过思路,入门学习.NET程序的调试过程。

0x01 调试环境

参照网络上Kingdee星空安装教程搭建即可,调试工具使用dnspy:

1、搜索dll文件定位exe程序

image-20231205162814438

2、附加进程到dnSpy中

调试-->附加到进程(P)-->附加

3、搜索要调试的程序集

调试-->窗口-->模块(如果搜不到说明dll文件还没加载进来,先触发使其加载后再搜索)

image-20231205165053340

4、断点调试

注意:如果调试时dnSpy报错:无法获取局部变量或参数的值,因为它在此时不可用,可能是被优化了

image-20231205152932598

根据 dnSpy的wiki描述:创建ini文件禁用编译优化(创建完后需重启iis服务器);配置环境变量。

0x02 漏洞分析

根据网络上的POC可以发现漏洞的url路径都是以.kdsvc结尾的,我们在web.config文件中搜索可以发现handlers对Http Request请求做了处理:

image-20231205145807144

path="*kdsvc"处理程序要处理的请求路径,即当应用程序收到路径以kdsvc结尾的请求时进行处理
verb="*"处理程序要处理的HTTP动作,在这里*表示该处理程序将处理所有类型的HTTP请求包括GET、POST等
type="xxx,xxx"处理程序的类型,这里的类型由两部分组成,处理程序类型的完全限定名,命令空间(dll文件)

断点入口找到dll文件展开后定位到具体类:Kingdee.BOS.ServiceFacade.KDServiceFx.KDServiceHandler,该类是对传入的url参数进行处理后返回程序实例KDSVCHandler:

image-20231205171151154

查看该处理器,第一个方法为ProcessRequest

image-20231206161045763

其中方法RequestExtractor#Creat是对HTTP Request请求做处理,判断请求方法、是否为json格式:

image-20231206162514430

我们可以看到方法IsContainsJson根据ContentType是否包含json返回flag:

image-20231206162729205

然后通过requestExtractor = new JQueryRequestExtractor(request, isGet)将request传递的值进行属性的赋值:

image-20231206172733816

继续跟进该实例的方法RequestExcuteRuntime#StartRequest,该方法用于处理HTTP请求并根据请求的情况执行相应的逻辑包含返回相应状态码等:

image-20231206112439778

方法RequestExcuteRuntime#StartRequest里调用了方法RequestExcuteRuntime#BeginRquest,方法在处理完会话信息后,继续执行到69行我们可以发现这里的path为我们传递的url ,通过webCtx.Context.Server.MapPath(path) 生成一个localFile,接着调用了ServiceTypeManager#BuidServiceType方法:

image-20231206113840439

跟进该方法可以发现,首先会判断localFile是否包含common.kdsvc,如果包含会进入ServiceTypeManager.ReflectServiceType方法,该方法会通过查找缓存或在程序集中搜索提取出类名和方法名等:

image-20231206114549901

接着RequestExcuteRuntime#BeginRquest方法会走到RequestExcuteRuntime.pipeline.ExcuteRequest(kdserviceContext)处:

image-20231206151350980

跟进方法可以看到,对Modules的遍历会进入不同的OnProcess方法(是对request请求校验:分别为是否为https、session信息、version信息以及API鉴权):

image-20231206151556338

最后一次循环会进入ExecuteServiceModule#OnProcess方法中:

image-20231206175314632

方法RequestExtractor#GetServiceParameters用于处理参数的提取和验证(即payload位置这里重点关注下),对传入的参数进行判断,首先判断参数是否包含parameters,如果包含则创建一个JSON数组;当payload传入的参数为ap0时,会进入else里遍历所有的ap+数字:

image-20231206174524352

所以这里我们猜想payload的参数位置应该可以为两个:

{"parameters": "[\"payload\"]"}
{"ap*": "payload"}

对参数处理完后方法进入如下:

SerializerProxy serializeProxy = new SerializerProxy(requestExtractor.Format, contentEncoding, requestExtractor.Compressed, requestExtractor.UserAgent, requestExtractor.UrlForm_Encode);

this.Format=format对传入的参数format定义,我们传入的format=3会匹配到Binary(直接给format赋值为 Binary也是可以的):

image-20231206180628658

接着会到ServiceExecutor#Execute方法:

image-20231206152041905

该方法首先需要用构造函数参数来实例化一个特定类型的对象obj且传参只能为context即KDServiceContext,然后调用不同的serializer.Deserializer进行反序列化处理,所以如果任意一个类型的构造函数支持传递该参数都可以进入反序列化:

image-20231207150855570

进入的第一个反序列化方法为SerializerProxy#Deserialize,该方法用来对参数类型做判断方法如下,参数类型不能为(string、int、byte、float...):

        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方法用于不受信任的输入时,该方法永远都不安全):

image-20231207144047221

0x03 漏洞总结

1、Url路径:程序集.满足条件的类.common.kdsvc

满足构造函数支持传递KDServiceContext类型,且传递参数不为上述SerializerProxy#Deserialize方法中的类型即可,我们可以看到在程序集Kingdee.BOS.ServiceFacade.ServicesStub中满足条件的类非常多,这里我们就找第一个类说明下:

image-20231207154931752

再找一个满足传参的类,如:

SaveBizTipsInfos(string bizHost, List<IBizTipsInfos> tips)

2、参数

  1. 使用ap作为参数

由于第一个参数为string所以我们需要用第二个参数来传递payload,这里我们用ysoserial.exe生成:

ysoserial.exe -c "a.cs;System.Windows.Forms.dll;System.Web.dll;System.dll" -f BinaryFormatter -o base64 -g ActivitySurrogateSelectorFromFile

image-20231207155405575

  1. 使用parameters作为参数

parameters根据上述RequestExtractor#GetServiceParameters方法可以得知,需要传递一个数组对象,同样由于第一个参数为string不能放payload,所以将paylaod放置在数组的第二个位置即可:

image-20231207172937604

0x04 修复&绕过

漏洞修复:启用金蝶数据签名校验格式KigndeeXml格式并禁用了二进制序列化:

<add key="KDSVCDefaultFormat" value="4"/>
<add key="EnabledKDSVCBinary" value="false"/>

绕过思路:修改format为4,即赋值为KingdeeXml,我们可以看到代码最终还是会通过BinaryFormatter#Deserialize反序列化为KingdeeXMLPack对象:

KingdeeXMLPack kingdeeXMLPack = obj as KingdeeXMLPack;
if (kingdeeXMLPack != null)
{
    BinaryFormatterProxy binaryFormatterProxy = new BinaryFormatterProxy(this.encoding, false);
    obj = binaryFormatterProxy.Deserialize(kingdeeXMLPack.Data, type);
}

所以我们可以将参数修改为KingdeeXMLPack形式:

{"ap0": "<KingdeeXMLPack>BinaryPayload</KingdeeXMLPack>", "format": "4"}

我们发现也是可以成功执行的:

image-20231214151852764


打赏

本文链接:https://www.kinber.cn/post/4704.html 转载需授权!

分享到:


推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

 您阅读本篇文章共花了: 

群贤毕至

访客