########################################################################## # package ScanTools.pm # This Perl module contains functions which employ scanners to probe hosts # V Who When What # --------------------------------------------------------------------------- # 1.7.5 skendric 02-13-2009 Skip routes for which we cannot find a mask # 1.7.4 skendric 07-03-2008 Wrap NetAddr::IP calls with eval # 1.7.3 skendric 07-02-2008 Change skip_routers to flag_router # 1.7.2 skendric 06-16-2008 More debugging # 1.7.1 skendric 03-12-2007 Stylistic mods # 1.7.0 skendric 09-23-2006 Employ Nmap::Parser callback to process # one host at a time, rather than the # entire result set at once # 1.6.9 skendric 08-06-2006 More error handling in gather_nmap_os_guess # 1.6.8 skendric 07-11-2006 Employ Net::IPAddress to validate IP addresses # 1.6.7 skendric 08-27-2005 Don't invoke SomaCrud routines when Nessus # scan fails # 1.6.6 skendric 07-12-2005 Support Nmap-Parser-1.01 # 1.6.5 skendric 07-05-2005 Add more printer-related strings to # nessus_exclude # 1.6.4 skendric 06-24-2005 Support new %routeTable hash # 1.6.3 skendric 06-15-2005 Create 'undef' for unresolved nodenames # 1.6.2 skendric 06-13-2005 Check subroutine parameters, ping Nessus # targets before processing them # 1.6.1 skendric 03-03-2005 Divide gather_nessus_vuln into two # versions: *_addr and *_route # 1.6.0 skendric 02-25-2005 Modify gather_nessus_vuln to take a list # of addresses rather than a route # 1.5.3 skendric 02-24-2005 Add more debugging traces # 1.5.2 skendric 02-14-2005 Add logging to gather_nessus_vuln # 1.5.1 skendric 02-13-2005 Error checking # 1.5.0 skendric 02-11-2005 Add iu_* functions to Nessus scan routine # 1.4.0 skendric 02-06-2005 Streamline Nessus scan routine # 1.3.0 skendric 02-04-2005 Remove database updates from Nessus # scan routines # 1.2.2 skendric 01-26-2005 Identify printers using nmap # 1.2.1 skendric 01-23-2005 Expand Nessus excludes # 1.2.0 skendric 01-12-2005 Add Nessus exclude lists and prefs override # 1.1.0 skendric 01-03-2005 Integrate database updates into nessus # scan routines # 1.0.0 skendric 12-06-2004 First version # # # # Authors: Stuart Kendrick # # Source: http://www.skendric.com/soma # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # package FHCRC::VDOPS::ScanTools; #### Load modules #### use strict; use warnings; use threads; use threads::shared; use Carp qw(carp cluck croak confess); use Data::Dumper; use English; use Exporter; use FileHandle; # Fixes weird "Can't locate object method read via package # FileHandle at ... XML/Parser/Expat.pm line 469" message use NetAddr::IP; use Net::IPAddress qw(validaddr); #use Net::Nessus::ScanLite; use Nmap::Parser 1.05; use Perl6::Say; use lib '/home/soma/lib'; use FHCRC::VDOPS::DBTools; use FHCRC::VDOPS::HostTools; use FHCRC::VDOPS::NetworkTools; use FHCRC::VDOPS::PingTools; use FHCRC::VDOPS::QuerySoma; use FHCRC::VDOPS::SomaCrud; use FHCRC::VDOPS::SomaData; use FHCRC::VDOPS::Utilities; #### Set-up export stuff #### our @ISA = qw(Exporter); our @EXPORT = qw( gather_nmap_os_guess gather_nessus_vuln_addr gather_nessus_vuln_route override_nessus_prefs ); # Declare package local variables ##### Only subroutines below here #### ####################################################################### # Given a reference to a list of addresses: # (a) Check route includes/excludes # (b) loop thru the list of addresses # (c) check the current address and associated nodename against # various %nessusExclude* globals # (d) launch a Nessus scan # (e) submit the results to Soma ######################################################################## sub gather_nessus_vuln_addr { my $targets = shift; # Ref to a list of IP addresses my $code; # Temp variable for $nessus->code my $error; # Temp variable for $nessus->error my $loginResult; # Success/failure for login invocation my $mask; # Subnet mask associated with $route my $prefs; # Local copy of the global $nessusPrefs my $route = shift; # Route within which $addrs live my $target; # IP address to scan # Debug trace trace_location('begin') if $debug > 2; # Sanity checking confess 'Not enough parameters' unless (defined $targets and defined $route); confess 'Wrong type for first parameter' unless ref $targets eq 'ARRAY'; confess "$route does not exist in route table" unless exists $routeTable{$route}; # If this route is on the exclude list, skip it if ( exists($nessusExcludeRoute{$route}) ) { log_it("Skipping Nessus scan on $route/$routeTable{$route}: belongs to nessusExcludeRoute") if $logNessusSkips or $debug; } # Unless this route is on the 'include' list, skip it elsif (not exists($nessusIncludeRoute{$route})) { log_it("Skipping Nessus scan on $route/$routeTable{$route}: does not belong to nessusIncludeRoute") if $logNessusSkips or $debug; } # Otherwise, we're allowed to scan this route, keep going else { # Walk the targets for my $target (@$targets) { undef $loginResult; # Check excludes next if nessus_exclude($target); # Ping it next unless ping_it($target); # If we have made it this far, scan it undef $loginResult; # Debug info print_it("Processing $target\n") if $debug > 2; # Define object my $nessus = Net::Nessus::ScanLite->new( host => $nessusHost, port => $nessusPort, user => $nessusUser, password => $nessusPass, ssl => $nessusTLS, ); $nessus->preferences($nessusPrefs); # Login $loginResult = $nessus->login(); if ($loginResult == 1) { say 'Nessus login succeeded' if $debug > 2; } else { $code = $nessus->code; $error = $nessus->error; say "Nessus login failed: $code\n$error" if $debug; log_it("Nessus login failed: $code\n$error\n"); } # Launch scan if ($loginResult == 1) { say "Launching Nessus scan on $target" if $debug > 2; eval { $nessus->attack($target) }; if ($@) { print_it("Nessus scan failed for $target: $@"); undef $nessus; } else { say "Nessus scan launched on $target" if $debug > 2; log_it("Nessus scan launched on $target"); } # Hand the results to Soma if (defined $nessus) { iu_vulnerabilities($nessus); log_it("Entering iu_host_vulnerabilities for $target"); iu_host_vulnerabilities($target, $nessus); log_it("Entering iu_corrected_host_vulnerabilities for $target"); iu_corrected_host_vulnerabilities($target, $nessus); log_it("Finished scanning $target"); } # Debug info if ($debug > 3) { say "Nessus scan completed on $target"; nessus_print_holes($target, $nessus) if defined $nessus; } } # Finish 'Launch scan' } # Finish 'walk the targets' } # Finish 'otherwise keep going' # Debug trace trace_location('end') if $debug > 2; return 1; } ####################################################################### # Given a route, # (a) build a list of the addresses within the route # (b) loop thru the list of addresses # (c) check the current address and associated nodename against # various %nessusExclude* globals # (d) launch a Nessus scan # (e) submit the results to Soma ######################################################################## sub gather_nessus_vuln_route { my $code; # Temp variable for $nessus->code my $error; # Temp variable for $nessus->error my $ip; # NetAddr::IP object my $loginResult; # Success/failure for login invocation my $mask; # Subnet mask associated with $route my $prefs; # Local copy of the global $nessusPrefs my $route = shift; # Route to analyze my $route_obj; # NetAddr::IP object created from $route my $target; # IP address to scan # Debug trace trace_location('begin') if $debug > 2; # Sanity checking confess 'No parameters' unless defined $route; confess "Cannot find $route in route table" unless exists $routeTable{$route}; confess "Cannot find mask for $route" unless defined $routeTable{$route}; # Assign local variables $mask = $routeTable{$route}; # If this route is on the exclude list, skip it if ( exists($nessusExcludeRoute{$route}) ) { log_it("Skipping Nessus scan on $route/$mask: belongs to nessusExcludeRoute") if ($logNessusSkips or $debug); } # Unless this route is on the 'include' list, skip it elsif (not exists($nessusIncludeRoute{$route})) { log_it("Skipping Nessus scan on $route/$mask: not an included route") if ($logNessusSkips or $debug); } # Otherwise, we're allowed to scan this route, keep going else { my $addresses; # Create NetAddr::IP object eval { $route_obj = NetAddr::IP->new($route, $mask) }; if ($@ or not defined $route_obj) { log_it("Bad route $route / $mask: $@"); goto END; } # Build list of addresses within this route $addresses = $route_obj->hostenumref(); # Walk the hosts, checking excludes and launching Nessus ADDR: for my $addr (@$addresses) { ($target) = ($addr =~ /(\d+\.\d+\.\d+\.\d+)\/32/); # Ping it next ADDR unless ping_it($target); # Check excludes next ADDR if nessus_exclude($target); # Log into the Nessus server undef $loginResult; # Debug info print_it("Processing $target\n") if $debug > 2; # Define object my $nessus = Net::Nessus::ScanLite->new( host => $nessusHost, port => $nessusPort, user => $nessusUser, password => $nessusPass, ssl => $nessusTLS, ); $nessus->preferences($nessusPrefs); # Login $loginResult = $nessus->login(); if ($loginResult == 1) { say 'Nessus login succeeded' if $debug > 2; } else { $code = $nessus->code; $error = $nessus->error; say "Nessus login failed: $code\n$error" if $debug; log_it("Nessus login failed: $code\n$error\n"); } # Launch scan if ($loginResult == 1) { say "Launching Nessus scan on $target" if $debug > 2; eval { $nessus->attack($target) }; if ($@) { print_it("Nessus scan failed for $target: $@"); undef $nessus; } else { say "Nessus scan launched on $target" if $debug > 2; log_it("Nessus scan launched on $target"); } # Hand the results to Soma if (defined $nessus) { iu_vulnerabilities($nessus); iu_host_vulnerabilities($target, $nessus); iu_corrected_host_vulnerabilities($target, $nessus); } # Debug info if ($debug > 3) { say "Nessus scan completed on $target"; nessus_print_holes($target, $nessus) if defined $nessus; } } # Finish 'if we have made it this far, attack it' } # Finish walking the hosts } # Finish 'otherwise keep going' # Debug trace trace_location('end') if $debug > 2; return 1; } ####################################################################### # Given a route, use Nmap to populate %osFamily, %osVendor, and # %osVersion ######################################################################## sub gather_nmap_os_guess { my $cidrmask; # Subnet mask, in bits, associated with $route my $route = shift; # IP route my $mask; # Subnet mask, in decimal, associated with $route my $network; # "$route/$mask" where mask is expressed in # number of bits my $np; # Nmap::Parser object my $route_obj; # NetAddr::IP object created from $route # Debug trace trace_location('begin') if $debug; # Sanity checking confess 'No parameters' unless defined $route; confess "Cannot find $route in route table" unless exists $routeTable{$route}; confess "Cannot find mask for $route" unless defined $routeTable{$route}; confess "$nmap does not exist" unless -e $nmap; confess "Cannot execute $nmap" unless -x $nmap; confess "Must have root to run $nmap" unless $EUID == 0 or -u $nmap; # Assign variables $mask = $routeTable{$route}; # Create NetAddr::IP object eval { $route_obj = NetAddr::IP->new($route, $mask) }; if ($@ or not defined $route_obj) { log_it("Bad route $route / $mask: $@"); goto END; } # Define $network $cidrmask = $route_obj->masklen(); $network = $route . '/' . $cidrmask; # Define scanner $np = Nmap::Parser->new; $np->callback(\&parse_nmap_results); # Perform the scan log_it("Beginning $nmap $nmapParam $network"); $np->parsescan($nmap, $nmapParam, $network); log_it("Ending $nmap $nmapParam $network"); # Debug trace trace_location('end') if $debug; return 1; } ####################################################################### # Given an IP address, check whether or not the address belongs to any # of the nessusExclude* lists. Also, check to see if Soma thinks this is # a printer. Return 1 if it does, 0 if it doesn't ######################################################################## sub nessus_exclude { my $addr = shift; # IP address to test my $flag; # "1" for found, "0" for not found my $mac; # hosts.mac for $addr my $nodename; # nodename of $addr my $osName; # version_name.operating_systems for $addr my $suffix; # Suffix (delimited by "-") on $nodename, generally # undef my $sysDescr; # hosts.snmp_sys_descr for $addr my $sysObjectID; # hosts.snmp_sys_descr for $addr # Debug trace trace_location('begin') if $debug; # Sanity checking confess 'No paramters' unless defined $addr; confess "$addr is not an IP address" unless validaddr($addr); # Initialize variables $flag = 0; # Find nodename $nodename = get_nodename($addr); $nodename = "undef" unless defined $nodename; ($suffix) = ($nodename =~ /(\-\w+$)/); # Check $nessusExcludeNode if ( exists($nessusExcludeNode{$nodename}) ) { say "Skipping $nodename, belongs to nessusExcludeNode" if $debug > 2; $flag = 1; } # Check $nessusExcludeAddr if ( $flag == 0 and exists($nessusExcludeAddr{$addr}) ) { say "Skipping $addr, belongs to nessusExcludeAddr" if $debug > 2; $flag = 1; } # Check $nessusExcludeSuffix if ($flag == 0 and defined $suffix) { if ( exists($nessusExcludeSuffix{$suffix}) ) { say "Skipping $nodename, belongs to nessusExcludeSuffix" if $debug > 2; $flag = 1; } } # Check $nessusExcludeString if ($flag == 0) { for my $string (keys %nessusExcludeString) { if ($nodename =~ /$string/) { $flag = 1; last; } } say "Skipping $nodename, belongs to nessusExcludeString" if ($flag and $debug > 2); } # Check to see if Soma thinks this is a printer if ($flag == 0 ) { # Ask Soma about SNMP variables ($mac, $sysDescr, $sysObjectID) = ask_snmp($addr); if (defined $sysDescr) { if ($sysDescr =~ /brother|canon|efi|jetdirect|konica|phaser|print/i) { $flag = 1; } } if (defined $sysObjectID) { if ($sysObjectID =~ /print/i) { $flag = 1; } } # Ask Soma about osName if ($flag == 0) { if (defined $mac) { ($mac, $osName) = ask_osname($addr); if (defined $osName and $osName =~ /print/i) { $flag = 1; } } } say "Skipping $nodename/$addr, this is a printer" if ($flag and $debug > 2); } # Logging if ($flag == 1) { if ($logNessusSkips or $debug > 0) { log_it("Skipping Nessus scan of $nodename/$addr because it belongs to an exclude list"); } } # Debug trace trace_location('end') if $debug; return $flag; } ####################################################################### # Given an address and a reference to a Net::Nessus::ScanLite object, # print the holes ######################################################################## sub nessus_print_holes { my $addr = shift; my $nodename; my $nessus = shift; # Debug trace trace_location('begin') if $debug; # Sanity checking confess 'Not enough paramters' unless (defined $addr and defined $nessus); confess "$addr is not an IP address" unless validaddr($addr); # Make things look pretty say "\n"; # Define $nodename if (defined $nodename{$addr}) { $nodename = $nodename{$addr}; } else { $nodename = get_nodename($addr); $nodename{$addr} = $nodename; } # Do the work printf("For host %s, total holes = %d\n", $nodename, $nessus->total_holes); for my $hole ($nessus->hole_list) { my ($id, $port, $service, $protocol, $description); $id = $hole->ScanID; $port = $hole->Port; $service = $hole->Service; $protocol = $hole->Proto; $description = $hole->Description; say "ID: $id" if defined $id; say "Port: $port" if defined $port; say "Service: $service" if defined $service; say "Protocol: $protocol" if defined $protocol; say "Description: $description" if defined $description; } # Make things look pretty say "\n"; # Debug trace trace_location('end') if $debug; return 1; } ####################################################################### # Given a reference to a hash of Nessus preferences, selectively # override the entries in the global %nessusPrefs hash ######################################################################## sub override_nessus_prefs { my $override = shift; # Debug trace trace_location('begin') if $debug; # Sanity checking confess 'No paramters' unless defined $override; confess 'Wrong type for parameter' unless ref $override eq 'HASH'; # Walk the $override hash for my $key (keys %$override) { log_it("Overriding $key = $nessusPrefs->{$key} with '$override->{$key}'\n"); $nessusPrefs->{$key} = $override->{$key}; } # Debug trace trace_location('end') if $debug; return 1; } ####################################################################### # This is an Nmap::Parser callback function for interpreting the results # of an Nmap scan and entering those results in to global data # data structures ######################################################################## sub parse_nmap_results { my $addr_obj; my $host = shift; my $mask; # Debug trace trace_location('begin') if $debug; # Sanity checking confess 'No parameters' unless defined $host; # Variables acquired from Nmap::Parser host object my ($ip, $os, $osfamily, $osname, $nameCount, $osgen, $osvendor, $ostype); # Acquire os_sig object $os = $host->os_sig(); # Populate variables $nameCount = $os->name_count(); $ip = $host->addr; $osname = $os->name(); $osfamily = $os->osfamily(); $osgen = $os->osgen(); $osvendor = $os->vendor(); $ostype = $os->type(); # If IP address is messed up, skip unless (validaddr($ip)) { log_it("IP $ip for host $host is messed up in parse_nmap_results"); goto END; } # Find mask $mask = find_mask($ip); goto END unless defined $mask; # Create NetAddr::IP object eval { $addr_obj = NetAddr::IP->new($ip, $mask) }; if ($@ or not defined $addr_obj) { log_it("Bad addr $ip / $mask: $@"); goto END; } # Skip routers goto END if flag_router($addr_obj); # If Nmap didn't identify vendor, skip goto END unless defined $osvendor; # Make sure everything is defined $nameCount = 'unknown' unless defined $nameCount; $osname = 'unknown' unless defined $osname; $osfamily = 'unknown' unless defined $osfamily; $osgen = 'unknown' unless defined $osgen; $osvendor = 'unknown' unless defined $osvendor; $ostype = 'unknown' unless defined $ostype; # Populate %osName my $osName; if ($ostype eq 'printer') { $osName = $osvendor . $DASH . 'printer'; } elsif ($osfamily eq 'embedded') { $osName = $osvendor . $DASH . 'embedded'; } else { $osName = $osfamily; } $osName{$ip} = $osvendor . $COLON . $osName; # Populate %osVendor $osVendor{$ip} = $osvendor; # Populate %osVersion. $verName is what Soma users see in reports my $verName; if ($osname eq 'unknown') { $verName = $osName . $SPACE . $osgen; } else { $verName = $osname; } $osVersion{$ip} = $osName . ',' . $osgen . ',' . 'unknown' . ',' . $verName; # Debug info if ($debug > 3) { say "For $ip"; say " nameCount = $nameCount"; say " name = $osname"; say " osfamily = $osfamily"; say " osgen = $osgen"; say " vendor = $osvendor"; say " type = $ostype"; say " osVersion = $osVersion{$ip}"; say(); } END: # Debug trace trace_location('end') if $debug; return 1; } ####################################################################### # Delete all port scanners except for the one specified in # $nessusFavScanner. This is so gross ... but I haven't figured out a # way to do this using PREFERENCES messages ######################################################################## ####################################################################### # Delete all port scanners except for the one specified in # $nessusFavScanner. This is so gross ... but I haven't figured out a # way to do this using PREFERENCES messages ######################################################################## sub yank_nessus_port_scanners { my $file; # Debug trace trace_location('begin') if $debug; # Delete unwanted port scanners for my $scanner (@nessusScanner) { $file = $nessusPluginDir . "/" . $scanner; if ($scanner ne $nessusFavScanner) { if (-e $file) { unless (unlink $file) { log_it ("Cannot delete $file; nessusd will invoke this port scanner"); } } } } # Debug trace trace_location('end') if $debug; return 1; } 1;