source: trunk/plugins/nikto_core.plugin @ 268

Revision 268, 74.9 KB checked in by sullo, 3 years ago (diff)

Removed http_eol global -- it's set in LW automagically
Code cleanups

  • Property svn:keywords set to Id
Line 
1#VERSION,2.1.1
2# $Id$
3###############################################################################
4#  Copyright (C) 2006 CIRT, Inc.
5
6#  This program is free software; you can redistribute it and/or
7#  modify it under the terms of the GNU General Public License
8#  as published by the Free Software Foundation; version 2
9#  of the License only.
10
11#  This program is distributed in the hope that it will be useful,
12#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14#  GNU General Public License for more details.
15
16#  You should have received a copy of the GNU General Public License
17#  along with this program; if not, write to the Free Software
18#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19###############################################################################
20# PURPOSE:
21# Nikto core functionality
22###############################################################################
23sub change_variables
24{
25   # $line is the unfiltered variable
26   my $line=$_[0];
27   my @subtests; # @subtests is the returned array of expanded variables
28   my $cooked;
29
30   my $shname=$mark->{hostname} || $mark->{ip};
31   $line =~ s/\@IP/$mark->{ip}/g;
32   $line =~ s/\@HOSTNAME/$shname/g;
33   $line =~ s/JUNK\(([0-9]+)\)/LW2::utils_randstr($1)/e;
34
35   if ($line !~ "\@")
36   {
37      push(@subtests, $line);
38   }
39   else
40   {
41      foreach my $varname (keys %VARIABLES)
42      {
43         if ($line =~ "$varname")
44         {
45            # We've found the variable; now to expand it!
46            foreach my $value (split(/ /, $VARIABLES{$varname}))
47            {
48               $cooked = $line;
49               $cooked =~ s/$varname/$value/g;
50               push(@subtests, change_variables($cooked));
51            }
52         }
53      }
54   }
55
56   return @subtests;
57}
58
59###############################################################################
60sub is_404
61{
62 my ($uri, $content, $rescode) = @_;
63 $ext=get_ext($uri);
64
65 if   (($FoF{$ext}{mode} eq "STD") && (($rescode eq 401) || ($rescode eq 403) || ($rescode eq 404) || ($rescode eq 410))) { return 1; }
66 elsif ($FoF{$ext}{mode} eq "STD")  { return 0; }
67 elsif ($FoF{$ext}{mode} eq "REDIR")
68  {
69   if ($result{location} eq $FoF{$ext}{location}) { return 1; }
70  }
71 elsif (($FoF{$ext}{type} eq "CONTENT")  && ($content =~ /$FoF{$ext}{match}/i))    { return 1; }
72 elsif (($FoF{$ext}{type} eq "BLANK") && ($content eq ""))                         { return 1; }
73 elsif ($FoF{$ext}{type} eq "HASH")
74   {
75     my $content=rm_active_content($content, $uri);
76     if (LW2::md4($content) eq $FoF{$ext}{match}) { return 1; }
77   }
78return 0;
79}
80###############################################################################
81sub nprint
82{
83   my $line=$_[0];
84   chomp($line);
85   # don't print debug & verbose to output file...
86   if (defined $_[1])
87   {
88      if ($_[1] eq "d" && $OUTPUT{debug})
89      {
90         print "D:" . localtime() . " $line\n";
91      }
92      if ($_[1] eq "v" && $OUTPUT{verbose})
93      {
94         print "V:" . localtime() . " $line\n";
95      }
96      return;
97   }
98   # print errors to STDERR
99   if ($line =~ /^+ ERROR\:/) { print STDERR "$line\n"; return; }
100   
101   # don't print to STDOUT if output file is "-"
102   if ($CLI{'file'} eq "-") { return; }
103
104   # print to scan details to standard output if the users wants another format and is saving results to a file
105   $line =~ s/((CVE|CAN)\-[0-9]{4}-[0-9]{4})/http:\/\/cve.mitre.org\/cgi-bin\/cvename.cgi?name\=$1/g;
106   $line =~ s/(CA\-[0-9]{4}-[0-9]{2})/http:\/\/www.cert.org\/advisories\/$1.html/g;
107   $line =~ s/BID\-([0-9]{4})/http:\/\/www.securityfocus.com\/bid\/$1/g;
108   $line =~ s/(IN\-[0-9]{4}\-[0-9]{2})/http:\/\/www.cert.org\/incident_notes\/$1.html/gi;
109   $line =~ s/(MS[0-9]{2}\-[0-9]{3})/http:\/\/www.microsoft.com\/technet\/security\/bulletin\/$1.asp/gi;
110   print "$line\n";
111 
112   return;
113}
114###############################################################################
115sub get_ext
116{
117 my $uri=$_[0] || return;
118 if ($uri =~ /\/$/) { return "DIRECTORY"; }
119 $uri =~ s/^.*\///;
120 $uri =~ s/(\?|\&|\%).*$//;
121 if ($uri =~ /^\.[^.%]/) { return "DOTFILE"; }
122 if ($uri !~ /\./) { return "NONE"; }
123 $uri =~ s/\".*$//;
124 $uri =~ s/^.*\.//;
125 return $uri;
126}
127###############################################################################
128sub date_disp
129{
130 my @time=localtime($_[0]);
131 $time[5]+=1900;
132 $time[4]++; if (length($time[4]) eq 1) { $time[4]="0$time[4]"; }
133 $time[3]++; if (length($time[3]) eq 1) { $time[3]="0$time[3]"; }
134 if (length($time[0]) eq 1) { $time[0]="0$time[0]"; }
135 if (length($time[1]) eq 1) { $time[1]="0$time[1]"; }
136 if (length($time[2]) eq 1) { $time[0]="0$time[2]"; }
137 return "$time[5]-$time[4]-$time[3] $time[2]:$time[1]:$time[0]";
138}
139###############################################################################
140sub map_codes
141{
142 my %REQS;
143 my $rs=LW2::utils_randstr(8);
144
145 # / for OK response
146 $NIKTO{totalrequests}++;
147 $request{'whisker'}->{'uri'} = "/";
148 delete $request{'whisker'}->{'data'};
149 $request{'whisker'}->{'method'} = GET;
150
151 delete $request{'whisker'}->{'Content-Length'};
152 LW2::http_close(\%request);  # force-close any old connections
153 dump_var("Request Hash", \%request);
154 if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
155 LW2::http_do_request_timeout(\%request,\%result);
156 $NIKTO{totalrequests}++;
157 dump_var("Result Hash", \%result);
158 if (defined $result{location})
159  {
160   nprint("- Root page / redirects to: $result{location}");
161    if ($result{location} =~ /^$request{'whisker'}{'host'}/i) # same host
162     {
163      $request{'whisker'}->{'uri'} = $result{location};
164      $request{'whisker'}->{'uri'} =~ s/^http(s)?\:\/\/$request{'whisker'}{'host'}//i;
165      LW2::http_close(\%request);  # force-close any old connections
166      LW2::http_fixup_request(\%request);
167      dump_var("Request Hash", \%request);
168      if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
169      LW2::http_do_request_timeout(\%request,\%result);
170      $NIKTO{totalrequests}++;
171      dump_var("Result Hash", \%result);
172     }
173    else  # different host... ugh... guess
174     {
175      $FoF{okay}{response}=200;
176      $FoF{okay}{type}="STD";
177     }
178  }
179 else
180  {
181   $FoF{okay}{response}=$result{'whisker'}->{'code'};
182   my $content=rm_active_content($result{'whisker'}->{'data'});
183   $FoF{okay}{type}="HASH";
184   $FoF{okay}{match}=LW2::md4($content);
185  }
186
187 # these are some used in mutate that may not be in the db_tests
188 $db_extensions{bak}=1;
189 $db_extensions{data}=1;
190 $db_extensions{dbc}=1;
191 $db_extensions{dbf}=1;
192 $db_extensions{lst}=1;
193 $db_extensions{htx}=1;
194
195 foreach my $ext (keys %db_extensions)
196   {
197    if ($ext eq "DIRECTORY") { next; }  # don't test generic type holder as real extension (added to db_extensions by get_ext)
198    if ($ext eq "NONE")      { next; }  # don't test generic type holder as real extension (added to db_extensions by get_ext)
199    if ($ext eq "DOTFILE")   { next; }  # don't test generic type holder as real extension (added to db_extensions by get_ext)
200    $REQS{"/$rs.$ext"}=$ext;
201   }
202 # add those generic type holders back as real files
203 $REQS{"/$rs/"}="DIRECTORY";
204 $REQS{"/$rs"}="NONE";
205 $REQS{"/.$rs"}="DOTFILE";
206 
207 foreach my $file (keys %REQS)
208  {
209   nprint("- Testing error for file: $file\n","v");
210   $NIKTO{totalrequests}++;
211   $request{'whisker'}->{'uri'}    = $file;
212   $request{'whisker'}->{'method'} = GET;
213   LW2::http_close(\%request);  # force-close any old connections
214   delete $request{'whisker'}->{'data'};
215   delete $request{'Content-Encoding'};
216   delete $request{'Content-Length'};
217   LW2::http_fixup_request(\%request);
218
219   dump_var("Request Hash", \%request);
220   if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
221   LW2::http_do_request_timeout(\%request,\%result);
222   $NIKTO{totalrequests}++;
223   dump_var("Result Hash", \%result);
224
225   $ext=$REQS{$file};
226   $FoF{$ext}{response} = $result{'whisker'}->{'code'};
227
228   # handle .com to .org redirs or whatnot
229   if (defined $result{location})
230     {
231        $FoF{$ext}{location} = $result{location};
232        $file=char_escape($file);
233        $FoF{$ext}{location} =~ s/$file//;
234     }
235
236   # if it is not specific type, figure out Content or HASH method...
237   if    ($FoF{$ext}{response} eq 404) { $FoF{$ext}{mode} = "STD";    next; }
238   elsif ($FoF{$ext}{response} eq 200) { $FoF{$ext}{mode} = "OK";     }
239   elsif ($FoF{$ext}{response} eq 410) { $FoF{$ext}{mode} = "STD";    next; }
240   elsif ($FoF{$ext}{response} eq 401) { $FoF{$ext}{mode} = "STD";    next; }
241   elsif ($FoF{$ext}{response} eq 403) { $FoF{$ext}{mode} = "STD";    next; }
242   elsif ($FoF{$ext}{response} eq 300) { $FoF{$ext}{mode} = "REDIR";  next; }
243   elsif ($FoF{$ext}{response} eq 301) { $FoF{$ext}{mode} = "REDIR";  next; }
244   elsif ($FoF{$ext}{response} eq 302) { $FoF{$ext}{mode} = "REDIR";  next; }
245   elsif ($FoF{$ext}{response} eq 303) { $FoF{$ext}{mode} = "REDIR";  next; }
246   elsif ($FoF{$ext}{response} eq 307) { $FoF{$ext}{mode} = "REDIR";  next; }
247   else  { $FoF{$ext}{mode} = "OTHER";  }
248
249   # if we've got an OK/OTHER response, look at content first
250   my $done=0;
251   
252   foreach my $string (keys %ERRSTRINGS)
253       {
254        nprint ("- Testing error string: $ERRSTRINGS{$string}","d");
255        if ($result{'whisker'}->{'data'} =~ /$ERRSTRINGS{$string}/i)
256        {
257          $FoF{$ext}{type}="CONTENT";
258          $FoF{$ext}{match}=$ERRSTRINGS{$string};
259          $done=1;
260          last;
261         }
262       }
263
264   if (!$done) # we have to get desperate...
265     {
266      if (length($result{'whisker'}->{'data'}) eq 0) # blank content
267       {
268        $FoF{$ext}{type}="BLANK";
269        $FoF{$ext}{match}="";
270        $done=1;
271       }
272      if (!$done)  # md4!
273      {
274       my $content=rm_active_content($result{'whisker'}->{'data'});
275       $FoF{$ext}{match}=LW2::md4($content);
276       $FoF{$ext}{type}="HASH";
277      }
278     }
279  }
280
281 # lastly, get a hash of index.php so we can cut down on some false positives...
282   $NIKTO{totalrequests}++;
283   $request{'whisker'}->{'uri'}    = "/index.php?";
284   $request{'whisker'}->{'method'} = GET;
285   LW2::http_close(\%request);  # force-close any old connections
286   delete $request{'whisker'}->{'data'};
287   delete $request{'Content-Encoding'};
288   delete $request{'Content-Length'};
289   LW2::http_fixup_request(\%request);
290
291   dump_var("Request Hash", \%request);
292   if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
293   LW2::http_do_request_timeout(\%request,\%result);
294   $NIKTO{totalrequests}++;
295   dump_var("Result Hash", \%result);
296
297   my $content=rm_active_content($result{'whisker'}->{'data'});
298   $FoF{"index.php"}{match}=LW2::md4($content);
299   $FoF{"index.php"}{type}="HASH";
300
301 # foreach $ext (keys %FoF)   { nprint "$ext: mode $FoF{$ext}{mode}, response $FoF{$ext}{response}, type $FoF{$ext}{type}\n"; }
302return;
303}
304###############################################################################
305sub rm_active_content
306{
307 # Try to remove active content which could mess up the file's signature
308 my $cont=$_[0];
309 
310 # Dates
311 $cont =~ s/([0-9]{4}|[0-9]{1,2})(\-|\.|\/)[0-9]{1,2}(\-|\.|\/)([0-9]{4}|[0-9]{1,2})//g;
312 $cont =~ s/(([0-9]{2}:[0-9]{2}(:)?([0-9]{2})?)|([0-9]{8,14}|[0-9]{6}))//g;
313 $cont =~ s/(mon|tue|wed|thu|fri|sat|sun)(,)? [0-9]{1,2} (jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) [0-9]{4} ([0-9]{2}:[0-9]{2}(:)?([0-9]{2})?)?//ig;
314 $cont =~ s/([0-9]{2,4})? ?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)([0-9]{2,4})?(\/)?([0-9]{2})?([0-9]{2})?//gi;
315 
316 # Page load times
317 $cont =~ s/[0-9\.]+ second//gi;
318 $cont =~ s/[0-9]+ queries//gi; # wordpress
319 
320 # Advertising
321 
322 # URI, if provided, plus encoded versions of it
323 if (defined $_[1])
324   {
325      # match pages which link to themselves w/diff args
326      my $e = $_[1];
327      $e =~ s/^\/$_[1]\??//; 
328      $e =~ s/([^a-zA-Z0-9\s])/\\$1/g; # escaping
329      $cont =~ s/$e//gs;
330
331      # again but with the index in place
332      $e = $_[1];
333      $e =~ s/([^a-zA-Z0-9\s])/\\$1/g; # escaping
334      $cont =~ s/$e//gs;
335
336      # base 64
337      $e=LW2::encode_base64($_[1]);
338      $cont =~ s/$e//gs;
339   
340      # hex encoded
341      $e=LW2::encode_uri_hex($_[1]);
342      $cont =~ s/$e//gs;
343
344      # unicode encoded
345      $e=LW2::encode_unicode($_[1]);
346      $cont =~ s/$e//gs;
347
348      # url encoding, full url
349      $e = $_[1];
350      $e =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
351      $cont =~ s/$e//gs;
352
353      # url encoding, query portion
354      if ($_[1] =~ /\?/)
355         {
356            $e = $_[1];
357            $e =~ s/\?(.*$)//;
358            my $qs = $1;
359            $qs =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
360            $e .= "?$qs";
361            $cont =~ s/$e//gs;
362         }
363   }
364
365 return $cont;
366}
367###############################################################################
368sub dump_target_info
369{
370   my ($mark) = @_;
371   my $sslprint="";
372
373   if ($mark->{ssl}) {
374      my $sslcyphers=$result{whisker}->{ssl_cipher}      || "Unknown";
375      my $sslissuers=$result{whisker}->{ssl_cert_issuer} || "Unknown";
376      my $sslinfo=$result{whisker}->{ssl_cert_subject}   || "Unknown";
377      $sslprint="$DIV\n";
378      $sslprint.="+ SSL Info:        Ciphers: $sslcyphers\n                   Info:    $sslissuers\n                   Subject: $sslinfo";
379
380     $mark->{sslcyphers}=$sslcyphers;
381     $mark->{sslissuers}=$sslissuers;
382     $mark->{sslinfo}=$sslinfo;
383   }
384
385   if ($mark->{ip} =~ /[a-z]/i) {
386                nprint("+ Target IP:       (proxied)");
387   }
388   else {
389                nprint("+ Target IP:          $mark->{ip}");
390   }
391
392   nprint("+ Target Hostname:    $mark->{hostname}");
393   nprint("+ Target Port:        $mark->{port}");
394   if ((defined $CLI{'vhost'}) && ($CLI{'vhost'} ne $mark->{hostname})) {
395                nprint("+ Virtual Host:   $CLI{'vhost'}");
396   }
397   if (defined $request{'whisker'}->{'proxy_host'}) {
398                nprint("- Proxy:              $request{'whisker'}->{'proxy_host'}:$request{'whisker'}->{'proxy_port'}");
399   }
400   if (defined $NIKTO{hostid}) {
401                nprint("- Host Auth:       ID: $NIKTO{hostid}, PW: $NIKTO{hostpw}, Realm: $NIKTO{hostdomain}","v");
402   }
403   if ($mark->{ssl}) {
404                nprint($sslprint);
405   }
406   if (defined $NIKTO{anti_ids} && defined $CLI{'evasion'}) {
407      for (my $i=1;$i<=(keys %{$NIKTO{anti_ids}});$i++) {
408        if ($CLI{'evasion'} =~ /$i/) {
409                nprint("+ Using IDS Evasion:  $NIKTO{anti_ids}{$i}");
410                }
411        }
412   }
413      if (defined $NIKTO{mutate_opts} && defined $CLI{'mutate'}) {
414      for (my $i=1;$i<=(keys %{$NIKTO{mutate_opts}});$i++) {
415                if ($CLI{'mutate'} =~ /$i/) {
416                        nprint("+ Using Mutation:     $NIKTO{mutate_opts}{$i}");
417                }
418        }
419   }
420   my $time=date_disp($mark->{start_time});
421   nprint("+ Start Time:         $time");
422   nprint($DIV);
423
424   if ($mark->{banner} ne "") {
425      nprint("+ Server: $mark->{banner}");
426   } else {
427      nprint("+ Server: No banner retrieved");
428   }
429
430   return;
431}
432###############################################################################
433sub general_config
434{
435 ## gotta set these first
436 $|=1;
437 $NIKTO{anti_ids}{1}="Random URI encoding (non-UTF8)";
438 $NIKTO{anti_ids}{2}="Directory self-reference (/./)";
439 $NIKTO{anti_ids}{3}="Premature URL ending";
440 $NIKTO{anti_ids}{4}="Prepend long random string";
441 $NIKTO{anti_ids}{5}="Fake parameter";
442 $NIKTO{anti_ids}{6}="TAB as request spacer";
443 $NIKTO{anti_ids}{7}="Change the case of the URL";
444 $NIKTO{anti_ids}{8}="Use Windows directory separator (\\)";
445
446 $NIKTO{mutate_opts}{1}="Test all files with all root directories";
447 $NIKTO{mutate_opts}{2}="Guess for password file names";
448 $NIKTO{mutate_opts}{3}="Enumerate user names via Apache (/~user type requests)";
449 $NIKTO{mutate_opts}{4}="Enumerate user names via cgiwrap (/cgi-bin/cgiwrap/~user type requests)";
450 $NIKTO{mutate_opts}{5}="Attempt to brute force sub-domain names, assume that the host name is the parent domain";
451 $NIKTO{mutate_opts}{6}="Attempt to guess directory names from the supplied dictionary file";
452
453 $NIKTO{display}{1}="Show redirects";
454 $NIKTO{display}{2}="Show cookies received";
455 $NIKTO{display}{3}="Show all 200/OK responses";
456 $NIKTO{display}{4}="Show URLs which require authentication";
457 $NIKTO{display}{V}="Verbose Output";
458 $NIKTO{display}{D}="Debug Output";
459 
460 $NIKTO{tuning}{1}="Interesting File / Seen in logs";
461 $NIKTO{tuning}{2}="Misconfiguration / Default File";
462 $NIKTO{tuning}{3}="Information Disclosure";
463 $NIKTO{tuning}{4}="Injection (XSS/Script/HTML)";
464 $NIKTO{tuning}{5}="Remote File Retrieval - Inside Web Root";
465 $NIKTO{tuning}{6}="Denial of Service";
466 $NIKTO{tuning}{7}="Remote File Retrieval - Server Wide";
467 $NIKTO{tuning}{8}="Command Execution / Remote Shell";
468 $NIKTO{tuning}{9}="SQL Injection";
469 $NIKTO{tuning}{0}="File Upload";
470 $NIKTO{tuning}{a}="Authentication Bypass";
471 $NIKTO{tuning}{b}="Software Identification";
472 $NIKTO{tuning}{c}="Remote Source Inclusion";
473 $NIKTO{tuning}{x}="Reverse Tuning Options (i.e., include all except specified)";
474
475 $NIKTO{options_short}= "
476       -Cgidirs+           scan these CGI dirs: 'none', 'all', or values like \"/cgi/ /cgi-a/\"
477       -dbcheck            check database and other key files for syntax errors (cannot be abbreviated)
478       -evasion+           ids evasion technique
479       -Format+            save file (-o) format
480       -host+              target host
481       -Help               Extended help information
482       -id+                host authentication to use, format is userid:password
483       -list-plugins       List all available plugins
484       -mutate+            Guess additional file names
485       -mutate-options+    Provide extra information for mutations
486       -output+            Write output to this file
487       -nocache            Disables the URI cache
488       -nossl              Disables using SSL
489       -no404              Disables 404 checks
490       -Plugins            List of plugins to run (default ALL)
491       -port+              Port to use (default 80)
492       -Display+           Turn on/off display outputs
493       -ssl                Force ssl mode on port
494       -Single             Single request mode
495       -timeout+           Timeout (default 2 seconds)
496       -Tuning+            Scan tuning
497       -update             Update databases and plugins from cirt.net (cannot be abbreviated)
498       -Version            Print plugin and database versions
499       -vhost+             Virtual host (for Host header)
500   + requires a value
501   ";
502
503 $NIKTO{options}="
504   Options:
505       -config+            Use this config file
506       -Cgidirs+           Scan these CGI dirs: 'none', 'all', or values like \"/cgi/ /cgi-a/\"
507       -Display+           Turn on/off display outputs:\n";
508   foreach my $k (sort keys %{$NIKTO{display}})
509      { $NIKTO{options} .= "                               $k     $NIKTO{display}{$k}\n"; }
510
511 $NIKTO{options}.="       -dbcheck           Check database and other key files for syntax errors (cannot be abbreviated)
512       -evasion+          IDS evasion technique:\n";
513   foreach my $k (sort keys %{$NIKTO{anti_ids}})
514      { $NIKTO{options} .= "                               $k     $NIKTO{anti_ids}{$k}\n"; }
515
516 $NIKTO{options}.="       -findonly          Find http(s) ports only, don't perform a full scan
517       -Format+           Save file (-o) format:
518                                htm   HTML Format
519                                csv   Comma-separated-value
520                                txt   Plain text (default if not specified)
521                                xml   XML Format
522       -host+             Target host
523       -Help              Extended help information
524       -id+               Host authentication to use, format is userid:password
525       -list-plugins      List all available plugins, perform no testing
526       -mutate+           Guess additional file names:\n";
527   foreach my $k (sort keys %{$NIKTO{mutate_opts}})
528      { $NIKTO{options} .= "                               $k     $NIKTO{mutate_opts}{$k}\n"; }
529
530 $NIKTO{options}.="       -mutate-options    Provide information for mutates
531       -nocache           Disables the URI cache
532       -nossl             Disables using SSL
533       -no404             Disables nikto attempting to guess a 404 page
534       -output+           Write output to this file
535       -Plugins           List of plugins to run (default ALL)
536       -port+             Port to use (default 80)
537       -Pause+            Pause between tests (seconds)\n";
538
539 $NIKTO{options}.="       -root+             Prepend root value to all requests, format is /directory
540       -ssl               Force ssl mode on port
541       -Single            Single request mode
542       -timeout+          Timeout (default 2 seconds)
543       -Tuning+           Scan tuning:\n";
544   foreach my $k (sort keys %{$NIKTO{tuning}})
545      { $NIKTO{options} .= "                               $k     $NIKTO{tuning}{$k}\n"; }
546
547 $NIKTO{options}.="       -useproxy          Use the proxy defined in config.txt
548       -update            Update databases and plugins from cirt.net (cannot be abbreviated)
549       -Version           Print plugin and database versions
550       -vhost+            Virtual host (for Host header)
551   + requires a value
552   ";
553
554 ### CLI STUFF
555 $CLI{'pause'}=$CLI{'html'}=$OUTPUT{'verbose'}=$CLI{'skiplookup'}=$NIKTO{totalrequests}=$OUTPUT{debug}=0;
556 $CLI{'all_options'}=join(" ",@ARGV);
557 
558 # preprocess CLI options which cannot be abbreviated
559 for (my $i=0;$i<=$#ARGV;$i++)
560  {
561   if    ($ARGV[$i] eq '-dbcheck')  { dbcheck(); }   
562   elsif ($ARGV[$i] eq '-update')   { check_updates(); }
563  }
564
565 GetOptions
566 (
567           "nolookup"                => \$CLI{'skiplookup'},
568           "config=s"                => \$CLI{'config'},
569           "Cgidirs=s"               => \$CLI{'forcecgi'},
570           "mutate=s"                => \$CLI{'mutate'},
571           "mutate-options=s"        => \$CLI{'mutate-options'},
572           "id=s"                    => \$CLI{'hostauth'},
573           "evasion=s"               => \$CLI{'evasion'},
574           "port=s"                  => \$CLI{'ports'},
575           "findonly"                => \$CLI{'findonly'},
576           "root=s"                  => \$CLI{'root'},
577           "timeout=s"               => \$CLI{'timeout'},
578           "Pause=s"                 => \$CLI{'pause'},
579           "ssl"                     => \$CLI{'ssl'},
580           "nocache"                 => \$CLI{'nocache'},
581           "nossl"                   => \$CLI{'nossl'},
582           "no404"                   => \$CLI{'nofof'},
583           "useproxy"                => \$CLI{'useproxy'},
584           "Help"                    => \$CLI{'help'},
585           "vhost=s"                 => \$CLI{'vhost'},
586           "host=s"                  => \$CLI{'host'},
587           "output=s"                => \$CLI{'file'},
588           "Format=s"                => \$CLI{'format'},
589           "Display=s"               => \$CLI{'display'},
590           "Single"                  => \$CLI{'Single'},
591           "Tuning=s"                => \$CLI{'tuning'},
592           "Version"                 => \$CLI{'version'},
593           "Plugins=s"               => \$CLI{'plugins'},
594           "list-plugins"            => \$CLI{'list-plugins'},
595 ) or usage(0);
596
597 if    ($CLI{'help'})         { usage(2);  }
598 elsif ($CLI{'version'})      { version(); }
599 elsif ($CLI{'Single'})       { single();  }
600 elsif ($CLI{'list-plugins'}) { list_plugins(); }
601 
602 # output file
603 if (!defined $CLI{'format'}) {
604    # Check what output has
605    $CLI{'format'} = "none";
606    if (defined $CLI{'file'}) {
607       $CLI{'format'} = lc($CLI{'file'});
608       $CLI{'format'} =~ s/(^.*\.)([^.]*$)/$2/g;
609    }
610 }
611
612 if    ($CLI{'format'} =~ /te?xt/i) { $CLI{'format'}="txt"; }
613 elsif ($CLI{'format'} =~ /html?/i) { $CLI{'format'}="htm"; }
614 elsif ($CLI{'format'} =~ /csv/i)   { $CLI{'format'}="csv"; }
615 elsif ($CLI{'format'} =~ /xml/i)   { $CLI{'format'}="xml"; }
616 elsif ($CLI{'format'} eq 'none')   { }
617 else                             { nprint("+ ERROR: Invalid output format"); exit; }
618
619 if (($CLI{'file'} ne "") && ($CLI{'format'} eq "")) { nprint("+ERROR: Output file specified without a format"); exit; }
620 
621 # verify readable dtd
622 if ($CLI{'format'} eq 'xml' && !-r $NIKTOCONFIG{NIKTODTD})   { nprint("+ ERROR: reading DTD"); exit; }
623
624 # screen output
625 if (defined $CLI{'display'})
626 {
627    if ($CLI{'display'} =~ /d/i)  { $OUTPUT{debug}=1; }
628    if ($CLI{'display'} =~ /v/i)  { $OUTPUT{verbose}=1; }
629 }
630 
631 # port(s)
632 if (defined $CLI{'ports'})
633 {
634    $CLI{'ports'}=~s/^\s+//;
635    $CLI{'ports'}=~s/\s+$//;
636    if ($CLI{'ports'} =~ /[^0-9\-\, ]/) { nprint("+ ERROR: Invalid port option '$CLI{'ports'}'"); exit; }
637 }
638
639 # Fixup
640 if (defined $CLI{'root'})
641 {
642    $CLI{'root'} =~ s/\/$//;
643    if (($CLI{'root'} !~ /^\//) && ($CLI{'root'} ne "")) { $CLI{'root'} = "/$CLI{'root'}"; }
644 }
645
646 if (defined $CLI{'hostauth'})
647 {
648  my @x=split(/:/,$CLI{'hostauth'});
649  if (($#x > 2) || ($x[0] eq ""))
650   { nprint("+ ERROR: \'$CLI{'hostauth'}\' (-i option) syntax is 'user:password' or 'user:password:domain' for host authentication."); exit;  }
651 }
652
653 if (defined $CLI{'evasion'})
654 {
655    $CLI{'evasion'}=~s/[^0-9]//g;
656 }
657
658 if (!defined $CLI{'plugins'})
659 {
660    $CLI{'plugins'}="ALL";
661 }
662
663 $NIKTO{timeout}=$CLI{'timeout'} || 10;
664
665 $NIKTO{useragent}="Mozilla/4.75 ($NIKTO{name}/$NIKTO{version})";
666 
667 # SSL Test
668 if (!LW2::ssl_is_available()) { nprint("- ***** SSL support not available (see docs for SSL install instructions) *****"); }
669 
670 # Notices
671 my $notice="";
672 if (defined $CLI{'root'})
673   { $notice .= "Prepending \'$CLI{'root'}\' to requests"; }
674 if ($CLI{'pause'} > 0)
675   { $notice .= ", Pausing $CLI{'pause'} seconds per request"; }
676 $notice =~ s/^, //;
677 if ($notice ne '')
678   { nprint("-***** $notice *****"); }
679
680 # get core version
681 open(FI,"<$NIKTOCONFIG{PLUGINDIR}/nikto_core.plugin");
682 my @F=<FI>;
683 close(FI);
684 my @VERS=grep(/^#VERSION/,@F);
685 $NIKTO{core_version}=$VERS[0];
686 $NIKTO{core_version}=~s/\#VERSION,//;
687 chomp($NIKTO{core_version});
688 $NIKTO{TMPL_HCTR}=0;
689 $NIKTO{TMPL_SUMMARY}=0;
690
691 return;
692}
693###############################################################################
694sub resolve
695{
696   my $ident=$_[0] || return;
697   my ($name, $ip, $dn)="";
698
699   # ident is name, lookup IP
700   if ($ident =~ /[^0-9\.]/)   # not an IP, assume name
701   {
702      if ($CLI{'skiplookup'})
703      {
704         nprint("+ ERROR: -skiplookup set, but given name\n");
705         exit;
706      }
707      $ip=gethostbyname($ident);
708      if (($ip eq "") && ($request{'whisker'}->{'proxy_host'} ne ""))    # can't resolve name to IP, but using proxy
709      {
710         $name=$ident;
711         $ip=$name;
712      }
713      elsif (($ip eq "") && ($request{'whisker'}->{'proxy_host'} eq "")) # can't resolve name to IP, no proxy set
714      {
715         nprint("+ ERROR: Cannot resolve hostname '$ident'\n");
716         return;
717      }
718      else
719      {
720         use IO::Socket;
721         $ip=inet_ntoa($ip);
722         if (($ip !~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) && ($ip ne "")) # trap for proxy...
723         {
724            nprint("+ ERROR: Invalid IP '$ip'\n\n");
725            return;
726         }
727         $name=$ident;
728      }
729   }
730   else   # ident is IP, lookup name
731   {
732      if (($ident !~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) && ($ident ne "")) # trap for proxy...
733      {
734         nprint("+ ERROR: Invalid IP '$ident'\n\n");
735         return;
736      }
737
738      $ip=$ident;
739      if (!$CLI{'skiplookup'})
740      {
741         use IO::Socket;
742         my $temp_ip=inet_aton($ip);
743         $name=gethostbyaddr($temp_ip,AF_INET);
744         # check reverse dns to avoid an inet_aton error
745         my $rdnsip=gethostbyname($name);
746         if ($rdnsip ne "")
747         {
748            $rdnsip=inet_ntoa($rdnsip);
749            if ($ip ne $rdnsip) { $name=$ip; }  # Reverse DNS does not match
750         }
751         else { $name = $ip; }  # Reverse DNS does not exist
752      }
753      if ($name eq "") { $name=$ip; }
754   }
755
756   # set displayname -- name takes precedence
757   if ($name ne "") { $dn=$name; }
758   else             { $dn=$ip;   }
759
760   return $name,$ip,$dn;
761}
762###############################################################################
763sub set_targets
764{
765   my ($hostlist, $portlist, $ssl, $root)=@_;
766   my $host_ctr=1;
767   my @hosts=split(/,/,$hostlist);
768   my @ports=split(/,/,$portlist);
769   my @checkhosts;
770   my @results;
771   my @marks;
772   my $defaultport=($ssl)?443:80;
773
774   # Check for old style portlist and expand
775   my @newports;
776   foreach my $port (@ports)
777   {
778      if ($port =~ /-/)
779      {
780         my $start, $end;
781         my @temp=split(/-/,$port);
782         $start=$temp[0];
783         $end=$temp[1];
784         if ($start eq "") { $start=0; }
785         if ($end eq "") { $end=65535; }
786         if ($start > $end)
787         {
788            nprint("+ ERROR port range $port doesn't make sense - assuming 80/tcp");
789            next;
790         }
791         for (my $i=$start;$i<=$end;$i++)
792         {
793            push(@newports,$i);
794         }
795      }
796      else
797      {
798         push(@newports, $port);
799      }
800   }
801   @ports=@newports;
802   
803   nprint("- Getting targets","v");
804   if (scalar(@ports) == 1)
805   {
806      # Only one port is set, assume that is the default port
807      $defaultport=$ports[0];
808   }
809   
810   # check whether it's a file or an entry
811   foreach my $host (@hosts)
812   {
813      if (-e $host || $host eq "-")
814      {
815         @results=parse_hostfile($host);
816         push(@checkhosts,@results);
817      }
818      else
819      {
820         push(@checkhosts,$host);
821      }
822   }
823   
824   # Now parse the list of checkhosts
825   foreach my $host (@checkhosts)   
826   {
827      my $defhost;
828      my $defport;
829      $host =~ s/\s+//g;
830      if (!defined $host) { next; }
831
832      # is it a URL?
833      if ($host =~ /^https?:\/\//)
834      {
835         my @hostdata=LW2::uri_split($host);
836         
837         $defhost = $hostdata[2];
838         $defport = $hostdata[3];
839
840         if ((!defined $root) && (defined $hostdata[0]))
841         {
842            $root=$hostdata[0];
843            nprint("- Added -root value of '$root'","d");
844         }
845      }
846      else
847      {
848         my @h = split(/\:|\,/, $host);
849         
850         $defhost = $h[0];
851         $defport = $h[1];
852      }
853     
854      # Now skip through all ports if port hasn't been added
855      if ($defport eq "" && scalar(@ports) > 0)
856      {
857
858         foreach $port (@ports)
859         {
860            my $markhash={};
861            $markhash->{ident} = $defhost;
862            $markhash->{port} = $port;
863            nprint("- Target:$markhash->{ident} port:$markhash->{port}","v");
864            push(@marks,$markhash);
865         }
866      }
867      else
868      {
869         if ($defport eq "")
870         {
871            $defport = $defaultport;
872         }
873         my $markhash={};
874         $markhash->{ident} = $defhost;
875         $markhash->{port} = $defport;
876         
877         nprint("- Target:$markhash->{ident} port:$markhash->{port}","v");
878         push(@marks,$markhash);
879      }
880   }
881
882   return @marks;
883}
884
885sub parse_hostfile
886{
887   my ($file)=@_;
888   my $nmap=0;
889   my @results;
890   my $hostdesc;
891
892   open(IN,$file) || die print STDERR "+ ERROR: Cannot open '$file':$@\n";
893   while(<IN>)
894   {
895      my $found=0;
896      # Check whether this is an nmap oG file
897      chomp;
898      if (/^# Nmap [0-9.]* scan initiated/)
899      {
900         $nmap=1;
901      }
902      s/\#.*$//;
903      if ($_ eq "") { next; }
904      # Parse for nmap files
905      if ($nmap == 1)
906      {
907         # First get the host name
908         my @line=split(/ /);
909         my @name=split(/\(|\)/, $line[2]);
910         $hostdesc=($name[1] ne "")?$name[1]:$line[1];
911         
912         # now parse the ports list
913         for (my $i = 3; $i <= $#line; $i++)
914         {
915            my @ports=split(/\//,$line[$i]);
916            if ($ports[1] eq "open" && $ports[4] eq "http")
917            {
918               $found=1;
919               $hostdesc .= ":" . $ports[0];
920            }
921         }
922      }
923      else
924      {
925         # just add it to the list
926         $hostdesc=$_;
927         $found=1;
928      }
929      push(@results,$hostdesc) if ($found);
930   }
931   close(IN);
932   return(@results);
933}
934
935###############################################################################
936sub load_databases
937{
938 my @dbs=qw/db_404_strings  db_outdated  db_realms  db_tests  db_variables/;
939 my $prefix = $_[0];
940
941 unless (defined $prefix)
942 {
943    $prefix="";
944 }
945
946 # verify required files
947 for my $file (@dbs)
948  {
949   if (!-r "$NIKTOCONFIG{PLUGINDIR}/$file") { die nprint("+ ERROR: Can't find/read required file \"$NIKTOCONFIG{PLUGINDIR}/$file\""); }
950  }
951
952 for my $file (@dbs)
953  {
954   my $filename = $NIKTOCONFIG{PLUGINDIR} . "/" . $prefix . $file;
955   if (!-r $filename) { next; }
956   open(IN,"<$filename") || die nprint("+ ERROR: Can't open \"$filename\":$!\n");
957   
958   # db_tests
959   if ($file eq 'db_tests') { push(@DBFILE,<IN>); next; }
960   
961   # all the other files require per-line processing
962   else
963     {
964       my @file;
965       # Cleanup
966       while (<IN>)
967        {
968          chomp;
969          $_ =~ s/#.*$//;
970          $_ =~ s/\s+$//;
971          $_ =~ s/^\s+//;
972          if ($_ ne "") { push(@file,$_); }
973        }
974       
975        # db_variables
976        if ($file eq 'db_variables')
977          {
978            foreach my $l (@file)
979              {
980                if ($l =~ /^@/)
981                  {
982                    my @temp=split(/=/,$l);
983                    $VARIABLES{$temp[0]}.= "$temp[1]";
984                  }
985               }
986            }
987
988       # db_404_strings
989       elsif ($file eq 'db_404_strings')
990          {
991            my $db_404_ctr=keys %ERRSTRINGS;
992            foreach my $l (@file)
993              {
994                $ERRSTRINGS{$db_404_ctr} = $l;
995                $db_404_ctr++;
996              }
997           }
998   
999       # db_outdated
1000       elsif ($file eq 'db_outdated')
1001          {
1002            foreach my $l (@file)
1003              {
1004                my @T=parse_csv($l);
1005                $OVERS{$T[1]}{$T[2]}=$T[3];
1006                $OVERS{$T[1]}{tid}=$T[0];
1007              }
1008           }
1009   
1010       # db_realms
1011       elsif ($file eq 'db_realms')
1012          {
1013            my $db_realms_ctr= keys %REALMS;
1014            foreach my $l (@file)
1015              {
1016                my @t=parse_csv($l);
1017                $REALMS{$db_realms_ctr}{tid}    = $t[0];
1018                $REALMS{$db_realms_ctr}{realm}  = $t[1];
1019                $REALMS{$db_realms_ctr}{id}     = $t[2];
1020                $REALMS{$db_realms_ctr}{pw}     = $t[3];
1021                $REALMS{$db_realms_ctr}{msg}    = $t[4];
1022                $db_realms_ctr++;
1023              }
1024            # Cheat and add CLI directly to REALMS
1025            if (defined $CLI{'hostauth'})
1026            {
1027             my @x=split(/:/,$CLI{'hostauth'});
1028
1029             $REALMS{$db_realms_ctr}{tid} = "700500";
1030             $REALMS{$db_realms_ctr}{realm} = (defined $x[2])?$x[2]:'@ANY';
1031             $REALMS{$db_realms_ctr}{pw} = $x[1];
1032             $REALMS{$db_realms_ctr}{id} = $x[0];
1033             $REALMS{$db_realms_ctr}{msg} = "Credentials provided by CLI.";
1034            }
1035           }
1036
1037   close(IN);
1038  }
1039 }
1040 
1041 return;
1042}
1043###############################################################################
1044sub dbcheck
1045{
1046 my @dbs=qw/db_404_strings  db_outdated  db_realms  db_tests  db_variables/;
1047 my $prefix = $_[0];
1048
1049 if ($prefix eq ""  )  { nprint "\n-->\tNikto Databases\n"; }
1050 if ($prefix eq "u"  ) { nprint "\n-->\tUser Databases\n"; }
1051
1052 for my $file (@dbs)
1053  {
1054   my $filename = $NIKTOCONFIG{PLUGINDIR} . "/" . $prefix . $file;
1055   if (!-r $filename) { next; }
1056   open(IN,"<$filename") || die nprint("+ ERROR: Can't open \"$filename\":$!\n");
1057   
1058   nprint "Syntax Check: $filename\n";
1059   
1060   if ($file eq 'db_outdated')
1061     {
1062       foreach $line (<IN>)
1063         {
1064           $line =~ s/^\s+//;
1065           if ($line =~ /^\#/) { next; }
1066           chomp($line);
1067           if ($line eq "") { next; }
1068           my @L=parse_csv($line);
1069           if ($line !~ /^\".*\"\,\".*\"\,\".*\"$/) { print STDERR "\t+ ERROR: Invalid syntax ($#L): $line\n"; next; }
1070           if ($#L ne 3) { print STDERR "\t+ ERROR: Invalid syntax ($#L): $line\n"; next; }
1071           $ENTRIES{"$L[0]"}++;
1072         }
1073
1074          foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print STDERR "\t+ ERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } }
1075          nprint "\t" . keys(%ENTRIES) . " entries\n";
1076     }
1077   elsif ($file eq 'db_tests')
1078     {
1079       my %ENTRIES;
1080       foreach my $line (<IN>)
1081         {
1082           if ($line !~ /^\"/)  { next; }
1083           my @L=parse_csv($line);
1084           if ($L[4] !~ /(GET|POST|TRACE|TRACK|OPTIONS|SEARCH|INDEX)/i) { print STDERR "\t+ ERROR: Possibly invalid method: $L[4] on ($line)\n"; }
1085           if ($L[5] eq "") { print STDERR "\t+ ERROR: blank conditional: $line"; next; }
1086           if ($line !~ /^\".*\",\".*\",\".*\",\".*\",\".*\"/) { print STDERR "\t+ ERROR: Invalid syntax ($#L): $line\n"; next; }
1087           if ($line !~ /^(\".*\",){11}\".*\"/) { print STDERR "\t+ ERROR: Invalid syntax ($#L): $line\n"; next; }
1088           if (($L[3] =~ /^\@CG/) && ($L[3] !~ /^\@CGIDIRS/)) { print STDERR "\t+ ERROR: Possible \@CGIDIRS misspelling: $line"; }
1089           $ENTRIES{"$L[3],$L[4],$L[5],$L[6],$L[7],$L[8],$L[9],$L[11],$L[12]"}++;
1090         }
1091
1092       foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print STDERR "\t+ ERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } }
1093       nprint "\t" . keys(%ENTRIES) . " entries\n";
1094     }
1095   elsif ($file eq 'db_variables')
1096     {
1097       my $ctr=0;
1098       foreach $line (<IN>)
1099         {
1100           if ($line !~ /^\@/)  { next; }
1101           if ($line !~ /^\@.+\=.+$/i ) { print STDERR "\t+ ERROR: Invalid syntax: $line\n"; }
1102           $ctr++;
1103         }
1104        nprint "\t$ctr entries\n";
1105     }
1106   elsif ($file eq 'db_realms')
1107     {
1108       my $ctr=0;
1109       foreach $line (<IN>)
1110         {
1111           if ($line !~ /^\"/)  { next; }
1112           chomp($line);
1113           my @L=parse_csv($line);
1114           if ($#L ne 4) { print STDERR "\t+ ERROR: Invalid syntax: $line\n"; }
1115           $ctr++;
1116         }
1117        nprint "\t$ctr entries\n";
1118     }
1119   elsif ($file eq 'db_404_strings')
1120     {
1121       my $ctr=0;
1122       foreach $line (<IN>)
1123         {
1124           # not really any syntax to check
1125           $ctr++;
1126         }
1127        nprint "\t$ctr entries\n";
1128     }
1129
1130   close(IN); 
1131  }
1132 
1133 if ($_[0] eq "") { dbcheck('u'); }  # do this once
1134
1135 nprint "\n";
1136 exit;
1137}
1138###############################################################################
1139sub get_banner
1140{
1141   my ($mark) = @_;
1142   my %headers;
1143   
1144   (my $res, $content) = nfetch($mark,"/","GET","",\%headers);
1145
1146   return $headers{server};
1147}
1148###############################################################################
1149sub port_check
1150{
1151   my ($hostname, $ip, $port) = @_;
1152   my %m, %headers;
1153
1154   $m->{hostname}=$hostname;
1155   $m->{ip}=$ip;
1156   $m->{port}=$port;
1157   $m->{ssl}=0;
1158
1159   my @checktypes=('HTTP', 'HTTPS');
1160   if ($CLI{'ssl'}) { shift(@checktypes); }
1161   if ($CLI{'nossl'}) { pop(@checktypes); }
1162
1163   foreach my $method (split(/ /,$NIKTOCONFIG{CHECKMETHODS}))
1164   {
1165      $request{'whisker'}->{'method'}=$method;
1166      foreach my $checkssl (@checktypes)
1167      {
1168         nprint("- Checking for $checkssl on port $ip:$port, using $method","v");
1169         $m->{ssl}=($checkssl eq "HTTP")?0:1;
1170         proxy_check($m);
1171         my ($res, $content)=nfetch($m, "/", $method, "", \%headers);
1172
1173         if ($res)
1174         {
1175            # this will fix for some Apaches that are smart enough to answer non ssl reqs on an ssl server
1176            if (defined $content &&
1177                $content =~ /speaking plain HTTP to an SSL/)
1178            {
1179               dump_var("Result Hash", \%result);
1180               next;
1181            }
1182            nprint("- $checkssl Server found: $ip:$port \t$headers{server}","d");
1183            return $m->{ssl}+1;
1184         }
1185      }
1186   }
1187
1188
1189   nprint("+ No web server found on $ip:$port");
1190   nprint("---------------------------------------------------------------------------");
1191   
1192   return 0;
1193}
1194
1195###############################################################################
1196sub load_plugins
1197{
1198   my @pluginlist=dirlist("$NIKTOCONFIG{PLUGINDIR}",'\.plugin$');
1199   
1200   # Check if running plugins is NONE - if so, don't bother initalising
1201   # plugins
1202   if ($CLI{'plugins'} eq "NONE") {
1203      return;
1204   }
1205   my @torun=split(/,/,$CLI{'plugins'});
1206
1207   foreach my $plugin (@pluginlist) {
1208      my $plugin_name = $plugin;
1209      $plugin_name =~ s/\.plugin$//;
1210      my $plugin_init = $plugin_name . "_init";
1211      eval {
1212         require "$NIKTOCONFIG{PLUGINDIR}/$plugin";
1213      };
1214      if ($@) {
1215         nprint("- Could not load or parse plugin: $plugin_name\n Error: ");
1216         warn $@;
1217         nprint("- The plugin could not be run.");
1218      }
1219      else {
1220         nprint("- Initialising plugin $plugin_name","v");
1221         # Call initialisation method
1222         if (defined &$plugin_init) {
1223            my $pluginhash=&$plugin_init;
1224            # Add default weights if not already assigned
1225            $pluginhash->{recon_weight}=50 unless (defined $pluginhash->{recon_weight});
1226            $pluginhash->{scan_weight}=50 unless (defined $pluginhash->{scan_weight});
1227            $pluginhash->{report_weight}=50 unless (defined $pluginhash->{report_weight});
1228            # Check that the plugin is to be run
1229            # Perl doesn't allow us to use "in", pity
1230            foreach my $torun_plugin (@torun) {
1231               if ($torun_plugin eq "ALL" ||
1232                   $pluginhash->{name} eq $torun_plugin) {
1233                  $pluginhash->{run}=true;
1234               }
1235            }
1236               
1237            push(@PLUGINS, $pluginhash);
1238            nprint("- Loaded \"$pluginhash->{full_name}\" plugin.","v");
1239         }
1240      }
1241   }
1242}
1243
1244###############################################################################
1245# this is ugly, and potentially dangerous if untrusted plugins are present
1246###############################################################################
1247sub run_plugins
1248{
1249   my ($mark) = @_;
1250   nprint("- Entering recon phase","v");
1251   # This is a frig until I can think of a better way of achieving it
1252   foreach my $i (1..100)
1253   {
1254      foreach my $plugin (@PLUGINS)
1255      {
1256         if ($plugin->{run} && defined $plugin->{recon_method} && $plugin->{recon_weight} == $i)
1257         {
1258            my $run=1;
1259            # first check for conditionals
1260            if (defined $plugin->{recon_cond})
1261            {
1262               # Evaluate condition
1263               $run=eval($plugin->{recon_cond});
1264            }
1265            if ($run)
1266            {   
1267               nprint("- Running recon for \"$plugin->{full_name}\" plugin","v");
1268               &{$plugin->{recon_method}}($mark);
1269            }
1270         }
1271      }
1272   }
1273
1274   nprint("- Entering scan phase","v");
1275   # This is a frig until I can think of a better way of achieving it
1276   foreach my $i (1..100)
1277   {
1278      foreach my $plugin (@PLUGINS)
1279      {
1280         if ($plugin->{run} && defined $plugin->{scan_method} && $plugin->{scan_weight} == $i)
1281         {
1282            my $run=1;
1283            # first check for conditionals
1284            if (defined $plugin->{scan_cond})
1285            {
1286               # Evaluate condition
1287               $run=eval "$plugin->{scan_cond}";
1288            }
1289            if ($run)
1290            {   
1291               nprint("- Running scan for \"$plugin->{full_name}\" plugin","v");
1292               &{$plugin->{scan_method}}($mark);
1293            }
1294         }
1295      }
1296   }
1297   
1298   return;
1299}
1300   
1301sub report_head
1302{
1303   my ($format,$file) = @_;
1304   nprint("- Opening reports","v");
1305   
1306   # For tuning set up a list of report methods, formats and handles
1307   
1308   # This is a frig until I can think of a better way of achieving it
1309   foreach my $i (1..100)
1310   {
1311      foreach my $plugin (@PLUGINS)
1312      {
1313         if ($plugin->{run} && defined $plugin->{report_item} && $plugin->{report_weight} == $i)
1314         {
1315            my $run=1;
1316            # first check for conditionals
1317            if (defined $plugin->{report_format})
1318            {
1319               # Evaluate condition
1320               $run=($format eq $plugin->{report_format});
1321            }
1322            if ($run)
1323            {   
1324               nprint("- Opening report for \"$plugin->{full_name}\" plugin","v");
1325               my $handle;
1326               if (defined $plugin->{report_head})
1327               {
1328                  $handle=&{$plugin->{report_head}}($file);
1329               }
1330               # Now store this
1331               my $report_entry=
1332               {
1333                  host_start => $plugin->{report_host_start},
1334                  host_end   => $plugin->{report_host_end},
1335                  item       => $plugin->{report_item},
1336                  close      => $plugin->{report_close},
1337                  handle     => $handle,
1338               };
1339               push(@REPORTS, $report_entry);
1340            }
1341         }
1342      }
1343   }
1344   return;
1345}
1346###############################################################################
1347sub report_host_start
1348{
1349   my ($mark) = @_;
1350   # Go through all reporting modules
1351   foreach my $reporter (@REPORTS)
1352   {
1353      if (defined $reporter->{host_start})
1354      {
1355         &{$reporter->{host_start}}($reporter->{handle}, $mark);
1356      }
1357   }
1358}
1359###############################################################################
1360sub report_host_end
1361{
1362   my ($mark) = @_;
1363   # Go through all reporting modules
1364   foreach my $reporter (@REPORTS)
1365   {
1366      if (defined $reporter->{host_end})
1367      {
1368         &{$reporter->{host_end}}($reporter->{handle}, $mark);
1369      }
1370   }
1371}
1372###############################################################################
1373sub report_item
1374{
1375   my ($mark, $item) = @_;
1376   # Go through all reporting modules
1377   foreach my $reporter (@REPORTS)
1378   {
1379      if (defined $reporter->{item})
1380      {
1381         &{$reporter->{item}}($reporter->{handle}, $mark, $item);
1382      }
1383   }
1384}
1385###############################################################################
1386sub report_close
1387{
1388   # Go through all reporting modules
1389   foreach my $reporter (@REPORTS)
1390   {
1391      if (defined $reporter->{close})
1392      {
1393         &{$reporter->{close}}($reporter->{handle});
1394      }
1395   }
1396}
1397###############################################################################
1398sub check_updates
1399{
1400 LW2::http_init_request(\%request);
1401 my (%REMOTE, %LOCAL, @DBTOGET) = ();
1402 my ($pluginmsg, $remotemsg) = "";
1403 my $code_updates=0;
1404 my $serverdir="/nikto/UPDATES/$NIKTO{version}";
1405 
1406 my $server="www.cirt.net";
1407 $request{'whisker'}->{'version'}="1.1";
1408 $request{'whisker'}->{'port'}=80;
1409 $request{'whisker'}->{'anti_ids'}="";
1410 $request{'User-Agent'}="Nikto Update Agent";
1411 $request{'whisker'}->{'host'}=$server;
1412 
1413 for (my $i=0;$i<=$#ARGV;$i++)
1414 { if (($ARGV[$i] eq "-u") || ($ARGV[$i] eq "-useproxy")) { $CLI{'useproxy'}=1; last; } }
1415 
1416 if (($NIKTOCONFIG{PROXYHOST} ne "") && ($CLI{'useproxy'}))
1417  {
1418   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
1419   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
1420  }
1421
1422 # retrieve versions file
1423 LW2::http_close(\%request);  # force-close any old connections
1424 LW2::http_fixup_request(\%request);
1425 (my $RES, $CONTENT) = fetch("$serverdir/versions.txt","GET");
1426 if ($RES eq 407) # requires Auth
1427  {
1428   if ($NIKTOCONFIG{PROXYUSER} eq "") 
1429      {
1430      $NIKTOCONFIG{PROXYUSER}=read_data("Proxy ID: ","");
1431      $NIKTOCONFIG{PROXYPASS}=read_data("Proxy Pass: ","noecho");
1432     }
1433   LW2::auth_set("proxy-basic",\%request,$NIKTOCONFIG{PROXYUSER},$NIKTOCONFIG{PROXYPASS});
1434
1435   # and try again
1436   LW2::http_close(\%request);  # force-close any old connections
1437   LW2::http_fixup_request(\%request);
1438   ($RES, $CONTENT) = fetch("$serverdir/versions.txt","GET");
1439  }
1440
1441 if ($RES eq "") # lookup failure?
1442  {
1443      LW2::http_close(\%request);  # force-close any old connections
1444   $request{'whisker'}->{'host'}=$NIKTOCONFIG{CIRT};
1445   $request{'Host'}="www.cirt.net";
1446   LW2::http_fixup_request(\%request);
1447   ($RES, $CONTENT) = fetch("$serverdir/versions.txt","GET");
1448  }
1449
1450 if ($RES ne 200)
1451  { print STDERR "+ ERROR ($RES): Unable to get $request{'whisker'}->{'host'}$serverdir/versions.txt\n";
1452    exit; }
1453
1454 # make hash
1455 for (split(/\n/,$CONTENT))
1456 {
1457  my @l=parse_csv($_);
1458  if ($_ =~ /^msg/) { $remotemsg="$l[1]"; next; }
1459  $REMOTE{$l[0]}=$l[1];
1460 }
1461 
1462 # get local versions of plugins/dbs
1463 my @NIKTOFILES=dirlist($NIKTOCONFIG{PLUGINDIR},"");
1464
1465 foreach my $file (@NIKTOFILES)
1466 {
1467  my $v="";
1468  open(LOCAL,"<$NIKTOCONFIG{PLUGINDIR}/$file") || print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/$file' for read: $@\n";
1469  my @l=<LOCAL>;
1470  close(LOCAL);
1471 
1472  my @VERS=grep(/^#VERSION/,@l);
1473  chomp($VERS[0]);
1474  $LOCAL{$file}=(parse_csv($VERS[0]))[1];
1475 }
1476
1477 # check main nikto versions
1478 foreach my $remotefile (keys %REMOTE)
1479 {
1480  if ($remotefile eq "nikto") # main program version
1481   {
1482    if ($REMOTE{$remotefile} > $NIKTO{version})
1483     {
1484      nprint "+ Nikto has been updated to $REMOTE{$remotefile}, local copy is $NIKTO{version}\n";
1485      nprint "+ No update has taken place. Please upgrade Nikto by visiting http://$server/\n";
1486      if ($remotemsg ne "") {  nprint "+ $server message: $remotemsg\n"; }
1487      exit;
1488     }
1489    next;
1490   }
1491
1492  if (($LOCAL{$remotefile} eq "") || ($REMOTE{$remotefile} > $LOCAL{$remotefile}))
1493   {
1494    push(@DBTOGET,$remotefile);
1495    if ($remotefile !~ /^db_/) { $code_updates=1; }
1496   }
1497  elsif ($REMOTE{$remotefile} < $LOCAL{$remotefile})  # local is newer (!)
1498   { print STDERR "+ ERROR: Local '$remotefile' (ver $LOCAL{$remotefile}) is NEWER than remote (ver $REMOTE{$remotefile}).\n";  }
1499 }
1500
1501 # replace local files if updated
1502 foreach my $toget (@DBTOGET)
1503 {
1504  nprint "+ Retrieving '$toget'\n";
1505  (my $RES, $CONTENT) = fetch("$serverdir/$toget","GET");
1506  if ($RES ne 200) { print STDERR "+ ERROR: Unable to get $server$serverdir/$toget\n"; exit; }
1507  if ($CONTENT ne "") {
1508   open(OUT,">$NIKTOCONFIG{PLUGINDIR}/$toget") || die print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/$toget' for write: $@\n";
1509   print OUT $CONTENT;
1510   close(OUT);
1511  }
1512 }
1513
1514# CHANGES file
1515if ($code_updates)
1516 {
1517  nprint "+ Retrieving 'CHANGES.txt'\n";
1518  (my $RES, $CONTENT) = fetch("$serverdir/CHANGES.txt","GET");
1519  if (($CONTENT ne "") && ($RES eq 200)) {
1520   open(OUT,">$NIKTOCONFIG{DOCUMENTDIR}/CHANGES.txt") || die print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{DOCUMENTDIR}/CHANGES.txt' for write: $@\n";
1521   print OUT $CONTENT;
1522   close(OUT);
1523  }
1524 }
1525
1526if ($#DBTOGET < 0 )   { nprint "+ No updates required.\n"; }
1527if ($remotemsg ne "") { nprint "+ $server message: $remotemsg\n"; }
1528exit;
1529}
1530###############################################################################
1531# do_auth
1532# Inputs: $request, $result
1533# Returns: $request
1534sub do_auth
1535{
1536   my $request = $_[0];
1537   my $result = $_[1];
1538   my $mark = $_[2];
1539   my $authtype = "basic";
1540   my $realm = "";
1541
1542   unless (defined $result{'www-authenticate'})
1543   {
1544      nprint("+ ERROR: No authentication header defined");
1545   }
1546   # Split up www-authenticate to realm and method
1547   my @authenticate = split(/ /,$result{'www-authenticate'});
1548   if ($#authenticate == 0)
1549   { # Only one parameter: realm
1550      $realm = $authenticate[0];
1551      if ($realm =~ /^ntlm/i)
1552      {
1553         $realm="";
1554         $authtype=$authenticate[0];
1555      }
1556   }
1557   else
1558   {
1559      $authtype = $authenticate[0];
1560      $realm = $authenticate[1];
1561      $realm =~ s/^realm=//;
1562   }
1563
1564   # Now we have this we can try guessing the password
1565   foreach my $REALM (keys %REALMS)
1566   {
1567      next unless ($realm =~ /$REALMS{$REALM}{realm}/i || $REALMS{$REALM}{realm} eq '@ANY');
1568      if (($REALMS{$REALM}{id} eq "") && ($REALMS{$REALM}{pw} eq ""))
1569      {
1570         my $uridir=$request{whisker}->{uri};
1571         $uridir =~ s#/[^/]*$#/#g;
1572         add_vulnerability($mark,"Blank credentials found at $uridir ($request{whisker}->{uri}), $REALMS{$REALM}{realm}: $REALMS{$REALM}{msg}", $REALMS{$REALM}{tid},0,"GET",$uridir,\%result);
1573      }
1574      else
1575      {
1576         my $save_auth=$result{'www-authenticate'};
1577         LW2::http_close(\%request);  # force-close any old connections
1578         LW2::auth_set($authtype,\%request,$REALMS{$REALM}{id},$REALMS{$REALM}{pw});
1579         # Path to fix short reads
1580         $request{'whisker'}->{allow_short_reads}=1;
1581         LW2::http_fixup_request(\%request);
1582         if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
1583         LW2::http_do_request_timeout(\%request,\%result); # test auth
1584             $NIKTO{totalrequests}++;
1585         dump_var("Auth Request",\%request);
1586         dump_var("Auth Response",\%result);
1587         if ($result{'www-authenticate'} =~ /^ntlm/i)
1588         {
1589            # Deal with ntlm
1590            my @ntlm_x=split(/ /,$result{'www-authenticate'});
1591            if ($#ntlm_x == 1)
1592            {
1593               LW2::http_do_request_timeout(\%request,\%result);
1594               $NIKTO{totalrequests}++;
1595            }
1596         }
1597         if ($result{'www-authenticate'} eq "" &&
1598             !defined $result{'whisker'}->{'error'}) { 
1599            unless ($REALMS{$REALM}{tid} == 700500 ||
1600                    $REALMS{$REALM}{checked} == 1) {
1601               my $uridir=$request{whisker}->{uri};
1602               $uridir =~ s#/[^/]*$#/#g;
1603               add_vulnerability($mark,"Default account found for '$realm' at $uridir ($request{whisker}->{uri}) (ID '$REALMS{$REALM}{id}', PW '$REALMS{$REALM}{pw}'). $REALMS{$REALM}{msg}",$REALMS{$REALM}{tid},0,"GET",$uridir,\%result);
1604               $REALMS{$REALM}{checked}=1;
1605            }
1606            # Finally repeat the check
1607            LW2::http_do_request_timeout(\%request,\%result); # test auth
1608            $NIKTO{totalrequests}++;
1609            # and leave
1610            last;
1611         }
1612         else { $result->{'www-authenticate'}=$save_auth; }
1613      }
1614   }
1615   LW2::auth_unset(\%request);
1616   return $result;
1617}
1618
1619###############################################################################
1620# read_data ( prompt, mode )
1621# read STDIN data from the user
1622# portions of this (POSIX code) were taken from the
1623# Term::ReadPassword module by Tom Phoenix <rootbeer@redcat.com> (many thanks).
1624# it has been modified to not require Term::ReadLine, but still requires
1625# POSIX::Termios of it's a POSIX machine
1626###############################################################################
1627sub read_data
1628{
1629 if ($NIKTOCONFIG{PROMPTS} =~ /no/i) { return; }
1630 my($prompt, $mode, $POSIX) = @_;
1631 my $input = "";
1632
1633 if ($^O =~ /Win32/)  { $POSIX=0; }
1634 else                 { $POSIX=1; }
1635
1636 my %SPECIAL = (
1637    "\x03"      => 'INT',       # Control-C, Interrupt
1638    "\x08"      => 'DEL',       # Backspace
1639    "\x7f"      => 'DEL',       # Delete
1640    "\x0d"      => 'ENT',       # CR, Enter
1641    "\x0a"      => 'ENT',       # LF, Enter
1642  );
1643
1644 # if we're on a non-POSIX machine we can't not-echo the
1645 # characters, so just use getc to avoid the dependency on
1646 # POSIX::Termios. We would be best to get rid of this
1647 # entirely and use another way...
1648
1649 if ($POSIX)
1650  {
1651   local(*TTY, *TTYOUT);
1652   open TTY, "<&STDIN" or return;
1653   open TTYOUT, ">>&STDOUT" or return;
1654
1655   # Don't buffer it!
1656   select( (select(TTYOUT), $|=1)[0] );
1657   print TTYOUT $prompt;
1658
1659   # Remember where everything was
1660   my $fd_tty = fileno(TTY);
1661   my $term = POSIX::Termios->new();
1662   $term->getattr($fd_tty);
1663   my $original_flags = $term->getlflag();
1664
1665   if ($mode eq "noecho")
1666     {
1667      my $new_flags = $original_flags & ~(ISIG | ECHO | ICANON);
1668      $term->setlflag($new_flags);
1669     }
1670   $term->setattr($fd_tty, TCSAFLUSH);
1671KEYSTROKE:
1672   while (1) {
1673        my $new_keys = '';
1674        my $count = sysread(TTY, $new_keys, 99);
1675        if ($count) {
1676            for my $new_key (split //, $new_keys) {
1677                if (my $meaning = $SPECIAL{$new_key}) {
1678                    if ($meaning eq 'ENT')    { last KEYSTROKE; }
1679                    elsif ($meaning eq 'DEL') { chop $input; }
1680                    elsif ($meaning eq 'INT') { last KEYSTROKE; }
1681                    else { $input .= $new_key; }
1682                }
1683                else { $input .= $new_key; }
1684            }
1685        }
1686        else { last KEYSTROKE; }
1687    }
1688    # Done with waiting for input. Let's not leave the cursor sitting
1689    # there, after the prompt.
1690    print TTY "\n";
1691    nprint "\n";
1692
1693    # Let's put everything back where we found it.
1694    $term->setlflag($original_flags);
1695    $term->setattr($fd_tty, TCSAFLUSH);
1696    close(TTY);
1697    close(TTYOUT);
1698  }
1699 else # non-POSIX
1700  {
1701   print $prompt;
1702   $input=<STDIN>;
1703   chomp($input);
1704  }
1705
1706return $input;
1707}
1708###############################################################################
1709sub proxy_setup
1710{
1711   if (!$CLI{'useproxy'}) { return; }
1712
1713   # HTTP proxy
1714   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
1715   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
1716
1717   return;
1718}
1719###############################################################################
1720sub proxy_check
1721{
1722  my ($mark) = @_;
1723 
1724 if (defined $request{'whisker'}->{'proxy_host'} && $CLI{'useproxy'})  # proxy is set up
1725 {
1726  LW2::http_close(\%request);  # force-close any old connections
1727  setup_hash(\%request, $mark);
1728  $request{'whisker'}->{'method'}="GET";
1729  $request{'whisker'}->{'uri'}="/";
1730 
1731  LW2::http_fixup_request(\%request);
1732 
1733  if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
1734  LW2::http_do_request_timeout(\%request,\%result);
1735  $NIKTO{totalrequests}++;
1736  dump_var("Request Hash", \%request);
1737  dump_var("Result Hash", \%result);
1738
1739  # First check that we can connect to the proxy
1740  if (exists $result{'whisker'}{'error'})
1741  {
1742     if ($result{'whisker'}{'error'} =~ /Transport endpoint is not connected/)
1743     {
1744        nprint("+ ERROR: Could not connect to the defined proxy $NIKTOCONFIG{PROXYHOST}");
1745        exit 1;
1746     }
1747     nprint("+ ERROR: Proxy error: $result{'whisker'}{'error'}", "v");
1748  }
1749  if ($result{'whisker'}{'code'} eq "407")       # proxy requires auth
1750  {
1751   # have id/pw?
1752   if ($NIKTOCONFIG{PROXYUSER} eq "")
1753     {
1754      $NIKTOCONFIG{PROXYUSER}=read_data("Proxy ID: ","");
1755      $NIKTOCONFIG{PROXYPASS}=read_data("Proxy Pass: ","noecho");
1756     }
1757   if ($result{'proxy-authenticate'} !~ /Basic/i)
1758    {
1759     my @x=split(/ /,$result{'proxy-authenticate'});
1760     nprint("+ Proxy server uses '$x[0]' rather than 'Basic' authentication. $NIKTO{name} $NIKTO{version} can't do that.");
1761     exit;
1762    }
1763   
1764   # test it...
1765   LW2::http_close(\%request);  # force-close any old connections
1766   LW2::auth_set("proxy-basic",\%request,$NIKTOCONFIG{PROXYUSER},$NIKTOCONFIG{PROXYPASS});   # set auth
1767   LW2::http_fixup_request(\%request);
1768   if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
1769   LW2::http_do_request_timeout(\%request,\%result);
1770   $NIKTO{totalrequests}++;
1771   dump_var("Request Hash", \%request);
1772   dump_var("Result Hash", \%result);
1773   if ($result{'proxy-authenticate'} ne "")
1774    {
1775     my @pauthinfo=split(/ /,$result{'proxy-authenticate'});
1776     my @pauthinfo2=split(/=/,$result{'proxy-authenticate'});
1777     $pauthinfo2[1]=~s/^\"//; $pauthinfo2[1]=~s/\"$//;
1778     nprint("+ Proxy requires authentication for '$pauthinfo[0]' realm '$pauthinfo2[1]', unable to authenticate.");
1779     exit;
1780    }
1781   else { nprint("- Successfully authenticated to proxy.","v"); }
1782  }
1783 }
1784 
1785 return;
1786}
1787###############################################################################
1788sub dirlist
1789{
1790   my $DIR=$_[0] || return;
1791   my $PATTERN=$_[1] || "";
1792   my @FILES_TMP = ();
1793
1794   opendir(DIRECTORY,$DIR) || die print STDERR "+ ERROR: Can't open directory '$DIR': $@";
1795   foreach my $file (readdir(DIRECTORY))
1796   {
1797      if ($file =~ /^\./)    { next; } # skip hidden files, '.' and '..'
1798      if ($PATTERN ne "") { if ($file =~ /$PATTERN/) { push (@FILES_TMP,$file); } }
1799      else { push (@FILES_TMP,$file); }
1800   }
1801   closedir(DIRECTORY);
1802
1803   return @FILES_TMP;
1804}
1805#######################################################################
1806sub dump_var
1807{
1808   return if !$OUTPUT{debug};
1809   my $msg     = $_[0];
1810   my %hash_in = %{$_[1]};
1811   my $display = LW2::dump('', \%hash_in);
1812   $display =~ s/^\$/'$msg'/;
1813   nprint($display,"d");
1814   return;
1815}
1816######################################################################
1817sub content_present
1818{
1819   my $result=FALSE;
1820   my $res=$_[0];
1821
1822   # perform an extra check just in case the web server lies about finds
1823   # basically assume that the value for a non-extension is the true
1824   # code for "File not Found".
1825   if ($res ne $FoF{NONE}{response})
1826   {
1827      foreach $found (split(' ', $VARIABLES{"\@HTTPFOUND"}))
1828      {
1829         if ($res eq $found)
1830         {
1831            $result=TRUE;
1832         }
1833      }
1834   }
1835
1836   return $result;
1837}
1838#######################################################################
1839sub fetch
1840{
1841 if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
1842 LW2::http_close(\%request);  # force-close any old connections
1843 $request{'whisker'}->{'uri'} = $_[0];
1844 if (defined $CLI{'root'})
1845 {
1846    $request{'whisker'}->{'uri'}    = $CLI{'root'} . $_[0];   # prepend -root option value
1847 }
1848 $request{'whisker'}->{'method'} = $_[1];
1849 delete $request{'whisker'}->{'data'};
1850 delete $request{'Content-Encoding'};
1851 delete $request{'Content-Length'};
1852 my $header_hash=$_[3];
1853
1854 if (defined $_[2])
1855 {
1856    my $r=$_[2];
1857    $r =~ s/\\\"/\"/g;
1858    $request{'whisker'}->{'data'} = $r;
1859 }
1860 
1861 # check for extra HTTP headers
1862 if (defined $_[3])
1863 {
1864    # loop through the hash ref passed and add each header to request
1865    while (my($key, $value) = each(%$header_hash))
1866    {
1867       $request{$key}=$value;
1868    }
1869 }
1870 unless ($_[4]) { LW2::http_fixup_request(\%request); }
1871 # Cache
1872 if (defined($CACHE{$request{whisker}->{uri}}) && !defined($CLI{'nocache'}) &&
1873     ($CACHE{$request{whisker}->{uri}}{method} eq $request{whisker}->{method}))
1874 {
1875    # Get from cache
1876    nprint("- Retrieving $request{whisker}->{uri} from cache.","v");
1877    $result{whisker}->{code}=$CACHE{$request{whisker}->{uri}}{res};
1878    $result{whisker}->{data}=$CACHE{$request{whisker}->{uri}}{content};
1879 }
1880 else
1881 {
1882    LW2::http_do_request_timeout(\%request,\%result);
1883    $NIKTO{totalrequests}++;
1884    unless (defined $CLI{'nocache'})
1885    {
1886       $CACHE{$request{whisker}->{uri}}{method}=$request{whisker}->{method};
1887       $CACHE{$request{whisker}->{uri}}{res}=$result{whisker}->{code};
1888       $CACHE{$request{whisker}->{uri}}{content}=$result{whisker}->{data};
1889    }
1890    if ($OUTPUT{debug})
1891    {
1892       dump_var("Request Hash", \%request);
1893       dump_var("Result Hash", \%result);
1894    }
1895 }
1896 
1897 if (defined $CLI{'display'} && ($CLI{'display'} =~ /2/) && (defined($result{'whisker'}->{'cookies'})))
1898  {
1899   foreach my $c (@{$result{'whisker'}->{'cookies'}})
1900    {
1901      nprint("+ $request{'whisker'}->{'uri'} sent cookie: $c");
1902    }
1903  }
1904
1905 # Clean up extra headers
1906 if (defined $_[3])
1907 {
1908    while (my($key, $value) = each(%$header_hash))
1909    {
1910       delete $request{$key};
1911    }
1912 }
1913 return $result{'whisker'}->{'code'}, $result{'whisker'}->{'data'};
1914}
1915#######################################################################
1916sub setup_hash
1917{
1918   my ($reqhash,$mark) = @_;
1919
1920   # Do the standard set up for the hash
1921   LW2::http_init_request($reqhash);
1922   $reqhash->{whisker}->{ssl_save_info}=1;
1923   $reqhash->{whisker}->{lowercase_incoming_headers}=1;
1924   $reqhash->{whisker}->{timeout}=$NIKTO{timeout};
1925   if (defined $CLI{'evasion'})
1926   {
1927      $reqhash->{whisker}->{encode_anti_ids}=$CLI{'evasion'};
1928   }
1929   $reqhash->{'User-Agent'}=$NIKTO{useragent};
1930   $reqhash->{whisker}->{retry}=0;
1931   $reqhash->{whisker}->{host} = $mark->{hostname} || $mark->{ip};
1932
1933   if ($mark->{vhost})
1934   {
1935      $request{Host} = $mark->{vhost};
1936   }
1937   $reqhash->{whisker}->{port} = $mark->{port};
1938   $reqhash->{whisker}->{ssl} = $mark->{ssl};
1939   
1940   # Proxy stuff
1941   if (defined $NIKTOCONFIG{PROXYHOST} && defined $CLI{'useproxy'})
1942   {
1943      $reqhash->{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
1944      $reqhash->{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
1945      LW2::auth_set("proxy-basic",$reqhash,$NIKTOCONFIG{PROXYUSER},
1946                    $NIKTOCONFIG{PROXYPASS});   # set auth
1947   }
1948   
1949   return $reqhash;
1950}
1951#######################################################################
1952sub nfetch
1953{
1954   my ($mark, $uri, $method, $data, $headers, $noclean) = @_;
1955   if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; }
1956   my %request, %result;
1957   setup_hash(\%request, $mark);
1958 
1959   $request{whisker}->{uri} = $uri;
1960   if (defined $CLI{'root'})
1961   {
1962      $request{whisker}->{uri} = $CLI{'root'} . $uri; # prepend -root option value
1963   }
1964   $request{whisker}->{method} = $method;
1965   my $header_hash=$headers;
1966
1967   if (defined $data)
1968   {
1969      my $r=$data;
1970      $r =~ s/\\\"/\"/g;
1971      $request{whisker}->{data} = $r;
1972   }
1973 
1974   # check for extra HTTP headers
1975   if (defined $headers)
1976   {
1977      # loop through the hash ref passed and add each header to request
1978      while (my($key, $value) = each(%$header_hash))
1979      {
1980         $request{$key}=$value;
1981      }
1982   }
1983   unless ($noclean) { LW2::http_fixup_request(\%request); }
1984
1985   # Cache
1986   if (defined $CACHE{$uri} && !defined $CLI{'nocache'} &&
1987       $CACHE{$uri}{method} eq $method && $CACHE{$uri}{mark} eq $mark)
1988   {
1989      # Get from cache
1990      nprint("- Retrieving $uri from cache.","v");
1991      $result{whisker}->{code}=$CACHE{$uri}{res};
1992      $result{whisker}->{data}=$CACHE{$uri}{content};
1993   }
1994   else
1995   {
1996      LW2::http_do_request_timeout(\%request,\%result);
1997      $NIKTO{totalrequests}++;
1998      unless (defined $CLI{'nocache'})
1999      {
2000         $CACHE{$uri}{method}=$result{whisker}->{method};
2001         $CACHE{$uri}{res}=$result{whisker}->{code};
2002         $CACHE{$uri}{content}=$result{whisker}->{data};
2003         $CACHE{$uri}{mark}=$mark;
2004      }
2005      if ($OUTPUT{debug})
2006      {
2007         dump_var("Request Hash", \%request);
2008         dump_var("Result Hash", \%result);
2009      }
2010   }
2011 
2012   if (defined $CLI{'display'} && ($CLI{'display'} =~ /2/) && (defined($result{'whisker'}->{'cookies'})))
2013   {
2014      foreach my $c (@{$result{'whisker'}->{'cookies'}})
2015      {
2016         nprint("+ $uri sent cookie: $c");
2017      }
2018   }
2019
2020   # If headers is defined, copy the whisker headers to the hash
2021   if (defined $headers)
2022   {
2023      # First clear the hash
2024      foreach my $header (keys %$headers)
2025      {
2026         delete($headers->{$header});
2027      }
2028      while (my ($key, $value) = each(%result))
2029      {
2030         if ($key ne "whisker" && $key ne "connection")
2031         {
2032            $headers->{$key}=$value;
2033         }
2034      }
2035   }
2036
2037   return $result{'whisker'}->{'code'}, $result{'whisker'}->{'data'}, $result{'whisker'}->{'error'};
2038}
2039#######################################################################
2040sub set_scan_items
2041{
2042 my ($mark) = @_;
2043 # load the tests
2044 my $shname=$mark->{hostname} || $mark->{ip};
2045 %TESTS = ();
2046 my @SKIPLIST=();
2047 if (defined $NIKTOCONFIG{SKIPIDS})
2048 {
2049    @SKIPLIST = split(/ /,$NIKTOCONFIG{SKIPIDS});
2050 }
2051 
2052 # now load checks
2053 foreach my $line (@DBFILE)
2054 {
2055  if ($line =~ /^\"/)  # check
2056  {
2057   chomp($line);
2058   my @item=parse_csv($line);
2059   my $add=1;
2060
2061   # check tuning options
2062   if ((defined $CLI{'tuning'}) && (defined $item[2]))
2063   {
2064      # Work out the required tuning from the CLI string
2065      my $exclude=0;
2066      foreach my $tune (split(//,$CLI{'tuning'}))
2067      {
2068         if ($tune eq "x")
2069         {
2070            $exclude=1;
2071            next;
2072         }
2073         if ($exclude == 0)
2074         {
2075            if ($item[2] !~ /$tune/) { $add = 0; }
2076            next;
2077         }
2078         if ($exclude == 1)
2079         {
2080            if ($item[2] =~ /$tune/) { $add = 0; }
2081         }
2082      }
2083   }
2084    # Skip list
2085    foreach my $id (@SKIPLIST) { if ($id eq $item[0]) { $add=0; } }
2086
2087    if ($add)
2088    {
2089     # This nasty set of loops allows for multiple values per line
2090     my $ext = get_ext($item[3]);
2091     $db_extensions{$ext}=1;
2092
2093     # This escapes regex characters in the conditionals. This will have to change if regex is ever allowed in the db
2094     for (my $y=5;$y<=9;$y++) { $item[$y] =~ s/([^a-zA-Z0-9\s])/\\$1/g; }
2095
2096     $mark->{total_checks}++;
2097     $TESTS{$item[0]}{uri}=$item[3];
2098     $TESTS{$item[0]}{osvdb}=$item[1];
2099     $TESTS{$item[0]}{method}=$item[4];
2100     $TESTS{$item[0]}{match_1}=$item[5];
2101     $TESTS{$item[0]}{match_1_or}=$item[6];
2102     $TESTS{$item[0]}{match_1_and}=$item[7];
2103     $TESTS{$item[0]}{fail_1}=$item[8];
2104     $TESTS{$item[0]}{fail_2}=$item[9];
2105     $TESTS{$item[0]}{message}=$item[10];
2106     $TESTS{$item[0]}{data}=$item[11];
2107     $TESTS{$item[0]}{headers}=$item[12];
2108    }
2109   }
2110  }
2111
2112nprint("- $mark->{total_checks} server checks loaded","v");
2113if ($mark->{total_checks} eq 0 && !defined $CLI{'tuning'}) { nprint("+ Unable to load valid checks!"); exit; }
2114return;
2115}
2116#######################################################################
2117sub max_test_id
2118{
2119   return (sort {$a<=>$b} keys %TESTS)[-1];
2120}
2121#######################################################################
2122sub char_escape
2123{
2124   $_[0] =~ s/([^a-zA-Z0-9 ])/\\$1/g;
2125   return $_[0];
2126}
2127#######################################################################
2128sub parse_csv
2129{
2130   my $text = $_[0] || return;
2131   my @new = ();
2132   push(@new, $+) while $text =~ m{
2133      "([^\"\\]*(?:\\.[^\"\\]*)*)",?
2134       |  ([^,]+),?
2135       | ,
2136   }gx;
2137   push(@new, undef) if substr($text, -1,1) eq ',';
2138   return @new;
2139}
2140#######################################################################
2141sub version
2142{
2143   my @NIKTOFILES=dirlist($NIKTOCONFIG{PLUGINDIR},"(^nikto|^db_)");
2144
2145   nprint "$DIV\n$NIKTO{name} Versions\n$DIV\n";
2146   nprint "File                               Version      Last Mod\n";
2147   nprint "-----------------------------      --------     ----------\n";
2148   nprint "Nikto main                         $NIKTO{version}\n";
2149   nprint "LibWhisker                         $LW2::VERSION\n";
2150
2151   foreach my $FILE (sort @NIKTOFILES)
2152   {
2153      open(FI,"<$NIKTOCONFIG{PLUGINDIR}/$FILE") || die print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/$FILE': $!\n";;;
2154      my @F=<FI>;
2155      close(FI);
2156      my @VERS=grep(/^#VERSION/,@F);
2157      my @MODS=grep(/^# \$Id:/,@F);
2158      chomp($VERS[0]);
2159      chomp($MODS[0]);
2160      my @modification=split(/ /,$MODS[0]);
2161      $VERS[0] =~ s/^#VERSION,//;
2162      my $ws1=(35-length($FILE));
2163      my $ws2=(13-length($VERS[0]));
2164      nprint "$FILE". " " x $ws1  . "$VERS[0]". " " x $ws2. "$modification[4]\n";
2165   }
2166   nprint "$DIV\n";
2167
2168   exit;
2169}
2170#######################################################################
2171sub send_updates
2172{
2173 if ($NIKTOCONFIG{UPDATES} !~ /yes|auto/i) { return; }
2174 
2175 my $have_updates=0;
2176 my ($updated_version, $answer, $RES);
2177 foreach my $ver (keys %UPDATES)
2178  {
2179   if ($ver !~ /[0-9]/)   { next; }        # no version info...useless
2180   if ($ver eq "Win32") { next; }          # also no use
2181   if ($ver eq "Linux-Mandrake") { next; } # just... usually garbage
2182   $have_updates=1;
2183   $updated_version .= "$ver ";
2184  }
2185 
2186 if ((!$have_updates) || ($updated_version eq "")) { return; }
2187 
2188 # make sure the db_outdatedb isn't *too* old
2189 open(OD,"<$NIKTOCONFIG{PLUGINDIR}/db_outdated") || die print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/db_outdated': $!\n";;
2190 @F=<OD>;
2191 close(OD);
2192 
2193 my @LASTUPDATED=grep(/^\# \$Id: db_outdated/, @F);
2194 $LASTUPDATED[0] =~ /([0-9]{4}\-[0-9]{2})/;
2195 $lm = $1;
2196 $lm =~ s/\-//g;
2197 
2198 my @NOW=localtime(time);
2199 $NOW[5]+=1900;
2200 $NOW[4]++;
2201 if ($NOW[4] < 10) { $NOW[4]="0$NOW[4]"; }
2202 my $now="$NOW[5]$NOW[4]";
2203 if (($now - $lm) > 120) { return; }  # DB is 4 months old... ignore the updates!
2204 
2205 $updated_version =~ s/\s+$//;
2206 $updated_version =~ s/^\s+//;
2207 
2208 if ($NIKTOCONFIG{UPDATES} eq "auto") { $answer = "y"; }
2209 else
2210  {
2211   $answer=read_data("\n
2212   ***** Portions of the server's ident string ($updated_version)
2213   are not in the Nikto database or is newer than the known string.
2214   Would you like to submit this information (*no server specific data*)
2215   to CIRT.net for a Nikto update (or you may email to sullo\@cirt.net)
2216   (y/n)? ","");
2217  }
2218
2219 if ($answer !~ /y/i) { return; }
2220 
2221 LW2::http_init_request(\%request);
2222 my $server="www.cirt.net";
2223 $request{'whisker'}->{'version'}="1.1";
2224 $request{'whisker'}->{'port'}=80;
2225 $request{'whisker'}->{'anti_ids'}="";
2226 $request{'User-Agent'}="Nikto Update Agent";
2227 $request{'Host'}=$server;
2228 
2229 for (my $i=0;$i<=$#ARGV;$i++) {
2230        if (($ARGV[$i] eq "-u") || ($ARGV[$i] eq "-useproxy")) {
2231                $CLI{'useproxy'}=1;
2232                last;
2233        }
2234 }
2235 
2236 my  $ip=gethostbyname($server);
2237 if ($ip ne "") { $request{'whisker'}->{'host'}= inet_ntoa($ip); }
2238 else { $request{'whisker'}->{'host'}=$server; }
2239 if (($NIKTOCONFIG{PROXYHOST} ne "") && ($CLI{'useproxy'}))
2240
2241  {
2242   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
2243   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
2244  }
2245
2246 # send data
2247 LW2::http_close(\%request);  # force-close any old connections
2248 LW2::http_fixup_request(\%request);
2249 ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");
2250
2251 # if res is blank... maybe only proxy to get to net?
2252 if (($RES eq "") && ($NIKTOCONFIG{PROXYHOST} ne ""))
2253  {
2254   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
2255   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
2256   ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");
2257  }
2258
2259 if ($RES eq 407) # requires Auth
2260  {
2261   if ($NIKTOCONFIG{PROXYUSER} eq "") 
2262      {
2263      $NIKTOCONFIG{PROXYUSER}=read_data("Proxy ID: ","");
2264      $NIKTOCONFIG{PROXYPASS}=read_data("Proxy Pass: ","noecho");
2265     }
2266   LW2::auth_set("proxy-basic",\%request,$NIKTOCONFIG{PROXYUSER},$NIKTOCONFIG{PROXYPASS});
2267
2268   # and try again
2269   LW2::http_close(\%request);  # force-close any old connections
2270   LW2::http_fixup_request(\%request);
2271  ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");
2272  }
2273
2274 if ($RES eq "") # lookup failure?
2275  {
2276        LW2::http_close(\%request);  # force-close any old connections
2277        $request{'whisker'}->{'host'}=$NIKTOCONFIG{CIRT};
2278        $request{'Host'}="www.cirt.net";
2279        LW2::http_fixup_request(\%request);
2280   ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");
2281  }
2282
2283 if ($CONTENT !~ /SUCCESS/)
2284  {
2285   print STDERR "+ ERROR: ($RES, $CONTENT): Unable to send updated version string(s) to CIRT.net\n";
2286  }
2287 else
2288  {
2289   nprint "- Sent updated version string(s) to CIRT.net\n";
2290  }
2291
2292return;
2293}
2294#######################################################################
2295sub usage
2296{
2297   if ($_[0] eq 2) { nprint($NIKTO{options}); }
2298   else { nprint($NIKTO{options_short}); }
2299   exit;
2300}
2301#######################################################################
2302sub init_db
2303{
2304   my $dbname=$_[0];
2305   my $filename="$NIKTOCONFIG{PLUGINDIR}/" . $dbname;
2306   my (@dbarray, @headers);
2307   my $hashref = {};
2308
2309   # Check that the database exists
2310   unless (open(IN, "<$filename"))
2311   {
2312      nprint("+ ERROR: Unable to open database file $dbname: $!.");
2313      return $dbarray;
2314   }
2315
2316   # Now read the header values
2317   while (<IN>)
2318   {
2319      chomp;
2320      s/\#.*$//;
2321      if ($_ eq "" ) { next };
2322      unless (@headers)
2323      {
2324         @headers=parse_csv($_);
2325      }
2326      else
2327      {
2328         # contents; so split them up and apply to hash
2329         my @contents=parse_csv($_);
2330         my $hashref={};
2331         for (my $i=0; $i<=$#contents; $i++)
2332         {
2333            $hashref->{$headers[$i]} = $contents[$i];
2334         }
2335         push(@dbarray, $hashref);
2336      }
2337   }
2338   return \@dbarray;
2339}
2340#######################################################################
2341sub add_vulnerability
2342{
2343   my ($mark, $message, $nikto_id, $osvdb, $method, $uri) = @_;
2344   $uri="/" unless (defined $uri);
2345   $method="GET" unless (defined $method);
2346   $osvdb="0" unless (defined $osvdb);
2347
2348   my $result="";
2349   if (defined $_[7])
2350   {
2351      $result=$_[7]->{whisker}->{data};
2352   }
2353   my $outmessage=$message;
2354   my $resulthash={};
2355
2356   unless ($osvdb eq "")
2357   {
2358      $outmessage ="OSVDB-$osvdb: $message";
2359   }
2360   nprint("+ $outmessage");
2361   %$resulthash = (
2362      mark     => $mark,
2363      message  => $message,
2364      nikto_id => $nikto_id,
2365      osvdb    => $osvdb,
2366      method   => $method,
2367      uri      => $uri,
2368      result   => $result,
2369   );
2370   $mark->{total_vulns}++;
2371   push(@RESULTS,$resulthash);
2372   # Now report it
2373   report_item($mark, $resulthash);
2374}
2375
2376sub list_plugins {
2377   # Just do a load_plugins, then loop through the array and print out name,
2378   # description and copyright
2379
2380   load_plugins();
2381
2382   foreach my $plugin (@PLUGINS) {
2383      nprint("Plugin $plugin->{name}");
2384      nprint(" $plugin->{full_name} - $plugin->{description}");
2385      nprint(" Written by $plugin->{author}, Copyright (C) $plugin->{copyright}");
2386      nprint("\n");
2387   }
2388
2389   exit(0);
2390}
2391#######################################################################
2392sub nikto_core { return; } # trap for this plugin being called to run. lame.
2393#######################################################################
2394
23951;
Note: See TracBrowser for help on using the repository browser.