source: trunk/plugins/nikto_tests.plugin @ 483

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

Update version numbers... lost sync!

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