source: trunk/plugins/nikto_tests.plugin @ 500

Revision 500, 9.8 KB checked in by sullo, 3 years ago (diff)

Fix for #172, Status reports differ

Line 
1#VERSION,2.02
2# $Id: nikto_tests.plugin 79 2008-09-21 15:34:39Z deity $
3###############################################################################
4#  Copyright (C) 2007 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# Perform the full database of nikto tests against a target
22###############################################################################
23sub nikto_tests_init {
24    my $id = { name        => "tests",
25               full_name   => "Nikto Tests",
26               author      => "Sullo, Deity",
27               description => "Test host with the standard Nikto tests",
28               copyright   => "2008 CIRT Inc.",
29               hooks       => {
30                                 scan => {
31                                    method => \&nikto_tests,
32                                    weight => 99,
33                                 },
34                              },             
35               options     => {
36                         passfiles => "Flag to indicate whether to check for common password files",
37                         all => "Flag to indicate whether to check all files with all directories",
38                         report => "Report a status after the passed number of tests",
39                         }
40                 };
41    return $id;
42}
43
44sub nikto_tests {
45    my ($mark, $parameters) = @_;
46
47    # this is the actual the looped code for all the checks
48    foreach my $checkid (sort keys %TESTS) {
49        if ($checkid >= 500000) { next; }    # skip TESTS added manually during run (for reports)
50                                             # replace variables in the uri
51        my @urilist = change_variables($TESTS{$checkid}{'uri'});
52
53        # Now repeat for each uri
54        foreach my $uri (@urilist) {
55            my %headers;
56            (my $res, $content, $error) =
57              nfetch($mark, $uri,
58                     $TESTS{$checkid}{'method'},
59                     $TESTS{$checkid}{'data'},
60                     \%headers, "", $checkid);
61
62            # auth is now done in nfetch
63            if ($res eq 200) {
64                nprint("+ $uri - 200/OK Response could be $TESTS{$checkid}{'message'}")
65                  if $OUTPUT{'show_ok'};
66            }
67            elsif ($res =~ /30(?:[0-3]|7)/) {
68                nprint(  "+ $uri - Redirects ($res) to "
69                       . $headers{'location'}
70                       . " , $TESTS{$checkid}{'message'}")
71                  if $OUTPUT{'show_redirects'};
72            }
73
74            my $m1_method = my $m1o_method = my $m1a_method = my $f2_method = my $f1_method =
75              "content";
76            my $positive = 0;
77
78            # how to check each conditional
79            if ($TESTS{$checkid}{'match_1'}     =~ /^[0-9]{3}$/) { $m1_method  = "code"; }
80            if ($TESTS{$checkid}{'match_1_or'}  =~ /^[0-9]{3}$/) { $m1o_method = "code"; }
81            if ($TESTS{$checkid}{'match_1_and'} =~ /^[0-9]{3}$/) { $m1a_method = "code"; }
82            if ($TESTS{$checkid}{'fail_1'}      =~ /^[0-9]{3}$/) { $f1_method  = "code"; }
83            if ($TESTS{$checkid}{'fail_2'}      =~ /^[0-9]{3}$/) { $f2_method  = "code"; }
84
85            # basic match for positive result
86            if ($m1_method eq "content") {
87                if ($content =~ /$TESTS{$checkid}{'match_1'}/) {
88                    $positive = 1;
89                }
90            }
91            else {
92                if (($res eq $TESTS{$checkid}{'match_1'}) || ($res eq $FoF{'okay'}{'response'})) {
93                    $positive = 1;
94                }
95            }
96
97            # no match, check optional match
98            if ((!$positive) && ($TESTS{$checkid}{'match_1_or'} ne "")) {
99                if ($m1o_method eq "content") {
100                    if ($content =~ /$TESTS{$checkid}{'match_1_or'}/) {
101                        $positive = 1;
102                    }
103                }
104                else {
105                    if (   ($res eq $TESTS{$checkid}{'match_1_or'})
106                        || ($res eq $FoF{'okay'}{'response'})) {
107                        $positive = 1;
108                    }
109                }
110            }
111
112            # matched on something, check fails/ands
113            if ($positive) {
114                if ($TESTS{$checkid}{'fail_1'} ne "") {
115                    if ($f1_method eq "content") {
116                        if ($content =~ /$TESTS{$checkid}{'fail_1'}/) { next; }
117                    }
118                    else {
119                        if ($res eq $TESTS{$checkid}{'fail_1'}) { next; }
120                    }
121                }
122                if ($TESTS{$checkid}{'fail_2'} ne "") {
123                    if ($f2_method eq "content") {
124                        if ($content =~ /$TESTS{$checkid}{'fail_2'}/) { next; }
125                    }
126                    else {
127                        if ($res eq $TESTS{$checkid}{'fail_2'}) { next; }
128                    }
129                }
130                if ($TESTS{$checkid}{'match_1_and'} ne "") {
131                    if ($m1a_method eq "content") {
132                        if ($content !~ /$TESTS{$checkid}{'match_1_and'}/) { next; }
133                    }
134                    else {
135                        if ($res ne $TESTS{$checkid}{'match_1_and'}) { next; }
136                    }
137                }
138
139                # if it's an index.php, check for normal /index.php to see if it's a FP
140                if ($uri =~ /^\/index.php\?/) {
141                    my $content = rm_active_content($content, $uri);
142                    if (LW2::md4($content) eq $FoF{'index.php'}{'match'}) { next; }
143                }
144
145                # lastly check for a false positive based on file extension or type
146                if (($m1_method eq "code") || ($m1o_method eq "code")) {
147                    if (is_404($uri, $content, $res, $headers{'location'})) { next; }
148                }
149
150                $TESTS{$checkid}{'osvdb'} =~ s/\s+/ OSVDB\-/g;
151                add_vulnerability($mark, "$uri: $TESTS{$checkid}{'message'}",
152                                  $checkid,
153                                  $TESTS{$checkid}{'osvdb'},
154                                  $TESTS{$checkid}{'method'}, $uri);
155            }
156        }
157
158        # Percentages
159        if ($OUTPUT{'progress'}) {
160            if ($parameters->{'report'}) {
161                if (($NIKTO{'totalrequests'} % $parameters->{'report'}) == 0) {
162                    my $line = sprintf("- Completed: %d tests, approximately %.0f%% complete", $NIKTO{'totalrequests'},
163                                       ($NIKTO{'totalrequests'} / ($NIKTO{'total_checks'} * $NIKTO{'total_targets'}) * 100));
164                    nprint($line);
165                }
166            }
167        }
168    }    # end check loop
169
170    # Perform mutation tests
171    if ($parameters->{'passfiles'}) {
172        passchecks($mark);
173    }
174    if ($parameters->{'all'}) {
175        allchecks($mark);
176    }
177
178    return;
179}
180
181sub passchecks {
182    my ($mark) = @_;
183    my @DIRS   = (split(/ /, $VARIABLES{"\@PASSWORDDIRS"}));
184    my @PFILES = (split(/ /, $VARIABLES{"\@PASSWORDFILES"}));
185    my @EXTS = qw(asp bak dat data dbc dbf exe htm html htx ini lst txt xml php php3 phtml);
186
187    nprint("- Performing passfiles mutation", "v");
188
189    foreach my $dir (@DIRS) {
190        foreach my $file (@PFILES) {
191            next if ($file eq "");
192
193            # dir/file
194            testfile($mark, "$dir$file", "passfiles", "299998");
195
196            foreach my $ext (@EXTS) {
197
198                # dir/file.ext
199                testfile($mark, "$dir$file.$ext", "passfiles", "299998");
200
201                foreach my $cgi (@CGIDIRS) {
202
203                    # dir/file.ext
204                    testfile($mark, "$cgi$dir$file.$ext", "passfiles", "299998");
205
206                    # dir/file
207                    testfile($mark, "$cgi$dir$file", "passfiles", "299998");
208                }
209            }
210        }
211    }
212}
213
214sub allchecks {
215    my ($mark) = @_;
216
217    # Hashes to temporarily store files/dirs in
218    # We're using hashes to ensure that duplicates are removed
219    my (%FILES, %DIRS);
220
221    # build the arrays
222    nprint("- Loading root level files", "v");
223    foreach my $checkid (keys %TESTS) {
224
225        # Expand out vars so we get full matches
226        my @uris = change_variables($TESTS{$checkid}{'uri'});
227
228        foreach my $uri (@uris) {
229            my $dir  = LW2::uri_get_dir($uri);
230            my $file = $uri;
231
232            if ($dir ne "") {
233                $DIRS{$dir} = "";
234                $dir  =~ s/([^a-zA-Z0-9])/\\$1/g;
235                $file =~ s/$dir//;
236            }
237            if (($file ne "") && ($file !~ /^\?/)) {
238                $FILES{$file} = "";
239            }
240        }
241    }
242
243    # Now do a check for each item - just check the return status, nothing else
244    foreach my $dir (keys %DIRS) {
245        foreach my $file (keys %FILES) {
246            testfile($mark, "$dir$file", "all checks", 299999);
247        }
248    }
249}
250
251sub testfile {
252    my ($mark, $uri, $name, $tid) = @_;
253    my ($res, $content, $error) = nfetch($mark, "$uri", "GET", "", "", "", "Tests: $name");
254    nprint("- $res for $uri", "v");
255    if ($error) {
256        $mark->{'total_errors'}++;
257        nprint("+ ERROR: $uri returned an error: $error", "e");
258        return;
259    }
260    if ($res == 200) {
261        add_vulnerability($mark, "$uri: file found during $name mutation", "$tid", "0", "GET");
262    }
263}
264
2651;
Note: See TracBrowser for help on using the repository browser.