/** * Processes a raw template for conditionals, phrases etc into PHP code for eval() * * @param string Template * * @return string */ function compile_template($template, &$errors = array()) { $orig_template = $template; $template = preg_replace('#[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]#', '', $template); $new_syntax = (strpos($template, '<vb:') !== false OR strpos($template, '{vb:') !== false); $old_syntax = (strpos($template, '<if') !== false OR strpos($template, '<phrase') !== false); $maybe_old_syntax = preg_match('/(^|[^{])\$[a-z0-9_]+\[?/si', $template); if (!$new_syntax AND ($old_syntax OR $maybe_old_syntax)) { $template = addslashes($template); $template = process_template_conditionals($template); $template = process_template_phrases('phrase', $template, 'parse_phrase_tag'); $template = process_seo_urls($template); if (!function_exists('replace_template_variables') OR !function_exists('validate_string_for_interpolation')) { require_once(DIR . '/includes/functions_misc.php'); } //only check the old style syntax, the new style doesn't use string interpolation and isn't affected //by this exploit. The new syntax doesn't 100% pass this check. if(!validate_string_for_interpolation($template)) { global $vbphrase; echo "<p> </p><p> </p>"; print_form_header('', '', 0, 1, '', '65%'); print_table_header($vbphrase['vbulletin_message']); print_description_row($vbphrase['template_text_not_safe']); print_table_footer(2, construct_button_code($vbphrase['go_back'], 'javascript:history.back(1)')); print_cp_footer(); exit; } $template = replace_template_variables($template, false); $template = str_replace('\\\\$', '\\$', $template); if (function_exists('token_get_all')) { $tokens = @token_get_all('<?php $var = "' . $template . '"; ?>'); foreach ($tokens AS $token) { if (is_array($token)) { switch ($token[0]) { case T_INCLUDE: case T_INCLUDE_ONCE: case T_REQUIRE: case T_REQUIRE_ONCE: { global $vbphrase; echo "<p> </p><p> </p>"; print_form_header('', '', 0, 1, '', '65%'); print_table_header($vbphrase['vbulletin_message']); print_description_row($vbphrase['file_inclusion_not_permitted']); print_table_footer(2, construct_button_code($vbphrase['go_back'], 'javascript:history.back(1)')); print_cp_footer(); exit; } } } } } } else { require_once(DIR . '/includes/class_template_parser.php'); $parser = new vB_TemplateParser($orig_template); try { $parser->validate($errors); } catch (vB_Exception_TemplateFatalError $e) { global $vbphrase; echo "<p> </p><p> </p>"; print_form_header('', '', 0, 1, '', '65%'); print_table_header($vbphrase['vbulletin_message']); print_description_row($vbphrase[$e->getMessage()]); print_table_footer(2, construct_button_code($vbphrase['go_back'], 'javascript:history.back(1)')); print_cp_footer(); exit; } $template = $parser->compile(); // TODO: Reimplement these - if done, $session[], $bbuserinfo[], $vboptions will parse in the template without using {vb:raw, which isn't what we // necessarily want to happen /* if (!function_exists('replace_template_variables')) { require_once(DIR . '/includes/functions_misc.php'); } $template = replace_template_variables($template, false); */ } if (function_exists('verify_demo_template')) { verify_demo_template($template); } ($hook = vBulletinHook::fetch_hook('template_compile')) ? eval($hook) : false; return $template; }
/** * Processes a raw template for conditionals, phrases etc into PHP code for eval() * * @param string Template * * @return string */ function compile_template($template) { $orig_template = $template; $template = addslashes($template); $template = process_template_conditionals($template); $template = process_template_phrases('phrase', $template, 'parse_phrase_tag'); if (!function_exists('replace_template_variables')) { require_once DIR . '/includes/functions_misc.php'; } $template = replace_template_variables($template, false); ($hook = vBulletinHook::fetch_hook('template_compile')) ? eval($hook) : false; $template = str_replace('\\\\$', '\\$', $template); if (function_exists('token_get_all')) { $tokens = @token_get_all('<?php $var = "' . $template . '"; ?>'); foreach ($tokens as $token) { if (is_array($token)) { switch ($token[0]) { case T_INCLUDE: case T_INCLUDE_ONCE: case T_REQUIRE: case T_REQUIRE_ONCE: global $vbphrase; echo "<p> </p><p> </p>"; print_form_header('', '', 0, 1, '', '65%'); print_table_header($vbphrase['vbulletin_message']); print_description_row($vbphrase['file_inclusion_not_permitted']); print_table_footer(2, construct_button_code($vbphrase['go_back'], 'javascript:history.back(1)')); print_cp_footer(); exit; } } } } if (function_exists('verify_demo_template')) { verify_demo_template($template); } return $template; }
function process_template_conditionals($template_cond) { global $logger; $if_lookfor = '<if condition='; $if_location = -1; $if_end_lookfor = '</if>'; $if_end_location = -1; $else_lookfor = '<else />'; $else_location = -1; $condition_value = ''; $true_value = ''; $false_value = ''; static $safe_functions; if (!is_array($safe_functions)) { $safe_functions = array(0 => 'and', 1 => 'or', 2 => 'xor', 'in_array', 'is_null', 'is_array', 'is_numeric', 'isset', 'empty', 'defined', 'number_format'); } // ############################################################################# while (1) { $condition_end = 0; $strlen = strlen($template_cond); $if_location = strpos($template_cond, $if_lookfor, $if_end_location + 1); // look for opening <if> if ($if_location === false) { // conditional started not found break; } $condition_start = $if_location + strlen($if_lookfor) + 2; // the beginning of the conditional $delimiter = $template_cond[$condition_start - 1]; if ($delimiter != '"' and $delimiter != '\'') { // ensure the conditional is surrounded by a valid character $if_end_location = $if_location + 1; continue; } $if_end_location = strpos($template_cond, $if_end_lookfor, $condition_start + 3); // location of conditional terminator if ($if_end_location === false) { // move this code above the rest, if no end condition is found then the code below would get stuck return false; // no </if> found -- return the original template } for ($i = $condition_start; $i < $strlen; $i++) { // find the end of the conditional if ($template_cond["{$i}"] == $delimiter and $template_cond[$i - 2] != '\\' and $template_cond[$i + 1] == '>') { // this char is delimiter and not preceded by backslash $condition_end = $i - 1; break; } } if (!$condition_end) { // couldn't find an end to the condition, so don't even parse the template anymore return false; } $condition_value = substr($template_cond, $condition_start, $condition_end - $condition_start); if (empty($condition_value)) { // something went wrong $if_end_location = $if_location + 1; continue; } else { if (preg_match_all('#([a-z0-9_{}$>-]+)(\\s|/\\*.*\\*/|(\\#|//)[^\\r\\n]*(\\r|\\n))*\\(#si', $condition_value, $matches)) { $functions = array(); foreach ($matches[1] as $key => $match) { if (!in_array(strtolower($match), $safe_functions)) { $funcpos = strpos($condition_value, $matches[0]["{$key}"]); $functions[] = array('func' => stripslashes($match), 'usage' => substr($condition_value, $funcpos, strpos($condition_value, ')', $funcpos) - $funcpos + 1)); } } if (!empty($functions)) { unset($safe_functions[0], $safe_functions[1], $safe_functions[2]); $logger->log('process_template_conditionals', "You've used some 'unsafe' functions."); } } } if ($template_cond[$condition_end + 2] != '>') { // the > doesn't come right after the condition must be malformed $if_end_location = $if_location + 1; continue; } // look for recursive case in the if block -- need to do this so the correct </if> is looked at $recursive_if_loc = $if_location; while (1) { $recursive_if_loc = strpos($template_cond, $if_lookfor, $recursive_if_loc + 1); // find an if case if ($recursive_if_loc === false or $recursive_if_loc >= $if_end_location) { //not found or out of bounds break; } // the bump first level's recursion back one </if> at a time $recursive_if_end_loc = $if_end_location; $if_end_location = strpos($template_cond, $if_end_lookfor, $recursive_if_end_loc + 1); if ($if_end_location === false) { return false; // no </if> found -- return the original template } } $else_location = strpos($template_cond, $else_lookfor, $condition_end + 3); // location of false portion // this is needed to correctly identify the <else /> tag associated with the outermost level while (1) { if ($else_location === false or $else_location >= $if_end_location) { // else isn't found/in a valid area $else_location = -1; break; } $temp = substr($template_cond, $condition_end + 3, $else_location - $condition_end + 3); $opened_if = substr_count($temp, $if_lookfor); // <if> tags opened between the outermost <if> and the <else /> $closed_if = substr_count($temp, $if_end_lookfor); // <if> tags closed under same conditions if ($opened_if == $closed_if) { // if this is true, we're back to the outermost level // and this is the correct else break; } else { // keep looking for correct else case $else_location = strpos($template_cond, $else_lookfor, $else_location + 1); } } if ($else_location == -1) { // no else clause $read_length = $if_end_location - strlen($if_end_lookfor) + 1 - $condition_end + 1; // number of chars to read $true_value = substr($template_cond, $condition_end + 3, $read_length); // the true portion $false_value = ''; } else { $read_length = $else_location - $condition_end - 3; // number of chars to read $true_value = substr($template_cond, $condition_end + 3, $read_length); // the true portion $read_length = $if_end_location - strlen($if_end_lookfor) - $else_location - 3; // number of chars to read $false_value = substr($template_cond, $else_location + strlen($else_lookfor), $read_length); // the false portion } if (strpos($true_value, $if_lookfor) !== false) { $true_value = process_template_conditionals($true_value); } if (strpos($false_value, $if_lookfor) !== false) { $false_value = process_template_conditionals($false_value); } // clean up the extra slashes $str_find = array('\\"', '\\\\'); $str_replace = array('"', '\\'); if ($delimiter == "'") { $str_find[] = "\\'"; $str_replace[] = "'"; } $str_find[] = '\\$delimiter'; $str_replace[] = $delimiter; $condition_value = str_replace($str_find, $str_replace, $condition_value); $conditional = "\".(({$condition_value}) ? (\"{$true_value}\") : (\"{$false_value}\")).\""; $template_cond = substr_replace($template_cond, $conditional, $if_location, $if_end_location + strlen($if_end_lookfor) - $if_location); $if_end_location = $if_location + strlen($conditional) - 1; // adjust searching position for the replacement above } $template_cond = str_replace("\\'", "'", $template_cond); return $template_cond; }