source: trunk/plugins/nikto_core.plugin @ 70

Revision 70, 78.1 KB checked in by deity, 5 years ago (diff)

Fix for ticket #38 and new dynamic database api

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