#VERSION,2.09
# $Id$
###############################################################################
#  Copyright (C) 2006 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 
#  Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
###############################################################################
# PURPOSE:
# Check for outdated items in banner
###############################################################################
sub nikto_outdated_init {
    my $id = { name        => "outdated",
               full_name   => "Outdated",
               author      => "Sullo",
               description => "Checks to see whether the web server is the latest version.",
               copyright   => "2008 CIRT Inc.",
               hooks       => { scan => { method => \&nikto_outdated, }, },
               };
    return $id;
}

sub nikto_outdated {
    my ($mark) = @_;
    return if $mark->{'terminate'};

    # Process banner to populate @BUILDITEMS for later eval

    # If banner has nothing that can be a version... return
    if ($mark->{'banner'} !~ /(?:\d|\/|\.)/) { return; }

    # If Apache, split on space...
    elsif (   ($mark->{'banner'} =~ /apache/i)
           || (($mark->{'banner'} =~ /\s/) && ($mark->{'banner'} =~ /\//))) {
        foreach my $item (split(/ /, $mark->{'banner'})) {
            $mark->{'components'}->{$item} = 1;
        }
    }

    # Some specific (and common) banner processing
    # WebLogic: strip all the date info...
    elsif ($mark->{'banner'} =~ /weblogic/i) {
        my @T = split(/ /, $mark->{'banner'});
        $mark->{'components'}->{ $T[0] . '\/' . $T[1] } = 1;
    }

    # SiteScope: strip all the date info...
    elsif ($mark->{'banner'} =~ /sitescope/i) {
        my @T = split(/ /, $mark->{'banner'});
        $mark->{'components'}->{ $T[0] } = 1;
    }

    # Some, like WEBrick, have multiple items but no spaces
    elsif (($mark->{'banner'} !~ /\s/) && ($mark->{'banner'} =~ /\/.*\//) && ($mark->{'banner'} =~ /\(/)) {

        # try converting () t spaces
        foreach my $item (split(/[\(\)]/, $mark->{'banner'})) {
            $mark->{'components'}->{$item} = 1;
        }
    }

    # Finally, unknown banners
    else {

        # use the last non 0-9 . a-z char as a sepr (' ', '-', '_' etc)
        my $sepr = $mark->{'banner'};
        $sepr =~ s/[a-zA-Z0-9\.\(\)]//gi;
        if ($sepr eq '') {
            $MATCHSTRING = $mark->{'banner'};
        }
        else {
            $sepr = substr($sepr, (length($sepr) - 1), 1);

            # break up ID string on $sepr
            my @T = split(/$sepr/, $mark->{'banner'});

            # assume last is version...
            for ($i = 0 ; $i < $#T ; $i++) { $MATCHSTRING .= "$T[$i] "; }
        }
        $MATCHSTRING =~ s/\s+$//;
        $mark->{'components'}->{$MATCHSTRING} = 1;
        nprint("Server Version String:$MATCHSTRING", "d");
    }

    my ($v, $V, $BI, $k) = "";

    # For each running component
    foreach $BI (keys %{ $mark->{'components'} }) {
        my $have_match = 0;

        # Check it against each value from db_outdated
        foreach $V (sort keys %OVERS) {
            next if $V eq '';
            if ($BI =~ /^$V/i)    # software name matched
            {
                $have_match = 1;

                # nab version
                foreach $k (keys %{ $OVERS{$V} }) {
                    if ($k eq "")    { next; }
                    if ($k eq "tid") { next; }
                    $v = $k;
                }

                # do version check (return true if we should alert)
                if (vereval($v, $BI, $V, $mark)) {
                    my $msg = $OVERS{$V}{$v};
                    $msg =~ s/\@RUNNING_VER/$BI/g;
                    $msg =~ s/\@CURRENT_VER/$v/g;
                    chomp($msg);
                    add_vulnerability($mark, $msg, $OVERS{$V}{'tid'}, 0, "HEAD");
                }
            }
        }
        if (!$have_match) { $mark->{'components'}->{'$BI'} = 2; }
    }
    return;
}

# do version evaluation
# arguments:
#	0: latest version as defined in db_outdated
#	1: running item being evaluated
#	2: item being matched against in db_outdated
#	3: mark hash
sub vereval {

    # split both by last char of $_[2], as it is the name to version separator
    my $sepr = substr($_[2], (length($_[2]) - 1), 1);
    nprint("nikto_outdated.plugin: verstring: $_[2], sepr:$sepr", "d");

    my $CURRENT      = lc($_[0]);
    my $RUNNING      = lc($_[1]);
    my $CURRENT_ORIG = $CURRENT;
    my $RUNNING_ORIG = $RUNNING;
    my $mark         = $_[3];

    nprint("nikto_outdated.plugin: \$CURRENT:$CURRENT:\$RUNNING:$RUNNING:", "d");

    my @T = split(/$sepr/, $CURRENT);
    $CURRENT = $T[$#T];                    # should be version...
    @T       = split(/$sepr/, $RUNNING);
    $RUNNING = $T[$#T];                    # should be version...

    # convert alphas to numerics so we can do a real comparison
    $CURRENT =~ s/([^0-9\.]){1}/"." . ord($1) . "."/eg;
    $RUNNING =~ s/([^0-9\.]){1}/"." . ord($1) . "."/eg;
    $RUNNING =~ s/\.+/\./g;
    $CURRENT =~ s/\.+/\./g;
    $RUNNING =~ s/^\.//;
    $CURRENT =~ s/^\.//;
    $RUNNING =~ s/\.$//;
    $CURRENT =~ s/\.$//;

    nprint("nikto_outdated.plugin: \$CURRENT:$CURRENT:\$RUNNING:$RUNNING\: (after numberifcation)",
           "d");

    if (($CURRENT !~ /[a-z]/) && ($RUNNING !~ /[a-z]/)) {
        @CUR = split(/\./, $CURRENT);
        @RUN = split(/\./, $RUNNING);
    }
    else {
        @CUR = split(//, $CURRENT);
        @RUN = split(//, $RUNNING);
    }

    # start with 0... eval each in turn...
    for (my $i = 0 ; $i <= $#CUR ; $i++) {
        nprint("nikto_outdated.plugin: major compare: \$CUR[$i]:$CUR[$i]: \$RUN[$i]:$RUN[$i]:",
               "d");
        if ($CUR[$i] > $RUN[$i]) { return 1; }    # running is older
        if (($CUR[$i] ne "") && ($RUN[$i] eq "")) { return 1; }    # running is older
        if ($CUR[$i] < $RUN[$i])                                   # running is newer
        {
            my $string = $_[1];
            $string =~ s/\s/\%20/g;
            $mark->{'components'}->{$string} = 2;
            return 0;
        }
    }
    return 0;    # running is the same version if we make it here
}

1;
