#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;
