source: trunk/plugins/nikto_core.plugin @ 267

Revision 267, 75.4 KB checked in by sullo, 3 years ago (diff)

Tweaks to how report formats are validated

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