Headline
CVE-2023-5745: text-blocks.php in reusable-text-blocks/tags/1.5.3 – WordPress Plugin Repository
The Reusable Text Blocks plugin for WordPress is vulnerable to Stored Cross-Site Scripting via ‘text-blocks’ shortcode in versions up to, and including, 1.5.3 due to insufficient input sanitization and output escaping on user supplied attributes. This makes it possible for authenticated attackers with author-level and above permissions to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
1<?php2/*3Plugin Name: Text Blocks4Plugin URI: http://halgatewood.com/text-blocks5Description: Blocks of content that can be used throughout the site in theme templates and widgets.6Author: Hal Gatewood7Author URI: http://www.halgatewood.com8Text Domain: text-blocks9Domain Path: /languages10Version: 1.5.311*/1213/*14This program is free software; you can redistribute it and/or modify15it under the terms of the GNU General Public License as published by16the Free Software Foundation; version 2 of the License.1718This program is distributed in the hope that it will be useful,19but WITHOUT ANY WARRANTY; without even the implied warranty of20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the21GNU General Public License for more details.2223You should have received a copy of the GNU General Public License24along with this program; if not, write to the Free Software25Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA26*/272829// ADDS30add_action( 'plugins_loaded’, ‘text_block_setup’ );31function text_block_setup()32{33 add_action( 'init’, ‘create_text_block_type’ );34 add_action( 'admin_head’, ‘textblocks_css’ );35 add_filter( 'manage_edit-text-blocks_columns’, ‘textblocks_columns’ );36 add_action( 'manage_text-blocks_posts_custom_column’, ‘textblocks_add_columns’ );37 add_action( 'widgets_init’, ‘text_block_register_widget’ );38 add_shortcode( 'text-blocks’, ‘text_blocks_shortcode’);39 add_action( 'add_meta_boxes’, ‘text_blocks_create_metaboxes’ );40 41 if( is_admin() )42 {43 add_action( 'media_buttons’, 'text_blocks_media_button’, 11 );44 add_action( 'admin_footer’, ‘text_blocks_admin_footer_for_thickbox’ );45 }46 47}4849function text_block_register_widget()50{51 register_widget(“TextBlocksWidget”);52}5354// INIT:55// LANGUAGES56// CUSTOM POST TYPE57function create_text_block_type()58{59 60 // LOAD TEXT DOMAIN61 load_plugin_textdomain( 'text-blocks’, false, dirname( plugin_basename( __FILE__ ) ) . ‘/languages/’ );62 63 $labels = array(64 ‘name’ => __('Text Blocks’, ‘text-blocks’),65 ‘singular_name’ => __('Text Block’, ‘text-blocks’),66 ‘add_new’ => __('Add New’, ‘text-blocks’),67 ‘add_new_item’ => __('Add New Block’, ‘text-blocks’),68 ‘edit_item’ => __('Edit Text Block’, ‘text-blocks’),69 ‘new_item’ => __('New Block’, ‘text-blocks’),70 ‘all_items’ => __('All Text Blocks’, ‘text-blocks’),71 ‘view_item’ => __('View Block’, ‘text-blocks’),72 ‘search_items’ => __('Search Text Blocks’, ‘text-blocks’),73 ‘not_found’ => __('No blocks found’, ‘text-blocks’),74 ‘not_found_in_trash’ => __('No blocks found in Trash’, ‘text-blocks’),75 ‘parent_item_colon’ => '’,76 ‘menu_name’ => __('Text Blocks’, ‘text-blocks’)77 );7879 $args = array(80 ‘labels’ => $labels,81 ‘public’ => false,82 ‘publicly_queryable’ => true,83 ‘show_ui’ => true,84 ‘show_in_menu’ => true,85 ‘query_var’ => true,86 ‘rewrite’ => array(‘with_front’ => false),87 ‘capability_type’ => 'post’,88 ‘has_archive’ => false,89 ‘hierarchical’ => false,90 ‘menu_position’ => 26.4,91 ‘exclude_from_search’ => true,92 ‘supports’ => array( 'title’, 'editor’, 'thumbnail’, ‘revisions’ )93 );9495 register_post_type( 'text-blocks’, apply_filters('text_blocks_post_type_args’, $args ) );96}979899// ADMIN: WIDGET ICONS100function textblocks_css()101{102 global $wp_version;103104 if($wp_version >= 3.8)105 {106107 echo '108 <style>109 #adminmenu #menu-posts-text-blocks div.wp-menu-image:before { content: “\f180"; }110 </style>111 ';112 }113 else114 {115116117 $icon = plugins_url( ‘reusable-text-blocks’ ) . “/menu-icon.png";118 $icon_32 = plugins_url( ‘reusable-text-blocks’ ) . “/icon-32.png";119120 echo “121 <style>122 #menu-posts-text-blocks .wp-menu-image { background: url({$icon}) no-repeat 6px -26px !important; }123 #menu-posts-text-blocks.wp-has-current-submenu .wp-menu-image { background-position:6px 6px!important; }124 .icon32-posts-text-blocks { background: url({$icon_32}) no-repeat 0 0 !important; }125 </style>126 “;127 }128}129130131// CUSTOM COLUMNS132function textblocks_columns( $columns )133{134 return array(135 ‘cb’ => '<input type="checkbox” />’,136 ‘title’ => __( 'Title’, ‘text-blocks’ ),137 ‘shortcode’ => __( 'Shortcode’, ‘text-blocks’ ),138 ‘text’ => __( 'Text’, ‘text-blocks’ )139 );140}141142143// CUSTOM COLUMN DATA144function textblocks_add_columns( $column )145{146 global $post;147 $edit_link = get_edit_post_link( $post->ID );148149 if ( $column == ‘text’ ) echo strip_tags($post->post_content);150 if( $column == “shortcode”)151 {152 echo “153 [text-blocks id=\"{$post->ID}\”]<br />154 [text-blocks id=\"{$post->post_name}\”]<br /><hr />155 [text-blocks id=\"{$post->ID}\” plain=\"1\”]<br />156 [text-blocks id=\"{$post->post_name}\” plain=\"1\"]157 “;158 }159}160161162// METABOXES163function text_blocks_create_metaboxes()164{165 // IF ON EDIT SHOW THE SHORTCODE166 if(isset($_GET[‘action’]) AND $_GET[‘action’] == “edit”)167 {168 add_meta_box( 'text_blocks_shortcode_metabox’, __('Text Block Shortcode’, ‘text-blocks’), 'text_blocks_shortcode_metabox’, 'text-blocks’, 'normal’, ‘default’ );169 }170}171172173// SHORTCODE DISPLAY HELPER174function text_blocks_shortcode_metabox()175{176 global $post;177178 echo “<p><b>” . __('Like WordPress Content:’, ‘text-blocks’) . “</b><br />[text-blocks id=\"{$post->ID}\”] or [text-blocks id=\"{$post->post_name}\”]</p>";179180 echo “<p><b>” . __('No extra markup:’, ‘text-blocks’) . “</b><br />[text-blocks id=\"{$post->ID}\” plain=\"1\"] or [text-blocks id=\"{$post->post_name}\" plain=\"1\"]</p>";181182 echo “<p><b>” . __('In Theme Template:’, ‘text-blocks’) . “</b><br /><?php if(function_exists(‘show_text_block’)) { echo show_text_block('{$post->post_name}’, true); } ?></p>";183184 echo ‘<span class="description">’ . __('Put one of the above codes wherever you want the text block to appear’, ‘text-blocks’) . '</span>’;185}186187188189// TEXT BLOCK WIDGET190class TextBlocksWidget extends WP_Widget191{192 function __construct() { parent::__construct(false, $name = ‘Text Blocks Widget’); }193194 function widget($args, $instance)195 {196 extract( $args );197 $title = isset($instance[‘title’]) ? $instance[‘title’] : false;198 $id = (int) $instance[‘id’];199 $block = get_post( $id );200 $wpautop = isset($instance[‘wpautop’]) ? $instance[‘wpautop’] : false;201 $hide_title = isset($instance[‘hide_title’]) ? $instance[‘hide_title’] : false;202203 $block_content = $block->post_content;204 if($wpautop == “on”) { $block_content = wpautop($block_content); }205 ?>206 <?php echo $before_widget; ?>207 <?php if ( $title && !$hide_title ) echo $before_title . $title . $after_title; ?>208 <div class="text-block <?php echo $block->post_name ?>"><?php echo apply_filters( 'text_blocks_widget_html’, $block_content); ?></div>209 <?php echo $after_widget; ?>210 <?php211 }212213 function update($new_instance, $old_instance)214 {215 $instance = $old_instance;216 $instance[‘title’] = strip_tags($new_instance[‘title’]);217 $instance[‘id’] = strip_tags($new_instance[‘id’]);218 $instance[‘wpautop’] = strip_tags($new_instance[‘wpautop’]);219 $instance[‘hide_title’] = strip_tags($new_instance[‘hide_title’]);220 return $instance;221 }222223 function form($instance)224 {225 $title = isset($instance[‘title’]) ? esc_attr($instance[‘title’]) : “";226 $selected_block = isset($instance[‘id’]) ? esc_attr($instance[‘id’]) : 0;227 $wpautop = isset($instance[‘wpautop’]) ? esc_attr($instance[‘wpautop’]) : 0;228 $hide_title = isset($instance[‘hide_title’]) ? esc_attr($instance[‘hide_title’]) : 0;229230 $blocks = get_posts( array(‘post_type’ => 'text-blocks’, ‘numberposts’ => -1, ‘orderby’ => 'title’, ‘order’ => ‘ASC’ ));231 ?>232 <p>233 <label for="<?php echo $this->get_field_id(‘title’); ?>"><?php _e('Title:’, ‘text-blocks’); ?></label>234 <input class="widefat” id="<?php echo $this->get_field_id(‘title’); ?>” name="<?php echo $this->get_field_name(‘title’); ?>" type="text" value="<?php echo $title; ?>" />235 </p>236 <p>237 <label for="<?php echo $this->get_field_id(‘id’); ?>"><?php _e('Text Block:’, ‘text-blocks’); ?></label>238 <select class="widefat" id="<?php echo $this->get_field_id(‘id’); ?>" name="<?php echo $this->get_field_name(‘id’); ?>">239 <?php foreach($blocks as $block) { ?>240 <option value="<?php echo $block->ID; ?>"<?php if($selected_block == $block->ID) echo " selected=\"selected\""; ?>><?php echo $block->post_title; ?></option>241 <?php } ?>242 </select>243 </p>244245 <p>246 <input id="<?php echo $this->get_field_id(‘wpautop’); ?>" name="<?php echo $this->get_field_name(‘wpautop’); ?>" type="checkbox"<?php if($wpautop == “on”) echo " checked=’checked’"; ?>> 247 <label for="<?php echo $this->get_field_id(‘wpautop’); ?>"><?php _e('Automatically add paragraphs’, ‘text-blocks’); ?></label>248 </p>249 <p>250 <input id="<?php echo $this->get_field_id(‘hide_title’); ?>" name="<?php echo $this->get_field_name(‘hide_title’); ?>" type="checkbox"<?php if($hide_title == “on”) echo " checked=’checked’"; ?>> 251 <label for="<?php echo $this->get_field_id(‘hide_title’); ?>"><?php _e('Hide widget title on front end’, ‘text-blocks’); ?></label>252 </p>253 <?php254 }255}256257258// SHOW TEXT BLOCK259function show_text_block($id, $plain = false, $atts = false)260{261 $id = apply_filters( 'text_blocks_show_text_block_id’, $id );262263264 // IF ID IS NOT NUMERIC CHECK FOR SLUG265 if(!is_numeric($id))266 {267 $page = get_page_by_path( $id, null, ‘text-blocks’ );268 $id = apply_filters( 'wpml_object_id’, $page->ID, ‘text-blocks’ );269 }270271 if( !$id ) return false;272 if ( get_post_status( $id ) != ‘publish’ ) return false;273 274 // LOOK FOR TEMPLATE IN THEME275 $template = isset($atts[‘template’]) ? $atts[‘template’] : $id;276 $template = locate_template( apply_filters( 'text_blocks_template_location’, array( “text-blocks/text-blocks-{$template}.php", “text-blocks-{$template}.php” ) ) );277 278 279 // LOAD TEMPLATE IF FOUND280 if( $template )281 {282 $content = get_post_field( 'post_content’, $id );283 if( !isset($atts[‘stop_detect’])) $content = text_blocks_att_swap( $content, $atts );284 285 ob_start();286 include( $template );287 $output = ob_get_contents();288 ob_end_clean();289 return $output;290 }291292 // LOAD PLAIN CONTENT293 if( $plain )294 {295 $content = get_post_field( 'post_content’, $id );296 if( !isset($atts[‘stop_detect’])) $content = text_blocks_att_swap( $content, $atts );297 return apply_filters( 'text_blocks_shortcode_html’, $content, $atts );298 }299300301 // APPLY ‘the_content’ FILTER TO BLEND WITH EVERYTHING ELSE302 $content = apply_filters( 'the_content’, get_post_field( 'post_content’, $id), $atts );303 if( !isset($atts[‘stop_detect’])) $content = text_blocks_att_swap( $content, $atts );304 return apply_filters( 'text_blocks_shortcode_html’, $content, $atts, $id ); 305}306307308// SHORT CODE309function text_blocks_shortcode($atts)310{311 $id = isset($atts[‘id’]) ? $atts[‘id’] : false;312 $plain = isset($atts[‘plain’]) ? 1 : 0;313 if($id) { return show_text_block( $id, $plain, $atts ); }314 else { return false; }315}316317318// DETECT ATTS FUNCTION319function text_blocks_att_swap( $content, $atts )320{321 if( !is_array($atts) ) $atts = (array) $atts;322 323 $dont_detect = apply_filters('text_blocks_dont_detect_words’, array('template’, 'id’, ‘detect’) );324 325 $replace_front = apply_filters('text_blocks_replace_front’, ‘{{’);326 $replace_back = apply_filters('text_blocks_replace_back’, ‘}}’);327328 foreach( $atts as $slug => $att )329 {330 if( in_array($att, $dont_detect) ) continue;331 $content = str_replace($replace_front . $slug . $replace_back, $atts[$slug], $content);332 }333 334 return $content;335}336337338// MEDIA BUTTON339function text_blocks_media_button() 340{341 global $pagenow, $typenow, $wp_version;342 $output = '’;343 if ( version_compare( $wp_version, '3.5’, ‘>=’ ) AND in_array( $pagenow, array( 'post.php’, 'page.php’, 'post-new.php’, ‘post-edit.php’ ) ) && $typenow != ‘text-blocks’ ) 344 {345 $img = '<style>#text-blocks-media-button::before { font: 400 18px/1 dashicons; content: \’\f180\’; }</style><span class="wp-media-buttons-icon” id="text-blocks-media-button"></span>’;346 $output = ‘<a href="#TB_inline?width=640&inlineId=add-text-blocks" class="thickbox button text-blocks-thickbox" title="’ . __( 'Add Block’, ‘text-blocks’ ) . '" style="padding-left: .4em;"> ' . $img . __( 'Add Block’, ‘text-blocks’ ) . '</a>’;347 }348 echo $output;349}350351352// MEDIA BUTTON FUNCTIONALITY353function text_blocks_admin_footer_for_thickbox() 354{355 global $pagenow, $typenow, $wp_version;356357 // Only run in post/page creation and edit screens358 if ( version_compare( $wp_version, '3.5’, ‘>=’ ) AND in_array( $pagenow, array( 'post.php’, 'page.php’, ‘post-new.php’, ‘post-edit.php’ ) ) && $typenow != ‘text-blocks’ ) { ?>359 360 <script type="text/javascript">361 function insertReusableTextBlock() 362 {363 var id = jQuery(‘#text-blocks-select-box’).val();364 if (‘’ === id)365 {366 alert(‘<?php _e( "You must choose a block", “text-blocks” ); ?>’);367 return;368 }369 370 var slug = jQuery(‘#text-blocks-select-box option:selected’).data(‘slug’);371 372 window.send_to_editor('[text-blocks id="’ + id + ‘" slug="’ + slug + '"]');373 }374 </script>375376 <div id="add-text-blocks" style="display: none;">377 <div class="wrap" style="font-family: 'Helvetica Neue’, Helvetica, Arial, sans-serif;">378 <?php379 380 $blocks = get_posts( array(‘post_type’ => 'text-blocks’, ‘numberposts’ => -1, ‘orderby’ => 'title’, ‘order’ => ‘ASC’ ));381 382 if( $blocks ) { ?>383 <select id="text-blocks-select-box" style="clear: both; display: block; margin-bottom: 1em;">384 <option value=""><?php _e('Choose a Text Block’, ‘text-blocks’); ?></option>385 <?php386 foreach ( $blocks as $block ) 387 {388 echo ‘<option value="’ . $block->ID . ‘" data-slug="’ . $block->post_name . ‘">’ . $block->post_title . '</option>’;389 }390 ?>391 </select>392 <?php } else { echo __('No text blocks have been created yet. Please create one first and then you will be able to select it here.’, ‘text-blocks’); } ?>393394 <p class="submit">395 <input type="button" id="text-blocks-insert-download" class="button-primary" value="<?php echo __( 'Insert Block’, ‘text-blocks’ ); ?>" onclick="insertReusableTextBlock();" />396 <a id="text-blocks-cancel-add" class="button-secondary" onclick="tb_remove();" title="<?php _e( 'Cancel’, ‘text-blocks’ ); ?>"><?php _e( 'Cancel’, ‘text-blocks’ ); ?></a>397 </p>398 </div>399 </div>400 <?php401 }402}