1#!/usr/local/bin/perl 2use strict; 3#VERSION,2.1.6 4############################################################################### 5# Modules are now loaded in a function so errors can be trapped and evaluated 6load_modules(); 7############################################################################### 8# Nikto # 9############################################################################### 10# Copyright (C) 2001 CIRT, Inc. 11# 12# This program is free software; you can redistribute it and/or 13# modify it under the terms of the GNU General Public License 14# as published by the Free Software Foundation; version 2 15# of the License only. 16# 17# This program is distributed in the hope that it will be useful, 18# but WITHOUT ANY WARRANTY; without even the implied warranty of 19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20# GNU General Public License for more details. 21# 22# You should have received a copy of the GNU General Public License 23# along with this program; if not, write to 24# Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25# 26# Contact Information: 27# Sullo (sullo@cirt.net) 28# http://cirt.net/ 29####################################################################### 30# See the LICENSE.txt file for more information on the License Nikto is distributed under. 31# 32# This program is intended for use in an authorized manner only, and the author 33# can not be held liable for anything done with this program, code, or items discovered 34# with this program's use. 35####################################################################### 36 37# global var/definitions 38use vars qw/$TEMPLATES %CLI %VARIABLES %TESTS/; 39use vars qw/%NIKTO %CONFIGFILE %COUNTERS %db_extensions/; 40use vars qw/@RESULTS @PLUGINS @DBFILE @REPORTS %CONTENTSEARCH/; 41 42# setup 43Getopt::Long::Configure('no_ignore_case'); 44$COUNTERS{'scan_start'} = time(); 45$VARIABLES{'DIV'} = "-" x 75; 46$VARIABLES{'name'} = "Nikto"; 47$VARIABLES{'version'} = "2.1.6"; 48$VARIABLES{'configfile'} = "/usr/local/etc/nikto.conf"; ### Change if it's having trouble finding it 49 50# signal trap so we can close down reports properly 51$SIG{'INT'} = \&safe_quit; 52 53config_init(); 54setup_dirs(); 55require "$CONFIGFILE{'PLUGINDIR'}/nikto_core.plugin"; 56nprint("T:" . localtime($COUNTERS{'scan_start'}) . ": Starting", "d"); 57require "$CONFIGFILE{'PLUGINDIR'}/LW2.pm"; 58$VARIABLES{'GMTOFFSET'} = gmt_offset(); 59 60# use LW2; ### Change this line to use a different installed version 61 62#set SSL Engine 63LW2::init_ssl_engine($CONFIGFILE{'LW_SSL_ENGINE'}); 64 65my ($a, $b) = split(/\./, $LW2::VERSION); 66die("- You must use LW2 2.4 or later\n") if ($a != 2 || $b < 4); 67 68general_config(); 69load_databases(); 70load_databases('u'); 71nprint("- $VARIABLES{'name'} v$VARIABLES{'version'}"); 72nprint($VARIABLES{'DIV'}); 73 74# No targets - quit while we're ahead 75if ($CLI{'host'} eq "") { 76 nprint("+ ERROR: No host specified"); 77 usage(); 78} 79 80$COUNTERS{'total_targets'} = $COUNTERS{'hosts_completed'} = 0; 81load_plugins(); 82 83# Parse the supplied list of targets 84my @MARKS = set_targets($CLI{'host'}, $CLI{'ports'}, $CLI{'ssl'}, $CLI{'root'}); 85 86if (defined($CLI{'key'}) || defined($CLI{'cert'})) { 87 $CLI{'key'} = $CLI{'cert'} unless (defined($CLI{'key'})); 88 $CLI{'cert'} = $CLI{'key'} unless (defined($CLI{'cert'})); 89} 90 91# Now check each target is real and remove duplicates/fill in extra information 92foreach my $mark (@MARKS) { 93 $mark->{'test'} = 1; 94 $mark->{'failures'} = 0; 95 96 # Try to resolve the host 97 ($mark->{'hostname'}, $mark->{'ip'}, $mark->{'display_name'}) = resolve($mark->{'ident'}); 98 99 # Skip if we can't resolve the host - we'll error later 100 if (!defined $mark->{'ip'}) { 101 $mark->{'test'} = 0; 102 next; 103 } 104 105 # Check that the port is open 106 my $open = 107 port_check(time(), $mark->{'hostname'}, $mark->{'ip'}, $mark->{'port'}, $CLI{'key'}, $CLI{'cert'}); 108 if (defined $CLI{'vhost'}) { $mark->{'vhost'} = $CLI{'vhost'} } 109 if ($open == 0) { 110 $mark->{'test'} = 0; 111 next; 112 } 113 else { 114 $COUNTERS{'total_targets'}++; 115 } 116 $mark->{'ssl'} = $open - 1; 117 118 if ($mark->{'ssl'}) { 119 $mark->{'key'} = $CLI{'key'}; 120 $mark->{'cert'} = $CLI{'cert'}; 121 } 122} 123 124# Open reporting 125report_head($CLI{'format'}, $CLI{'file'}); 126 127# Load db_tests 128set_scan_items(); 129 130# Start hook to allow plugins to load databases etc 131run_hooks("", "start"); 132 133# Now we've done the precursor, do the scan 134foreach my $mark (@MARKS) { 135 next unless ($mark->{'test'}); 136 $mark->{'start_time'} = time(); 137 $VARIABLES{'TEMPL_HCTR'}++; 138 139 if (defined $CLI{'vhost'}) { 140 $mark->{'vhost'} = $CLI{'vhost'}; 141 } 142 143 # Saving responses 144 if ($CLI{'saveresults'} ne '') { 145 $mark->{'save_dir'} = save_createdir($CLI{'saveresults'}, $mark); 146 $mark->{'save_prefix'} = save_getprefix($mark); 147 } 148 149 # Cookies 150 if (defined $CONFIGFILE{'STATIC-COOKIE'}) { 151 $mark->{'cookiejar'} = LW2::cookie_new_jar(); 152 153 # parse conf line into name/value pairs 154 foreach my $p (split(/;/, $CONFIGFILE{'STATIC-COOKIE'})) { 155 $p =~ s/(?:^\s+|\s+$)//; 156 $p =~ s/"(?:[ ]+)?=(?:[ ]+)?"/","/g; 157 my @cv = parse_csv($p); 158 159 # Set into the jar 160 LW2::cookie_set(\%{ $mark->{'cookiejar'} }, $cv[0], $cv[1]); 161 } 162 } 163 164 $mark->{'total_vulns'} = 0; 165 $mark->{'total_errors'} = 0; 166 167 my %FoF = (); 168 169 nfetch($mark, "/", "GET", "", "", { noprefetch => 1, nopostfetch => 1 }, "getinfo"); 170 171 report_host_start($mark); 172 if ($CLI{'findonly'}) { 173 my $protocol = "http"; 174 if ($mark->{'ssl'}) { $protocol .= "s"; } 175 if ($mark->{'banner'} eq "") { 176 $mark->{'banner'} = "(no identification possible)"; 177 } 178 179 add_vulnerability($mark, 180 "Server: $protocol://$mark->{'display_name'}:$mark->{'port'}\t$mark->{'banner'}", 181 0); 182 } 183 else { 184 dump_target_info($mark); 185 unless ((defined $CLI{'nofof'}) || ($CLI{'plugins'} eq '@@NONE')) { map_codes($mark) } 186 run_hooks($mark, "recon"); 187 run_hooks($mark, "scan"); 188 } 189 $mark->{'end_time'} = time(); 190 $mark->{'elapsed'} = $mark->{'end_time'} - $mark->{'start_time'}; 191 if (!$CLI{'findonly'}) { 192 if (!$mark->{'terminate'}) { 193 nprint( 194 "+ $COUNTERS{'totalrequests'} requests: $mark->{'total_errors'} error(s) and $mark->{'total_vulns'} item(s) reported on remote host" 195 ); 196 } 197 else { 198 nprint( 199 "+ Scan terminated: $mark->{'total_errors'} error(s) and $mark->{'total_vulns'} item(s) reported on remote host" 200 ); 201 } 202 nprint( "+ End Time: " 203 . date_disp($mark->{'end_time'}) 204 . " (GMT$VARIABLES{'GMTOFFSET'}) ($mark->{'elapsed'} seconds)"); 205 } 206 nprint($VARIABLES{'DIV'}); 207 208 $COUNTERS{'hosts_completed'}++; 209 report_host_end($mark); 210} 211$COUNTERS{'scan_end'} = time(); 212$COUNTERS{'scan_elapsed'} = ($COUNTERS{'scan_end'} - $COUNTERS{'scan_start'}); 213report_summary(); 214report_close(); 215 216if (!$CLI{'findonly'}) { 217 nprint("+ $COUNTERS{'hosts_completed'} host(s) tested"); 218 nprint("+ $COUNTERS{'totalrequests'} requests made in $COUNTERS{'scan_elapsed'} seconds", "v"); 219 220 send_updates(@MARKS); 221} 222 223nprint("T:" . localtime() . ": Ending", "d"); 224 225exit; 226 227################################################################################# 228# Load config files in order 229sub config_init { 230 231 # read just the --config option 232 { 233 my %optcfg; 234 Getopt::Long::Configure('pass_through', 'noauto_abbrev'); 235 GetOptions(\%optcfg, "config=s"); 236 Getopt::Long::Configure('nopass_through', 'auto_abbrev'); 237 if (defined $optcfg{'config'}) { $VARIABLES{'configfile'} = $optcfg{'config'}; } 238 } 239 240 # Read the config files in order 241 my ($error, $home); 242 my $config_exists = 0; 243 $error = load_config("$VARIABLES{'configfile'}"); 244 $config_exists = 1 if ($error eq ""); 245 246 # Guess home directory -- to support Windows 247 foreach my $var (split(/ /, "HOME USERPROFILE")) { 248 $home = $ENV{$var} if ($ENV{$var}); 249 } 250 $error = load_config("$home/nikto.conf"); 251 $config_exists = 1 if ($error eq ""); 252 253 # Guess Nikto current directory 254 my $NIKTODIR = $0; 255 chomp($NIKTODIR); 256 $NIKTODIR =~ s#[\\/]nikto.pl$##; 257 $error = load_config("$NIKTODIR/nikto.conf"); 258 $config_exists = 1 if ($error eq ""); 259 260 $error = load_config("nikto.conf"); 261 $config_exists = 1 if ($error eq ""); 262 263 if ($config_exists == 0) { 264 die "- Could not find a valid nikto config file\n"; 265 } 266 267 return; 268} 269 270############################################################################### 271sub load_modules { 272 my $errors=0; 273 my @modules = qw/Getopt::Long Time::Local IO::Socket/; 274 push(@modules,"List::Util qw(sum)"); 275 foreach my $mod (@modules) { 276 eval "use $mod"; 277 if ($@) { 278 print "ERROR: Required module not found: $mod\n"; 279 $errors=1; 280 } 281 } 282 283 @modules = (); 284 push(@modules,"Time::HiRes qw(ualarm gettimeofday tv_interval)"); 285 push(@modules,"POSIX qw(:termios_h)"); 286 foreach my $mod (@modules) { 287 eval "use $mod"; 288 if ($@ && $^O !~ /MSWin32/) { 289 # Allow this to work on Windows 290 if ($@) { print "ERROR: Required module not found: $mod\n"; $errors=1; } 291 } 292 } 293 294 if ($errors) { exit; } 295} 296 297################################################################################# 298# load config file 299# error=load_config(FILENAME) 300sub load_config { 301 my $configfile = $_[0]; 302 303 open(CONF, "<$configfile") || return "+ ERROR: Unable to open config file '$configfile'"; 304 my @CONFILE = <CONF>; 305 close(CONF); 306 307 foreach my $line (@CONFILE) { 308 $line =~ s/\#.*$//; 309 chomp($line); 310 $line =~ s/\s+$//; 311 $line =~ s/^\s+//; 312 next if ($line eq ""); 313 my @temp = split(/=/, $line, 2); 314 if ($temp[0] ne "") { $CONFIGFILE{ $temp[0] } = $temp[1]; } 315 } 316 317 # add CONFIG{'CLIOPTS'} to ARGV if defined... 318 if (defined $CONFIGFILE{'CLIOPTS'}) { 319 my @t = split(/ /, $CONFIGFILE{'CLIOPTS'}); 320 foreach my $c (@t) { push(@ARGV, $c); } 321 } 322 323 # Check for necessary config items 324 check_config_defined("CHECKMETHODS", "HEAD"); 325 check_config_defined('@@MUTATE', 'dictionary;subdomain'); 326 check_config_defined('@@DEFAULT', '@@ALL,-@@MUTATE'); 327 328 return ""; 329} 330################################################################################# 331# find plugins directory 332sub setup_dirs { 333 my $CURRENTDIR = $0; 334 chomp($CURRENTDIR); 335 $CURRENTDIR =~ s#[\\/]nikto.pl$##; 336 $CURRENTDIR = "." if $CURRENTDIR =~ /^nikto.pl$/; 337 338 # First assume we get it from CONFIGFILE 339 unless (defined $CONFIGFILE{'EXECDIR'}) { 340 if (-d "$ENV{'PWD'}/plugins") { 341 $CONFIGFILE{'EXECDIR'} = $ENV{'PWD'}; 342 } 343 elsif (-d "$CURRENTDIR/plugins") { 344 $CONFIGFILE{'EXECDIR'} = $CURRENTDIR; 345 } 346 elsif (-d "./plugins") { 347 $CONFIGFILE{'EXECDIR'} = $CURRENTDIR; 348 } 349 else { 350 print STDERR "Could not work out the nikto EXECDIR, try setting it in nikto.conf\n"; 351 exit; 352 } 353 } 354 unless (defined $CONFIGFILE{'PLUGINDIR'}) { 355 $CONFIGFILE{'PLUGINDIR'} = "$CONFIGFILE{'EXECDIR'}/plugins"; 356 } 357 unless (defined $CONFIGFILE{'TEMPLATEDIR'}) { 358 $CONFIGFILE{'TEMPLATEDIR'} = "$CONFIGFILE{'EXECDIR'}/templates"; 359 } 360 unless (defined $CONFIGFILE{'DOCDIR'}) { 361 $CONFIGFILE{'DOCDIR'} = "$CONFIGFILE{'EXECDIR'}/docs"; 362 } 363 unless (defined $CONFIGFILE{'DBDIR'}) { 364 $CONFIGFILE{'DBDIR'} = "$CONFIGFILE{'EXECDIR'}/databases"; 365 } 366 return; 367} 368 369###################################################################### 370## check_config_defined(item, default) 371## Checks whether config has been set, warns and sets to a default 372sub check_config_defined { 373 my $item = $_[0]; 374 my $default = $_[1]; 375 376 if (!defined $CONFIGFILE{$item}) { 377 print STDERR 378 "- Warning: $item is not defined in Nikto configuration, setting to \"$default\"\n"; 379 $CONFIGFILE{$item} = $default; 380 } 381 382 return; 383} 384