SentinelScan-WSS / backend /scanners /nikto /plugins /nikto_apacheusers.plugin
larxius's picture
Deploy SentinelScan WSS to HF Spaces
d543fc1 verified
Raw
History Blame Contribute Delete
7.49 kB
###############################################################################
# SPDX-License-Identifier: GPL-3.0-only
# PURPOSE: Apache user enumeration
###############################################################################
sub nikto_apacheusers_init {
my $id = {
name => "apacheusers",
full_name => "Apache Users",
author => "Javier Fernandez-Sanguinoi Pena",
description => "Checks whether we can enumerate usernames directly from the web server",
hooks => { scan => { method => \&nikto_apacheusers, }, },
copyright => "2008 Chris Sullo",
options => {
enumerate => "Flag to indicate whether to attempt to enumerate users",
dictionary => "Filename for a dictionary file of users",
size => "Maximum size of username if bruteforcing",
home => "Look for ~user to enumerate",
cgiwrap => "User cgi-bin/cgiwrap to enumerate"
}
};
return $id;
}
sub nikto_apacheusers {
my ($mark, $parameters) = @_;
return if $mark->{'terminate'};
my $apacheusers = 0;
# If we haven't been asked to enumerate users then return
return unless (defined $parameters->{'enumerate'}
&& $parameters->{'enumerate'} == 1);
# First ensure that the server is vulnerable
my ($res, $content, $error, $request, $response) = nfetch($mark, "/~bin", "GET", "", "", "", "apacheusers: known user");
if ($content =~ /forbidden/i) # good on "bin"
{
my ($res, $content, $errors, $request, $response) =
nfetch($mark, "/~" . LW2::utils_randstr(8),
"GET", "", "", "", "apacheusers: invalid user");
if ($content !~ /forbidden/i) # Good, it gave an error instead of forbidden
{
add_vulnerability(
$mark,
"/~bin: Enumeration of users is possible by requesting ~username (responds with 'Forbidden' for users and 'not found' for non-existent users).",
999999,
"CVE-2001-1013",
"GET",
"/~bin",
$request,
$response
);
}
$apacheusers = 1;
}
# If we can't enumerate users then return
return unless ($apacheusers == 1);
# Now we can attempt to enumerate the users
my ($url, $dictfile, $size);
my @cgiwraps;
my @CFGCGI = split(/ /, $VARIABLES{"\@CGIDIRS"});
if (defined $parameters->{'dictionary'}) {
$dictfile = $parameters->{'dictionary'};
}
if (defined $parameters->{'size'}) {
$size = $parameters->{'size'} if $parameters->{'size'} =~ /^\d+$/;
}
# Set the URL according to the parameters
if (defined $parameters->{'cgiwrap'}) {
# Check for existence of cgiwrap
foreach my $cgidir (@CFGCGI) {
my $curl = "$cgidir" . "cgiwrap";
my ($response, $content) =
nfetch($mark, $curl, "GET", "", "", "", "user_enum_apache: cgiwrap");
if ($content =~ /check your URL/i) {
push(@cgiwraps, "$curl");
}
}
foreach my $cgiwrap (@cgiwraps) {
$url = "$cgiwrap/~";
# First check whether we use a dictionary attack of brute force it
if (defined $dictfile) {
# We have options - assume it is a dictionary attack
nikto_user_enum_apache_dictionary($url, $mark, $dictfile);
}
else {
nikto_user_enum_apache_brute($url, $mark, $size);
}
}
}
if (defined $parameters->{'home'}) {
$url = "/~";
# First check whether we use a dictionary attack of brute force it
if (defined $dictfile) {
# We have options - assume it is a dictionary attack
nikto_user_enum_apache_dictionary($url, $mark, $dictfile);
}
else {
nikto_user_enum_apache_brute($url, $mark, $size);
}
}
}
sub nikto_user_enum_apache_brute {
# Note1: This script only generates names with letters A-Z (no numbers)
#
# Note2: this script will generate SUM(26^n)(n=$min to $max)
# it's probably faster to write this to a file than to generate it
# on the fly BTW.
#
# Of course, it could be optimized to skip some "strange"
# combinations of usernames, but hey, then it wouldn't
# be 'brute force' would it? (jfs)
my ($url, $mark, $size) = @_;
$size //= 5;
nprint("- Enumerating Apache users (1 to $size characters).", "v", "apacheusers");
my $text = "a";
my $ctr = 0;
my $message = "Valid users found via Apache enumeration: ";
my ($response, $content);
my @foundusers = ();
while (length($text) <= $size) {
return if $mark->{'terminate'};
if (($ctr % 500) == 0) {
nprint("- User enumeration guess $ctr ($text)", "v", "apacheusers");
}
my ($res, $content, $errors, $request, $response) =
nfetch($mark, $url . $text, "HEAD", "", "", "", "user_enum_apache: enumeration");
my $user = nikto_user_enum_apache_check($response, $text);
if (defined $user && $user ne "") {
push(@foundusers, $user);
}
$text++;
$ctr++;
}
if (scalar(@foundusers)) {
my $u = join(', ', @foundusers);
add_vulnerability($mark, $message . $u,
"000479", "CVE-2001-1013", "HEAD", "/", $request, $response);
}
}
sub nikto_user_enum_apache_dictionary {
my ($url, $mark, $filename) = @_;
my $message = "Valid users found via Apache enumeration: ";
my @foundusers = ();
my ($response, $content);
my $ctr = 0;
nprint("- Enumerating Apache users (using dictionary $filename).", "v", "apacheusers");
open(my $fh, "<", $filename)
or do { nprint("+ ERROR: Unable to open dictionary file $filename: $!."); return; };
# Now attempt on each entry
while (<$fh>) {
return if $mark->{'terminate'};
chomp;
s/\#.*$//;
# remove preceding ~ just in case
s/^~//;
if ($_ eq "") { next }
if (($ctr % 500) == 0) { nprint("- User enumeration guess $ctr ($_)", "v", "apacheusers"); }
my($res, $content, $errors, $request, $response) =
nfetch($mark, $url . $_, "HEAD", "", "", "", "user_enum_apache: dictionary");
my $user = nikto_user_enum_apache_check($response, $_);
if ($user) {
push(@foundusers, $user);
}
$ctr++;
}
close($fh);
if (scalar(@foundusers)) {
my $u = join(', ', @foundusers);
add_vulnerability($mark, $message . $u,
"000478", "CVE-2001-1013", "HEAD", "/", $request, $response);
}
}
sub nikto_user_enum_apache_check {
(my $code, my $user) = @_;
my $response = "";
foreach my $found (split(/ /, $VARIABLES{"\@HTTPFOUND"})) {
if ($code eq $found) {
$response = $user;
last;
}
}
return $response;
}
1;