Building Your Own Passive DNS Collection System

9 July 2015

In this post, I'll be going over how to set up an Ubuntu server running Bro IDS and bro-pdns to log all DNS queries and answers to a SQL database.

Passive DNS can be great for security research, since it can help you build out the adversary's infrastructure and help answer questions like:

  • What IP did this domain resolve to in the past?
  • What other domains has this IP resolved to?
  • When was the first/last time this domain was seen?

Passive DNS can be great to help triage incidents in your SOC as well. By identifying other IP's that a particular malicious domain resolved to, you can then search your logs for other hosts in your environment that have communicated with those IP's, possibly identifying other compromised hosts.

There are a ton of sites out there that currently allow access to their Passive DNS system, sites like virustotal, passivetotal, CIRCL to name a few. There are plenty of others available, but there are definitely benefits to running your own. For example, users in your environment typically browse to sites relevant to their job (e.g. bankers visiting banking sites, nurses visiting medical sites, etc.), so by running your own PDNS system you would typically have more answers for the sites your users are interested in. I'm not saying the other sites wouldn't have entries for those sites, I just think you would have better luck running your own in order to get more records available for research purposes.

Installing Bro

First thing we need to do is get our Ubuntu box up and running. We can easily do all the stuff we need to using Security Onion, but, since we only are focusing on Bro, we can just use a vanilla install of Ubuntu to keep things simple.

We can use the guide from Digital Ocean to help us get Bro up and running, which basically comes down to this:

> apt-get update && apt-get upgrade
> apt-get install cmake make gcc g++ flex bison libpcap-dev/
/libgeoip-dev libssl-dev python-dev zlib1g-dev
/libmagic-dev swig2.0
> wget
> tar -xvzf bro-2.4.tar.gz
> cd bro-2.4
> ./configure --prefix=/nsm/bro
> make
> make install
> export PATH=/nsm/bro/bin:$PATH

This guide is assuming we are doing a standalone instance of Bro, and only monitoring one interface. If you have other requirements, you'll need to do further reading or other configuration manually.

The above commands should have us good to go, next we'll need to configure a few files (...maybe).

Since I'm only monitoring one interface, I didn't need to change any file. But, just to be sure, I edited $PREFIX/etc/node.cfg to verify that eth0 was my only interface. Note: $PREFIX is /nsm/bro in our case

Now we can go ahead and start Bro:

> broctl
[BroControl] install
[BroControl] start
[BroControl] status

If your path isn't set correctly, you can run broctl by using it's full path, /nsm/bro/bin/broctl.

You should be good to go!

Installing MySQL

After Bro is installed, we'll need to get MySQL working so we can log the queries and answers from Bro into the database.

This is relatively painless though, so let's get started:

> sudo apt-get install mysql-server libapache2-mod-auth-mysql php5-mysql
> sudo mysql_install_db
> sudo /usr/bin/mysql_secure_installation

That should do it. Next, we'll need to create a database to use:

> mysql -u root -p
mysql> CREATE DATABASE pdns;

Installing Other Requirements

We'll need to install two other packages to get everything working, but first we need to get pip installed.

> wget
> python

Next, we can install Bottle like so:

> pip install bottle

And then install SQLAlchemy using apt:

> sudo apt-get install python-sqlalchemy

Installing bro-pdns

Alright, now that we have all our requirements installed, let's get bro-pdns working...

First, let's clone a copy on our machine. If you don't have git installed, let's get a copy of that.

> apt-get install git


> git clone

Next, move the folder bro-pdns into /nsm/bro/share/bro/site

Now let's open up local.bro in /nsm/bro/share/bro/site and at the end of the file add the following:

@load ./bro_pdns
redef PDNS::uri = "mysql://root:password@localhost/pdns";

Use the password you specified when setting up your MySQL database earlier.

That should do it. Next, run broctl deploy to restart everything and load the new scripts into Bro.

Setting Up the HTTP API Server

This section is super easy, but, it'll get the webserver up and running so you can make calls to the API for domains and IP's.

site/bro-pdns/ serve

Using the API

Now that everything should be working, let's give it a go.

Once you made some queries to some sites to populate your dns.log, you can then query the API to see any relevant records.

You can run the command below to retrieve any records:

> curl http://localhost:8081/dns/

Obviously, replace with whatever domain or IP you want.


The results aren't that pretty though, since it's in JSON format, so we can just write up a quick Python script to parse through the results and print out the results a little cleaner.

Here's a little script I wrote just to show things more readable.


import requests
import json
import sys

indicator = sys.argv[1]

url = "http://localhost:8081/dns/"

r = requests.get(url+indicator)
j = json.loads(r.text)

print "+"+"-"*24+"+"+"-"*24+"+"+"-"*8+"+"+"-"*7+"+"+"-"*30+"+"
print "|"+" "*7+"First Seen"+" "*7+"|"+" "*7+"Last Seen"+" "*8+ \
"|"+" "*2+"Type"+" "*2+"|"+" "*2+"TTL"+" "*2+"|"+" "*12+"Answer"+" "*12+"|"
print "+"+"-"*24+"+"+"-"*24+"+"+"-"*8+"+"+"-"*7+"+"+"-"*30+"+"

for record in j['records']:
    print "  ",record['first'],"\t   ",record['last'],"     ",\
    record['type'],"      ",record['ttl'],"   ",record['answer']

Here's what it looks like in action:



Like I mentioned in the beginning, a tool like this internal to your organization can be a great addition to your analysts toolkit.

Being able to build out adversary's infrastructure and expanding your list of possible Indicator's of Compromise (IOCs) is a great capability to have, and can possibly help identify other compromised machines in your network.

If you have any questions or comments, please feel free to email me at or on twitter, @brian_warehime.

Bonus Round

One last thing that would be useful if you are already ingesting your Bro logs into Splunk is a little passive DNS search.

sourcetype=bro:dns answers!="-"
| stats earliest(_time) AS first_time latest(_time) AS latest_time by answers,
| eval first_time=strftime(first_time, "%m/%d/%y %H:%M:%S"
| eval latest_time=strftime(latest_time, "%m/%d/%y %H:%M:%S"
| rename first_time AS "Earliest Time" latest_time AS "Latest Time" 
answers AS "Answer" record_type AS "Record Type"