#VERSION,2.00 # $Id: nikto_tests.plugin 79 2008-09-21 15:34:39Z deity $ ############################################################################### # Copyright (C) 2007 CIRT, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 2 # of the License only. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### ############################################################################### # PURPOSE # Perform the full database of nikto tests against a target ############################################################################### sub nikto_tests_init { my $id = { name => "Tests", full_name => "Nikto Tests", author => "Sullo, Deity", description => "Test host with the standard Nikto tests", copyright => "2008 CIRT Inc.", scan_method => \&nikto_tests, scan_weight => 99, }; return $id; } sub nikto_tests { my ($mark) = @_; # this is the actual the looped code for all the checks foreach my $CHECKID (sort keys %TESTS) { if ($CHECKID >= 500000) { next; } # skip TESTS added manually during run (for reports) # replace variables in the uri my @urilist=change_variables($TESTS{$CHECKID}{uri}); # Now repeat for each uri foreach my $uri (@urilist) { (my $RES, $CONTENT) = fetch($uri,$TESTS{$CHECKID}{method},$TESTS{$CHECKID}{data}); nprint("- $RES for $TESTS{$CHECKID}{method}:\t$request{whisker}{uri}","v"); # Check for errors to reduce false positives if (defined $result{whisker}->{error}) { # An error occured, show in verbose mode and skip # Try it again before we report it fully sleep(1); ($RES, $CONTENT) = fetch($uri,$TESTS{$CHECKID}{method},$TESTS{$CHECKID}{data}); nprint("- $RES for $TESTS{$CHECKID}{method}:\t$request{whisker}{uri}","v"); if (defined $result{whisker}->{error}) { nprint("- ERROR: $uri returned an error: $result{whisker}{error}\n"); next; } } $NIKTO{resp_counts}{$RES}{total}++; # do auth/redir first, independent of test pass/fail if ($RES eq 401) { $result{'www-authenticate'} =~ /realm=\"(.+)\"/; my $R = $1; if ($R eq '') { $R = $result{'www-authenticate'} } do_auth($request, $result, $mark); nprint("+ $uri - Requires Authentication for realm '$R'") if $CLI{display} =~ /4/; $RES=$result{'whisker'}->{'code'}; $CONTENT=$result{'whisker'}->{'data'}; } elsif (($RES eq 300) || ($RES eq 301) || ($RES eq 302) || ($RES eq 303) || ($RES eq 307)) { nprint("+ $uri - Redirects ($RES) to " . $result{'location'} . " , $TESTS{$CHECKID}{message}") if $CLI{display} =~ /1/; } elsif ($RES eq 200) { nprint("+ $uri - 200/OK Response could be $TESTS{$CHECKID}{message}") if $CLI{display} =~ /3/; } my $m1_method= my $m1o_method= my $m1a_method= my $f2_method= my $f1_method ="content"; my $positive=0; # how to check each conditional if ($TESTS{$CHECKID}{match_1} =~ /^[0-9]{3}$/) { $m1_method="code"; } if ($TESTS{$CHECKID}{match_1_or} =~ /^[0-9]{3}$/) { $m1o_method="code"; } if ($TESTS{$CHECKID}{match_1_and} =~ /^[0-9]{3}$/) { $m1a_method="code"; } if ($TESTS{$CHECKID}{fail_1} =~ /^[0-9]{3}$/) { $f1_method="code"; } if ($TESTS{$CHECKID}{fail_2} =~ /^[0-9]{3}$/) { $f2_method="code"; } # basic match for positive result if ($m1_method eq "content") { if ($CONTENT =~ /$TESTS{$CHECKID}{match_1}/) { $positive=1; } } else { if (($RES eq $TESTS{$CHECKID}{match_1}) || ($RES eq $FoF{okay}{response})) { $positive=1; } } # no match, check optional match if ((!$positive) && ($TESTS{$CHECKID}{match_1_or} ne "")) { if ($m1o_method eq "content") { if ($CONTENT =~ /$TESTS{$CHECKID}{match_1_or}/) { $positive=1; } } else { if (($RES eq $TESTS{$CHECKID}{match_1_or}) || ($RES eq $FoF{okay}{response})) { $positive=1; } } } # matched on something, check fails/ands if ($positive) { if ($TESTS{$CHECKID}{fail_1} ne "") { if ($f1_method eq "content") { if ($CONTENT =~ /$TESTS{$CHECKID}{fail_1}/) { next; } } else { if ($RES eq $TESTS{$CHECKID}{fail_1}) { next; } } } if ($TESTS{$CHECKID}{fail_2} ne "") { if ($f2_method eq "content") { if ($CONTENT =~ /$TESTS{$CHECKID}{fail_2}/) { next; } } else { if ($RES eq $TESTS{$CHECKID}{fail_2}) { next; } } } if ($TESTS{$CHECKID}{match_1_and} ne "") { if ($m1a_method eq "content") { if ($CONTENT !~ /$TESTS{$CHECKID}{match_1_and}/) { next; } } else { if ($RES ne $TESTS{$CHECKID}{match_1_and}) { next; } } } # if it's an index.php, check for normal /index.php to see if it's a FP if ($uri =~ /^\/index.php\?/) { my $CONTENT=rm_active_content($CONTENT, $uri); if (LW2::md4($CONTENT) eq $FoF{'index.php'}{match}) { next; } } # lastly check for a false positive based on file extension or type if (($m1_method eq "code") || ($m1o_method eq "code")) { if (is_404($request{whisker}{uri},$CONTENT,$RES)) { next; } } $TESTS{$CHECKID}{osvdb} =~ s/\s+/ OSVDB\-/g; add_vulnerability($mark,"$request{whisker}{uri}: $TESTS{$CHECKID}{message}",$CHECKID,$TESTS{$CHECKID}{osvdb},$TESTS{$CHECKID}{method},$uri); } } } # end check loop return; } 1;