Monday, June 30, 2014

python and ZMap


During the reconnaissance phase of network penetration testing engagements or black box web application testing, there will come a point when scanning all ports on a target network simply needs to be done. In the past I have relied on nmap for this task and indeed still do for aggressive scanning and to interrogate discovered services. More recently I have been testing and using ZMap to aid with service discovery, more specifically to determine if a port is open. ZMap has some distinct advantages over other networking scanning tools, mainly its speed; ZMap's claim to fame is that it can be used to scan the entire IPv4 address space in under 45 minutes... one port of the IPv4 space anyway. Doing so requires significant bandwidth resources such as gigabit Ethernet otherwise it may take a bit longer. At any rate (no pun intended) it does it's job quickly and quite efficiently.

While ZMap can come in handy during penetration testing it can also be quite useful from a blue team perspective. As the old adage goes, “the best defense is a good offense” using ZMap to scan your own corporate or private network can keep you apprised of what services are available externally. For the home user this could mean discovering services that your new router has available and turned on by default. For the corporate security professional this is your check and balance for all of the firewall rule requests that you see being submitted or are directly responsible for approving.

Given the scenarios above I found it useful to write a python script that would help with the task of network discovery. From an offensive perspective this script can be used to quickly identify open ports on a target network or host. From a defensive perspective this script can be used for the same thing; schedule it and validate the results with what you expect to see then remediate as necessary in terms of disabling firewall rules.

A couple of things to note as this is a beta release, you will need to identify your primary network interface and manually provide the interface name to the script. There are several enhancements in the pipeline, but for now it should work as long as:

  • You can run it as root
  • The correct interface is specified
  • And the python environment is adequate



The advantage of this script over simply using ZMap outright is the port range option and some useful features in coming releases. Below is the script (full lines and proper formatting are preserved even though it looks like it wraps around) and remember that this is in beta and I am not claiming to be a great python writer.

#!/bin/python
## SB 6-2014
##Use ZMap to scan range of ports on provided network
##65535 seconds = 18.204 hours
##You may need to change the python path above and make sure the script is executable
##Expect the script to create a new directory each time it's invoked
##On line 53, change "em1" to the name of your network interface (ifconfig -a)
##This program is distributed in the hope that it will be useful,
##but WITHOUT ANY WARRANTY; without even the implied warranty of
##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

##import modules
import subprocess
import os
import datetime
import zipfile
import argparse
parser = argparse.ArgumentParser(
prog="zmapScan",
description="Scan ports on specified network using ZMap",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=('''\
Example: ./zmapScan.py -sp 80 -ep 80 -r example.com - scan only port 80 on example.com
Example: ./zmapScan.py -sp 0 -ep 65535 -r example.com - scan all ports on example.com
Example: ./zmapScan.py -sp 1 -ep 1024 -r 1.2.3.4/29 - scan ports 1-1024 on a /29

tis but a scratch...
 
'''))
parser.add_argument("-sp", help="starting port *required", dest="sp", metavar="starting port", type=int, required=True, choices=range(0, 65535))
parser.add_argument("-ep", help="ending port *required", dest="ep", metavar="ending port", type=int, required=True, choices=range(0, 65535))
parser.add_argument("-r", help="ip/hostname/range *required", dest="r", metavar="ip/hostname/range", type=str, required=True)
parser.add_argument("-v", "--version", action="version", version="zmapScan version .63 (July 2014)")
results=parser.parse_args()


##get the date/time of right now
d = datetime.datetime.now()
##set a as the datetime like this: 06-26-2016_142003_941871
dirname = d.strftime('%m-%d-%Y_%H%M%S_%f')
port = 0


##Create a new directory everytime program is run
os.mkdir(dirname)
## cd into new directory
os.chdir(dirname)


##Begin ZMap work
##Have ZMap loop through ports and output one file for each port. Each file will include IPs that have that port open
for port in range (results.sp, results.ep+1):
    cmd = "sudo zmap -p{0} -o zmapoutput_{1} -B 100k -c 2 -v 0 -q -i em1 {2}".format(port, port, results.r)
    subprocess.call(cmd.split(), shell=False)
    print "Port %s complete" % port
##End ZMap work


##Begin clean-up of ZMap output (delete any empty files)
##Get current working directory
    folder = os.getcwd()
##Loop through every file that ZMap outputs
    for file in os.listdir(folder):
## Remove empty files. Files that have data will be retained since they are useful
        try:
            if os.stat(file)[6]==0:
                os.remove(file)
        except Exception, e:
            print e
##End clean-up of ZMap output

##Begin zip ZMap results
## zip up result files
zf = zipfile.ZipFile("ZmapResults.zip", "w")
for dirname, subdirs, files in os.walk("./"):
    zf.write(dirname)
    for filename in files:
        zf.write(os.path.join(dirname, filename))
zf.close()
##End zip ZMap results