source: trunk/plugins/nikto_core.plugin @ 339

Revision 339, 84.3 KB checked in by sullo, 3 years ago (diff)

Fix a crash based on bad regex content

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