PunBB <= 1.2.13 Multiple Vulnerabilities

SQL Injection and Local File Inclusion


Author: Nms < nms (at) wargan (dot) com >
Affected application: PunBB <= 1.2.13
Type of vulnerability: SQL Injection and Local File Inclusion
Required informations : Register_globals enabled, PHP <= 4.4.2 or PHP <= 5.1.3 for the SQL Injection, none for the Local File Inclusion
Evaluated Risk : Critical
Solution Status : A new version has been released which fixes these vulnerabilities

[0] Application description & Deployment estimation

From punbb.org:
"PunBB is a fast and lightweight PHP powered discussion board. It is released under the GNU Public License. Its primary goal is to be a faster, smaller and less graphic alternative to otherwise excellent discussion boards such as phpBB, Invision Power Board or vBulletin. PunBB has fewer features than many other discussion boards, but is generally faster and outputs smaller pages."


[I] SQL Injection Vulnerability

1) Overview

IPB harbors a sanitization flaw in its registration form and user control panel (accessible once logged in). Incorrect e-mail address validation code allows an attacker to take over the admin account without prompting any alert but preventing the real admin to login afterwards. After a successful takeover, the attacker can plant a PHP backdoor using IPB's templating system. Thorough administrators will inspect total file system after they recover their hacked account, while other administrators might assume they are dealing with a bug, receive their new password using "Password recovery" system and leave the backdoor intact. Attacker may also use the "Retrieve password" process to mislead the admin into thinking their account was locked due to unsuccessful login attempts and not investigating further, thus preserving the backdoor.

B) Required data

1) Administrator's login name

PunBB is prone to an SQL injection in the search module, because of an unitialized variable which is undirectly passed into an SQL query without any check. Using this vulnerability, a visitor can perform blind SQL injections, which can lead to the content disclosure of any data stored in the database. The exploitation of this flaw uses the PHP Zend_Hash_Del_Key_Or_Index vulnerability, and thus requires register_globals enabled and PHP <= 4.4.2 or PHP <= 5.1.3 on the server where PunBB is installed.

2) Explanations

This vulnerability is grounded on both a mistake in PunBB code with an unitialized variable, and PHP Zend_Hash_Del_Key_Or_Index vulnerability which allows to bypass the globals deregistration process that comes with PunBB. First of all, have a look at the unregister_globals() function in "include/functions.php":
 ************************ BEGIN OF CODE ************************

    function unregister_globals()
    {
        // Prevent script.php?GLOBALS[foo]=bar
        if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS']))
            exit('I\'ll have a steak sandwich and... a steak
            sandwich.');

        // Variables that shouldn't be unset
        $no_unset = array('GLOBALS', '_GET', '_POST', '_COOKIE',
                    '_REQUEST', '_SERVER', '_ENV', '_FILES');

        // Remove elements in $GLOBALS that are present in any of the
        // superglobals
        $input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER,
              $_ENV, $_FILES, isset($_SESSION) &&
              is_array($_SESSION) ? $_SESSION : array());
        foreach ($input as $k => $v)
        {
            if (!in_array($k, $no_unset) && isset($GLOBALS[$k]))
                unset($GLOBALS[$k]);
        }
    }

    ************************* END OF CODE ***************************
Using Zend_Hash_Del_Key_Or_Index vulnerability, it is possible to bypass this globals deregistration process. All the details on this vulnerability - discovered by Stefan Esser - can be found in this article: http://www.hardened-php.net/hphp/zend_hash_del_key_or_index_vulnerability.html
To sum up, as long as PHP meets the required configuration for this vulnerability, an attacker is able to set any global variable he wants in PunBB. Now, have a look at the file "search.php", at the following lines :
 ************************ BEGIN OF CODE ************************

    $row = array();
    while ($temp = $db->fetch_row($result))
    {
         $row[$temp[0]] = 1;

         if (!$word_count)
             $result_list[$temp[0]] = 1;
         else if ($match_type == 'or')
             $result_list[$temp[0]] = 1;
         else if ($match_type == 'not')
             $result_list[$temp[0]] = 0;
    }

    [...]

    @reset($result_list);
    while (list($post_id, $matches) = @each($result_list))
    {
         if ($matches)
             $keyword_results[] = $post_id;
    }

    [...]

    if ($author && $keywords)
    {
        // If we searched for both keywords and author name we want
        // the intersection between the results
        $search_ids = array_intersect($keyword_results,
        $author_results);
        unset($keyword_results, $author_results);
    }
    else if ($keywords)
        $search_ids = $keyword_results;
    else
        $search_ids = $author_results;

    [...]

    if ($show_as == 'topics')
    {
        $result = $db->query('SELECT t.id FROM '.$db->prefix.'posts
        AS p INNER JOIN '.$db->prefix.'topics AS t ON
        t.id=p.topic_id INNER JOIN '.$db->prefix.'forums AS f ON
        f.id=t.forum_id LEFT JOIN '.$db->prefix.'forum_perms AS fp
        ON (fp.forum_id=f.id AND fp.group_id='.$pun_user['g_id'].')
        WHERE (fp.read_forum IS NULL OR fp.read_forum=1) AND p.id
        IN('.implode(',',$search_ids).')'.$forum_sql.' GROUP BY
        t.id', true) or error[...]

        $search_ids = array();
        while ($row = $db->fetch_row($result))
            $search_ids[] = $row[0];

        $db->free_result($result);

        $num_hits = count($search_ids);
    }

    ************************* END OF CODE *************************
In this piece of code, the $result_list array is obviously not initialized. Using the Zend_Hash_Del_Key_Or_Index vulnerability, we are thus able to populate this array with any possible content. Consequently, if you perform a search request with only a keyword and no author f.e., an attacker is thus able to populate $keyword_results and then $search_ids indexes with any possible content coming from $result_list. Finally, the $search_ids array is imploded and put in the SQL query without any protection. In a word, there is an SQL injection here.

3) Exploitation

With an adequate UNION query in the $result_list array, an attacker is able to perform blind SQL injections and f.e. retrieve the entire hash of any user just by looking if the script returned some results for his malicious search. For example, you can send the following request:
        search.php?action=search&keywords=hello&author=&forum=-1
    &search_in=all&sort_by=0&sort_dir=DESC&show_as=topics&search=1
    &result_list[< UNION SQL QUERY >/*]&1763905137=1&1121320991=1
With such a request, you can disclose each character of the admin hash for example. You don't even need to be registered to perform this blind SQL injection : all you need is the userid of an admin (or any user), and the correct $punbb_db_prefix (very often easily guessable if it's not the default one "punbb_"). Actually, it is possible to go a little bit further and forge quite easily an admin cookie. Indeed, the $cookie_seed variable which is used to forge more secure cookies, is created during the installation in install.php :
$cookie_seed = substr(md5(time()), -8)
If you are able to know the past time() of the forum creation, you are able to forge an admin cookie using the cookie_seed and the admin hash. A very simple way to know this past time() is to retrieve the admin value of the "registered" field in the users table, seing that the admin account is registered during the install, a few lines above the $cookie_seed line in install.php. Thus, using the previous SQL injection, an attacker can retrieve this "registered" field for the superadmin account and hence deduce the time() of the install, then the $cookie_seed value, and finally forge an admin cookie.

[II] Local File Inclusion / Remote Code Execution Vulnerability

1) Overview

PunBB is prone to a local file inclusion in common.php through the $pun_user['language'] variable, which can lead to remote PHP code execution on servers where PunBB is installed. The exploitation of this flaw does not require any special configuration of PHP.

2) Explanations

PunBB comes with a lot of langage files inclusions in all scripts. Among these inclusion, let's focus on the one which is systematically performed whatever punbb script is called, in include/common.php:
************************ BEGIN OF CODE ************************

@include PUN_ROOT.'lang/'.$pun_user['language'].'/common.php';
    
************************* END OF CODE *************************
For each user, the $pun_user['language'] variable takes the corresponding value of the user 'langage' field in the users table. There are only two ways for a user to set or modify this value. The first one is in profile.php, but the following instruction on line 723 :
************************ BEGIN OF CODE ************************
    
$form['language'] = preg_replace('#[\.\\\/]#', '',
                                      $form['language']);
    
************************* END OF CODE *************************
prevents to put any malicious content in the langage field. The second way is in register.php at the following lines :
************************ BEGIN OF CODE ************************

    $language = isset($_POST['language']) ? $_POST['language'] :
    $pun_config['o_default_lang'];
    
    [...]

    // Add the user
    $db->query('INSERT INTO '.$db->prefix.'users (username,group_id,
    password, email, email_setting, save_pass, timezone,
    language, style, registered, registration_ip, last_visit)
    VALUES(\''.$db->escape($username).'\', '.$intial_group_id.',
    \''.$password_hash.'\', \''.$email1.'\', '.$email_setting.',
    '.$save_pass.', '.$timezone.' , \''.$db->escape($language).'\',
    \''.$pun_config['o_default_style'].'\', '.$now.',
    \''.get_remote_address().'\','.$now.')') or error(...)

************************* END OF CODE *************************
The $langage variable is filled with the value of the user-input 'langage' without any security check, and is then passed through the INSERT query, which allows a newly registered user to put any malicious content in his 'langage' field in the users table. This obviously leads to a local file inclusion possibility in include/common.php .

3) Exploitation

In order to get rid of the suffix '/common.php' in the include instruction, an attacker can use the classical NULL Byte trick. No matter if PunBB addslashes this NULL Byte, because MySQL stripslashes it before storing it in the database. At this point, it is very classical (and quite simple) to execute PHP code using this local inclusion. For example, if avatars are enabled, all an attacker has to do is upload a valid GIF file with a malicious PHP content with a previous account, then register as a new user and post a 'language' value containing the relative path to the malicious image : this way he finally gets a shell on the server just by logging in with his new account. Besides, he can also, depending on the server configuration, disclose the content of server files.

[III] Recommandations / Thanks

The vendor has released a new version which fixes these vulnerabilities. It is strongly recommended to upgrade to PunBB 1.2.14 which can be found at : http://www.punbb.org/downloads.php Special thanks to John and Roman0 for their help in testing these vulnerabilities.