431 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * File: parse_nmap.php
 | |
|  * Author: Ryan Prather
 | |
|  * Purpose: Parse an nmap result file
 | |
|  * Created: Jul 3, 2014
 | |
|  *
 | |
|  * Portions Copyright 2016: Cyber Perspectives, All rights reserved
 | |
|  * Released under the Apache v2.0 License
 | |
|  *
 | |
|  * Portions Copyright (c) 2012-2015, Salient Federal Solutions
 | |
|  * Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
 | |
|  * Released under Modified BSD License
 | |
|  *
 | |
|  * See license.txt for details
 | |
|  *
 | |
|  * Change Log:
 | |
|  *  - Jul 3, 2014 - File created
 | |
|  *  - Sep 1, 2016 - Copyright Updated, added CWD parameter, and
 | |
|  * 					updated function calls after class merger
 | |
|  *  - Oct 24, 2016 - Fixed bug (#6) when parsing port data, and added exclusion for "[host down]"
 | |
|  *  - Nov 7, 2016 - Added d parameter documentation
 | |
|  *  - Dec 7, 2016 - Added check for "Interesting ports on {IP}" line
 | |
|  *  - Jan 30, 2017 - Updated to use parse_config.ini file, and added populating new targets with shortened os software string if available.
 | |
|  */
 | |
| $cmd = getopt("f:", ['debug::', 'help::']);
 | |
| 
 | |
| if (!isset($cmd['f']) || isset($cmd['help'])) {
 | |
|     die(usage());
 | |
| }
 | |
| 
 | |
| $conf = parse_ini_file("parse_config.ini");
 | |
| 
 | |
| if (!$conf) {
 | |
|     die("Could not find parse_config.ini configuration file");
 | |
| }
 | |
| 
 | |
| chdir($conf['doc_root']);
 | |
| 
 | |
| include_once 'config.inc';
 | |
| include_once 'database.inc';
 | |
| include_once 'helper.inc';
 | |
| 
 | |
| chdir(TMP);
 | |
| set_time_limit(0);
 | |
| 
 | |
| $db        = new db();
 | |
| $base_name = basename($cmd['f']);
 | |
| $err       = new Sagacity_Error($cmd['f']);
 | |
| 
 | |
| if (!file_exists($cmd['f'])) {
 | |
|     $db->update_Running_Scan($base_name, ['name' => 'status', 'value' => 'ERROR']);
 | |
|     $err->script_log("File not found", E_ERROR);
 | |
| }
 | |
| 
 | |
| $db->update_Running_Scan($base_name, ['name' => 'pid', 'value' => getmypid()]);
 | |
| $src = $db->get_Sources("NMAP");
 | |
| $existing_scan = $db->get_ScanData($conf['ste'], $base_name);
 | |
| 
 | |
| if (is_array($existing_scan) && count($existing_scan)) {
 | |
|     $scan = $existing_scan[0];
 | |
| }
 | |
| else {
 | |
|     $mtime   = filemtime($cmd['f']);
 | |
|     $dt      = DateTime::createFromFormat("U", $mtime);
 | |
|     $ste     = $db->get_STE($conf['ste'])[0];
 | |
|     $scan    = new scan(null, $src, $ste, 1, $base_name, $dt->format("Y-m-d"));
 | |
|     $scan_id = $db->save_Scan($scan);
 | |
|     $scan->set_ID($scan_id);
 | |
| }
 | |
| 
 | |
| //echo "\$steid ($steid) will be used later\n";
 | |
| # file is cool - reads the whole file into an array with one command...
 | |
| $lines  = file($cmd['f']);
 | |
| $target = [];
 | |
| foreach ($lines as $line_num => $line) {
 | |
|     $db->help->select("scans", ['status'], [
 | |
|         [
 | |
|             'field' => 'id',
 | |
|             'op'    => '=',
 | |
|             'value' => $scan->get_ID()
 | |
|         ]
 | |
|     ]);
 | |
|     $thread_status = $db->help->execute();
 | |
|     if ($thread_status['status'] == 'TERMINATED') {
 | |
|         rename(realpath(TMP . "/{$scan->get_File_Name()}"), TMP . "/terminated/{$scan->get_File_Name()}");
 | |
|         $err->script_log("File parsing terminated by user");
 | |
|         die();
 | |
|     }
 | |
| 
 | |
|     if (preg_match('/^[\r\n]+$/', $line)) {
 | |
|         continue;
 | |
|     } # skip blank lines
 | |
|     $line = trim($line, "\t\n\r"); # chomp would be nice...
 | |
|     if (!isset($filetype)) {
 | |
|         if (preg_match('/Starting|\-oN/', $line)) {
 | |
|             $filetype = "text";
 | |
|         }
 | |
|         elseif (preg_match('/\-oG/', $line)) {
 | |
|             $filetype = "grep";
 | |
|         }
 | |
|         elseif (preg_match('/xml version/', $line)) {
 | |
|             $filetype = "xml";
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         if ($line_num >= 1 && !isset($filetype)) {
 | |
|             $err->script_log("ERROR File Type not found");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if ($filetype == "text") {
 | |
|         //echo "Text:$line_num: $line\n";
 | |
|         if (preg_match("/Host is up|Not shown|PORT\s+STATE|\[host down\]/", $line)) {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         if (preg_match("/Interesting ports on ([\d\.]+)/", $line, $matches)) {
 | |
|             $ip                      = $matches[1];
 | |
|             $target[$ip]             = [];
 | |
|             $target[$ip]['hostname'] = $ip;
 | |
|         }
 | |
|         elseif (preg_match("/Nmap scan report for ([^ ]+) \(([0-9\.]+)\)/", $line, $matches)) {
 | |
|             $ip = $matches[2];
 | |
|             if (preg_match('/\./', $matches[1])) {
 | |
|                 $name                    = explode('.', $matches[1]);
 | |
|                 $target[$ip]['hostname'] = $name[0];
 | |
|                 $target[$ip]['fqdn']     = $matches[1];
 | |
|             }
 | |
|             else {
 | |
|                 $target[$ip]['hostname'] = $matches[1];
 | |
|             }
 | |
|             $err->script_log($target[$ip]['hostname'] . ":$ip");
 | |
|         }
 | |
|         elseif (preg_match("/Nmap scan report for ([0-9\.]+)/", $line, $matches)) {
 | |
|             $ip                      = $matches[1];
 | |
|             $target[$ip]['hostname'] = "";
 | |
|             $err->script_log($target[$ip]['hostname'] . ":$ip");
 | |
|         }
 | |
|         elseif (preg_match("/^Discovered ([a-z]+) port (\d+)\/([udtcp]+) on (\d\.]+)$/", $line, $matches)) {
 | |
|             $state = $matches[1];
 | |
|             $port  = $matches[2];
 | |
|             $proto = $matches[3];
 | |
|             $ip    = $matches[4];
 | |
| 
 | |
|             $target[$ip][$proto][$port]['state'] = $state;
 | |
| 
 | |
|             $err->script_log("\t$ip:$port:$proto:$state");
 | |
|         }
 | |
|         elseif (preg_match("/Other addresses.*: ([0-9\. ]+)/", $line, $matches)) {
 | |
|             $target[$ip]['otherips'] = $matches[1];
 | |
|             $err->script_log("\tOther:" . $matches[1]);
 | |
|         }
 | |
|         elseif (preg_match('/(\d+)\/([udtcp]+)\s+(\S+)\s+(\S+)/', $line, $matches)) {
 | |
|             if ($matches[3] == 'unknown') {
 | |
|                 continue;
 | |
|             }
 | |
|             $port  = $matches[1];
 | |
|             $proto = $matches[2];
 | |
|             if (!empty($ip)) {
 | |
|                 $target[$ip][$proto][$port]['state'] = $matches[3];
 | |
|                 $target[$ip][$proto][$port]['iana']  = $matches[4];
 | |
|             }
 | |
|             $err->script_log("\t$port:$proto:$matches[3]:$matches[4]");
 | |
| 
 | |
|             if (preg_match("/\d+\/[udtcp]+\s+\S+\s+\S+\s+(.*)/", $line, $matches)) {
 | |
|                 $target[$ip][$proto][$port]['banner'] = $matches[1];
 | |
|                 $err->script_log("\tBanner:$matches[1]");
 | |
|             }
 | |
|         }
 | |
|         elseif (preg_match('/MAC Address: ([A-F0-9:]+)/', $line, $matches)) {
 | |
|             $target[$ip]['mac'] = $matches[1];
 | |
|             if (preg_match('/MAC Address: [A-F0-9:]+\s+\((.*)\)/', $line, $matches)) {
 | |
|                 $target[$ip]['description'] = $matches[1];
 | |
|                 $err->script_log("\t" . $target[$ip]['mac'] . ": Interface:$matches[1]");
 | |
|             }
 | |
|         }
 | |
|         elseif (preg_match('/Service Info: OS: (\S+);/', $line, $matches)) {
 | |
|             $target[$ip]['OS'] = $matches[1];
 | |
|             if (preg_match('/Service Info: OS: \S+; CPE: (.+)/', $line, $matches)) {
 | |
|                 $target[$ip]['cpe'] = $matches[1];
 | |
|             }
 | |
|             else {
 | |
|                 $target[$ip]['cpe'] = null;
 | |
|             }
 | |
|             $err->script_log("\t" . $target[$ip]['OS'] . ", " . $target[$ip]['cpe']);
 | |
|         }
 | |
|     }
 | |
|     elseif ($filetype == "grep") {
 | |
|         $err->script_log("Grep:$line_num: $line" . PHP_EOL);
 | |
|         # -oG grep format is not recommended - it discards helpful information like hostname
 | |
|         if (preg_match("/Host: ([0-9\.]+) \((.*)\)\s+(Ports|Protocols):(.*)/", $line, $matches)) {
 | |
|             $ip = $matches[1];
 | |
|             if (preg_match('/\./', $matches[2])) {
 | |
|                 $name                    = explode('.', $matches[2]);
 | |
|                 $target[$ip]['hostname'] = $name[0];
 | |
|                 $target[$ip]['fqdn']     = $matches[2];
 | |
|             }
 | |
|             else {
 | |
|                 $target[$ip]['hostname'] = $matches[2];
 | |
|             }
 | |
|             $err->script_log("$ip:" . $matches[2]);
 | |
|             $type         = $matches[3]; # will be used later when we support IP protocol scans
 | |
|             $ports_string = $matches[4];
 | |
|             $ports_list   = explode(",", $ports_string);
 | |
|             foreach ($ports_list as $port_num => $port_str) {
 | |
|                 # fields: port, state, owner, service/sunRPC/banner
 | |
|                 # need to read the manual for grepable!
 | |
|                 $port_info                            = explode("/", $port_str);
 | |
|                 $port                                 = $port_info[0];
 | |
|                 $proto                                = $port_info[2];
 | |
|                 $target[$ip][$proto][$port]['state']  = $port_info[1];
 | |
|                 $target[$ip][$proto][$port]['iana']   = $port_info[4];
 | |
|                 $target[$ip][$proto][$port]['banner'] = $port_info[6];
 | |
|                 $err->script_log("\t$port:$proto:" . $port_info[1] . $port_info[4]);
 | |
|                 $err->script_log("\tBanner: " . $port_info[6]);
 | |
|             }
 | |
|         }
 | |
|     } # end Grep parsing
 | |
| } # end foreach line in file
 | |
| 
 | |
| if ($filetype == "xml") {
 | |
|     $err->script_log("Parsing XML");
 | |
|     $xml   = new DOMDocument();
 | |
|     $xml->load($cmd['f']);
 | |
|     $hosts = getValue($xml, "/nmaprun/host", null, true);
 | |
|     $count = 0;
 | |
|     foreach ($hosts as $host) {
 | |
|         $addrs = getValue($xml, "address", $host, true);
 | |
|         foreach ($addrs as $addr) {
 | |
|             $addrtype = $addr->getAttribute("addrtype");
 | |
|             if ($addrtype == "ipv4") {
 | |
|                 $ip = $addr->getAttribute("addr");
 | |
|             }
 | |
|             elseif ($addrtype == "mac") {
 | |
|                 $vendor = $addr->getAttribute("vendor");
 | |
|                 $mac    = $addr->getAttribute("addr");
 | |
|             }
 | |
|         }
 | |
|         $target[$ip]['hostname']    = getValue($xml, "hostnames/hostname[@type='user']/@name", $host);
 | |
|         $target[$ip]['mac']         = $mac;
 | |
|         $target[$ip]['description'] = $vendor;
 | |
|         # Iterate through ports
 | |
|         $ports                      = getValue($xml, "ports/port", $host, true);
 | |
|         $tcp_ports                  = [];
 | |
|         $udp_ports                  = [];
 | |
|         foreach ($ports as $portxml) {
 | |
|             $portid = $portxml->getAttribute("portid");
 | |
|             $proto  = $portxml->getAttribute("protocol");
 | |
| 
 | |
|             if ($proto == 'tcp') {
 | |
|                 $port = $db->get_TCP_Ports($portid)[0];
 | |
|             }
 | |
|             else {
 | |
|                 $port = $db->get_UDP_Ports($portid)[0];
 | |
|             }
 | |
| 
 | |
|             $target[$ip][$proto][$portid]['state']  = getValue($xml, "state/@state", $portxml);
 | |
|             $target[$ip][$proto][$portid]['iana']   = getValue($xml, "service/@name", $portxml);
 | |
|             $product                                = getValue($xml, "service/@product", $portxml);
 | |
|             $version                                = getValue($xml, "service/@version", $portxml);
 | |
|             $extrainfo                              = getValue($xml, "service/@extrainfo", $portxml);
 | |
|             $target[$ip][$proto][$portid]['banner'] = "$product $version $extrainfo";
 | |
| 
 | |
|             $port->set_Banner("$product $version $extrainfo");
 | |
|             $port->set_IANA_Name(getValue($xml, "service/@name", $portxml));
 | |
| 
 | |
|             if ($proto == 'tcp') {
 | |
|                 $tcp_ports[] = $port;
 | |
|             }
 | |
|             else {
 | |
|                 $udp_ports[] = $port;
 | |
|             }
 | |
| 
 | |
|             //echo "$portid, $proto, " .$target[$ip][$proto][$portid]['banner'] ."\n";
 | |
|         } # end foreach port
 | |
| 
 | |
|         $target[$ip]['OS'] = getValue($xml, "os/osmatch/@name", $host);
 | |
|         $err->script_log($target[$ip]['OS']);
 | |
|     } # end foreach host
 | |
| } # end XML parsing
 | |
| ###################################
 | |
| 
 | |
| $db->update_Running_Scan($base_name, ['name' => 'host_count', 'value' => count($target)]);
 | |
| $count  = 0;
 | |
| $tgt_ip = null;
 | |
| foreach ($target as $ip => $tgt) {
 | |
|     # get target ID
 | |
|     $tgt_id = 0;
 | |
|     if (!in_array($ip, ['0.0.0.0', '127.0.0.1', '::0'])) {
 | |
|         $tgt_ip = $ip;
 | |
|     }
 | |
|     if ($tgt['hostname']) {
 | |
|         $tgt_id = $db->check_Target($conf['ste'], $tgt['hostname']);
 | |
|     }
 | |
|     if (!$tgt_id) {
 | |
|         $tgt_id = $db->check_Target($conf['ste'], $ip);
 | |
|     }
 | |
|     if (!$tgt_id) { # insert
 | |
|         $sw      = $db->get_Software("cpe:/o:generic:generic:-")[0];
 | |
|         $tgt_obj = new target(($tgt['hostname'] ? $tgt['hostname'] : $ip));
 | |
|         $tgt_obj->set_STE_ID($conf['ste']);
 | |
|         //$tgt_obj->set_Notes("New target found by NMap");
 | |
|         $tgt_obj->set_OS_ID($sw->get_ID());
 | |
|         if ($sw->get_Shortened_SW_String()) {
 | |
|             $tgt_obj->set_OS_String($sw->get_Shortened_SW_String());
 | |
|         }
 | |
|         else {
 | |
|             $tgt_obj->set_OS_String($sw->get_SW_String());
 | |
|         }
 | |
|         $tgt_obj->set_Location(($conf['location'] ? $conf['location'] : ''));
 | |
| 
 | |
|         $tgt_obj->interfaces["{$ip}"] = new interfaces(null, null, null, $ip, null, $tgt_obj->get_Name(), (isset($tgt['fqdn']) ? $tgt['fqdn'] : $tgt_obj->get_Name()), (isset($tgt['description']) ? $tgt['description'] : ""));
 | |
| 
 | |
|         if (isset($tgt['tcp'])) {
 | |
|             foreach ($tgt['tcp'] as $port_num => $port) {
 | |
|                 if ($port['state'] != 'open') {
 | |
|                     continue;
 | |
|                 }
 | |
|                 $tcp = $db->get_TCP_Ports($port_num)[0];
 | |
|                 if (!empty($port['banner'])) {
 | |
|                     $tcp->set_Banner($port['banner']);
 | |
|                 }
 | |
|                 $tcp->set_IANA_Name($port['iana']);
 | |
|                 //$tcp->set_Notes("Found in scan file " . $scan->get_File_Name());
 | |
| 
 | |
|                 $tgt_obj->interfaces["{$ip}"]->add_TCP_Ports($tcp);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (isset($tgt['udp'])) {
 | |
|             foreach ($tgt['udp'] as $port_num => $port) {
 | |
|                 if ($port['state'] != 'open') {
 | |
|                     continue;
 | |
|                 }
 | |
|                 $udp = $db->get_UDP_Ports($port_num)[0];
 | |
|                 if (!empty($port['banner'])) {
 | |
|                     $udp->set_Banner($port['banner']);
 | |
|                 }
 | |
|                 $udp->set_IANA_Name($port['iana']);
 | |
|                 //$udp->set_Notes("Found in scan file " . $scan->get_File_Name());
 | |
| 
 | |
|                 $tgt_obj->interfaces["{$ip}"]->add_UDP_Ports($udp);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $tgt_obj->set_ID($tgt_id = $db->save_Target($tgt_obj));
 | |
|     }
 | |
|     else { #Update
 | |
|         $db_tgt = $db->get_Target_Details($conf['ste'], $tgt_id)[0];
 | |
| 
 | |
|         if (isset($tgt['tcp'])) {
 | |
|             foreach ($tgt['tcp'] as $port_num => $port) {
 | |
|                 if ($port['state'] != 'open') {
 | |
|                     continue;
 | |
|                 }
 | |
|                 $tcp = new tcp_ports(null, $port_num, $port['iana'], (isset($port['banner']) ? $port['banner'] : ""), "");
 | |
|                 if (!isset($db_tgt->interfaces["{$ip}"])) {
 | |
|                     $db_tgt->interfaces["{$ip}"] = new interfaces(null, null, null, $ip, null, $tgt['hostname'], $tgt['hostname'], (isset($tgt['description']) ? $tgt['description'] : ""));
 | |
|                 }
 | |
| 
 | |
|                 if ($db_tgt->interfaces["{$ip}"]->is_TCP_Port_Open($port_num)) {
 | |
|                     $db_tgt->interfaces["{$ip}"]->update_TCP_Port($tcp);
 | |
|                 }
 | |
|                 else {
 | |
|                     $db_tgt->interfaces["{$ip}"]->add_TCP_Ports($tcp);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (isset($tgt['udp'])) {
 | |
|             foreach ($tgt['udp'] as $port_num => $port) {
 | |
|                 if ($port['state'] != 'open') {
 | |
|                     continue;
 | |
|                 }
 | |
|                 $udp = new udp_ports(null, $port_num, $port['iana'], (isset($port['banner']) ? $port['banner'] : ""), "");
 | |
|                 if (!isset($db_tgt->interfaces["{$ip}"])) {
 | |
|                     $interface               = new interfaces(null, $tgt_id, null, $ip, null, $tgt['hostname'], $tgt['hostname'], (isset($tgt['description']) ? $tgt['description'] : ""));
 | |
|                     $db_tgt->interfaces["{$ip}"] = $interface;
 | |
|                 }
 | |
| 
 | |
|                 if ($db_tgt->interfaces["{$ip}"]->is_UDP_Port_Open($port_num)) {
 | |
|                     $db_tgt->interfaces["{$ip}"]->update_UDP_Port($udp);
 | |
|                 }
 | |
|                 else {
 | |
|                     $db_tgt->interfaces["{$ip}"]->add_UDP_Ports($udp);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $db->save_Target($db_tgt);
 | |
|     }
 | |
| 
 | |
|     $count++;
 | |
|     $db_tgt = $db->get_Target_Details($conf['ste'], $tgt_id)[0];
 | |
| 
 | |
|     $hl               = new host_list();
 | |
|     $hl->setTargetId($db_tgt->get_ID());
 | |
|     $hl->setTargetName($db_tgt->get_Name());
 | |
|     $hl->setTargetIp($tgt_ip);
 | |
|     $hl->setFindingCount(0);
 | |
|     $hl->setScanError(false);
 | |
| 
 | |
|     $scan->add_Target_to_Host_List($hl);
 | |
|     $db->update_Running_Scan($base_name, ['name' => 'perc_comp', 'value' => ($count / count($target) * 100)]);
 | |
|     $db->update_Running_Scan($base_name, ['name' => 'last_host', 'value' => $db_tgt->get_Name()]);
 | |
| }
 | |
| 
 | |
| $db->update_Scan_Host_List($scan);
 | |
| $db->update_Running_Scan($base_name, ['name' => 'perc_comp', 'value' => 100, 'complete' => 1]);
 | |
| if (!isset($cmd['debug'])) {
 | |
|     rename($cmd['f'], TMP . "/nmap/" . $base_name);
 | |
| }
 | |
| 
 | |
| function usage()
 | |
| {
 | |
|     print <<<EOO
 | |
| Purpose: To import an NMap result file
 | |
| 
 | |
| Usage: php parse_nmap.php -s={ST&E ID} -f={NMap result file} -d={Document root} [--debug] [--help]
 | |
| 
 | |
|  -s={ST&E ID}       The ST&E ID this result file is being imported for
 | |
|  -f={NMap file}     The result file to import (will import text, XML, and grepable files)
 | |
|  -d={Document Root} The document root of the web server
 | |
| 
 | |
|  --debug            Debugging output
 | |
|  --help             This screen
 | |
| 
 | |
| EOO;
 | |
| }
 |