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('22.214.171.124') >>> ip IPAddress('126.96.36.199') >>> 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
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) except: print "***** ERROR *****: " + splitRange endRange = splitRange try: int(endRange) if int(endRange) <= 255: endNet = str(IPNetwork(str(startIP).strip() + '/24').network) endIP = endNet.rsplit(".", 1) + "." + endRange else: print "***** ERROR *****: " + endRange except ValueError: try: endIP = IPAddress(endRange) except: print "***** ERROR *****: " + splitRange 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(".") + "-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.
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.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.