source: trunk/plugins/nikto_apacheusers.plugin @ 449

Revision 449, 7.4 KB checked in by sullo, 3 years ago (diff)

Fix for #133: regular expression matching causes errors.
Removed char_escape and some other regexs in favor of the faster quotemeta().
Optimized rm_active_content() a little by shuffling code and reducing some mem copies/regexs. Needs more work.

  • Property svn:keywords set to Id
Line 
1#VERSION,2.03
2# $Id$
3###############################################################################
4#  Copyright (C) 2004 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# Apache user enumeration
22###############################################################################
23
24sub nikto_apacheusers_init {
25    my $id = {
26            name        => "apacheusers",
27            full_name   => "Apache Users",
28            author      => "Javier Fernandez-Sanguinoi Pena",
29            description => "Checks whether we can enumerate usernames directly from the web server",
30            scan_method => \&nikto_apacheusers,
31            copyright   => "2008 CIRT Inc.",
32            options     =>  {
33                                enumerate   => "Flag to indicate whether to attempt to enumerate users",
34                                dictionary  => "Filename for a dictionary file of users",
35                                size        => "Maximum size of username if bruteforcing",
36                                home        => "Look for ~user to enumerate",
37                                cgiwrap     => "User cgi-bin/cgiwrap to enumerate"
38                            }
39            };
40    return $id;
41}
42
43sub nikto_apacheusers {
44    my ($mark, $parameters) = @_;
45    my $apacheusers=0;
46   
47    # First ensure that the server is vulnerable
48    (my $result, $content) = nfetch($mark, "/~root", "GET", "", "", "", "apacheusers: known user");
49
50    $content = quotemeta($content);
51    if ($content =~ /forbidden/i)    # good on "root"
52    {
53        (my $result, $content) = nfetch($mark, "/~" . LW2::utils_randstr(8),
54                                        "GET", "", "", "", "apacheusers: invalid user");
55
56        $content = quotemeta($content);
57        if ($content !~ /forbidden/i)    # Good, it gave an error instead of forbidden
58        {
59            add_vulnerability(
60                $mark,
61                "Enumeration of users is possible by requesting ~username (responds with 'Forbidden' for users, 'not found' for non-existent users).",
62                999999,
63                637,
64                "GET",
65                "/~root"
66                );
67        }
68        $apacheusers=1;
69    }
70   
71    # If we can't enumerate users then return
72    return unless ($apacheusers == 1);
73    # If we haven't been asked to enumerate users then return
74    return unless (defined $parameters->{'enumerate'} &&
75                   $parameters->{'enumerate'} == 1);
76   
77    # Now we can attempt to enumerate the users
78    my ($url, $dictfile, $size);
79    my @cgiwraps;
80    my @cfgcgi = split(/ /, $VARIABLES{"\@CGIDIRS"});
81
82    if (defined $parameters->{'dictionary'}) {
83        $dictfile = $parameters->{'dictionary'};
84    }
85    if (defined $parameters->{'size'}) {
86        $size = $parameters->{'size'};
87    }
88
89    # Set the URL according to the parameters
90    if (defined $parameters->{'cgiwrap'}) {
91
92        # Check for existence of cgiwrap
93        foreach my $cgidir (@CFGCGI) {
94            my $curl = "$cgidir" . "cgiwrap";
95            (my $result, $content) =
96                nfetch($mark, $curl, "GET", "", "", "", "user_enum_apache: cgiwrap");
97            if ($content =~ /check your URL/i) {
98                push(@cgiwraps, "$curl");
99            }
100        }
101
102        foreach my $cgiwrap (@cgiwraps) {
103            $url = "$cgiwrap/~";
104
105            # First check whether we use a dictionary attack of brute force it
106            if (defined $dictfile) {
107
108                # We have options - assume it is a dictionary attack
109                nikto_user_enum_apache_dictionary($url, $mark, $dictfile);
110            }
111            else {
112                nikto_user_enum_apache_brute($url, $mark, $size);
113            }
114        }
115    }
116    if (defined $parameters->{'home'}) {
117        $url = "/~";
118
119        # First check whether we use a dictionary attack of brute force it
120        if (defined $dictfile) {
121
122            # We have options - assume it is a dictionary attack
123            nikto_user_enum_apache_dictionary($url, $mark, $dictfile);
124        }
125        else {
126            nikto_user_enum_apache_brute($url, $mark, $size);
127        }
128    }
129}
130
131sub nikto_user_enum_apache_brute {
132
133    # Note1: This script only generates names with letters A-Z (no numbers)
134    #
135    # Note2: this script will generate SUM(26^n)(n=$min to $max)
136    # it's probably faster to write this to a file than to generate it
137    # on the fly BTW.
138    #
139    # Of course, it could be optimized to skip some "strange"
140    # combinations of usernames, but hey, then it wouldn't
141    # be 'brute force' would it? (jfs)
142
143    my ($url, $mark, $size) = @_;
144    $size = 5 if ($size eq "");
145    nprint("- Enumerating Apache users (1 to $size characters).", "v");
146
147    my $text    = "a";
148    my $ctr     = 0;
149    my $message = "Valid users found via Apache enumeration: ";
150    my ($result, $content);
151    my @foundusers = ();
152    while (length($text) <= $size) {
153        if (($ctr % 500) eq 0) { nprint("- User enumeration guess $ctr ($text)", "v"); }
154        ($result, $content) =
155          nfetch($mark, $url . $text, "HEAD", "", "", "", "user_enum_apache: enumeration");
156        my $user = nikto_user_enum_apache_check($result, $text);
157        if (defined $user && $user ne "") {
158            push(@foundusers, $user);
159        }
160        $text++;
161        $ctr++;
162    }
163    if (scalar(@foundusers)) {
164        add_vulnerability($mark, $message . join(', ', @foundusers), 999997, "637", "HEAD", "/");
165    }
166
167}
168
169sub nikto_user_enum_apache_dictionary {
170    my ($url, $mark, $filename) = @_;
171    my $message    = "Valid users found via Apache enumeration: ";
172    my @foundusers = ();
173    my ($result, $content);
174    my $ctr = 0;
175
176    nprint("- Enumerating Apache users (using dictionary $filename).", "v");
177    unless (open(IN, "<$filename")) {
178        nprint("+ ERROR: Unable to open dictionary file $filename: $!.");
179    }
180
181    # Now attempt on each entry
182    while (<IN>) {
183        chomp;
184        s/\#.*$//;
185
186        # remove preceding ~ just in case
187        s/^~//;
188        if ($_ eq "") { next }
189        if (($ctr % 500) == 0) { nprint("- User enumeration guess $ctr ($_)", "v"); }
190        ($result, $content) =
191          nfetch($mark, $url . $_, "HEAD", "", "", "", "user_enum_apache: dictionary");
192        my $user = nikto_user_enum_apache_check($result, $_);
193        if ($user) {
194            push(@foundusers, $user);
195        }
196        $ctr++;
197    }
198    close(IN);
199    if (scalar(@foundusers)) {
200        add_vulnerability($mark, $message . join(', ', @foundusers), 999997, "637", "HEAD", "/");
201    }
202}
203
204sub nikto_user_enum_apache_check {
205    (my $code, $user) = @_;
206    my $result = "";
207
208    foreach my $found (split(/ /, $VARIABLES{"\@HTTPFOUND"})) {
209        if ($code eq $found) {
210            $result = $user;
211            last;
212        }
213    }
214
215    return $result;
216}
217
2181;
219
Note: See TracBrowser for help on using the repository browser.