UNPKG

arp-validator

Version:

Security tool to detect arp poisoning attacks

140 lines (127 loc) 5.55 kB
var args = require('./args.js'); var pcap = require('pcap'); var ip = require('ip'); var tcp = require('./tcp.js'); var logger = require('./logger.js'); var macaddress = require('macaddress'); const util = require('util'); const fs = require('fs'); var my_mac = ''; macaddress.one(args.params.iface, function (err, mac) { my_mac = mac; }); var pendingSYNs = {}; if (!fs.existsSync(args.params.hostdb)){ fs.writeFileSync(args.params.hostdb, JSON.stringify({})); } var validatedHostsRaw = fs.readFileSync(args.params.hostdb); var validatedHosts = JSON.parse(validatedHostsRaw); //send a TCP SYN to the host and wait for 2 sec to receive a RST or ACK function validateHost(host_ip, host_mac){ logger.log('[?] Validating: '+host_ip+' is at '+host_mac, 'debug'); if(validatedHosts[host_ip] != undefined){ //host is already validated if(validatedHosts[host_ip] === host_mac){//lets check current situation matches with validated one logger.log('[+] Already Validated: '+host_ip+' is at '+validatedHosts[host_ip], 'debug'); }else{ logger.log('[-] Validation Failed : '+host_ip+' at '+host_mac); } return; } //Host has not validated yet, let's do it var host_port = parseInt(Math.random()*(65535-1024) + 1024); var src_ip = ip.address(); var src_mac = my_mac; if(!tcp.sendSYN(args.params.iface, src_mac, host_mac, src_ip, 31337, host_ip, host_port)){ logger.log('[?] Sent TCP SYN to '+host_ip+':'+host_port+' at '+host_mac, 'debug'); pendingSYNs[host_ip +':'+ host_port] = [host_mac, Date.now()]; setTimeout(handleTimedOutTCPSYNs, 2000, host_ip, host_port); } } //No RST or ACK received for 2 sec function handleTimedOutTCPSYNs(host_ip, host_port){ if(pendingSYNs[host_ip + ':' + host_port] != undefined){//No RST or ACK received for this, most probably spoofing underway logger.log('[-] Validation Failed : '+host_ip+' at '+pendingSYNs[host_ip + ':' + host_port][0]); delete pendingSYNs[host_ip + ':' + host_port]; } } //some RST or ACK arrive at port 31337 function handleTCPPacket(etherPacket){ var host_ip = etherPacket.payload.saddr.addr.join('.'); var host_port = etherPacket.payload.payload.sport; var host_mac = arrToMacString(etherPacket.shost.addr); if(pendingSYNs[host_ip +':'+ host_port] != undefined){ if(pendingSYNs[host_ip +':'+ host_port][0] !== host_mac) return; if(etherPacket.payload.payload.flags.rst){ logger.log('[?] Received RST from '+host_ip+':'+host_port, 'debug'); }else{ logger.log('[?] Received ACK from '+host_ip+':'+host_port, 'debug'); } logger.log('[+] Validated: '+host_ip+' is at '+pendingSYNs[host_ip +':'+ host_port][0], 'debug'); validatedHosts[host_ip] = pendingSYNs[host_ip +':'+ host_port][0]; //Write to database on new validation fs.writeFileSync(args.params.hostdb, JSON.stringify(validatedHosts)); delete pendingSYNs[host_ip +':'+ host_port]; //TODO: clear timeout on validation } } //convert mac address(array in packet) to string //[255,12,58,6,86,45] -> 'ff:0c:3a:06:56:2d' function arrToMacString(mac){ for(var i=0; i<mac.length; i++){ var hexm = mac[i].toString(16); if(hexm.length < 2) mac[i] = '0'+ hexm; else mac[i] = hexm; } return mac.join(':'); } //match source and dst mac address in mac header and arp header function checkHeaders(ethrPacket){ var arpPacket = ethrPacket.payload; //checking source mac address if(arrToMacString(ethrPacket.shost.addr) !== arrToMacString(arpPacket.sender_ha.addr)) return false; //checking dst mac address var isGratuitous = arrToMacString(ethrPacket.dhost.addr) === 'ff:ff:ff:ff:ff:ff'; isGratuitous = isGratuitous || (arrToMacString(ethrPacket.dhost.addr) == '00:00:00:00:00:00'); isGratuitous = isGratuitous || (arrToMacString(arpPacket.target_ha.addr) === 'ff:ff:ff:ff:ff:ff'); isGratuitous = isGratuitous || (arrToMacString(arpPacket.target_ha.addr) === '00:00:00:00:00:00'); if(!isGratuitous){//not a gratuitous reply if(arrToMacString(ethrPacket.dhost.addr) !== arrToMacString(arpPacket.target_ha.addr)) return false; } return true; } //we're interested only in ARP replies function filterARPPackets(etherPacket){ arpPacket = etherPacket.payload; if(arpPacket.operation == 1) return false; if(arpPacket.operation == 2){ //No need to validate arp replies sent by itself if(arrToMacString(etherPacket.shost.addr) === arrToMacString(arpPacket.sender_ha.addr) && arrToMacString(arpPacket.sender_ha.addr) === my_mac) return false; } return true; } function handleARPPacket(etherPacket){ arpPacket = etherPacket.payload; if(!filterARPPackets(etherPacket)) return; if(!checkHeaders(etherPacket)){ logger.log('Mac-ARP Header Mismatch : ' + arrToMacString(arpPacket.sender_ha.addr) + ' spoofing ' + arpPacket.sender_pa.addr.join('.')); return; } validateHost(arpPacket.sender_pa.addr.join('.'), arrToMacString(arpPacket.sender_ha.addr)); } //Capture ARP(replies) and TCP(ACK or RST) packets function startCapture(){ var arpSession = pcap.createSession(args.params.iface, 'arp or (tcp dst port 31337)'); arpSession.on('packet', function (raw_packet) { var packet = pcap.decode.packet(raw_packet); if(packet.payload.ethertype == 0x0800){//TCP handleTCPPacket(packet.payload); }else if(packet.payload.ethertype == 0x0806){//ARP handleARPPacket(packet.payload); } }); } process.on('uncaughtException', function(err) { fs.appendFile(args.params.log, err + '\n'); }); exports.startCapture = startCapture;