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.: 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('')
>>> ip
>>> ip2 = IPAddress('')

Traceback (most recent call last):
  File "", line 1, in 
    ip2 = IPAddress('')
  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(''))
>>> ip_list
[IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress(''), IPAddress('') ...

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

>>> ip_list = list(IPNetwork(''))

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

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):
        ip = IPAddress(inLine)
        return False
    return True

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

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

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

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

    endRange = splitRange[1]

        if int(endRange) <= 255:
            endNet = str(IPNetwork(str(startIP).strip() + '/24').network)
            endIP = endNet.rsplit(".", 1)[0] + "." + endRange    
            print "***** ERROR *****: " + endRange
    except ValueError:
            endIP = IPAddress(endRange)
            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):
        elif isCIDR(line):
            expanded = expandCIDR(line)
        elif isRange(line):
            expanded = expandRange(line)
            print "***** ERROR *****: " + line

    newFile(sorted(finalList), inFile)

if __name__ == '__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
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 Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.

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.

As an Amazon Associate I earn from qualifying purchases.

Common passed on this blog, I made it to a jam.

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.