|
|
|
|
|
|
|
|
|
|
Для исправления уязвимостей Вы можете воспользоваться готовыми файлами. В архиве сохранена структура файлов для простоты использования. Делать это стоит только в том случае, если не было произведено никаких изменений в этих файлах.
Либо воспользоваться инструкциями приведенными ниже.
Злоумышленник может сформировать вложенный bb-тег, на основе bb-кода acronym, позволяющий внедрить собственный HTML код в финальную страницу, отдаваемую форумом. Опасность данной уязвимости не значительна, если Ваши форумы используют httpOnly cookies.
В файле admin/applications/members/extensions/usercpForms.php найти:
/** * Custom event: Remove avatar * * @access public * @author Matt Mecham * @return string Processed HTML */ public function customEvent_removeAvatar()
Добавить после:
//----------------------------------------- // Check secure hash... //----------------------------------------- if ( $this->request['secure_key'] != $this->member->form_hash ) { $this->registry->output->showError( 'authorization_error', 100, true, null, 403 ); }
Найти:
/** * Custom event: Remove photo * * @access public * @author Matt Mecham * @return string Processed HTML */ function customEvent_removePhoto() {
Добавить после:
//----------------------------------------- // Check secure hash... //----------------------------------------- if ( $this->request['secure_key'] != $this->member->form_hash ) { $this->registry->output->showError( 'authorization_error', 100, true, null, 403 ); }
Найти:
//----------------------------------------- // Reset forced form action? //----------------------------------------- if ( $is_reset ) { $this->settings['base_url'] = $original; }
Добавить после:
/* fix url */ $return = preg_replace( "#(area=removeavatar(&|&)do=remove)#", "\\1&secure_key=" . $this->member->form_hash, $return );
Найти:
$this->uploadFormMax = 5000000; return $this->registry->getClass('output')->getTemplate('ucp')->membersPhotoForm( $cur_photo, $show_size, $p_max, 500000, $rand );
Заменить на:
$this->uploadFormMax = 5000000; $return = $this->registry->getClass('output')->getTemplate('ucp')->membersPhotoForm( $cur_photo, $show_size, $p_max, 500000, $rand ); /* fix url */ $return = preg_replace( "#(area=removephoto(&|&)do=remove)#", "\\1&secure_key=" . $this->member->form_hash, $return ); return $return;
В файле admin/sources/classes/bbcode/core.php найти:
/** * Check against XSS * * @access public * @param string Original string * @param boolean Fix script HTML tags * @return string "Cleaned" text */ public function checkXss( $txt='', $fixScript=false ) { //----------------------------------------- // Opening script tags... // Check for spaces and new lines... //----------------------------------------- if ( $fixScript ) { $txt = preg_replace( "#<(\s+?)?s(\s+?)?c(\s+?)?r(\s+?)?i(\s+?)?p(\s+?)?t#is" , "<script" , $txt ); $txt = preg_replace( "#<(\s+?)?/(\s+?)?s(\s+?)?c(\s+?)?r(\s+?)?i(\s+?)?p(\s+?)?t#is", "</script", $txt ); } //----------------------------------------- // Here we can do some generic checking for XSS // This should not be considered fool proof, though can provide // a centralized point for maintenance and checking //----------------------------------------- $txt = preg_replace( "/(j)avascript/i" , "\\1avascript", $txt ); //$txt = str_ireplace( "alert" , "alert" , $txt ); $txt = str_ireplace( "behavior" , "behavior" , $txt ); $txt = preg_replace( "/(e)((\/\*.*?\*\/)*)x((\/\*.*?\*\/)*)p((\/\*.*?\*\/)*)r((\/\*.*?\*\/)*)e((\/\*.*?\*\/)*)s((\/\*.*?\*\/)*)s((\/\*.*?\*\/)*)i((\/\*.*?\*\/)*)o((\/\*.*?\*\/)*)n/is" , "\\1xp<b></b>ression" , $txt ); $txt = preg_replace( "/(e)((\\\|\)*)x((\\\|\)*)p((\\\|\)*)r((\\\|\)*)e((\\\|\)*)s((\\\|\)*)s((\\\|\)*)i((\\\|\)*)o((\\\|\)*)n/is" , "\\1xp<b></b>ression" , $txt ); $txt = preg_replace( "/m((\\\|\)*)o((\\\|\)*)z((\\\|\)*)\-((\\\|\)*)b((\\\|\)*)i((\\\|\)*)n((\\\|\)*)d((\\\|\)*)i((\\\|\)*)n((\\\|\)*)g/is" , "moz-<b></b>binding" , $txt ); $txt = str_ireplace( "about:" , "about:" , $txt ); $txt = str_ireplace( "<body" , "<body" , $txt ); $txt = str_ireplace( "<html" , "<html" , $txt ); $txt = str_ireplace( "document." , "document." , $txt ); $txt = str_ireplace( "window." , "window." , $txt ); $event_handlers = array( 'mouseover', 'mouseout', 'mouseup', 'mousemove', 'mousedown', 'mouseenter', 'mouseleave', 'mousewheel', 'contextmenu', 'click', 'dblclick', 'load', 'unload', 'submit', 'blur', 'focus', 'resize', 'scroll', 'change', 'reset', 'select', 'selectionchange', 'selectstart', 'start', 'stop', 'keydown', 'keyup', 'keypress', 'abort', 'error', 'dragdrop', 'move', 'moveend', 'movestart', 'activate', 'afterprint', 'afterupdate', 'beforeactivate', 'beforecopy', 'beforecut', 'beforedeactivate', 'beforeeditfocus', 'beforepaste', 'beforeprint', 'beforeunload', 'begin', 'bounce', 'cellchange', 'controlselect', 'copy', 'cut', 'paste', 'dataavailable', 'datasetchanged', 'datasetcomplete', 'deactivate', 'drag', 'dragend', 'dragleave', 'dragenter', 'dragover', 'drop', 'end', 'errorupdate', 'filterchange', 'finish', 'focusin', 'focusout', 'help', 'layoutcomplete', 'losecapture', 'mediacomplete', 'mediaerror', 'outofsync', 'pause', 'propertychange', 'progress', 'readystatechange', 'repeat', 'resizeend', 'resizestart', 'resume', 'reverse', 'rowsenter', 'rowexit', 'rowdelete', 'rowinserted', 'seek', 'syncrestored', 'timeerror', 'trackchange', 'urlflip', ); foreach( $event_handlers as $handler ) { $txt = str_ireplace( 'on' . $handler, 'on' . $handler, $txt ); } return $txt; }
Заменить на:
public function checkXss( $txt='', $fixScript=false, $tag='' ) { //----------------------------------------- // Opening script tags... // Check for spaces and new lines... //----------------------------------------- if ( $fixScript ) { $txt = preg_replace( "#<(\s+?)?s(\s+?)?c(\s+?)?r(\s+?)?i(\s+?)?p(\s+?)?t#is" , "<script" , $txt ); $txt = preg_replace( "#<(\s+?)?/(\s+?)?s(\s+?)?c(\s+?)?r(\s+?)?i(\s+?)?p(\s+?)?t#is", "</script", $txt ); } /* got a tag? */ if ( $tag ) { $tag = strip_tags( $tag, '<br>' ); switch ($tag) { case 'entry': case 'blog': case 'topic': case 'post': $test = str_replace( array( '"', "'", '"', ''' ), "", $txt ); if ( ! is_numeric( $test ) ) { $txt = false; } break; case 'acronym': $test = str_replace( array( '"', "'", '"', ''' ), "", $txt ); $test1 = IPSText::alphanumericalClean( $test, '.+ ' ); if ( $test != $test1 ) { $txt = false; } break; case 'email': $test = str_replace( array( '"', "'", '"', ''' ), "", $txt ); $test = ( IPSText::checkEmailAddress( $test ) ) ? $txt : FALSE; break; case 'font': case 'color': case 'background': /* Make sure it's clean */ $test = str_replace( array( '"', "'", '"', ''' ), "", $txt ); $test1 = IPSText::alphanumericalClean( $test, '#.+, ' ); if ( $test != $test1 ) { $txt = false; } break; } /* If we didn't actually get any option data, then return false */ $test = str_replace( array( '"', "'", '"', ''' ), "", $txt ); if ( strlen($txt) AND strlen( $test ) < 1 ) { $txt = false; } if ( $txt === false ) { return false; } /* Still here? Safety, then */ $txt = strip_tags( $txt, '<br>' ); if( strpos( $txt, '[' ) !== false OR strpos( $txt, ']' ) !== false ) { $txt = str_replace( array( '[', ']' ), array( '[', ']' ), $txt ); } } //----------------------------------------- // Here we can do some generic checking for XSS // This should not be considered fool proof, though can provide // a centralized point for maintenance and checking //----------------------------------------- $txt = preg_replace( "/(j)avascript/i" , "\\1avascript", $txt ); //$txt = str_ireplace( "alert" , "alert" , $txt ); $txt = str_ireplace( "behavior" , "behavior" , $txt ); $txt = preg_replace( "/(e)((\/\*.*?\*\/)*)x((\/\*.*?\*\/)*)p((\/\*.*?\*\/)*)r((\/\*.*?\*\/)*)e((\/\*.*?\*\/)*)s((\/\*.*?\*\/)*)s((\/\*.*?\*\/)*)i((\/\*.*?\*\/)*)o((\/\*.*?\*\/)*)n/is" , "\\1xp<b></b>ression" , $txt ); $txt = preg_replace( "/(e)((\\\|\)*)x((\\\|\)*)p((\\\|\)*)r((\\\|\)*)e((\\\|\)*)s((\\\|\)*)s((\\\|\)*)i((\\\|\)*)o((\\\|\)*)n/is" , "\\1xp<b></b>ression" , $txt ); $txt = preg_replace( "/m((\\\|\)*)o((\\\|\)*)z((\\\|\)*)\-((\\\|\)*)b((\\\|\)*)i((\\\|\)*)n((\\\|\)*)d((\\\|\)*)i((\\\|\)*)n((\\\|\)*)g/is" , "moz-<b></b>binding" , $txt ); $txt = str_ireplace( "about:" , "about:" , $txt ); $txt = str_ireplace( "<body" , "<body" , $txt ); $txt = str_ireplace( "<html" , "<html" , $txt ); $txt = str_ireplace( "document." , "document." , $txt ); $txt = str_ireplace( "window." , "window." , $txt ); $event_handlers = array( 'mouseover', 'mouseout', 'mouseup', 'mousemove', 'mousedown', 'mouseenter', 'mouseleave', 'mousewheel', 'contextmenu', 'click', 'dblclick', 'load', 'unload', 'submit', 'blur', 'focus', 'resize', 'scroll', 'change', 'reset', 'select', 'selectionchange', 'selectstart', 'start', 'stop', 'keydown', 'keyup', 'keypress', 'abort', 'error', 'dragdrop', 'move', 'moveend', 'movestart', 'activate', 'afterprint', 'afterupdate', 'beforeactivate', 'beforecopy', 'beforecut', 'beforedeactivate', 'beforeeditfocus', 'beforepaste', 'beforeprint', 'beforeunload', 'begin', 'bounce', 'cellchange', 'controlselect', 'copy', 'cut', 'paste', 'dataavailable', 'datasetchanged', 'datasetcomplete', 'deactivate', 'drag', 'dragend', 'dragleave', 'dragenter', 'dragover', 'drop', 'end', 'errorupdate', 'filterchange', 'finish', 'focusin', 'focusout', 'help', 'layoutcomplete', 'losecapture', 'mediacomplete', 'mediaerror', 'outofsync', 'pause', 'propertychange', 'progress', 'readystatechange', 'repeat', 'resizeend', 'resizestart', 'resume', 'reverse', 'rowsenter', 'rowexit', 'rowdelete', 'rowinserted', 'seek', 'syncrestored', 'timeerror', 'trackchange', 'urlflip', ); foreach( $event_handlers as $handler ) { $txt = str_ireplace( 'on' . $handler, 'on' . $handler, $txt ); } return $txt; }
Найти:
//----------------------------------------- // If this is a single tag, that's it //----------------------------------------- if( $_bbcode['bbcode_single_tag'] ) { $txt = substr_replace( $txt, $this->_bbcodeToHtml( $_bbcode, $_option, '' ), $this->cur_pos, ($open_length + strlen($_option) + 1) ); } //----------------------------------------- // Otherwise replace out the content too //----------------------------------------- else { $close_tag = '[/' . $_tag . ']'; if( stripos( $txt, $close_tag, $new_pos ) !== false ) { $_content = substr( $txt, ($this->cur_pos + $open_length + strlen($_option) + 1), (stripos( $txt, $close_tag, $this->cur_pos ) - ($this->cur_pos + $open_length + strlen($_option) + 1)) ); $txt = substr_replace( $txt, $this->_bbcodeToHtml( $_bbcode, $_option /*? $_option : $_content*/, $_content ), $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); } else { //----------------------------------------- // If there's no close tag, no need to continue //----------------------------------------- break; } }
Заменить на:
//----------------------------------------- // Protect against XSS //----------------------------------------- $_optionStrLen = IPSText::mbstrlen( $_option ); $_option = $this->checkXss($_option, false, $_tag); if ( $_option !== FALSE ) { //----------------------------------------- // If this is a single tag, that's it //----------------------------------------- if( $_bbcode['bbcode_single_tag'] ) { $txt = substr_replace( $txt, $this->_bbcodeToHtml( $_bbcode, $_option, '' ), $this->cur_pos, ($open_length + $_optionStrLen + 1) ); } //----------------------------------------- // Otherwise replace out the content too //----------------------------------------- else { $close_tag = '[/' . $_tag . ']'; if( stripos( $txt, $close_tag, $new_pos ) !== false ) { $_content = substr( $txt, ($this->cur_pos + $open_length + $_optionStrLen + 1), (stripos( $txt, $close_tag, $this->cur_pos ) - ($this->cur_pos + $open_length + $_optionStrLen + 1)) ); $txt = substr_replace( $txt, $this->_bbcodeToHtml( $_bbcode, $_option /*? $_option : $_content*/, $_content ), $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); } else { //----------------------------------------- // If there's no close tag, no need to continue //----------------------------------------- break; } } }
В файле admin/sources/classes/bbcode/custom/defaults.php найти:
class bbcode_quote extends bbcode_parent_class implements bbcodePlugin { /** * Constructor * * @access public * @param object Registry object * @return void */ public function __construct( ipsRegistry $registry ) { $this->currentBbcode = 'quote'; parent::__construct( $registry ); } /** * Method that is run before the content is stored in the database * You are responsible for ensuring you mark the replaced text appropriately so that you * are able to unparse it, if you wish to have bbcode parsed on save * * @access public * @param string $txt BBCode text from submission to be stored in database * @return string Formatted content, ready for display */ public function preDbParse( $txt ) { $this->cache->updateCacheWithoutSaving( '_tmp_bbcode_quotes', 0 ); return parent::preDbParse( $txt ); } /** * Method that is run before the content is displayed to the user * This is the safest method of parsing, as the original submitted text is left in tact. * No markers are necessary if you use parse on display. * * @access public * @param string $txt BBCode/parsed text from database to be displayed * @return string Formatted content, ready for display */ public function preDisplayParse( $txt ) { $this->cache->updateCacheWithoutSaving( '_tmp_bbcode_quotes', 0 ); return parent::preDisplayParse( $txt ); } /** * Do the actual replacement * * @access protected * @param string $txt Parsed text from database to be edited * @return string BBCode content, ready for editing */ protected function _replaceText( $txt ) { $_tags = $this->_retrieveTags(); foreach( $_tags as $_tag ) { //----------------------------------------- // Determine open and close tags //----------------------------------------- $open_tag = '[' . $_tag; $close_tag = '[/' . $_tag . ']'; //----------------------------------------- // Ok, quotes suck A LOT // We have to first match the CLOSE tag, unlike other bbcode // Then we back-track to find the matching opening tag // Do the replacement, and repeat.... //----------------------------------------- while( ( $this->end_pos = stripos( $txt, $close_tag, $this->end_pos ) ) !== false ) { //----------------------------------------- // Ok at this point, we have a closing quote tag // Set start position to end position point //----------------------------------------- $this->cur_pos = $this->end_pos; //----------------------------------------- // We are at 0 or above still... //----------------------------------------- while( $this->cur_pos > 0 ) { //----------------------------------------- // Loop over characters moving backwards 1 by 1 //----------------------------------------- $this->cur_pos = $this->cur_pos - 1; //----------------------------------------- // Shouldn't hit this, but if we do we're done //----------------------------------------- if( $this->cur_pos < 0 ) { break; } //----------------------------------------- // Have we finally hit the first preceeding open tag? //----------------------------------------- if( stripos( $txt, $open_tag, $this->cur_pos ) === $this->cur_pos ) { //----------------------------------------- // Start figuring out open tag length //----------------------------------------- $open_length = strlen($open_tag); //----------------------------------------- // Extract the options (like surgery) //----------------------------------------- $_option = ''; $quoteOptions = array(); //----------------------------------------- // Does we haz it? //----------------------------------------- if( substr( $txt, $this->cur_pos + strlen($open_tag), 1 ) == ' ' ) { $open_length += 1; $_option = substr( $txt, $this->cur_pos + $open_length, (strpos( $txt, ']', $this->cur_pos ) - ($this->cur_pos + $open_length)) ); $quoteOptions = $this->_extractSurgicallyTehOptions( $_option ); } //----------------------------------------- // If not, [u] != [url] (for example) //----------------------------------------- else if( (strpos( $txt, ']', $this->cur_pos ) - ( $this->cur_pos + $open_length )) !== 0 ) { continue; } //----------------------------------------- // Otherwise replace out the content too //----------------------------------------- $_content = substr( $txt, ($this->cur_pos + $open_length + strlen($_option) + 1), (stripos( $txt, $close_tag, $this->cur_pos ) - ($this->cur_pos + $open_length + strlen($_option) + 1)) ); //----------------------------------------- // Turn attachments into links // Prevents em from breaking on other pages //----------------------------------------- preg_match_all( "#\[attachment=(.+?):(.+?)\]#", $_content, $matches ); if( is_array( $matches[1] ) && count( $matches[1] ) ) { foreach( $matches[1] as $idx => $attach_id ) { $_content = str_replace( "[attachment={$attach_id}:{$matches[2][$idx]}]", $this->registry->getClass('output')->getReplacement('post_attach_link') . " <a href='{$this->settings['base_url']}app=core&module=attach&section=attach&attach_rel_module=post&attach_id={$attach_id}' target='_blank'>{$matches[2][$idx]}</a>", $_content ); } } //----------------------------------------- // Trim off leading newlines / brs, etc //----------------------------------------- $_content = IPSText::br2nl( $_content ); $_content = trim( $_content ); $_content = nl2br( $_content ); //----------------------------------------- // And replace... //----------------------------------------- $txt = substr_replace( $txt, $this->_buildOutput( $_content, $quoteOptions ? $quoteOptions : '' ), $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); //----------------------------------------- // Now we break since we've done the inner replacement //----------------------------------------- break; } } //----------------------------------------- // And reset end position to one char further //----------------------------------------- $this->end_pos += 1; //----------------------------------------- // If end_pos is greater than length of text we're done //----------------------------------------- if( $this->end_pos > strlen($txt) ) { //----------------------------------------- // Need to reset for next "tag" //----------------------------------------- $this->end_pos = 0; break; } } } return $txt; } /** * Raw options string * * @access private * @param string $string The options submitted with the post as a string * @return array Key => value Option pairs */ private function _extractSurgicallyTehOptions( $options='' ) { if( !$options ) { return array(); } /** * Strip tags removes any added tags * @see http://community.invisionpower.com/tracker/issue-19960-problem-when-tags-is-put-inside-quote-starter-tag/ */ $options = str_replace( ''', "'", strip_tags($options) ); $options = str_replace( '"', '"', $options ); $finalOpts = array(); // Need to push through string, pulling out keys/options $pos = 0; $options = trim($options); $key = ''; $value = ''; $inKey = true; $inValue = false; while( $pos < strlen($options) ) { if( $options{$pos} == ' ' AND !$inKey AND !$inValue ) { $key = ''; $value = ''; $inKey = true; } else if( $options{$pos} == "'" OR $options{$pos} == '"' ) { if( $inKey ) { $inKey = false; $inValue = true; } else { $inValue = false; $finalOpts[ trim($key) ] = $value; } } else { if( $inKey ) { if( $options{$pos} != '=' ) { $key .= $options{$pos}; } } else if( $inValue ) { $value .= $options{$pos}; } } $pos++; } return $finalOpts; } /** * Build the actual output to show * * @access private * @param string $content This is the contents of the quote * @param array $options [Optional] Quote options * @return string Content to replace bbcode with */ private function _buildOutput( $content, $options=array() ) { //----------------------------------------- // Too many quotes? //----------------------------------------- $existing = $this->cache->getCache('_tmp_bbcode_quotes'); $existing = intval($existing) + 1; if ( $this->settings['max_quotes_per_post'] ) { if ($existing > $this->settings['max_quotes_per_post']) { $this->error = 'too_many_quotes'; return $content; } } $this->cache->updateCacheWithoutSaving( '_tmp_bbcode_quotes', $existing ); //----------------------------------------- // Build output and return it //----------------------------------------- $output = "<p class='citation'>"; $snapback = ''; if( $options['post'] ) { $snapback = "<a class='snapback' rel='citation' href='{$this->settings['base_url']}app=forums&module=forums&section=findpost&pid={$options['post']}'>" . $this->registry->output->getReplacement( 'snapback' ) . "</a>"; } if( $options['name'] OR $options['date'] OR $options['timestamp'] ) { // sort timestamp if ( !$this->settings['cc_on'] AND $options['timestamp'] AND strlen( $options['timestamp'] ) == 10 AND ( intval($options['timestamp']) == $options['timestamp'] ) ) { $options['date'] = $this->registry->getClass('class_localization')->getDate( $options['timestamp'], 'LONG' ); } if( $options['name'] AND $options['date'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_full_cite'], $options['name'], $options['date'] ) ; } else if( $options['name'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_name_cite'], $options['name'] ) ; } else if( $options['date'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_date_cite'], $options['date'] ) ; } } else { $output .= $snapback . $this->lang->words['bbc_quote']; } $output .= "</p>"; $output .= '<div class="blockquote"'; $output .= '>'; $output .= "<div class='quote'>"; $output .= $content; $output .= "</div>"; $output .= '</div>'; return $output; } }
Заменить на:
class bbcode_quote extends bbcode_parent_class implements bbcodePlugin { /**#@+ * Quote tracking * * @access protected * @var int */ protected $quote_open = 0; protected $quote_closed = 0; protected $quote_error = 0; /**#@-*/ /** * Constructor * * @access public * @param object Registry object * @return void */ public function __construct( ipsRegistry $registry ) { $this->currentBbcode = 'quote'; parent::__construct( $registry ); } /** * Method that is run before the content is stored in the database * You are responsible for ensuring you mark the replaced text appropriately so that you * are able to unparse it, if you wish to have bbcode parsed on save * * @access public * @param string $txt BBCode text from submission to be stored in database * @return string Formatted content, ready for display */ public function preDbParse( $txt ) { return parent::preDbParse( $txt ); } /** * Method that is run before the content is displayed to the user * This is the safest method of parsing, as the original submitted text is left in tact. * No markers are necessary if you use parse on display. * * @access public * @param string $txt BBCode/parsed text from database to be displayed * @return string Formatted content, ready for display */ public function preDisplayParse( $txt ) { return parent::preDisplayParse( $txt ); } /** * Do the actual replacement * * @access protected * @param string $txt Parsed text from database to be edited * @return string BBCode content, ready for editing */ protected function _replaceText( $txt ) { $txt = preg_replace_callback( "#(\[quote([^\]]+?)?\].*\[/quote\])#is" , array( $this, '_parseQuote' ), $txt ); return $txt; } /** * Build the actual output to show * * @access private * @param array $options [Optional] Quote options * @return string Content to replace bbcode with */ private function _buildOutput( $options=array() ) { //----------------------------------------- // Build output and return it //----------------------------------------- $output = "<p class='citation'>"; $snapback = ''; if( $options['post'] ) { $snapback = "<a class='snapback' rel='citation' href='{$this->settings['base_url']}app=forums&module=forums&section=findpost&pid={$options['post']}'>" . $this->registry->output->getReplacement( 'snapback' ) . "</a>"; } if( $options['name'] OR $options['date'] OR $options['timestamp'] ) { // sort timestamp if ( !$this->settings['cc_on'] AND $options['timestamp'] AND strlen( $options['timestamp'] ) == 10 AND ( intval($options['timestamp']) == $options['timestamp'] ) ) { $options['date'] = $this->registry->getClass('class_localization')->getDate( $options['timestamp'], 'LONG' ); } if( $options['name'] AND $options['date'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_full_cite'], $options['name'], $options['date'] ) ; } else if( $options['name'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_name_cite'], $options['name'] ) ; } else if( $options['date'] ) { $output .= $snapback . sprintf( $this->lang->words['bbc_date_cite'], $options['date'] ) ; } } else { $output .= $snapback . $this->lang->words['bbc_quote']; } $output .= "</p>"; $output .= '<div class="blockquote"'; $output .= '>'; $output .= "<div class='quote'>"; return $output; } /** * Callback for quote preg_replace call * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _parseQuote( $matches ) { $txt = trim( $matches[1] ); $_orig = $txt; if( !$txt ) { return ''; } $this->quote_open = 0; $this->quote_closed = 0; $this->quote_error = 0; //----------------------------------------- // Make sure we don't have too many embedded //----------------------------------------- if ( $this->settings['max_quotes_per_post'] ) { if ( substr_count( strtolower($txt), '[quote' ) > $this->settings['max_quotes_per_post'] ) { $this->error = 'too_many_quotes'; return $txt; } } //----------------------------------------- // Fix char 173 //----------------------------------------- $txt = str_replace( chr(173).']', ']', $txt ); //----------------------------------------- // Trim the quote content //----------------------------------------- $txt = preg_replace_callback( "#\[quote([^\]]+?)?\](.+?)\[/quote\]#si", array( $this, '_trimQuote' ), $txt ); //----------------------------------------- // Clean usernames with brackets and quotes //----------------------------------------- $txt = preg_replace_callback( "#(name=(?:&\#39;|"|'|\"))(.+?)(&\#39;|"|'|\")#si", array( $this, '_makeQuoteSafe' ), $txt ); $txt = preg_replace_callback( "#name=(&\#39;|"|'|\")(.+?)(\\1)\s?(post|date)=#si", array( $this, '_makeNameSafe' ), $txt ); //----------------------------------------- // Replace out end tag //----------------------------------------- $txt = str_ireplace( "[/quote]", "</div></div>", $txt, $this->quote_closed ); //----------------------------------------- // Replace the quote tag //----------------------------------------- $txt = preg_replace_callback( "#\[quote([^\]]+?)?\]#i", array( $this, '_replaceQuoteTag' ), $txt ); //----------------------------------------- // Newlines //----------------------------------------- //$txt = str_replace( "\n", "<br />", $txt ); $txt = str_replace( "<div class='quote'><br />", "<div class='quote'>", $txt ); //----------------------------------------- // Swap name replacement (_makeNameSafe) back //----------------------------------------- $txt = str_replace( "'", "'", $txt ); //----------------------------------------- // Turn attachments into links // Prevents em from breaking on other pages //----------------------------------------- preg_match_all( "#\[attachment=(.+?):(.+?)\]#", $txt, $_matches ); if( is_array( $_matches[1] ) && count( $_matches[1] ) ) { foreach( $_matches[1] as $idx => $attach_id ) { $txt = str_replace( "[attachment={$attach_id}:{$_matches[2][$idx]}]", $this->registry->getClass('output')->getReplacement('post_attach_link') . " <a href='{$this->settings['base_url']}app=core&module=attach&section=attach&attach_rel_module=post&attach_id={$attach_id}' target='_blank'>{$_matches[2][$idx]}</a>", $txt ); } } //----------------------------------------- // If open and close tags match, we're good. // Otherwise, return an error. //----------------------------------------- if ( ( $this->quote_open == $this->quote_closed ) and ( $this->quote_error == 0 ) ) { return $txt; } else { $this->error = 'quote_mismatch'; return $_orig; } } /** * Callback for triming quote * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _trimQuote( $matches ) { $txt = $matches[2]; $extra = $matches[1]; if( $txt == "" ) { return "[quote][/quote]"; } else { $txt = trim( $txt ); return "[quote{$extra}]{$txt}[/quote]"; } } /** * Make the quoted content safe for regex parsing * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _makeQuoteSafe( $matches ) { //----------------------------------------- // INIT //----------------------------------------- $begin = $matches[1]; $end = $matches[3]; $txt = $matches[2]; //----------------------------------------- // Sort name //----------------------------------------- $txt = str_replace( "+", "+" , $txt ); $txt = str_replace( "-", "-" , $txt ); $txt = str_replace( ":", ":" , $txt ); $txt = str_replace( "[", "[" , $txt ); $txt = str_replace( "]", "]" , $txt ); $txt = str_replace( ")", ")" , $txt ); $txt = str_replace( "(", "(" , $txt ); $txt = str_replace( "'", "'" , $txt ); return $begin . $txt . $end; } /** * Make the name used for the quote safe for regex parsing * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _makeNameSafe( $matches ) { $quote = $matches[1]; $name = $matches[2]; $next = $matches[4]; # Squeeze past the parser... $name = str_replace( ''', "'", $name ); return 'name=' . $quote . $name . $quote . ' ' . $next . '='; } /** * Replace the quote tag * * @access protected * @param array preg_replace_callback Matches * @return string Output */ protected function _replaceQuoteTag( $matches ) { //----------------------------------------- // INIT //----------------------------------------- $extra = str_replace( ''', "'", $matches[1] ); $post_id = 0; $date = ''; $timestamp = 0; $name = ''; //----------------------------------------- // Inc.. //----------------------------------------- $this->quote_open++; //----------------------------------------- // Post? //----------------------------------------- preg_match( "#post=([\"']|"|&\#039;|&\#39;)?(\d+)([\"']|"|&\#039;|&\#39;)?#", $extra, $match ); if ( isset($match[2]) AND intval( $match[2] ) ) { $post_id = intval( $match[2] ); } //----------------------------------------- // Name? //----------------------------------------- preg_match( "#name=([\"']|"|&\#039;|&\#39;)(.+?)([\"']|"|&\#039;|&\#39;)\s?(date|post)?#is", $extra, $match ); if ( isset($match[2]) AND $match[2] ) { $name = $this->_makeQuoteSafe( array( 2 => $match[2] ) ); } //----------------------------------------- // Date? //----------------------------------------- preg_match( "#date=([\"']|"|&\#039;|&\#39;)(.+?)([\"']|"|&\#039;|&\#39;)#", $extra, $match ); if ( isset($match[2]) AND $match[2] ) { $date = $this->_makeQuoteSafe( array( 2 => $match[2] ) ); } //----------------------------------------- // Timestamp? //----------------------------------------- preg_match( "#timestamp=([\"']|"|&\#039;|&\#39;)(.+?)([\"']|"|&\#039;|&\#39;)#", $extra, $match ); if ( isset($match[2]) AND $match[2] ) { $timestamp = intval( $match[2] ); } return $this->_buildOutput( array( 'name' => $name, 'date' => $date, 'post' => $post_id, 'timestamp' => $timestamp ) ); } }
Найти:
//----------------------------------------- // No closing tag //----------------------------------------- if( stripos( $txt, $close_tag, $new_pos ) === false ) { break; } $_content = substr( $txt, ($this->cur_pos + strlen($open_tag) + strlen($_option) + 1), (stripos( $txt, $close_tag, $this->cur_pos ) - ($this->cur_pos + strlen($open_tag) + strlen($_option) + 1)) ); //----------------------------------------- // If this is a single tag, that's it //----------------------------------------- if( $_content ) { $txt = substr_replace( $txt, $this->_buildOutput( $_option, $_content ), $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); } else { $txt = substr_replace( $txt, '', $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); }
Заменить на:
//----------------------------------------- // Protect against XSS //----------------------------------------- /* Make sure it's clean */ $test = str_replace( array( '"', "'", '"', ''' ), "", $_option ); $test1 = IPSText::alphanumericalClean( $test, '.+ ' ); if ( $test1 != $test ) { $_option = false; } //----------------------------------------- // No closing tag //----------------------------------------- if ( $_option !== false AND stripos( $txt, $close_tag, $new_pos ) !== false ) { $_content = substr( $txt, ($this->cur_pos + strlen($open_tag) + strlen($_option) + 1), (stripos( $txt, $close_tag, $this->cur_pos ) - ($this->cur_pos + strlen($open_tag) + strlen($_option) + 1)) ); //----------------------------------------- // If this is a single tag, that's it //----------------------------------------- if( $_content ) { $txt = substr_replace( $txt, $this->_buildOutput( $_option, $_content ), $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); } else { $txt = substr_replace( $txt, '', $this->cur_pos, (stripos( $txt, $close_tag, $this->cur_pos ) + strlen($close_tag) - $this->cur_pos) ); } }
После установки исправления обязательно сделайте перестроение сообщений/подписей/личных сообщений через админ-центр форума.
Работает на DokuWiki |