Security automation with Python — DNS and Reverse DNS lookups
— Tutorial, Python, DNS and Reverse DNS lookups, Bulk DNS lookups, Bulk Reverse DNS lookups, Security Automation, Security Information Automation — 3 min read
I began my security automation journey looking for opportunities to solve practical problems with Python. So, I started by identifying areas of an analyst's workflow where automation could be most effective. One that was tedious, and better suited for automation. I landed on the task of performing DNS and Reverse DNS lookups.
Sure, this is typically a quick and easy task to perform. But, when the volume of domains or IP addresses increase this can become a time consuming task. Automating DNS lookups significantly accelerates the speed in which this task can be completed.
In this tutorial I'm going to create a script that can perform single entry as well as bulk DNS or Reverse DNS lookups with a user defined list.
PLEASE NOTE ... I've recently updated this Python script, and posted a revised version on my Github page called DNS-Lookups-v2. The updated version combines both DNS and Reverse DNS lookups into a single Python script that functions like a CLI tool to perform both DNS and Reverse DNS lookups from either a single entry or in bulk from a list as input. The script also provides an additional option to output the results to a text file.
Automating DNS Lookups
I'll start by creating a conditional statement to let the user choose between entering a single domain. Based on the user's input, either the dnsLookup() or dnsLookupList() function will be called.
# Prompt user for input type of a single domain or import a list of domainssingle_or_multiple_domains = int(input("Enter '1' for single entry or '2' to import a list of domain names: \n"))
# Conditional statement to handle user input for a single domain# or call the function to handle the imported list of domainsif single_or_multiple_domains == 1: dnsLookup(input("\nEnter Domain: "))else: dnsLookupList()
Next I'll create the dnsLookup function that will be performing the dns lookup.
While I could use Python's "gethostbyname" or "gethostbyaddress" to perform the lookups, the output is pretty lean. DNS lookups are considered a "Data Enrichment" task and should return as much information as possible. To enhance the results returned by the dns lookups I'm going to use "subprocess" to access the operating system and run the "host" command instead.
To do this, I'll need to import the subprocess module at the beginning of the script.
1import subprocess
Now that the subprocess module is loaded into the script I can build the dnsLookup() function that calls the "host" command.
def dnsLookup(arg): # use subprocess to call the host command in the os process = subprocess.Popen(['host', arg], stdout=subprocess.PIPE, encoding='utf-8') data = process.communicate() print(data[0])
At this point I can already perform dns lookups from a user defined single entry. But now let's take this a step further and create the dnsLookupList() function to import a user defined list as our input.
def dnsLookupList(): # opening and reading the file containing a list of domain names with open(input("\nEnter file name and/or path: ")) as fcontent: fstring = fcontent.readlines()
While I'm at it, I'm going to create a regular expression to filter out any parts of the domain names that aren't formatted properly as well. In order to use regular expressions I need to import the regular expression module at the beginning of the script.
1import subprocess2import re
In the regular expression below, if an entry is listed as "://www.google.com" it will only grab "www.google.com". If an entry is listed as "ww.google.com" it will only grab "google.com". I recently found an online tool for testing regex patterns called Regex Tester that's an excellent resource for examples or to streamline the testing of your own regex pattern.
# declaring the regex pattern to grab the most complete parts of the domain entries from a list to reduce errors pattern = re.compile(r'(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?')
lst=[] # extracting the domains for line in fstring: lst.append(pattern.search(line)[0])
print(" ") print(lst) print(" ")
All that's left now is to loop the domains stored in our array though the dnsLookup() function.
# iterate through the array of domains # with the dnsLookup function for i in lst: dnsLookup(i)
Automating Reverse DNS Lookups
What if we wanted to use the same script for Reverse DNS Lookups?
With some minor tweaks we can use this same code to create a version for performing reverse DNS lookups. Conveniently, the "host" command can be used to perform both DNS and Reverse DNS lookups out of the box.
The only real difference will be the regex pattern which will now look for IP addresses. In fact, I want to sort public from private IP address ranges, and then again to validate the public IP address.
I still need to import both the subprocess and regular expressions modules. Even the dnsLookup() function is exactly the same.
1import subprocess2import re3
4def dnsLookup(arg):5 # use subprocess to call the host command in the os6 process = subprocess.Popen(['host', arg], 7 stdout=subprocess.PIPE,8 encoding='utf-8')9 data = process.communicate()10 print(data[0])
I need to modify the dnsLookupList() function so that it uses two separate regex patterns. The first regular expression will be used to sort public and private addresses in the list.
# declaring the regex pattern to filter Private from Public IP addresses in a list pattern = re.compile(r'(^0\.)|(^10\.)|(^100\.6[4-9]\.)|(^100\.[7-9]\d\.)|(^100\.1[0-1]\d\.)|(^100\.12[0-7]\.)|(^127\.)|(^169\.254\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.0\.0\.)|(^192\.0\.2\.)|(^192\.88\.99\.)|(^192\.168\.)|(^198\.1[8-9]\.)|(^198\.51\.100\.)|(^203.0\.113\.)|(^22[4-9]\.)|(^23[0-9]\.)|(^24[0-9]\.)|(^25[0-5]\.)')
Next I need to initialize the arrays for storing the sorted IP addresses, and create the loop to iterate over the imported list.
Private_IPs =[] Public_IPs=[]
# extracting the IP addresses for line in string: line = line.rstrip() result = pattern.search(line) if result: Private_IPs.append(line) else: Public_IPs.append(line)
Now that I have the public IP addresses loaded into an array, I need to create a second regular expression to validate them so I only send correctly formatted IP addresses to the dnsLookup() functions.
# declaring the regex pattern to further filter the list for valid Public IP addresses pattern2 =re.compile(r'(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])')
For the final step, I need to initialize the arrays for storing the sorted valid and invalid public IP addresses, and create the loop to iterate over the addresses in the Public_IPs array.
# initialized array values valid2 =[] invalid2=[]
for i in Public_IPs: i = i.rstrip() result = pattern2.search(i) if result: valid2.append(i) else: invalid2.append(i)
# displaying the sorted valid IP addresses prior to running the reverse dns lookup print("Valid Public IPs") print(valid2) print(" ")
All that's left now is to loop the valid public IP addresses stored in the array though the dnsLookup() function.
for i in valid2: dnsLookup(i)
For complete versions of the final Python scripts, you can visit my Github account and download them directly from the project repository.