source: trunk/plugins/nikto_tests.plugin @ 457

Revision 457, 9.6 KB checked in by sullo, 3 years ago (diff)

tidying up... a few other minor changes.

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