IpExpander v1.0 – Using Python netaddr to Expand IP Ranges

I know that I haven't released anything in a while, but I'd like to introduce IpExpander v1.0.

IpExpander - Introduction

The need for IpExpander arose from the fact that clients will use different conventions when submitting IP addresses that are in scope for engagements. Sometimes I will get a list of individual IPs, sometimes the file has both IPs and CIDRs, and sometimes it is a "range" (i.e.: 192.168.0.1-165). While most tools can handle these various formats, some cannot. In these situations, I've found it useful to just have a list of individual IP addresses to prevent issues.

Python netaddr and Installation

First, I tried to see if there were any open source libraries that would solve this problem for me.

The netaddr library came pretty close, and supports everything but the ranges that I was dealing with.

In this case, I decided to install the module and use it as the basis for my tool.

Rays-MacBook-Pro:Documents doyler$ pip install netaddr
Collecting netaddr
  Downloading netaddr-0.7.19-py2.py3-none-any.whl (1.6MB)
    100% |################################| 1.6MB 423kB/s
Installing collected packages: netaddr
Successfully installed netaddr-0.7.19

Next, I read through the tutorial to understand the library and what I could or couldn't do with it.

Testing the Library

With the library installed, I decided to play around with it a bit to get a better feel for it.

First, I worked out what was acceptable as an IP address.

Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec  5 2015, 20:32:19) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> from netaddr import *
import pprint
>>> ip = IPAddress('192.0.5.1')
>>> ip
IPAddress('192.0.5.1')
>>> ip2 = IPAddress('192.168.5.0/24')

Traceback (most recent call last):
  File "", line 1, in 
    ip2 = IPAddress('192.168.5.0/24')
  File "C:\Python27\Lib\site-packages\netaddr\ip\__init__.py", line 280, in __init__
    % self.__class__.__name__)
ValueError: IPAddress() does not support netmasks or subnet prefixes! See documentation for details.

Next, I used the list functionality to verify that it would handle CIDR addresses as expected.

>>> ip_list = list(IPNetwork('192.168.5.0/24'))
>>> ip_list
[IPAddress('192.168.5.0'), IPAddress('192.168.5.1'), IPAddress('192.168.5.2'), IPAddress('192.168.5.3'), IPAddress('192.168.5.4'), IPAddress('192.168.5.5'), IPAddress('192.168.5.6'), IPAddress('192.168.5.7'), IPAddress('192.168.5.8'), IPAddress('192.168.5.9'), IPAddress('192.168.5.10'), IPAddress('192.168.5.11'), IPAddress('192.168.5.12'), IPAddress('192.168.5.13'), IPAddress('192.168.5.14'), IPAddress('192.168.5.15') ...

Finally, I tried to see if it would support my hyphenated ranges out of the box.

>>> ip_list = list(IPNetwork('192.168.5.1-100'))

Traceback (most recent call last):
  File "", line 1, in 
    ip_list = list(IPNetwork('192.168.5.1-100'))
  File "C:\Python27\Lib\site-packages\netaddr\ip\__init__.py", line 938, in __init__
    raise AddrFormatError('invalid IPNetwork %s' % addr)
AddrFormatError: invalid IPNetwork 192.168.5.1-100

The Code

With a better understanding for the library, I wrote up my IpExpander script.

This will take in an input file of IP addresses, CIDRs, and ranges and output a file with one IP address per line.

Note that it will only support ranges where the last octet has a hyphen, and not any others yet.

from netaddr import *
import pprint

def isIP(inLine):
    try:
        ip = IPAddress(inLine)
    except:
        return False
    return True

def isCIDR(inLine):
    try:
        ip_list = IPNetwork(inLine)
    except:
        return False
    return True

def expandCIDR(inCIDR):
    return list(IPNetwork(inCIDR))

def isRange(inLine):
    if "-" in inLine:
        return True
    else:
        return False

def expandRange(inRange):
    splitRange = inRange.split("-")
    try:
        startIP = IPAddress(splitRange[0])
    except:
        print "***** ERROR *****: " + splitRange[0]

    endRange = splitRange[1]

    try:
        int(endRange)
        if int(endRange) <= 255:
            endNet = str(IPNetwork(str(startIP).strip() + '/24').network)
            endIP = endNet.rsplit(".", 1)[0] + "." + endRange    
        else:
            print "***** ERROR *****: " + endRange
    except ValueError:
        try:
            endIP = IPAddress(endRange)
        except:
            print "***** ERROR *****: " + splitRange[0]

    return list(iter_iprange(startIP, endIP))

def getTargets(inFile):
    with open(inFile) as f:
        lines = f.readlines()
    return lines

def newFile(outList, fileName):
    with open(fileName.split(".")[0] + "-expanded.txt", "w") as text_file:
        for addr in outList:
            text_file.write(str(addr) + "\n")

def main():
    inFile = "external-targets.txt"
    lines = getTargets(inFile)

    finalList = []

    for line in lines:
        line = line.strip()
        if isIP(line):
            finalList.append(IPAddress(line))
        elif isCIDR(line):
            expanded = expandCIDR(line)
            finalList.extend(expanded)
        elif isRange(line):
            expanded = expandRange(line)
            finalList.extend(expanded)
        else:
            print "***** ERROR *****: " + line

    newFile(sorted(finalList), inFile)

if __name__ == '__main__':
    main()

When I tested the script on my engagement, it worked, and I expanded my target list to 385 individual IP addresses!

Rays-MacBook-Pro:Documents doyler$ wc -l external-targets.txt
     47 external-targets.txt
Rays-MacBook-Pro:Documents doyler$ python ipExpander.py 
Rays-MacBook-Pro:Documents doyler$ wc -l external-targets-expanded.txt 
     385 external-targets-expanded.txt

IpExpander - Conclusion

Let me know if you have any questions, comments, suggestions, or ideas!

For now, I know that I'd like to have v1.1 support even crazier ranges (192.168.0-5.10-45).

Finally, you can find the code and updates in my GitHub repository.

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Principal Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.