source: trunk/plugins/nikto_core.plugin @ 504

Revision 504, 91.5 KB checked in by sullo, 3 years ago (diff)

Changes for reopened #168, proxy updates.

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