When scoping a penetration test, it's common that I'll receive a list of target IP addresses in use. Sometimes this is in the form of CIDR masks:
~ $ head -5 targetnetworks.txt 15.230.56.104/31 52.93.127.163/32 3.2.0.0/24 15.230.137.0/24 52.4.0.0/14 ~ $ wc -l targetnetworks.txt 576 targetnetworks.txt
In this article we'll look at some ways to count the number of IP addresses in a list of CIDR mask netblocks. You can download the targetnetworks.txt file if you want to follow along.
The easy way to count the number of IP addresses in this list is to use Nmap. First, I'll create an excerpt of the targetnetworks.txt
file called shortlist.txt
:
~ $ head -4 targetnetworks.txt > shortlist.txtUsing Nmap, we can disable name resolution (
-n
), and use the list scan (-sL
) feature to list the hosts that Nmap will scan, reading the list of hosts from the specified input file (-iL
). Sending the output to wc -l
allows us to count the number of lines:~ $ nmap -n -sL -iL shortlist.txt | head Starting Nmap 7.91 ( https://nmap.org ) at 2021-02-18 09:10 EST Nmap scan report for 15.230.56.104 Nmap scan report for 15.230.56.105 Nmap scan report for 52.93.127.163 Nmap scan report for 3.2.0.0 Nmap scan report for 3.2.0.1 Nmap scan report for 3.2.0.2 Nmap scan report for 3.2.0.3 Nmap scan report for 3.2.0.4 Nmap scan report for 3.2.0.5 ~ $ nmap -n -sL -iL shortlist.txt | wc -l 517
Nmap will include the opening Starting Nmap line, and an Nmap done line at the end, so we subtract two from the number of lines to get the number of IP addresses. This is probably the most common method to count the number of IP addresses in a list of CIDR masks, but it is less than ideal since it is very slow:
~ $ time (nmap -n -sL -iL targetnetworks.txt | wc -l) 32723890 real 2m49.440s user 2m32.900s sys 1m16.203s
I wanted to find a faster way to do this, so I put together a quick Python script. With Python 3.3 and later we have the ipaddress module, which includes the IPv4Network module to expand a CIDR mask into a generator of IP addresses:
~ $ python Python 3.9.1 (default, Feb 1 2021, 20:42:01) [Clang 12.0.0 (clang-1200.0.32.29)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import ipaddress >>> ipaddress.IPv4Network("192.168.1.0/29") IPv4Network('192.168.1.0/29') >>> [str(ip) for ip in ipaddress.IPv4Network("192.168.1.0/29")] ['192.168.1.0', '192.168.1.1', '192.168.1.2', '192.168.1.3', '192.168.1.4', '192.168.1.5', '192.168.1.6', '192.168.1.7']
Using ipaddress.IPv4Network, I created a little program to read the file with network numbers and CIDR masks, convert the generator into a list, and tally the number of entries:
~ $ cat countips.py #!/usr/bin/env python import sys, ipaddress def countips(netblock): iplist = [str(ip) for ip in ipaddress.IPv4Network(netblock.rstrip())] return len(iplist) if (len(sys.argv) != 2): print(f"Usage: {sys.argv[0]} <file with CIDR masks>") sys.exit(0) ipcount=0 with open(sys.argv[1]) as infile: for netblock in infile: ipcount += countips(netblock) print(ipcount) ~ $ python countips.py shortlist.txt 515
This works well, but it suffers from a similar fate as the Nmap example (it is slow):
~ $ time python countips.py targetnetworks.txt 32723888 real 1m7.475s user 1m6.485s sys 0m0.857s
The ipaddress
module isn't the issue here; the problem is my terrible use of list comprehension when converting the generator into a list for the purposes of counting the entries (e.g., the [str(ip) for ip in ipaddress.IPv4Network(netblock.rstrip())]
bit. Fortunately, there is a better way to do this.
Math!
We don't need to generate a list of IP addresses to get a count, we just need to calculate the host part of the CIDR mask, then expand it to identify the number of hosts that it represents.
When we have a CIDR mask of /24, we indicate that 24 bits are used for the network portion of the address, and 8 bits are used for the host portion of the address. To calculate the number of hosts, we subtract the mask value from 32, then use that as an exponent:
~ $ python Python 3.9.1 (default, Feb 1 2021, 20:42:01) [Clang 12.0.0 (clang-1200.0.32.29)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> cidr_mask=24 >>> 2**(32-cidr_mask) 256 >>> cidr_mask=16 >>> 2**(32-cidr_mask) 65536
A revised countips.py
script:
~ $ cat countips.py #!/usr/bin/env python import sys def countips(netblock): cidr = int(netblock.split('/')[1]) return 2**(32 - cidr) if (len(sys.argv) != 2): print(f"Usage: {sys.argv[0]} <file with CIDR masks>") sys.exit(0) ipcount=0 with open(sys.argv[1]) as infile: for netblock in infile: ipcount += countips(netblock) print(ipcount) ~ $ python countips.py shortlist.txt 515
This revised script performs much better:
~ $ time python countips.py targetnetworks.txt 32723888 real 0m0.046s user 0m0.029s sys 0m0.012s
A Soliloquy on Programming
I'm often asked if you need to know programming to be a good cyber security analyst. I believe the answer is no, but to be a great analyst, the answer is probably yes. This doesn't mean that you need to spend all day coding and be able to write your own RDBMS platform from scratch, but it does mean that sometimes you'll want to be able to modify or augment other code, or develop your own code to automate a task on your own. It takes time and practice, but I have found the ability to solve problems with code a worthwhile pursuit.
-Josh