Scanning firewalls for differences in IPv4 and IPv6 rules
Both IPv4 and IPv6 firewalls require separate rule sets and thus more administrative work. I scanned Alexa's Top 1M websites and tried to spot differences in the firewall's configurations.
The questions I had on my mind were:
- Are there administrators who configured iptables, but not ip6tables?
- Can I spot differences in their configurations?
Preparations
I thought that the Alexa Top 1M websites dataset would be suitable for answering the questions. Let's download the dataset and extract all domains from it:
$> wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip
$> unzip -p top-1m.csv.zip top-1m.csv | cut -d"," -f 2 > domains.txt
The next step was to request all A
(IPv4) and AAAA
(IPv6) addresses and their corresponding PTR
records. PTR records are "reversed" A/AAAA records. Usually a name resolves to an ip address, but the ptr record resolves an IP address to a hostname. Additionally, all domains which had less than one IPv4 or IPv6 address were removed.
#!/usr/bin/python
import dns.resolver
import dns.query
import dns.zone
import json
from IPy import IP
from multiprocessing import Pool
INPUTFILE = "domains.txt"
OUTPUTFILE = "dns-entries.json"
LOGFILE = "dns-reqs.log"
PROCESSES = 20
def get_host(ip):
ip = ip.strip()
try:
ptr_query = dns.resolver.query(ip,'PTR')
for p in ptr_query.rrset:
ptr = str(p)[:-1]
if ptr is None or ptr == "":
continue
return ptr
except:
pass
return None
def get_v4_v6(domain):
domain = domain.strip()
dns_data = {
'domain': domain,
'v4': [],
'v4_rev': [],
'v4_host': [],
'v6': [],
'v6_rev': [],
'v6_host': []
}
try:
a_query = dns.resolver.query(domain,'A')
for a in a_query.rrset:
a_ip = str(a)[:-1]
if a_ip is None or a_ip == "":
continue
dns_data['v4'].append(a_ip)
rev = reverseName(a_ip)
dns_data['v4_rev'].append(rev)
dns_data['v4_host'].append(get_host(rev))
except Exception as e:
pass
try:
aaaa_query = dns.resolver.query(domain,'AAAA')
for aaaa in aaaa_query.rrset:
aaaa_ip = str(aaaa)[:-1]
if aaaa_ip is None or aaaa_ip == "":
continue
dns_data['v6'].append(aaaa_ip)
rev = reverseName(aaaa_ip)
dns_data['v6_rev'].append(rev)
dns_data['v6_host'].append(get_host(rev))
except Exception as e:
pass
if len(dns_data['v4']) < 1 or len(dns_data['v6']) < 1:
dns_data = None
LOGFILE.write("Finished: " + domain + ":" + json.dumps(dns_data) +"\n")
LOGFILE.flush()
return dns_data
def reverseName(ip):
ip = IP(ip)
return ip.reverseName()[:-1]
def main():
global PROCESSES, LOGFILE, OUTPUTFILE, INPUTFILE
domains = open(INPUTFILE, "r").readlines()
OUTPUTFILE = open(OUTPUTFILE, "w")
LOGFILE = open(LOGFILE, "w")
pool = Pool(processes=PROCESSES)
data = pool.map(get_v4_v6, domains)
data = filter(lambda x: x != None, data)
OUTPUTFILE.write(json.dumps(data))
OUTPUTFILE.flush()
if __name__ == '__main__':
main()
I had to wait a couple of hours for this process to finish and I hope that my dns servers didn't blacklist me and/or drop my queries.
The first thing that surprised me was that only a merely 56k domains have an AAAA
entry / IPv6 address.
>>> import json
>>> d = json.load(open('dns-entries.json'))
>>> len(d)
56722
Anyway, the number will shrink far more after applying the following criteria:
- Less than one PTR record (hostname) for IPv4 / IPv6 address
- All hostnames are not
None
- IPv4 and IPv6 hostnames match.
I decided to use the last criterion, because I wanted to make sure that I will scan the firewall of the same host. I assumed that if the PTR records for a IPv4/IPv6 address match that both IPs point to the same system. Otherwise the firewall results wouldn't be comparable, because IPv4 and IPv6 isn't handled by the same server.
The following python script applied the criteria and created a new dataset:
import json
data = json.load(open("dns-entries.json","r"))
data_not_empty = filter(lambda x: len(x['v4_host']) > 0 and len(x['v6_host']) > 0, data)
data_not_none = filter(lambda x : 'None' not in ''.join(map(str,x['v6_host'])) and 'None' not in ''.join(map(str,x['v4_host'])), data_not_empty)
data_same_host = filter(lambda x: x['v6_host'] == x['v4_host'], data_not_none)
f = open("dns-entries-cleaned.json","w")
f.write(json.dumps(data_same_host))
f.close()
We're left with a total of 387
sites.
Running nmap
Now I wanted to run nmap against the IPv4 and IPv6 address. I first tried to use the python-nmap plugin, but it didn't work within a process pool, so I used a simple subproces.Popen()
. Another issue was IPv6 connectivity, because my ISP does not provide it. I used a DigitalOcean.com VM to fix this. They're also fine with nmap scans as long as you take responsibility for incoming abuse mails:
Port scanning is not explicitly against our ToS, however you are responsible for any abuse complaints filed against your account that are sent in by other administrators.
[...]
I'd say that we use common sense and normally a single complaint wouldn't get your service suspended. However the details and specific situation can change the outcome. I think we're very fair and we try our best to be as accommodating as possible.
DigitalOcean Support
I first started with a -sT -sU
scan, but that took ages, so I aborted it and decided to go with the -F
(fast scan) option.
import subprocess
import json
def scan_v4(domain):
ipv4 = domain['v4'][0]
scan = subprocess.Popen("nmap -PN -oG - -F " + ipv4, shell=True)
domain['v4_nmap'] = scan
return domain
def scan_v6(domain):
ipv6 = domain['v6'][0]
scan = subprocess.Popen("nmap -6 -PN -oG - -F " + ipv6, shell=True)
domain['v6_nmap'] = scan
return domain
data = json.load(open("dns-entries-cleaned.json","r"))
new_data = []
for item in data:
item = scan_v4(item)
item = scan_v6(item)
new_data.append(item)
with open("scan-data-fast.json","w") as f:
f.write(json.dumps(new_data))
So all we have now is a json file with nmap scans for the IPv4/IPv6 firewalls of a host.
Results
Let's have a look at the results. Here comes one last python script to parse the open ports from the nmap scans and remove sites where both scans have the same results.
import json
import re
d = json.load(open("scan-data-fast.json"))
RE_OPEN_PORTS = re.compile("(\d+)/open")
def get_open_ports(nmap_scan):
p = []
for m in RE_OPEN_PORTS.findall(nmap_scan):
p.append(int(m))
return p
def parse_nmap(item):
item['v4_ports'] = get_open_ports(item['v4_nmap'])
item['v6_ports'] = get_open_ports(item['v6_nmap'])
return item
d = map(parse_nmap, d)
d = filter(lambda x: x['v4_ports'] != x['v6_ports'], d)
print len(d)
for item in d:
print "===== {} ====".format(item['domain'])
print get_open_ports(item['v4_nmap'])
print get_open_ports(item['v6_nmap'])
Here are some things that caught my eye and are going to answer the second question (Can I spot differences in their configurations?): There are indeed differences in the firewall configurations.
The output below has open IPv4 ports in the first row and IPv6 in the second.
No anti-portscan protection
Some hosts seem to use some kind of anti-portscan techniques (all ports reported as open), but this isn't configured for the IPv6 firewall.
[7, 9, 13, 21, 22, 23, 25, 26, 37, 53, 79, 80, 81, 88, 106, 110, 111, 113, 119, 135, 139, 143, 144, 179, 199, 389, 427, 443, 444, 465, 513, 514, 515, 543, 544, 548, 554, 587, 631, 646, 873, 990, 993, 995, 1025, 1026, 1027, 1028, 1029, 1110, 1433, 1720, 1723, 1755, 1900, 2000, 2001, 2049, 2121, 2717, 3000, 3128, 3306, 3389, 3986, 4899, 5000, 5009, 5051, 5060, 5101, 5190, 5357, 5432, 5631, 5666, 5800, 5900, 6000, 6001, 6646, 7070, 8000, 8008, 8009, 8080, 8081, 8443, 8888, 9100, 9999, 10000, 32768, 49152, 49153, 49154, 49155, 49156, 49157]
[22, 80, 81, 443]
So nmap'ing the IPv6 address is quite useful in this scenario.
No open IPv6 ports
It looks like some hosts have an IPv6 address, but block all incoming traffic. So where's the point in adding it to the DNS in the first place?
[21, 22, 25, 80, 110, 143, 443, 465, 993, 995]
[]
or
[80, 81, 443]
[]
Not all IPv6 ports open
Either some services can't handle IPv6 and/or the ports are closed for the IPv6 network.
[21, 22, 25, 80, 443]
[21, 80, 443]
Conclusion
To my surprise it looks like the answer to the first question (Are there administrators who configured iptables, but not ip6tables?) is "No".
Nevertheless, it's interesting to see that there are differences between the configurations of the IPv4/IPv6 firewalls. However, my nmap scan results base on the limited set of ports ( -F
) and a very limited set of test systems, so maybe one can find more interesting stuff by analyzing more systems in more detail.
Update:
It looks like my colleague Patrik did similar research with similar results. I recommend watching his talk, although the audio is a bit messed up :(
-=-