Categories
http scripting web security

Python script to check for vulnerable printers

People often overlook printers when it comes to information security. Truth is that a ton of useful information can be found in printers. Employees will often scan sensitive documents such as social security cards, loan information, birth certificates, etc. I’ve also seen important organizational information on printers such as internal memos between higher up executives. The documents I’ve seen in the past were never meant to be shared but a default printer will more than happily share your sensitive information. Almost any new commercial printer will come with a ton of features to store and retrieve any documentation that flows through the printer (copy, scan, and print jobs). Almost all of these new printers also give you a web interface to retrieve that documentation, an example of a printer’s web interface can be seen here. When I’m performing a penetration test I always go for the web interface of a printer, the web interface is where I can grab all the sensitive information. These printers usually get unboxed and plugged into the network without much configuration from the default state, this means that the web interface is wide open with default usernames and passwords. Usually admin access to these printers will give you more access and it’s this admin access that I check for.

When you’ve only got a limited amount of time during a penetration test you want to get the best bang for your buck so I created a python script that will go and check for default usernames and passwords on certain models of printers. Below is the python script.

import urllib2
import sys

target = open(sys.argv[1])
eachIPinList = target.readlines(); target.close()
output = open(sys.argv[2], 'w')

for string in eachIPinList:
  try:
    print 'Trying ' + string.rstrip()

    theurl = 'http://' + string.rstrip() + '/index.html'
    username = 'root'
    password = ''

    passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
    passman.add_password(None, theurl, username, password)
    authhandler =  urllib2.HTTPBasicAuthHandler(passman)
    opener = urllib2.build_opener(authhandler)
    urllib2.install_opener(opener)
    pagehandle =  urllib2.urlopen(theurl)
    if pagehandle.getcode() == 200:
      output.write(string)
  except:
    pass

Usage:  at the command line type the following

python nameOfScript.py IPlist.txt output.txt

So this script takes two arguments, 1) A list of IP’s you’ll want to test against, 2) Name of an output file where successful attempts are logged. If you’re having troubles running the script read my other post about running a python script. The output.txt will contain a list of IP’s that the script was able to log into. There are three variables that you’ll have to modify for your particular printer model that you are trying to scan for on your network, they are listed below.

theurl = 'http://' + string.rstrip()  + '/index.html'
username = 'root'
password = ''

Username and password variables should be obvious, simply put in the default username and password of the printer on your network. The only thing you’ll have to change in ‘theurl’ variable is the last quoted string. In my case it was ‘/index.html’, in your case it could be ‘/auth/login.html’. Variable ‘theurl’ builds the http request used to log into your printer’s web interface. A full example is below.

http://192.168.1.5/index.html

This script is doing nothing more than trying to log into the web interface of a printer, that’s it. So the script is not limited to printers, it can be used against any web application that takes a username and password. Although this script can be used against any web application there is a limitation.  This script authenticates to the printer using Basic Access Authentication. There are three main ways to authenticate to a web application.

  1. HTTP Basic Access Authentication
  2. HTTP Digest Access Authentication
  3. HTML Form-based Authentication

So this script will not work if your web application (printer in this case) is using the second or third option. How would you know which one your printer or web application is using? Turns out OWASP has a nice write up on how to test which type of authentication your web application is using. Turns out that no one really uses one and two because they are not as secure as HTML Form-based Authentication wrapped inside SSL. Of course some printers use Basic Authentication because they are poorly built. Basic Authentication actually passes your username and password essentially in plaintext, the only way it tries to hide your username and password is by base64 encoding them which is easily transformed back into plaintext. I don’t want to get lost in the weeds to much but just knowing that your printer is using Basic Authentication is bad enough. Even if you set a strong username and password anyone sniffing network traffic would be able to determine your credentials.

I kicked this script over to Dave Huggins who has tons of experience developing Python applications and he quickly improved upon it by adding the functionality of IP ranges instead of a file. His enhancements can be seen below.

def IPRange(octets, func=""):
  if func == "":
    def func():
      pass

  octets = (octets.split('.'))
  ranges = []
  loop = 0
  for octet in octets:
    if octet.find('-') != -1:
      spot = octet.find('-') + 1
      octets[loop] = int(octet[:octet.find('-')])
      ranges.append(int(octet[spot:]) + 1)
    else:
      octets[loop] = int(octet)
      ranges.append(int(octet) + 1)
      loop += 1
  CurrentAddress = ""
  loop = 0
  output = []
  for one in range(octets[0], ranges[0]):
    for two in range(octets[1], ranges[1]):
      for three in range(octets[2], ranges[2]):
        for four in range(octets[3], ranges[3]):
          for item in (one, two, three, four):
            CurrentAddress += str \
                ((one, two, three, four)[loop]) + "."
              loop += 1
          CurrentAddress = CurrentAddress[:-1]
          output.append(func(CurrentAddress))
          CurrentAddress = ""
          loop = 0
  return output

if __name__ == '__main__':
  import os, sys, urllib2

  def defaultPrinter(ipAddress):
    try:
      print 'Trying ' + ipAddress
      theurl = 'http://' + ipAddress + '/indexConf.html'
      username = 'root'
      password = ''

      passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
      passman.add_password(None, theurl, username, password)
      authhandler =  urllib2.HTTPBasicAuthHandler(passman)
      opener = urllib2.build_opener(authhandler)
      urllib2.install_opener(opener)
      pagehandle =  urllib2.urlopen(theurl)
      if pagehandle.getcode() == 200:
        output.write(ipAddress)
    except:
      pass

  output = open(sys.argv[2], 'w')
  IPRange(sys.argv[1], defaultPrinter)

Happy printer hunting.

Categories
scripting windows

Search an IP range via the command line

So how do you manipulate a list of IP’s via the command line? Well there are several ways to go about this but I’ll present the way I went about it.

In my scenario I had a range of IP’s that I needed to extract/exclude out of a list of IP’s. This task needed to be done on a Windoze machine, I do most of my scripting on a Linux box, so I was trying to rely on the findstr command. Trying to use the findstr command to search, extract, or manipulate a list of IP’s will make you crazy. Now I’m sure there’s way smarter people out there that can craft a simple one line findstr command to hack and slash on an IP list but I’m not one of those people. I also tried to utilize some regular expression magic to manipulate an IP range. Google has this regular expression generator specifically for IP ranges, which seems neat at first but I couldn’t get it to work within findstr.

After no luck with findstr I was gonna turn to my old friend grep. Now for those that don’t know grep is a pattern / regular expression matching command within Linux. Grep has the ability to search for patterns within directories and files for a specific string (e.g. IP addresses). There is a grep Windows executable with basically the same functionality but it couldn’t handle Google’s regular expression either. After burning through two different programs to perform this task I was almost at a lost. My coworker reminded me of awk, how could I forget. Awk is a native program within Linux but you can download an exe version of the program. There are different flavors of awk (gawk and mawk) and different programmers that try and port over awk. I tried some awk.exe’s and some gawk.exe’s but I had the best success with mawk.exe, you can grab mawk.exe here. So enough yip yapping let’s walk through the solution. Below is a sample list of IP’s that we’ll hack and slash on, let’s assume these IP’s are in a file called IPlist.txt.

192.168.0.1
192.168.0.2
192.168.0.3
192.168.0.4
192.168.0.5
192.168.0.6
192.168.0.7
192.168.0.8
192.168.0.9
192.168.0.10
192.168.0.11
192.168.0.12
192.168.0.13
192.168.0.14
192.168.0.15
192.168.0.16
192.168.0.17
192.168.0.18
192.168.0.19
192.168.0.20
192.168.5.1
192.168.5.2
192.168.5.3
192.168.5.4
192.168.5.5
192.168.5.6
192.168.5.7
192.168.5.8
192.168.5.9
192.168.5.10
192.168.5.11
192.168.5.12
192.168.5.13
192.168.5.14
192.168.5.15
192.168.5.16
192.168.5.17
192.168.5.18
192.168.5.19
192.168.5.20

So let’s say we wanted to extract or exclude the range 192.168.0.5-192.168.0.15, you would use the mawk command below.

mawk "BEGIN {FS='.'}; $3<0 || $3>0 || ($3==0 &&($4<5 || $4>15)) {print $0}" IPlist.txt

Let me explain the command above. BEGIN simply processes the text before mawk starts munching. FS stands for field separator, here we are telling mawk that our filed separator is period (surrounded by single quotes). The $3 is basically a variable calling the 3rd field, in our case it’s the third number in our IP address. The || means “or”. The == is to determine is something is equivalent. The && is “and”. The $4 is the 4th number in our IP address because it’s the 4th field. So the command reads like this: separator is a period, we want the 3rd number to be less than zero or greater than zero or equal to 3 and we want the 4th number to be less than 5 or greater than 15. The $0 represents the entire line so the print statement is just printing out the entire line that matches our criteria. Let’s look at a similar example, say we want to extract 192.168.5.10-18.

mawk "BEGIN {FS='.'}; $3<5 || $3>5 || ($3==5 &&($4<10 || $4>18)) {print $0}" IPlist.txt

I’m sure there are probably other ways to go about performing the same task but this one works for me. Now feel free to go ahead and mawk it out.