Open anor731 opened 3 years ago
/* * NodeJS <-> NMAP interface * Author: John Horton * Purpose: Create an interface for NodeJS applications to make use of NMAP installed on the local system. */ const child_process = require('child_process'); const execSync = child_process.execSync; const exec = child_process.exec; const spawn = child_process.spawn; const fs = require('fs'); const EventEmitter = require('events').EventEmitter; const os = require('os'); const Queue = require('queued-up'); const xml2js = require('xml2js'); /** * * @param {*} xmlInput * @param {*} onFailure * @returns {host[]} - Array of hosts */ function convertRawJsonToScanResults(xmlInput) { let tempHostList = []; if (!xmlInput.nmaprun.host) { //onFailure("There was a problem with the supplied NMAP XML"); return tempHostList; }; xmlInput = xmlInput.nmaprun.host; tempHostList = xmlInput.map((host) => { const newHost = { hostname: null, ip: null, mac: null, openPorts: null, osNmap: null } //Get hostname if (host.hostnames && host.hostnames[0] !== "\r\n" && host.hostnames[0] !== "\n") { newHost.hostname = host.hostnames[0].hostname[0].$.name } //get addresses host.address.forEach((address) => { const addressType = address.$.addrtype const addressAdress = address.$.addr const addressVendor = address.$.vendor if (addressType === 'ipv4') { newHost.ip = addressAdress } else if (addressType === 'mac') { newHost.mac = addressAdress newHost.vendor = addressVendor } }) //get ports if (host.ports && host.ports[0].port) { const portList = host.ports[0].port const openPorts = portList.filter((port) => { return (port.state[0].$.state === 'open') }) newHost.openPorts = openPorts.map((portItem) => { // console.log(JSON.stringify(portItem, null, 4)) const port = parseInt(portItem.$.portid) const protocol = portItem.$.protocol const service = portItem.service[0].$.name const tunnel = portItem.service[0].$.tunnel const method = portItem.service[0].$.method const product = portItem.service[0].$.tunnel let portObject = {} if(port) portObject.port = port if(protocol) portObject.protocol = protocol if(service) portObject.service = service if(tunnel) portObject.tunnel = tunnel if(method) portObject.method = method if(product) portObject.product = product return portObject }) } if (host.os && host.os[0].osmatch && host.os[0].osmatch[0].$.name) { newHost.osNmap = host.os[0].osmatch[0].$.name } return newHost }) return tempHostList; } class NmapScan extends EventEmitter { constructor(range, inputArguments, runAsRoot = false) { super(); this.runAsRoot = runAsRoot this.command = []; this.nmapoutputXML = ""; this.timer; this.range = []; this.arguments = ['-oX', '-']; this.rawData = ''; this.rawJSON; this.child; this.cancelled = false; this.scanTime = 0; this.error = null; this.scanResults; this.scanTimeout = 0; this.commandConstructor(range, inputArguments); this.initializeChildProcess(); } startTimer() { this.timer = setInterval(() => { this.scanTime += 10; if (this.scanTime >= this.scanTimeout && this.scanTimeout !== 0) { this.killChild(); } }, 10); } stopTimer() { clearInterval(this.timer); } commandConstructor(range, additionalArguments) { if (additionalArguments) { if (!Array.isArray(additionalArguments)) { additionalArguments = additionalArguments.split(' '); } this.command = this.arguments.concat(additionalArguments); } else { this.command = this.arguments; } if (!Array.isArray(range)) { range = range.split(' '); } this.range = range; this.command = this.command.concat(this.range); } killChild() { this.cancelled = true; if (this.child) { this.child.kill(); } } initializeChildProcess() { let begin if(this.runAsRoot) { begin= "sudo -u nmap ".concat(nmap.nmapLocation) } else { begin = nmap.nmapLocation } this.startTimer(); this.child = spawn(begin, this.command); process.on('SIGINT', this.killChild); process.on('uncaughtException', this.killChild); process.on('exit', this.killChild); this.child.stdout.on("data", (data) => { if (data.indexOf("percent") > -1) { // console.log(data.toString()); } else { this.rawData += data; } }); this.child.on('error', (err) => { this.killChild(); if (err.code === 'ENOENT') { this.emit('error', 'NMAP not found at command location: ' + nmap.nmapLocation) } else { this.emit('error', err.Error) } }) this.child.stderr.on("data", (err) => { this.error = err.toString(); }); this.child.on("close", () => { process.removeListener('SIGINT', this.killChild); process.removeListener('uncaughtException', this.killChild); process.removeListener('exit', this.killChild); if (this.error) { this.emit('error', this.error); } else if (this.cancelled === true) { this.emit('error', "Over scan timeout " + this.scanTimeout); } else { this.rawDataHandler(this.rawData); } }); } startScan() { this.child.stdin.end(); } cancelScan() { this.killChild(); this.emit('error', "Scan cancelled"); } scanComplete(results) { this.scanResults = results; this.stopTimer(); this.emit('complete', this.scanResults); } rawDataHandler(data) { let results; //turn NMAP's xml output into a json object xml2js.parseString(data, (err, result) => { if (err) { this.emit('error', "Error converting XML to JSON in xml2js: " + err); } else { this.rawJSON = result; results = convertRawJsonToScanResults(this.rawJSON, (err) => { this.emit('error', "Error converting raw json to cleans can results: " + err + ": " + this.rawJSON); }); this.scanComplete(results); } }); } } class StealthScan extends NmapScan { constructor(range) { super(range, '-sS', true); } } class QuickScan extends NmapScan { constructor(range) { super(range, '-sP'); } } class OsAndPortScan extends NmapScan { constructor(range) { super(range, '-O'); } } class QueuedScan extends EventEmitter { constructor(scanClass, range, args, action = () => {}) { super(); this.scanResults = []; this.scanTime = 0; this.currentScan; this.runActionOnError = false; this.saveErrorsToResults = false; this.singleScanTimeout = 0; this.saveNotFoundToResults = false; this._queue = new Queue((host) => { if (args !== null) { this.currentScan = new scanClass(host, args); } else { this.currentScan = new scanClass(host); } if (this.singleScanTimeout !== 0) { this.currentScan.scanTimeout = this.singleScanTimeout; } this.currentScan.on('complete', (data) => { this.scanTime += this.currentScan.scanTime; if (data[0]) { data[0].scanTime = this.currentScan.scanTime; this.scanResults = this.scanResults.concat(data); } else if (this.saveNotFoundToResults) { data[0] = { error: "Host not found", scanTime: this.currentScan.scanTime } this.scanResults = this.scanResults.concat(data); } action(data); this._queue.done(); }); this.currentScan.on('error', (err) => { this.scanTime += this.currentScan.scanTime; let data = { error: err, scanTime: this.currentScan.scanTime } if (this.saveErrorsToResults) { this.scanResults = this.scanResults.concat(data); } if (this.runActionOnError) { action(data); } this._queue.done(); }); this.currentScan.startScan(); }); this._queue.add(this.rangeFormatter(range)); this._queue.on('complete', () => { this.emit('complete', this.scanResults); }); } rangeFormatter(range) { let outputRange = []; if (!Array.isArray(range)) { range = range.split(' '); } for (let i = 0; i < range.length; i++) { let input = range[i]; let temprange = range[i]; if (countCharacterOccurence(input, ".") === 3 && input.match(new RegExp("-", "g")) !== null && !input.match(/^[a-zA-Z]+$/) && input.match(new RegExp("-", "g")).length === 1 ) { let firstIP = input.slice(0, input.indexOf("-")); let network; let lastNumber = input.slice(input.indexOf("-") + 1); let firstNumber; let newRange = []; for (let j = firstIP.length - 1; j > -1; j--) { if (firstIP.charAt(j) === ".") { firstNumber = firstIP.slice(j + 1); network = firstIP.slice(0, j + 1); break; } } for (let iter = firstNumber; iter <= lastNumber; iter++) { newRange.push(network + iter); } //replace the range/host string with array temprange = newRange; } outputRange = outputRange.concat(temprange); } function countCharacterOccurence(input, character) { let num = 0; for (let k = 0; k < input.length; k++) { if (input.charAt(k) === character) { num++; } } return num; } return outputRange; } startRunScan(index = 0) { this.scanResults = []; this._queue.run(0); } startShiftScan() { this.scanResults = []; this._queue.shiftRun(); } pause() { this._queue.pause(); } resume() { this._queue.resume(); } next(iterations = 1) { return this._queue.next(iterations); } shift(iterations = 1) { return this._queue.shift(iterations); } results() { return this.scanResults; } shiftResults() { this._queue.shiftResults(); return this.scanResults.shift(); } index() { return this._queue.index(); } queue(newQueue) { if (Array.isArray(newQueue)) { return this._queue.queue(newQueue); } else { return this._queue.queue(); } } percentComplete() { return Math.round(((this._queue.index() + 1) / this._queue.queue().length) * 100); } } class QueuedNmapScan extends QueuedScan { constructor(range, additionalArguments, actionFunction = () => {}) { super(NmapScan, range, additionalArguments, actionFunction); } } class QueuedQuickScan extends QueuedScan { constructor(range, actionFunction = () => {}) { super(QuickScan, range, null, actionFunction); } } class QueuedOsAndPortScan extends QueuedScan { constructor(range, actionFunction = () => {}) { super(OsAndPortScan, range, null, actionFunction); } } let nmap = { nmapLocation: "nmap", NmapScan, QuickScan, OsAndPortScan, QueuedScan, StealthScan, QueuedNmapScan, QueuedQuickScan, QueuedOsAndPortScan, } module.exports = nmap;
I suggest, that doing root scan would be provided with nmap user, that's nologin user...
It's easier...