source: trunk/plugins/nikto_core.plugin @ 456

Revision 456, 90.2 KB checked in by sullo, 3 years ago (diff)

Make status output a bit more concise.

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