├── .gitignore ├── bin └── dns-validator ├── docs └── screenshot.png ├── app.js ├── .jshintrc ├── lib ├── args.js ├── logger.js ├── remoteDns.js ├── constants.js ├── cdn.json └── dns.js ├── package.json ├── ctrl.js └── README.MD /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules 3 | -------------------------------------------------------------------------------- /bin/dns-validator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../ctrl.js'); -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DhavalKapil/dns-validator/HEAD/docs/screenshot.png -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var dns = require('./lib/dns.js'); 2 | var args = require('./lib/args.js'); 3 | var logger = require('./lib/logger.js'); 4 | 5 | logger.config.log = args.params.log; 6 | logger.config.verbose = args.params.verbose; 7 | 8 | dns.startCapture(logger.config); -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "freeze": true, 6 | //"indent": 2, 7 | "newcap": true, 8 | "quotmark": "single", 9 | "maxdepth": 3, 10 | "maxstatements": 15, 11 | "maxlen": 80, 12 | "eqnull": true, 13 | "funcscope": true, 14 | "node": true 15 | } 16 | -------------------------------------------------------------------------------- /lib/args.js: -------------------------------------------------------------------------------- 1 | 2 | var argv = require('argv'); 3 | 4 | var CONST = require('./constants.js'); 5 | 6 | argv.version(CONST.VERSION); 7 | 8 | argv.info(CONST.HELP_DOC); 9 | 10 | for (var i = 0;i (https://dhavalkapil.com/)", 14 | "license": "MIT", 15 | "bin": { 16 | "dns-validator": "./bin/dns-validator" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/DhavalKapil/dns-validator.git" 21 | }, 22 | "dependencies": { 23 | "pcap": "*", 24 | "argv": "*", 25 | "request": "", 26 | "underscore": "*", 27 | "cheerio": "*", 28 | "daemonize2": "*", 29 | "node-notifier": "*" 30 | }, 31 | "engines": { 32 | "node": ">=0.10.25" 33 | }, 34 | "preferGlobal": true 35 | } 36 | 37 | -------------------------------------------------------------------------------- /ctrl.js: -------------------------------------------------------------------------------- 1 | var args = require('./lib/args.js'); 2 | var CONST = require('./lib/constants.js'); 3 | 4 | var fs = require('fs'); 5 | 6 | var daemon = require('daemonize2').setup({ 7 | main: 'app.js', 8 | name: CONST.NAME, 9 | }); 10 | 11 | switch (args.params.command) { 12 | case 'start': 13 | daemon.start(); 14 | break; 15 | 16 | case 'stop': 17 | daemon.stop(); 18 | break; 19 | 20 | case 'restart': 21 | daemon.stop(function(err) { 22 | daemon.start(); 23 | }); 24 | break; 25 | 26 | case 'status': 27 | var pid = daemon.status(); 28 | if (pid) { 29 | console.log(CONST.NAME + ' daemon running. PID: ' + pid); 30 | } 31 | else { 32 | console.log(CONST.NAME + ' daemon not running.'); 33 | } 34 | break; 35 | 36 | default: 37 | args.help(); 38 | } 39 | 40 | process.on('uncaughtException', function(err) { 41 | fs.appendFile('err', err + '\n'); 42 | }); -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var notifier = require('node-notifier'); 4 | 5 | var config = { 6 | log: false, 7 | verbose: false 8 | }; 9 | 10 | // The time to till which the notification will not display again 11 | var TIME_LIMIT = 1000*60*60; 12 | var notificationCache = {}; 13 | 14 | function log(str, type) { 15 | if (config.log !== false) { 16 | if (config.verbose === true || type !== 'debug') { 17 | fs.appendFile(config.log, str + '\n'); 18 | } 19 | } 20 | 21 | if (type !== 'debug') { 22 | var time = new Date().getTime(); 23 | if(notificationCache.hasOwnProperty(str)) { 24 | if( (notificationCache[str]+TIME_LIMIT) >= time) { 25 | // Don't display notification 26 | return; 27 | } 28 | } 29 | 30 | notifier.notify({ 31 | title: 'Notification', 32 | message: str 33 | }); 34 | 35 | notificationCache[str] = time; 36 | } 37 | } 38 | 39 | exports.log = log; 40 | exports.config = config; -------------------------------------------------------------------------------- /lib/remoteDns.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('request'); 3 | var cheerio = require('cheerio'); 4 | 5 | function getIpArray(body) { 6 | var $ = cheerio.load(body); 7 | 8 | var ipArray = []; 9 | 10 | $('#resultTable > tr').each( function(i, elem) { 11 | if (i === 0 || i === 1) { 12 | return; 13 | } 14 | 15 | // 4th and 5th column of the row's data inside 16 | var type = this.children[3].children[0].data; 17 | var ip = this.children[4].children[0].data; 18 | 19 | if (type === 'A') { 20 | ipArray.push(ip); 21 | } 22 | }); 23 | 24 | return ipArray; 25 | } 26 | 27 | function makeRequest(ip, callback) { 28 | var params = { 29 | ip: ip, 30 | query: 'A' 31 | }; 32 | 33 | request.post( 'http://216.92.207.177/toolbox/nslookup.php', { 34 | form: params 35 | }, function(err, httpResponse, body) { 36 | if (err) { 37 | // Site is unreachable 38 | return null; 39 | } 40 | 41 | var ipArray = getIpArray(body); 42 | 43 | callback(ipArray); 44 | }); 45 | } 46 | 47 | exports.makeRequest = makeRequest; -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 2 | var packageJson = require('../package.json'); 3 | var cdnJson = require('./cdn.json'); 4 | 5 | // Exporting version 6 | exports.VERSION = packageJson.version; 7 | 8 | // Exporting application name 9 | exports.NAME = packageJson.name; 10 | 11 | // Exporting description 12 | exports.DESCRIPTION = packageJson.description; 13 | 14 | exports.MODULES = [ 15 | { 16 | mod: 'start', 17 | description: 'start ' + exports.NAME + ' as a daemon process', 18 | options: [ 19 | { 20 | name: 'log', 21 | short: 'l', 22 | type: 'string', 23 | description: 'generate logs in external file (absolute path)', 24 | example: exports.NAME + ' start -l log_file or --log=log_file' 25 | }, 26 | { 27 | name: 'verbose', 28 | short: 'v', 29 | type: 'string', 30 | description: 'verbose detailed steps in log file', 31 | example: exports.NAME + ' start -l log_file --verbose|-v' 32 | } 33 | ] 34 | }, 35 | { 36 | mod: 'stop', 37 | description: 'stop ' + exports.NAME, 38 | options: [] 39 | }, 40 | { 41 | mod: 'restart', 42 | description: 'restart ' + exports.NAME, 43 | options: [] 44 | }, 45 | { 46 | mod: 'status', 47 | description: 'Get the status of ' + exports.NAME, 48 | options: [] 49 | } 50 | ]; 51 | 52 | // Exporting help docs 53 | function generateHelpDoc() { 54 | var helpDoc = exports.DESCRIPTION + '\n\nUSAGE:\n\n'; 55 | helpDoc += '\tsudo ' + exports.NAME + ' [action] [options]\n\n'; 56 | helpDoc += 'actions:\n\n'; 57 | 58 | for (var key in exports.MODULES) { 59 | var module = exports.MODULES[key]; 60 | helpDoc += '\t' + module.mod + '\t\t' + module.description + '\n\n'; 61 | 62 | if (module.options.length !== 0) { 63 | helpDoc += '\t\toptions:\n'; 64 | } 65 | 66 | for (var key2 in module.options) { 67 | var option = module.options[key2]; 68 | helpDoc += '\t\t\t--' + option.name + ', -' + option.short + '\n'; 69 | helpDoc += '\t\t\t\t' + option.description + '\n'; 70 | helpDoc += '\t\t\t\t' + option.example + '\n\n'; 71 | } 72 | 73 | helpDoc += '\n'; 74 | } 75 | 76 | helpDoc += 'global options:'; 77 | 78 | return helpDoc; 79 | } 80 | 81 | exports.HELP_DOC = generateHelpDoc(); 82 | 83 | exports.CDN_LIST = cdnJson; -------------------------------------------------------------------------------- /lib/cdn.json: -------------------------------------------------------------------------------- 1 | { 2 | ".akamai.net": "Akamai", 3 | ".akamaiedge.net": "Akamai", 4 | ".akamaihd.net": "Akamai", 5 | ".edgesuite.net": "Akamai", 6 | ".edgekey.net": "Akamai", 7 | ".srip.ne": "Akamai", 8 | ".akamaitechnologies.com": "Akamai", 9 | ".akamaitechnologies.fr": "Akamai", 10 | ".llnwd.net": "Limelight", 11 | "edgecastcdn.net": "Edgecast", 12 | ".systemcdn.net": "Edgecast", 13 | ".transactcdn.net": "Edgecast", 14 | ".v1cdn.net": "Edgecast", 15 | ".v2cdn.net": "Edgecast", 16 | ".v3cdn.net": "Edgecast", 17 | ".v4cdn.net": "Edgecast", 18 | ".v5cdn.net": "Edgecast", 19 | "hwcdn.net": "Highwinds", 20 | ".simplecdn.net": "Simple CDN", 21 | ".instacontent.net": "Mirror Image", 22 | ".footprint.net": "Level 3", 23 | ".ay1.b.yahoo.com": "Yahoo", 24 | ".yimg.": "Yahoo", 25 | ".yahooapis.com": "Yahoo", 26 | "facebook.com": "Facebook", 27 | "fbcdn": "Facebook", 28 | ".gmail.": "Google", 29 | ".google.": "Google", 30 | "googlesyndication.": "Google", 31 | "youtube.": "Google", 32 | ".googleusercontent.com": "Google", 33 | "googlehosted.com": "Google", 34 | ".gstatic.com": "Google", 35 | "github": "Github", 36 | ".insnw.net": "Instart Logic", 37 | ".inscname.net": "Instart Logic", 38 | ".internapcdn.net": "Internap", 39 | ".cloudfront.net": "Amazon CloudFront", 40 | ".netdna-cdn.com": "NetDNA", 41 | ".netdna-ssl.com": "NetDNA", 42 | ".netdna.com": "NetDNA", 43 | ".cotcdn.net": "Cotendo CDN", 44 | ".cachefly.net": "Cachefly", 45 | "bo.lt": "BO.LT", 46 | ".cloudflare.com": "Cloudflare", 47 | ".afxcdn.net": "afxcdn.net", 48 | ".lxdns.com": "ChinaNetCenter", 49 | ".att-dsa.net": "AT&T", 50 | ".vo.msecnd.net": "Windows Azure", 51 | ".voxcdn.net": "VoxCDN", 52 | ".bluehatnetwork.com": "Blue Hat Network", 53 | ".swiftcdn1.com": "SwiftCDN", 54 | ".cdngc.net": "CDNetworks", 55 | ".gccdn.net": "CDNetworks", 56 | ".panthercdn.com": "CDNetworks", 57 | ".fastly.net": "Fastly", 58 | ".nocookie.net": "Fastly", 59 | ".gslb.taobao.com": "Taobao", 60 | ".gslb.tbcache.com": "Alimama", 61 | ".mirror-image.net": "Mirror Image", 62 | ".yottaa.net": "Yottaa", 63 | ".cubecdn.net": "cubeCDN", 64 | ".r.cdn77.net": "CDN77", 65 | ".incapdns.net": "Incapsula", 66 | ".bitgravity.com": "BitGravity", 67 | ".r.worldcdn.net": "OnApp", 68 | ".r.worldssl.net": "OnApp", 69 | "tbcdn.cn": "Taobao", 70 | ".taobaocdn.com": "Taobao", 71 | ".ngenix.net": "NGENIX", 72 | ".pagerain.net": "PageRain", 73 | ".ccgslb.com": "ChinaCache", 74 | "cdn.sfr.net": "SFR", 75 | ".azioncdn.net": "Azion", 76 | ".azioncdn.com": "Azion", 77 | ".azion.net": "Azion", 78 | ".cdncloud.net.au": "MediaCloud", 79 | ".rncdn1.com": "Reflected Networks", 80 | ".cdnsun.net": "CDNsun", 81 | ".mncdn.com": "Medianova", 82 | ".mncdn.net": "Medianova", 83 | ".mncdn.org": "Medianova", 84 | "cdn.jsdelivr.net": "jsDelivr", 85 | ".nyiftw.net": "NYI FTW", 86 | ".nyiftw.com": "NYI FTW", 87 | ".resrc.it": "ReSRC.it", 88 | ".zenedge.net": "Zenedge", 89 | ".lswcdn.net": "LeaseWeb CDN" 90 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # dns-validator 2 | 3 | > Security tool to detect dns poisoning attacks 4 | 5 | ## Features: 6 | 7 | - Watches over the transfer of all DNS packets 8 | - Matches every request with it's response 9 | - Notifies in case both their `question` header do not match 10 | - Notifies if any stray packet arrives without a request 11 | - Gets the IP list for domains requested through an external service 12 | - Notifies if they don't match the ones in response packets 13 | - In built cache for speed improvements 14 | - Runs as daemon process without interfering with normal traffic 15 | - Log's to any external file 16 | 17 | ### Screenshot 18 | 19 | ![Screenshot](docs/screenshot.png) 20 | 21 | ## Requirements 22 | - **Mac OS X**: >= 10.8 or Growl if earlier. 23 | - **Linux**: notify-osd installed (Ubuntu should have this by default) 24 | - **Windows**: >= 8, task bar balloon if earlier or Growl if that is installed. 25 | - **General Fallback**: Growl 26 | 27 | ## Installation 28 | 29 | ``` 30 | [sudo] npm install dns-validator -g 31 | ``` 32 | 33 | ## Usage 34 | 35 | > To run dns-validator simply 36 | 37 | ``` 38 | [sudo] dns-validator start 39 | ``` 40 | 41 | > Complete usage: 42 | 43 | ``` 44 | [sudo] dns-validator [action] [options] 45 | 46 | actions: 47 | 48 | start start dns-validator as a daemon process 49 | 50 | options: 51 | --log, -l 52 | generate logs in external file (absolute path) 53 | dns-validator start -l log_file or --log=log_file 54 | 55 | --verbose, -v 56 | verbose detailed steps in log file 57 | dns-validator start -l log_file --verbose|-v 58 | 59 | stop stop dns-validator 60 | 61 | restart restart dns-validator 62 | 63 | status Get the status of dns-validator 64 | 65 | 66 | global options: 67 | 68 | --help, -h 69 | Displays help information about this script 70 | 'dns-validator -h' or 'dns-validator --help' 71 | 72 | --version 73 | Displays version info 74 | dns-validator --version 75 | ``` 76 | 77 | ## Dependencies 78 | 79 | - libpcap-dev: library for network traffic capture 80 | - [mranney/node_pcap](https://github.com/mranney/node_pcap) 81 | - [codenothing/argv](https://github.com/codenothing/argv) 82 | - [request/request](https://github.com/request/request) 83 | - [jashkenas/underscore](https://github.com/jashkenas/underscore) 84 | - [cheeriojs/cheerio](https://github.com/cheeriojs/cheerio) 85 | - [niegowski/node-daemonize2](https://github.com/niegowski/node-daemonize2) 86 | - [mikaelbr/node-notifier](https://github.com/mikaelbr/node-notifier) 87 | 88 | ## Issue 89 | 90 | Major websites use a Content Delivery Network (CDN) to host all their static resources. So the IP's that are retreived through some external source may not match the IP's meant for the region dns-validator is being used. Hence these will be notified to the user even if the dns is not really poisoned. Currently I am having a list of cdn websites in `cdn.js` and not matching their IP's. The list is incomplete for now. If you find any solution to this issue feel free to send a pull request! 91 | 92 | ## Developer 93 | 94 | [Dhaval Kapil](https://dhavalkapil.com/) -------------------------------------------------------------------------------- /lib/dns.js: -------------------------------------------------------------------------------- 1 | 2 | var pcap = require('pcap'); 3 | var _ = require('underscore'); 4 | 5 | var remoteDns = require('./remoteDns.js'); 6 | var logger = require('./logger.js'); 7 | var CONST = require('./constants.js'); 8 | 9 | var requestsUnanswered = {}; 10 | var domainIpMaps = {}; 11 | 12 | function handleDNSRequestPacket(dns) { 13 | var id = dns.header.id; 14 | logger.log('Analyzing DNS request package id: ' + id, 'debug'); 15 | 16 | if (requestsUnanswered.hasOwnProperty(id)) { 17 | requestsUnanswered[id].count++; 18 | } 19 | else { 20 | requestsUnanswered[id] = { 21 | count: 1, 22 | question: dns.question 23 | }; 24 | } 25 | } 26 | 27 | // Checks the question section only 28 | function checkQuestionAnswer(dns) { 29 | var id = dns.header.id; 30 | 31 | var q1 = dns.question; 32 | var q2 = requestsUnanswered[id].question; 33 | 34 | if (q1.length !== q2.length) { 35 | return false; 36 | } 37 | 38 | for (var i = 0;i -1) { 67 | logger.log('Found matching cdn: ' + key, 'debug'); 68 | 69 | return true; 70 | } 71 | } 72 | } 73 | 74 | return false; 75 | } 76 | 77 | function getIpArray(dns) { 78 | var IPArray = []; 79 | 80 | for (var i = 0;i