source: trunk/plugins/nikto_core.plugin @ 76

Revision 76, 77.8 KB checked in by deity, 5 years ago (diff)

Reduce warnings in nikto

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