source: trunk/plugins/nikto_core.plugin @ 75

Revision 75, 77.3 KB checked in by deity, 5 years ago (diff)

Fix for ticket #37

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