Skip to content

Instantly share code, notes, and snippets.

@moloch--
Last active September 30, 2022 09:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save moloch--/12556a4b0a0a337831bb to your computer and use it in GitHub Desktop.
Save moloch--/12556a4b0a0a337831bb to your computer and use it in GitHub Desktop.
Check SPF Records for Expired Domains
#!/usr/bin/env python
import dns.resolver
import dns.name
#import netaddr
from urlparse import urlparse
INFO = "\033[1m\033[36m[*]\033[0m "
WARN = "\033[1m\033[31m[!]\033[0m "
LOW = "\033[1m\033[34m[-]\033[0m "
MAYBE = "\033[1m\033[35m[?]\033[0m "
MONEY = "\033[1m\033[38m[$]\033[0m "
class SPFRecord(object):
def __init__(self, domain):
self.version = None
self.includes = []
self.ip4 = []
self.ip6 = []
self._dns_response = dns.resolver.query(domain, 'TXT')
self.txt_records = [txt.to_text() for txt in self._dns_response]
for txt in self.txt_records:
self._parse_txt(txt)
def _parse_txt(self, txt):
''' Parses a raw txt record '''
for entry in txt.split(' '):
if entry.startswith('v') and '=' in entry:
self._add_version(entry)
elif entry.startswith('include') and ':' in entry:
self._add_include(entry)
elif entry.startswith('ip4') and ':' in entry:
self._add_ip4(entry)
elif entry.startswith('ip6') and ':' in entry:
self._add_ip6(entry)
@property
def ips(self):
return self.ip4 + self.ip6
def _add_version(self, entry):
self.version = entry.split('=')[1]
def _add_include(self, entry):
self.includes.append(entry.split(':')[1])
def _add_ip4(self, entry):
ip = entry.split(':')[1]
self.ip4.append(ip)
def _add_ip6(self, entry):
ip = entry.split(':')[1]
self.ip6.append(ip)
def is_expired(domain):
try:
dns.resolver.query(domain)
return False
except dns.resolver.NXDOMAIN:
return True
except dns.resolver.NoAnswer:
return False
def get_spf_record(domain):
if is_expired(domain):
print WARN + "%s does not resolve" % domain
return None
print INFO + domain
try:
return SPFRecord(domain)
except dns.resolver.NoAnswer:
print '\t' + LOW + 'No TXT record for', domain
except dns.exception.Timeout:
print '\t' + WARN + "DNS timeout for", domain
except dns.resolver.NoNameservers:
print '\t' + WARN + "No name servers were found for", domain
except:
print '\t' + WARN + "Something went wrong ..."
return None
def check_spf(spf, domain):
for inc_domain in spf.includes:
try:
url = urlparse("mail://%s" % inc_domain).netloc
parent = '.'.join(url.split('.')[-2:])
if is_expired(parent):
print '\t' + MONEY,
print "%s's parent domain \"%s\" is not regiestered" % (
inc_domain, parent)
else:
print '\t' + LOW + \
"%s's parent domain is registered" % inc_domain
except dns.resolver.NoAnswer:
print '\t' + MAYBE + 'No answer for lookup of', inc_domain
except dns.exception.Timeout:
print '\t' + WARN + "DNS timeout for", parent
except dns.resolver.NoNameservers:
print '\t' + WARN + "No name servers were found for", parent
if __name__ == '__main__':
import os
import argparse
def read_files(files):
domains = []
for f in files:
if os.path.exists(f) and os.path.isfile(f):
with open(f, 'r') as fp:
domains += [line.split(',')[1].strip() for line in fp.readlines()]
else:
print WARN + "File does not exist", f
return domains
parser = argparse.ArgumentParser(
description='Check domains for expired SPF records',
)
parser.add_argument('--version',
action='version',
version='%(prog)s v0.0.1'
)
parser.add_argument('--domains', '-d',
help='domains to check',
dest='domains',
nargs='*',
)
parser.add_argument('--csv', '-c',
help='read domains from csv file(s)',
dest='files',
nargs='*',
)
args = parser.parse_args()
_domains = []
if args.files:
_domains += read_files(args.files)
if args.domains:
_domains += args.domains
for domain in _domains:
spf = get_spf_record(domain)
if spf is not None:
check_spf(spf, domain)
@TheRook
Copy link

TheRook commented Dec 16, 2014

It won't let me add a pull request. I forked this script and added recursion to enumerate the entire SPF record:
https://gist.github.com/TheRook/95f2b872bdc81bac2371

@LAKostis
Copy link

I've updated TheRook version, now with python3 support and other features and fixes:
https://gist.github.com/LAKostis/1993082b9cb153ad0f3452f7893ccf73

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment