source: trunk/plugins/nikto_core.plugin @ 458

Revision 458, 91.3 KB checked in by sullo, 3 years ago (diff)

rm_active_content regex speedups

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