source: trunk/plugins/nikto_tests.plugin @ 342

Revision 342, 10.3 KB checked in by sullo, 3 years ago (diff)

Lots of tidy love.

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