Struts2漏洞集合分析与梳理

abcdefg1234 2022-05-10 10:26:00

本文针对Struts2一些经典漏洞进行分析和梳理。为什么要分析struts2这些略久远的漏洞呢?最近热议的Spring jdk9 漏洞,其中涉及到Struts2+类加载器漏洞的利用方式,由此有了重新梳理Struts2历史漏洞的想法。漏洞虽然是过去的,但知识点永远是知识点。希望通过对Struts2的一些经典漏洞的分析,获取新的认识和知识。

0x00 S2-001

漏洞描述(https://cwiki.apache.org/confluence/display/WW/S2-001)

该漏洞是由于WebWork 2.1+ 和 Struts 2 的"altSyntax"特性引起的。“altSyntax"特性允许将OGNL表达式插入文本字符串并进行递归处理。这允许恶意用户通过HTML文本字段提交一个包含OGNL表达式的字符串,如果表单验证失败,服务器将执行该表达式。

影响版本

WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8

测试demo
1.png
漏洞调用链
image-20220417140247576.png
经分析,漏洞出现在doEndTag()处理过程中。当处理到表单中的\>时会通过org.apache.struts2.views.jsp.ComponentTagSupportdoEndTag方法解析相应标签的属性。跟踪到org.apache.struts2.components.UIBean#end中的evaluateParams方法,在处理表单中nameusernametextfield字段时,将该name包装为%{}的形式交给findValue处理。具体代码如下:
image-20220417134228165.png
分析struts2-corejar包中的findvalue方法,该漏洞版本支持altSyntax特性,并进入TextParseUtil.translateVariables('%', expr, this.stack);方法处理表达式expr
image-20220417135222701.png
image-20220417135812071.png
image-20220417140053242.png
xwor-2.0-beat-1.jar(本次漏洞测试环境)中的com.opensymphony.xwork2.util.TextParseUtil#translateVariables()方法处理表达式中进行递归处理,从而导致用户提交的value进行二次解析,导致S2-001漏洞的触发。
image-20220417140733022.png

漏洞修复

com.opensymphony.xwork2.util.TextParseUtil#translateVariables()中不再对表达式进行递归解析,如此用户输入的value值不会再被解析执行。
说明:本次测试S2-001的demo中并不是因为表单验证失败而触发漏洞,根据调试分析过程可见,触发漏洞根本原因在于,可以递归解析表单中的属性值从而导致用户输入的ognl表达式被执行。

0x01 S2-003 and S2-005

漏洞描述(https://cwiki.apache.org/confluence/display/WW/S2-003)

漏洞触发源于ParameterInterceptor拦截器中,可通过构造参数绕过正则限制从而执行ognl表达式。

影响版本

Struts 2.0.0 - Struts 2.1.8.1

漏洞分析

com.opensymphony.xwork2.interceptor.ParametersInterceptor拦截器通过doIntercept方法处理参数
image-20220419172705215.png
跟进setParameters方法看到熟悉的stack.setValue(name, value);代码,但中间要经过this.acceptableName(name进行验证。
image-20220419172836025.png
可见对传入参数进行正则表达式匹配[\p{Graph}&&[^,#:=]]*
image-20220419173343136.png
因此,绕过该正则即可实现ognl表达式。在S2-003中,通过unicode编码或八进制对"#","="等字符进行绕过。#编码为\u0023或者八进制\43,=编码为\u003d。其中,空格会被转义,因此将空格也用unicode编码为\u0020。最终poc如下,其中xwork.MethodAccessor.denyMethodExecution需设置为true才能执行命令。

测试环境:tomcat8 jdk8

payload:简单构造如下:

('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(t)(c)&('\u0023test\u003d@java.lang.Runtime@getRuntime().exec(\'open\u0020/System/Applications/Calculator.app\')')(a)(b)
image-20220423091920252.png
S2-005和S2-003的漏洞原理相同,S2-005的出现时因为官方对S2-003的修补的不完全而导致。官方通过增加安全配置禁止静态方法调用(allowStaticMethodAcces)和类方法执行(MethodAccessor.denyMethodExecution)等安全配置来修补,但是该方式可以通过OGNL表达式控制开关。

0x02 S2-007

漏洞描述(https://cwiki.apache.org/confluence/display/WW/S2-007

当配置了验证规则<ActionName>-validation.xml时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL 表达式解析,从而造成远程代码执行。

影响版本

Struts 2.0.0 - Struts 2.2.3

漏洞分析

本次漏洞环境:S2-007

UserAction-validation.xml文件中设置age类型为int
image-20220423093812611.png
当输入age为String类型时会经过com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor拦截器进行拦截处理。
image-20220423093825260.png
分析com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor拦截器拦截流程:
1、将验证错误的age和其value值存在conversionErrors
image-20220423093933161.png
2、通过Object value = entry.getValue();获取value值,然后通过getOverrideExpr()处理。
image-20220423094411203.png
image-20220423094522378.png
getOverrideExpr()处理后在value前后分别拼接',这也是为什么payload前后要加'闭合的原因。
3、关键:com.opensymphony.xwork2.interceptor.ConversionErrorInterceptorintercept方法中设置addPreResultListener,在beforeResult方法中通过 invocation.getStack().setExprOverrides(fakie);fakie中对应的ognl表达式添加到this.overrides中,后续会用到。
image-20220423094736723.png
image-20220423094745807.png
然后与S2-001同样的流程,在处理提交表单的闭合标签时经过
doEndTag --> end --> evaluateParams -->this.findValue(expr, valueClazz) -->TextParseUtil.translateVariables('%', expr, this.stack);流程处理。
重点分析TextParseUtil.translateVariables('%', expr, this.stack);方法。由于S2-001的补丁,不会再对expr表达式进行递归处理,因此name、email的value值不会导致ognl表达式执行。TextParseUtil.translateVariables('%', expr, this.stack);的代码执行流程如下图所示:
image-20220423094827332.pngimage-20220423094827332.png
其中关键方法定位到tryFindValue方法
image-20220423094854181.png
通过lookupForOverrides方法在this.overrides 中查找key为expr对应的值。由于上述分析,在经过com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor#intercept处理时将age和其value值put进this.overrides中。由此得到的expr为注入到age值的ognl表达式,通过this.getValue(expr,asType)成功执行。
image-20220423094942007.png
image-20220423095003387.png

漏洞修复

getOverrideExpr函数中进行了StringEscape,从而无法闭合单引号,也就无法构造OGNL表达式

0x03 S2-009

漏洞描述(https://cwiki.apache.org/confluence/display/WW/S2-009)

ParametersInterceptor拦截器只检查传入的参数名是否合法,不会检查参数值。因此可先将Payload设置为参数值注入到上下文中,而后通过某个特定语法取出来就可以执行之前设置过的Payload。

影响版本

Struts 2.0.0 - Struts 2.3.1.1

漏洞分析

和S2-003/S2-005一样都是由ParametersInterceptor拦截器引起的。在S2-009是由于ParametersInterceptor拦截器在只检查参数名是否合法,而不检查参数值,从而可通过设计参数名与参数值执行ognl表达式。
com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept拦截方法中通过this.setParameters(action, stack, parameters);设置参数。分析setParameters方法:
image.png
通过acceptableName(name)方法判断参数名是否合规,合规则将该参数名和参数值加入到acceptableParameters中。
image.png
this.acceptedPattern=[a-zA-Z0-9\.\]\[\(\)_'\s]+
image.png
this.excludeParams=[dojo.., ^struts..]
image.png
以上可见,只要参数值符合[a-zA-Z0-9\.\]\[\(\)_'\s]+的正则表达且不包含[dojo\..*, ^struts\..*]则可通过合法校验。而后,从acceptableParameters中依次取出参数,通过newStack.setValue(name, value);处理,从而触发ognl表达式执行。
image.png
newStack.setValue(name, value);触发ognl表达式执行流程:
image.png
最终到Ognl.setValue(this.compile(name), context, root, value);
image.png
执行完compile方法,再执行setValue方法,参数为四个:语法树、上下文、根对象以及传入的value值。
解释下payload为何这么写:
1、foo=(#context["xwork.MethodAccessor.denyMethodExecution"]= new java.lang.Boolean(false), #_memberAccess["allowStaticMethodAccess"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('open /System/Applications/Calculator.app'))(meh)
2、z[(foo)(aa)]=true
分别对应:
value1=(expression1)(constant)
z[(expression2)(constant)]=value2
由于Ognl解析引擎是这样处理的,每个括号对应语法树上的一个分支,并且从最右边的叶子节点开始解析执行。对于value1=(expression1)(constant)会执行value1=expression1。对于z[(expression2)(constant)]=value2则执行expression2=value2。对于本payload,expression2foo,已经赋值为ognl表达式,因此成功将ongl表达式带入参数key,变相绕过前方对参数名的合法校验,从而执行ongl表达式。
image.png

0x04 S2-013

漏洞影响

Struts 2.0.0 - Struts 2.3.14.1

漏洞分析

该漏洞同样与标签相关。Struts2 标签中 <s:a><s:url> 都包含一个 includeParams 属性,其值可设置为 none,get 或 all。<s:a>用来显示一个超链接,当includeParams=all的时候,会将本次请求的GET和POST参数都放在URL的GET参数上。在放置参数的过程中会将参数进行OGNL渲染,造成任意命令执行漏洞。
本次漏洞分析demo——index.jsp
image.png
与S2-001相同,按照doStartTag() ——> doEndTag()的顺序处理标签。
首先分析doStartTag()方法。
image.png
通过this.getBean获取该标签的component对象,这里<s:url>标签对应的是org.apache.struts2.components.URL对象,并进行初始化。然后经container.inject(this.component);调用对应component对象中经@Inject注解的方法,再通过this.component.start方法处理。
org.apache.struts2.components.URLstart方法中,
image.png
这里this.urlRendererincludeParams值被设置为all。这里其值也可设置为 none,get 。参考官方其对应意义如下:
1. none - 链接不包含请求的任意参数值(默认)
2. get - 链接只包含 GET 请求中的参数和其值
3. all - 链接包含 GET 和 POST 所有参数和其值
image.png
跟踪进入beforeRenderUrl()方法。通过mergeRequestParameters方法将请求参数放入parameters中。
image.png
补充一句,这里如果includeParams值被设置为get,也可通过this.includeGetParameters(urlComponent);方法将get请求的请求参数放入parameters中,从而触发漏洞。唯一不同是,all可添加post请求的参数,而get不可以。
接下来到了doEndTag()方法。进入org.apache.struts2.components.URLend方法。
image.png
跟进renderUrl ->determineActionURL ->buildURL,参数parameters为上面通过beforeRenderUrl()方法处理后得到的map。
image.png
继续跟进到org.apache.struts2.views.util.UrlHelper#buildParametersString()方法,将parameters中的key依次取出,再进入buildParametersString() -> translateAndEncode()
image.png
image.png
通过translateAndEncode()处理get请求的参数名,看到熟悉的translateVariable()方法,后面就是正常的ongl表达式解析流程了:translateVariable()->TextParseUtil.translateVariables()->translateVariables()->stack.findValue(var, asType)-> ...->Ognl.getValue()
image.png
调用链:
image.png
image.png
poc
${#_memberAccess["allowStaticMethodAccess"]=true,#a=@java.lang.Runtime@getRuntime().exec('open /System/Applications/Calculator.app')}

0x05 S2-016

漏洞描述(https://cwiki.apache.org/confluence/display/WW/S2-016

struts2中,DefaultActionMapper类支持以action:redirect:redirectAction:作为导航或是重定向前缀,但是这些前缀后面同时可以跟OGNL表达式,由于struts2没有对这些前缀做过滤,导致利用OGNL表达式调用java静态方法执行任意系统命令。

影响范围

Struts 2.0.0 - Struts 2.3.15

漏洞分析

org.apache.struts2.dispatcher.FilterDispatcherdoFilter方法拦截用户请求,doFilter创建值栈、上下文、包装request等一些初始化操作,然后进入DefaultActionMappergetMapping()方法。
image.png
image.png
通过uri解析对应action的namespaceactionname等配置信息。再进入handleSpecialParameters方法处理请求参数。在handleSpecialParameters中通过parameterAction.execute(key, mapping);进入DefaultActionMapper处理相应逻辑。
image.png
redirect.setLocation(key.substring("redirect:".length()));将请求参数redirect:后的内容设置为location,此时key值为redirect:ognl表达式,即ognl表达式被设置为location,然后将该ServletRedirectResult类型且location为ognl表达式的redirect对象设置为该mappingResult。然后关注location的值是如何被执行的。 逻辑回到FilterDispatcherdoFilter方法,通过this.dispatcher.serviceAction进入DispatcherserviceAction方法继续。
image.png
通过mapping.getResult();获取该mappingresult对象,也就是上面的ServletRedirectResult类型且location为ognl表达式的redirect对象。进入ServletRedirectResultexecute方法,一直跟踪到org.apache.struts2.dispatcher.StrutsResultSupportexecute方法。可见通过conditionalParse()方法处理location,最后经TextParseUtil.translateVariables()处理,从而执行location中的ognl表达式。
image.png
image.png

0x06 s2-045

漏洞描述(https://cwiki.apache.org/confluence/display/WW/S2-045

在使用基于Jakarta插件的文件上传功能时,恶意用户可在上传文件时通过修改HTTP请求头中的Content-Type值来触发该漏洞,进而执行系统命令。

漏洞影响

Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

漏洞分析

在Struts2中,所有的请求都会经过FilterDispatcherdoFilter方法拦截处理,doFilter创建值栈、上下文、包装request等一些初始化操作。自 Struts 2.5 以上已经将这个换成了 StrutsPrepareAndExecuteFilter 。在StrutsPrepareAndExecuteFilterdoFilter方法中通过this.prepare.wrapRequest(request);处理请求,一直跟踪到org.apache.struts2.dispatcher.DispatcherwrapRequest()方法,该方法判断请求的ContentType是否包含multipart/form-data,如果包含,则进入new MultiPartRequestWrapper()进行初始化。
image.png
跟进MultiPartRequestWrapper的构造方法,在this.multi.parse(request, saveDir);解析request。其中this.multiJakartaMultiPartRequest类的对象,跟进该对象的parse方法,在处理恶意构造的Content-Type数据时抛出异常,通过buildErrorMessage方法处理异常信息。
image.png
跟进buildErrorMessage方法,通过LocalizedTextUtil.findText()处理包含异常信息,其中异常信息完整包含了恶意构造的ognl表达式。
image.png
后续流程com.opensymphony.xwork2.util.LocalizedTextUtil的findText()->getDefaultMessage()->TextParseUtil.translateVariables()方法执行表达式。

image.png
image.png
S2-045是在Content-Type值注入ognl表达式从而引起解析异常,S2-046则是在上传文件的Content-Disposition中的Filename参数存在空字节,在检查时抛出异常,从而进入buildErrorMessage()方法。实则S2-045S2-046漏洞原理相同。

0x07 s2-053

漏洞描述

Struts2在使用Freemarker模板引擎渲染用户输入时,渲染后的ognl表达式可被执行。

漏洞影响

Struts 2.0.1 - Struts 2.3.33, Struts 2.5 - Struts 2.5.10

漏洞分析

本漏洞实质和S2-001原理是一样的。都是在解析标签时在org.apache.struts2.components.UIBeanend()方法中通过evaluateParams()对标签中的值进行解析从而执行ongl表达式的。在S2-053漏洞场景中,是通过Freemarker模版引擎渲染用户输入数据。如本demo场景中:通过Freemarker渲染index.ftl文件。
image.png
index.ftl文件内容:
image.png
这里message对应IndexAction中的message
image.png
当submit用户输入的message时,经IndexActionsetMessage()处理后重新利用Freemarker渲染index.ftl文件。此时${message}渲染为用户输入的包含ognl表达式的内容。
也即<@s.hidden name="${message}"><!--@s.hidden-->的内容为<@s.hidden name="ognl表达式"><!--@s.hidden-->,由此后续与S2-001流程一样,org.apache.struts2.components.UIBean#end()->evaluateParams()->findString()->findValue()->com.opensymphony.xwork2.util.TextParseUtil#translateVariables()
image.png
poc:
%25%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23_memberAccess%3F%28%23_memberAccess%3D%23dm%29%3A%28%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ognlUtil.getExcludedClasses%28%29.clear%28%29%29.%28%23context.setMemberAccess%28%23dm%29%29%29%29.%28%23cmd%3D%27id%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27%2Fc%27%2C%23cmd%7D%3A%7B%27%2Fbin%2Fbash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew+java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%40org.apache.commons.io.IOUtils%40toString%28%23process.getInputStream%28%29%29%29%7D%0D%0A
image.png

0x08 s2-059

漏洞描述(https://cwiki.apache.org/confluence/display/WW/S2-059

Struts2 会对某些标签属性(比如id,其他属性有待寻找)的属性值进行二次表达式解析,当这些标签属性中包含了%{xx}xx用户可控,则在渲染该标签时可造成ognl表达式执行。

漏洞影响

Struts 2.0.0 - Struts 2.5.20

漏洞分析

漏洞分析场景:index.jsp
image.png
其中skillNameIndexAction的属性值
image.png
image.png
首先通过com.opensymphony.xwork2.interceptor.ParametersInterceptorsetParameters()方法设置对应的IndexActionskillName属性,即%{3*5}。然后进入jsp中的标签处理环节。处理<s:label id="%{skillName}" value="label test"/>时,与S2-001一样,首先通过org.apache.struts2.views.jsp.ComponentTagSupportdoStartTag()方法根据不同标签属性进行初始化处理。跟踪populateParams()方法。
image.png
本demo中标签属性是label,因此进入LabelTagpopulateParams()方法,随后进入org.apache.struts2.views.jsp.ui.AbstractUITagpopulateParams()方法。该方法对所有可能涉及到的属性,如namethemevalue等进行set处理。
image.png
跟进org.apache.struts2.components.UIBeansetId()方法,通过this.findString(id)对id进行第一次解析,结果为%{skillName},由此完成对this.id的初始化赋值。
image.png
然后进入org.apache.struts2.views.jsp.ComponentTagSupportdoEndTag()方法完成对标签的闭合处理。流程同s2-001,doEndTag()->UIBean.end()->UIBean.evaluateParams()
UIBean.evaluateParams()中进入populateComponentHtmlId()方法。通过findStringIfAltSyntax()id进行第二次解析。此时this.id经过上面处理已经是skillName的值,也就是%{3*5}
image.png
跟进findStringIfAltSyntax()this.altSyntax()默认为true,因此通过this.findString(expr)->findValue()->TextParseUtil.translateVariables('%', expr, this.stack);执行ognl表达式。
image.png
调用链如图:
image.png
image.png
命令回显:
poc:
%25%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23_memberAccess%3F%28%23_memberAccess%3D%23dm%29%3A%28%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ognlUtil.getExcludedClasses%28%29.clear%28%29%29.%28%23context.setMemberAccess%28%23dm%29%29%29%29.%28%23cmd%3D%27id%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27%2Fc%27%2C%23cmd%7D%3A%7B%27%2Fbin%2Fbash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew+java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%40org.apache.commons.io.IOUtils%40toString%28%23process.getInputStream%28%29%29%29%7D%0D%0A<sub>~</sub>~
image.png

0x09 S2-061和S2-062

S2-061和S2-062漏洞原理与S2-059相同,是对S2-059沙盒绕过。由于漏洞原理方面并无不同,这里不再阐述。

总结

大致对Struts2历史漏洞做了个汇总和梳理,在整理跟踪过程中发现,Struts2各种漏洞的触发大多在于各种拦截器、标签属性处理过程中。后续则是对沙盒的各种绕过,譬如S2-061和S2-062。

评论

r0fus0d 2022-05-10 11:41:33

挺全的,学习了

素十八 2022-05-11 11:38:42

借楼,我之前也写了一篇,学习 Struts2 的伙伴可以结合着看下

https://su18.org/post/struts2-5/

草莓 2022-06-02 20:55:17

好像缺一个052

abcdefg1234

这个人很懒,没有留下任何介绍

twitter weibo github wechat

随机分类

企业安全 文章:40 篇
PHP安全 文章:45 篇
Ruby安全 文章:2 篇
渗透测试 文章:154 篇
SQL注入 文章:39 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录