Headline
CVE-2023-4599: class-email-encoder-bundle-run.php in email-encoder-bundle/tags/2.1.7/core/includes/classes – WordPress Plugin Repository
The Slimstat Analytics plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the ‘eeb_mailto’ shortcode in versions up to, and including, 2.1.7 due to insufficient input sanitization and output escaping on user supplied attributes. This makes it possible for authenticated attackers with contributor-level and above permissions to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
1<?php23/**4 * Class Email_Encoder_Run5 *6 * Thats where we bring the plugin to life7 *8 * @since 2.0.09 * @package EEB10 * @author Ironikus [email protected]11 */1213class Email_Encoder_Run{1415 /**16 * The main page name for our admin page17 *18 * @var string19 * @since 2.0.020 */21 private $page_name;2223 /**24 * The main page title for our admin page25 *26 * @var string27 * @since 2.0.028 */29 private $page_title;3031 /**32 * Our Email_Encoder_Run constructor.33 */34 function __construct(){35 $this->page_name = EEB()->settings->get_page_name();36 $this->page_title = EEB()->settings->get_page_title();37 $this->final_outout_buffer_hook = EEB()->settings->get_final_outout_buffer_hook();38 $this->widget_callback_hook = EEB()->settings->get_widget_callback_hook();39 $this->add_hooks();40 }4142 /**43 * Define all of our necessary hooks44 */45 private function add_hooks(){46 $filter_hook = (bool) EEB()->settings->get_setting( 'filter_hook’, true, ‘filter_body’ );47 if( $filter_hook ){48 $hook_name = 'init’;49 } else {50 $hook_name = 'wp’;51 }52 53 add_action( 'wp’, array( $this, ‘display_email_image’ ), EEB()->settings->get_hook_priorities( ‘display_email_image’ ) );54 add_action( 'init’, array( $this, ‘buffer_final_output’ ), EEB()->settings->get_hook_priorities( ‘buffer_final_output’ ) );55 add_action( 'init’, array( $this, ‘add_custom_template_tags’ ), EEB()->settings->get_hook_priorities( ‘add_custom_template_tags’ ) );56 add_action( $hook_name, array( $this, ‘setup_single_filter_hooks’ ), EEB()->settings->get_hook_priorities( ‘setup_single_filter_hooks’ ) );57 add_action( 'wp_enqueue_scripts’, array( $this, ‘load_frontend_header_styling’ ), EEB()->settings->get_hook_priorities( ‘load_frontend_header_styling’ ) );5859 //Add shortcodes60 add_shortcode( 'eeb_protect_emails’, array( $this, ‘protect_content_shortcode’ ) );61 add_shortcode( 'eeb_protect_content’, array( $this, ‘shortcode_eeb_content’ ) );62 add_shortcode( 'eeb_mailto’, array( $this, ‘shortcode_eeb_email’ ) );63 add_shortcode( 'eeb_form’, array( $this, ‘shortcode_email_encoder_form’ ) );6465 //BAckwards compatibility66 add_shortcode( 'eeb_content’, array( $this, ‘shortcode_eeb_content’ ) );67 add_shortcode( 'eeb_email’, array( $this, ‘shortcode_eeb_email’ ) );6869 do_action('eeb_ready’, array($this, ‘eeb_ready_callback_filter’), $this);7071 add_action( 'init’, array( $this, ‘reload_settings_for_integrations’ ), 5 );72 }7374 /**75 * ######################76 * ###77 * #### CALLBACK FILTERS78 * ###79 * ######################80 */8182 /**83 * WP filter callback84 * @param string $content85 * @return string86 */87 public function eeb_ready_callback_filter( $content ) {8889 if( EEB()->validate->is_post_excluded() ){90 return $content;91 }9293 $protect_using = (string) EEB()->settings->get_setting( 'protect_using’, true );94 95 return EEB()->validate->filter_content( $content, $protect_using );96 }9798 /**99 * Reload the settings to reflect100 * Third party and integration changes101 *102 * @since 2.1.6103 * @return void104 */105 public function reload_settings_for_integrations(){106 EEB()->settings->reload_settings();107 }108109 /**110 * ######################111 * ###112 * #### PAGE BUFFERING & WIDGET FILTER113 * ###114 * ######################115 */116117 /**118 * Buffer the final output on the init hook119 *120 * @return void121 */122 public function buffer_final_output(){123124 if( 125 defined( ‘WP_CLI’ ) //Bail if WP CLI command126 || defined( ‘DOING_CRON’ ) //Bail if it is a cron call127 ) {128 return;129 }130131 if( wp_doing_ajax() ){132133 //Maybe allow filtering for ajax requests134 $filter_ajax_requests = (int) EEB()->settings->get_setting( 'ajax_requests’, true, ‘filter_body’ );135 if( $filter_ajax_requests !== 1 ){136 return;137 }138 139 }140141 if( is_admin() ){142143 //Maybe allow filtering for admin requests144 $filter_admin_requests = (int) EEB()->settings->get_setting( 'admin_requests’, true, ‘filter_body’ );145 if( $filter_admin_requests !== 1 ){146 return;147 }148149 }150151 ob_start( array( $this, ‘apply_content_filter’ ) );152 }153154 /**155 * Apply the callabla function for ob_start()156 * 157 * @param string $content158 * @return string - the filtered content159 */160 public function apply_content_filter( $content ){161 $filteredContent = apply_filters( $this->final_outout_buffer_hook, $content );162163 // remove filters after applying to prevent multiple applies164 remove_all_filters( $this->final_outout_buffer_hook );165166 return $filteredContent;167 }168 169 /**170 * Filter for “dynamic_sidebar_params” hook171 * 172 * @deprecated 2.1.4173 * @global array $wp_registered_widgets174 * @param array $params175 * @return array176 */177 public function eeb_dynamic_sidebar_params( $params){178 global $wp_registered_widgets;179180 if ( is_admin() ) {181 return $params;182 }183184 $widget_id = $params[0][‘widget_id’];185186 // prevent overwriting when already set by another version of the widget output class187 if ( isset( $wp_registered_widgets[ $widget_id ][‘_wo_original_callback’] ) ) {188 return $params;189 }190191 $wp_registered_widgets[ $widget_id ][‘_wo_original_callback’] = $wp_registered_widgets[ $widget_id ][‘callback’];192 $wp_registered_widgets[ $widget_id ][‘callback’] = array( $this, ‘call_widget_callback’ );193194 return $params;195 }196 197 /**198 * The Widget Callback199 * 200 * @deprecated 2.1.4201 * @global array $wp_registered_widgets202 */203 public function call_widget_callback(){204 global $wp_registered_widgets;205206 $original_callback_params = func_get_args();207 $original_callback = null;208 209 $widget_id = $original_callback_params[0][‘widget_id’];210211 $original_callback = $wp_registered_widgets[ $widget_id ][‘_wo_original_callback’];212 $wp_registered_widgets[ $widget_id ][‘callback’] = $original_callback;213214 $widget_id_base = ( isset( $wp_registered_widgets[ $widget_id ][‘callback’][0]->id_base ) ) ? $wp_registered_widgets[ $widget_id ][‘callback’][0]->id_base : 0;215216 if ( is_callable( $original_callback ) ) {217 ob_start();218 call_user_func_array( $original_callback, $original_callback_params );219 $widget_output = ob_get_clean();220221 echo apply_filters( $this->widget_callback_hook, $widget_output, $widget_id_base, $widget_id );222223 // remove filters after applying to prevent multiple applies224 remove_all_filters( $this->widget_callback_hook );225 }226 }227228 /**229 * ######################230 * ###231 * #### SCRIPT ENQUEUEMENTS232 * ###233 * ######################234 */235236 public function load_frontend_header_styling(){237238 $js_version = date( "ymd-Gis", filemtime( EEB_PLUGIN_DIR . ‘core/includes/assets/js/custom.js’ ));239 $css_version = date( "ymd-Gis", filemtime( EEB_PLUGIN_DIR . ‘core/includes/assets/css/style.css’ ));240 $protection_activated = (int) EEB()->settings->get_setting( 'protect’, true );241 $without_javascript = (string) EEB()->settings->get_setting( 'protect_using’, true );242 $footer_scripts = (bool) EEB()->settings->get_setting( 'footer_scripts’, true );243 244 if( $without_javascript !== ‘without_javascript’ ){245 wp_enqueue_script( 'eeb-js-frontend’, EEB_PLUGIN_URL . 'core/includes/assets/js/custom.js’, array( ‘jquery’ ), $js_version, $footer_scripts );246 }247 248 wp_register_style( 'eeb-css-frontend’, EEB_PLUGIN_URL . 'core/includes/assets/css/style.css’, false, $css_version );249 wp_enqueue_style ( ‘eeb-css-frontend’ );250251 if( (string) EEB()->settings->get_setting( 'show_encoded_check’, true ) === ‘1’ ){252 wp_enqueue_style(‘dashicons’);253 }254255 }256257 /**258 * ######################259 * ###260 * #### CORE LOGIC261 * ###262 * ######################263 */264265 /**266 * Register all single filters to protect your content267 *268 * @return void269 */270 public function setup_single_filter_hooks(){271272 if( EEB()->validate->is_post_excluded() ){273 return;274 }275276 $protection_method = (int) EEB()->settings->get_setting( 'protect’, true );277 $filter_rss = (int) EEB()->settings->get_setting( 'filter_rss’, true, ‘filter_body’ );278 $remove_shortcodes_rss = (int) EEB()->settings->get_setting( 'remove_shortcodes_rss’, true, ‘filter_body’ );279 $protect_shortcode_tags = (bool) EEB()->settings->get_setting( 'protect_shortcode_tags’, true, ‘filter_body’ );280 $protect_shortcode_tags_valid = false;281282 if ( is_feed() ) {283 284 if( $filter_rss === 1 ){285 add_filter( $this->final_outout_buffer_hook, array( $this, ‘filter_rss’ ), EEB()->settings->get_hook_priorities( ‘filter_rss’ ) );286 }287 288 if ( $remove_shortcodes_rss ) {289 add_filter( $this->final_outout_buffer_hook, array( $this, ‘callback_rss_remove_shortcodes’ ), EEB()->settings->get_hook_priorities( ‘callback_rss_remove_shortcodes’ ) );290 }291 292 }293294 if ( $protection_method === 2 ) {295 $protect_shortcode_tags_valid = true;296297 $filter_hooks = array(298 'the_title’, 299 'the_content’, 300 'the_excerpt’, 301 'get_the_excerpt’,302303 //Comment related304 'comment_text’, 305 'comment_excerpt’, 306 'comment_url’,307 'get_comment_author_url’,308 'get_comment_author_url_link’,309310 //Widgets311 'widget_title’,312 'widget_text’,313 'widget_content’,314 'widget_output’,315 );316317 $filter_hooks = apply_filters( 'eeb/frontend/wordpress_filters’, $filter_hooks );318319 foreach ( $filter_hooks as $hook ) {320 add_filter( $hook, array( $this, ‘filter_content’ ), EEB()->settings->get_hook_priorities( ‘filter_content’ ) );321 }322 } elseif( $protection_method === 1 ){323 $protect_shortcode_tags_valid = true;324325 add_filter( $this->final_outout_buffer_hook, array( $this, ‘filter_page’ ), EEB()->settings->get_hook_priorities( ‘filter_page’ ) );326 }327328 if( $protect_shortcode_tags_valid ){329 if( $protect_shortcode_tags ){330 add_filter( 'do_shortcode_tag’, array( $this, ‘filter_content’ ), EEB()->settings->get_hook_priorities( ‘do_shortcode_tag’ ) );331 }332 }333 334 }335 336 /**337 * Filter the page itself338 * 339 * @param string $content340 * @return string341 */342 public function filter_page( $content ){343 $protect_using = (string) EEB()->settings->get_setting( 'protect_using’, true );344345 return EEB()->validate->filter_page( $content, $protect_using );346 }347348 /**349 * Filter the whole content350 * 351 * @param string $content352 * @return string353 */354 public function filter_content( $content ){355 $protect_using = (string) EEB()->settings->get_setting( 'protect_using’, true );356 return EEB()->validate->filter_content( $content, $protect_using );357 }358359 /**360 * Filter the rss content361 * 362 * @param string $content363 * @return string364 */365 public function filter_rss( $content ){366 $protection_type = (string) EEB()->settings->get_setting( 'protect_using’, true );367 return EEB()->validate->filter_rss( $content, $protection_type );368 }369370 /**371 * RSS Callback Remove shortcodes372 * @param string $content373 * @return string374 */375 public function callback_rss_remove_shortcodes( $content ) {376 // strip shortcodes like [eeb_content], [eeb_form]377 $content = strip_shortcodes($content);378379 return $content;380 }381 382 /**383 * ######################384 * ###385 * #### SHORTCODES386 * ###387 * ######################388 */389390 /**391 * Handle content filter shortcode392 * @param array $atts393 * @param string $content394 */395 public function protect_content_shortcode( $atts, $content = null ){396 $protect = (int) EEB()->settings->get_setting( 'protect’, true );397 $protect_using = (string) EEB()->settings->get_setting( 'protect_using’, true );398 $protection_activated = ( $protect === 1 || $protect === 2 ) ? true : false;399400 if ( ! $protection_activated ) {401 return $content;402 }403 404 if( isset( $atts[‘protect_using’] ) ){405 $protect_using = $atts[‘protect_using’];406 }407408 $content = EEB()->validate->filter_content( $content, $protect_using );409410 return $content;411 }412413 /**414 * Return the email encoder form415 * @param array $atts416 * @param string $content417 */418 public function shortcode_email_encoder_form( $atts = array(), $content = null ){419420 if( 421 EEB()->helpers->is_page( $this->page_name )422 || (bool) EEB()->settings->get_setting( 'encoder_form_frontend’, true, ‘encoder_form’ )423 ){424 return EEB()->validate->get_encoder_form();425 }426427 return '’;428 }429430 /**431 * Return the encoded content432 * @param array $atts433 * @param string $content434 */435 public function shortcode_eeb_content( $atts = array(), $content = null ){436437 $original_content = $content;438 $show_encoded_check = (string) EEB()->settings->get_setting( 'show_encoded_check’, true );439440 if( ! isset( $atts[‘protection_text’] ) ){441 $protection_text = __( EEB()->settings->get_setting( 'protection_text’, true ), ‘email-protection-text-eeb-content’ );442 } else {443 $protection_text = wp_kses_post( $atts[‘protection_text’] );444 }445446 if( isset( $atts[‘method’] ) ){447 $method = sanitize_title( $atts[‘method’] );448 } else {449 $method = 'rot13’;450 }451452 if( isset( $atts[‘do_shortcode’] ) && $atts[‘do_shortcode’] === ‘yes’ ){453 $content = do_shortcode( $content );454 }455456 switch( $method ){457 case 'enc_ascii’:458 case 'rot13’:459 $content = EEB()->validate->encode_ascii( $content, $protection_text );460 break;461 case 'enc_escape’:462 case 'escape’:463 $content = EEB()->validate->encode_escape( $content, $protection_text );464 break;465 case 'enc_html’:466 case 'encode’:467 default:468 $content = antispambot( $content );469 break;470 }471472 // mark link as successfullly encoded (for admin users)473 if ( current_user_can( EEB()->settings->get_admin_cap( ‘frontend-display-security-check’ ) ) && $show_encoded_check ) {474 $content .= EEB()->validate->get_encoded_email_icon();475 }476477 return apply_filters( 'eeb/frontend/shortcode/eeb_protect_content’, $content, $atts, $original_content );478 }479480 /**481 * Return the encoded email482 * @param array $atts483 * @param string $content484 */485 public function shortcode_eeb_email( $atts = array(), $content = null ){486487 $show_encoded_check = (bool) EEB()->settings->get_setting( 'show_encoded_check’, true );488 $protection_text = __( EEB()->settings->get_setting( 'protection_text’, true ), ‘email-encoder-bundle’ );489490 if( empty( $atts[‘email’] ) ){491 return '’;492 } else {493 $email = $atts[‘email’];494 }495496 if( empty( $atts[‘extra_attrs’] ) ){497 $extra_attrs = '’;498 } else {499 $extra_attrs = $atts[‘extra_attrs’];500 }501502 if( ! isset( $atts[‘method’] ) || empty( $atts[‘method’] ) ){503 $protect_using = (string) EEB()->settings->get_setting( 'protect_using’, true );504 if( ! empty( $protect_using ) ){505 $method = $protect_using;506 } else {507 $method = 'rot13’; //keep as fallback508 }509 } else {510 $method = sanitize_title( $atts[‘method’] );511 }512513 $custom_class = (string) EEB()->settings->get_setting( ‘class_name’, true );514 515 if( empty( $atts[‘display’] ) ) {516 $display = $email;517 } else {518 $display = html_entity_decode( $atts[‘display’] );519 }520 521 if( empty( $atts[‘noscript’] ) ) {522 $noscript = $protection_text;523 } else {524 $noscript = html_entity_decode( $atts[‘noscript’] );525 }526 527 $class_name = ' ' . trim( $extra_attrs );528 $class_name .= ' class="’ . esc_attr( $custom_class ) . '"’;529 $mailto = ‘<a href="mailto:’ . $email . '"’. $class_name . ‘>’ . $display . '</a>’;530 531 switch( $method ){532 case 'enc_ascii’:533 case 'rot13’:534 $mailto = EEB()->validate->encode_ascii( $mailto, $noscript );535 break;536 case 'enc_escape’:537 case 'escape’:538 $mailto = EEB()->validate->encode_escape( $mailto, $noscript );539 break;540 case 'with_javascript’:541 $mailto = EEB()->validate->dynamic_js_email_encoding( $mailto, $noscript );542 break;543 case 'without_javascript’:544 $mailto = EEB()->validate->encode_email_css( $mailto );545 break;546 case 'char_encode’:547 $mailto = EEB()->validate->filter_plain_emails( $mailto, null, ‘char_encode’ );548 break;549 case 'strong_method’:550 $mailto = EEB()->validate->filter_plain_emails( $mailto );551 break;552 case 'enc_html’:553 case 'encode’:554 default:555 $mailto = ‘<a href="mailto:’ . antispambot( $email ) . '"’. $class_name . ‘>’ . antispambot( $display ) . '</a>’;556 break;557 }558559 // mark link as successfullly encoded (for admin users)560 if ( current_user_can( EEB()->settings->get_admin_cap( ‘frontend-display-security-check’ ) ) && $show_encoded_check ) {561 $mailto .= EEB()->validate->get_encoded_email_icon();562 }563564 return apply_filters( 'eeb/frontend/shortcode/eeb_mailto’, $mailto );565 }566 567 /**568 * ######################569 * ###570 * #### EMAIL IMAGE571 * ###572 * ######################573 */574575 public function display_email_image(){576577 if( ! isset( $_GET[‘eeb_mail’] ) ){578 return;579 }580581 $email = sanitize_email( base64_decode( $_GET[‘eeb_mail’] ) );582 583 if( ! is_email( $email ) || ! isset( $_GET[‘eeb_hash’] ) ){584 return;585 }586587 $hash = (string) $_GET[‘eeb_hash’];588 $secret = EEB()->settings->get_email_image_secret();589590 if( EEB()->validate->generate_email_signature( $email, $secret ) !== $hash ){591 wp_die( __('Your signture is invalid.’, ‘email-encoder-bundle’) );592 }593594 $image = EEB()->validate->email_to_image( $email );595596 if( empty( $image ) ){597 wp_die( __('Your email could not be converted.’, ‘email-encoder-bundle’) );598 }599600 header(‘Content-type: image/png’);601 echo $image;602 die();603604 }605 606 /**607 * ######################608 * ###609 * #### TEMPLATE TAGS610 * ###611 * ######################612 */613614 public function add_custom_template_tags(){615 $template_tags = EEB()->settings->get_template_tags();616617 foreach( $template_tags as $hook => $callback ){618619 //Make sure we only call our own custom template tags620 if( is_callable( array( $this, $callback ) ) ){621 apply_filters( $hook, array( $this, $callback ), 10 );622 }623624 }625 }626627 /**628 * Filter for the eeb_filter template tag629 * 630 * This function is called dynamically by add_custom_template_tags 631 * using the EEB()->settings->get_template_tags() callback.632 * 633 * @param string $content - the default content634 * @return string - the filtered content635 */636 public function template_tag_eeb_filter( $content ){637 $protect_using = (string) EEB()->settings->get_setting( 'protect_using’, true );638 return EEB()->validate->filter_content( $content, $protect_using );639 }640641 /**642 * Filter for the eeb_filter template tag643 * 644 * This function is called dynamically by add_custom_template_tags 645 * using the EEB()->settings->get_template_tags() callback.646 * 647 * @param string $content - the default content648 * @return string - the filtered content649 */650 public function template_tag_eeb_mailto( $email, $display = null, $atts = array() ){651 if ( is_array( $display ) ) {652 // backwards compatibility (old params: $display, $attrs = array())653 $atts = $display;654 $display = $email;655 } else {656 $atts[‘href’] = 'mailto:’.$email;657 }658659 return EEB()->validate->create_protected_mailto( $display, $atts );660 }661662}