░▒▓█ Introduction
I've been making php static code analysis tool for a while and few months ago I ran it against ~1000 (more or less) top wordpress plugins.
Scanning results were manually verified in my spare time and delivered to official plugins@wordpress.org from 04.07.2015 to 31.08.2015. Most of reported plugins are already patched, some are not. Vulnerable and not patched plugins are already removed from official wordpress plugin repository.
░▒▓█ Results
103 plugins vulnerable with more than 4.000.000 active installations in total (~30.000.000 downloads)
List of reported plugins (original reports contain verification/reproduce sections and urls to plugin wordpress repository entries, where you can also verify changelog) :
- Cross-Site Scripting (XSS) in Duplicator 0.5.24 [original report - Sat, 15 Aug 2015]
- Cross-Site Scripting (XSS) in All In One WP Security 3.9.7 [original report - Thu, 13 Aug 2015]
- Cross-Site Scripting (XSS) in AddThis 5.0.12 [original report - Tue, 11 Aug 2015]
- Cross-Site Scripting (XSS) in Display Widgets 2.03 [original report - Tue, 11 Aug 2015]
- Blind SQL injection and XSS in SEO SearchTerms Tagging 2 1.535 [original report - Wed, 8 Jul 2015] NOT PATCHED
- Blind SQL injection in Pretty Link Lite 1.6.7 [original report - Wed, 8 Jul 2015]
- Blind SQL injection in WP Statistics 9.4 [original report - Thu, 9 Jul 2015]
- Cross-Site Scripting (XSS) in My Page Order 4.3 [original report - Thu, 13 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in Category Order and Taxonomy Terms Order 1.4.4 [original report - Tue, 18 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in WP Social Bookmarking Light 1.7.9 [original report - Wed, 19 Aug 2015]
- Cross-Site Scripting (XSS) in WP Google Fonts v3.1.3 [original report - Wed, 19 Aug 2015]
- Cross-Site Scripting (XSS) in Easy Table 1.5.2 [original report - Mon, 10 Aug 2015]
- Cross-Site Scripting (XSS) in My Category Order 4.3 [original report - Thu, 13 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in CKEditor for WordPress 4.5.3 [original report - Mon, 31 Aug 2015]
- Blind SQL injection in Huge IT Slider 2.8.6 [original report - Wed, 22 Jul 2015]
- Cross-Site Scripting (XSS) in Dynamic Widgets 1.5.10 [original report - Tue, 11 Aug 2015]
- Cross-Site Scripting (XSS) in Google Language Translator 4.0.9 [original report - Thu, 13 Aug 2015]
- Cross-Site Scripting (XSS) in JW Player 6 Plugin for Wordpress 2.1.14 [original report - Wed, 19 Aug 2015] NOT PATCHED
- Persistent Cross-Site Scripting (XSS) in Floating Social Media Icon 2.1 [original report - Wed, 19 Aug 2015]
- Blind SQL injections in Contact Form Builder 1.0.24 [original report - Tue, 7 Jul 2015]
- Arbitrary file upload and Cross-Site Scripting (XSS) in Slideshow Gallery 1.5.3 [original report - Thu, 20 Aug 2015]
- Blind SQL injection in Master Slider 2.5.1 [original report - Thu, 20 Aug 2015]
- Blind SQL injection and Reflected XSS in WP RSS Multi Importer 3.15 [original report - Wed, 8 Jul 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in Add Link to Facebook 2.2.7 [original report - Thu, 13 Aug 2015] NOT PATCHED
- Blind SQL injection in 404 to 301 2.0.2 [original report - Thu, 20 Aug 2015]
- Cross-Site Scripting (XSS) in Alpine PhotoTile for Instagram 1.2.7.5 [original report - Thu, 20 Aug 2015]
- Cross-Site Scripting (XSS) in Huge IT Image Gallery 1.5.1 [original report - Thu, 20 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in Visitor Maps and Who's Online 1.5.8.6 [original report - Thu, 20 Aug 2015]
- Cross-Site Scripting (XSS) in WP Google Map Plugin 2.3.9 [original report - Thu, 20 Aug 2015]
- Cross-Site Scripting (XSS) in WP Job Manager 1.23.7 [original report - Thu, 20 Aug 2015]
- SQL injection (+XSS) in Easy Social Icons 1.2.3.1 [original report - Wed, 22 Jul 2015]
- Cross-Site Scripting (XSS) in My Link Order 4.3 [original report - Thu, 13 Aug 2015] NOT PATCHED
- Persistent Cross-Site Scripting (XSS) in WP Database Backup 3.3 [original report - Thu, 20 Aug 2015]
- Arbitrary file upload and Reflected Cross-Site Scripting (XSS) in Theme Test Drive 2.9 [original report - Thu, 20 Aug 2015]
- Cross-Site Scripting (XSS) in Subscribe to Comments Reloaded 150611 [original report - Thu, 20 Aug 2015]
- Cross-Site Scripting (XSS) in SEO Redirection 2.8 [original report - Fri, 21 Aug 2015]
- Cross-Site Scripting (XSS) in qTranslate-X 3.4.3 [original report - Fri, 21 Aug 2015]
- Blind SQL injection in Gallery Bank Lite Edition Version 3.0.229 [original report - Fri, 21 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in Crazy Bone 0.5.5 [original report - Fri, 21 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in Social Media Widget by Acurax 2.2 [original report - Fri, 21 Aug 2015]
- Reflected Cross-Site Scripting (XSS) in Anti-spam by CleanTalk 5.21 [original report - Tue, 25 Aug 2015]
- Blind SQL injection in Smooth Slider 2.6.5 [original report - Wed, 15 Jul 2015]
- Cross-Site Scripting (XSS) in YITH Maintenance Mode 1.1.4 [original report - Fri, 21 Aug 2015]
- Multiple SQL injections and XSS in Email Subscribers 2.9 [original report - Mon, 10 Aug 2015]
- Cross Site Scripting (XSS) in Email newsletter 20.13.6 [original report - Mon, 10 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in Easy Pie Coming Soon 1.0.0 [original report - Mon, 10 Aug 2015]
- Cross-Site Scripting (XSS) in Easy Pie Coming Soon 1.0.0 [original report - Mon, 10 Aug 2015]
- Cross-Site Scripting (XSS) in Contact Bank Lite Edition 2.0.225 [original report - Thu, 13 Aug 2015]
- Cross-Site Scripting (XSS) in Kiwi Logo Carousel 1.7.1 [original report - Thu, 13 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in WP Legal Pages 1.0.1 [original report - Fri, 21 Aug 2015]
- Cross-Site Scripting (XSS) in WP Crontrol 1.2.3 [original report - Fri, 21 Aug 2015]
- Cross-Site Scripting (XSS) in Websimon Tables 1.3.4 [original report - Fri, 21 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in SEO Rank Reporter 2.2.2 [original report - Mon, 24 Aug 2015] NOT PATCHED
- Persistent Cross-Site Scripting (XSS) in WP Keyword Link 1.7 [original report - Mon, 24 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in Huge IT Portfolio Gallery 1.5.7 [original report - Mon, 24 Aug 2015]
- Cross-Site Scripting (XSS) in Manual Image Crop 1.10 [original report - Mon, 24 Aug 2015]
- Cross-Site Scripting (XSS) in iQ Block Country in 1.1.19 [original report - Mon, 24 Aug 2015]
- SQL injection and Cross-Site Scripting (XSS) in GigPress 2.3.10 [original report - Mon, 24 Aug 2015]
- Arbitrary file read in Multi Plugin Installer 1.1.0 [original report - Mon, 24 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in FV Wordpress Flowplayer 6.0.3.3 [original report - Mon, 24 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in Easy Coming Soon 1.8.1 [original report - Mon, 24 Aug 2015]
- Cross-Site Scripting (XSS) in Contact Form Manager 1.4.1 [original report - Mon, 24 Aug 2015] NOT PATCHED
- Blind SQL injection in WordPress Meta Robots 2.1 [original report - Tue, 25 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in Smart Slider 2 2.3.11 [original report - Wed, 26 Aug 2015]
- Cross-Site Scripting (XSS) in Soundcloud is Gold 2.3.1 [original report - Wed, 26 Aug 2015]
- Blind SQL injection in Contact Form Maker 1.7.30 [original report - Wed, 8 Jul 2015]
- Cross-Site Scripting (XSS) in Plugin Central 2.5 [original report - Tue, 25 Aug 2015]
- Blind SQL injection in yet another stars rating 0.9.0 [original report - Mon, 6 Jul 2015]
- Blind SQL injection in smart manager for wp e commerce 3.9.6 [original report - Wed, 8 Jul 2015]
- Blind SQL injection and CSRF for logged administrators in awesome filterable portfolio 1.8.6 [original report - Tue, 7 Jul 2015]
- Blind SQL injections in WP Shop 3.4.3.15 [original report - Wed, 8 Jul 2015]
- Cross-Site Scripting (XSS) in Job Manager 0.7.24 [original report - Tue, 25 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in "Post video players, slideshow albums, photo galleries and music / podcast playlist" 1.136 [original report - Tue, 25 Aug 2015]
- Arbitrary file modification in Child Theme Creator by Orbisius 1.2.6 [original report - Wed, 8 Jul 2015]
- SQL injection in WP-Stats-Dashboard 2.9.4 [original report - Tue, 25 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in WP Page Widget 2.7 [original report - Tue, 25 Aug 2015]
- Blind SQL injection in wti like post 1.4.2 [original report - Sun, 5 Jul 2015]
- SQL injection in Huge IT Google Map 2.2.5 [original report - Wed, 8 Jul 2015] NOT PATCHED
- Blind SQL injection and Reflected XSS vulnerabilities in broken link manager plugin 0.4.5 [original report - Sat, 4 Jul 2015]
- Blind SQL injection in Microblog Poster 1.6.0 [original report - Wed, 22 Jul 2015]
- SQL injection and Cross-Site Scripting (XSS) in GoCodes 1.3.5 [original report - Tue, 25 Aug 2015] NOT PATCHED
- Blind SQL injections in Auto Affiliate Links 4.9.9.4 [original report - Wed, 15 Jul 2015]
- Cross-Site Scripting (XSS) in WP Widget Cache 0.26 [original report - Tue, 25 Aug 2015] NOT PATCHED
- SQL injections and XSS in SendPress Newsletters 1.1.7.21 [original report - Thu, 23 Jul 2015]
- Cross-Site Scripting (XSS) in Email Users 4.7.5 [original report - Mon, 10 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in Social Share Button 2.1 [original report - Tue, 25 Aug 2015] NOT PATCHED
- Cross-Site Scripting (XSS) in Social Locker | BizPanda 4.2.0 [original report - Tue, 25 Aug 2015]
- Cross Site Scripting (XSS) in Email Encoder Bundle - Protect Email Address 1.4.1 [original report - Mon, 10 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in Page Restrict 2.2.1 [original report - Tue, 25 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in Multicons 2.1 [original report - Tue, 25 Aug 2015]
- Persistent Cross-Site Scripting (XSS) in PlugNedit Adaptive Editor 5.2.0 [original report - Tue, 25 Aug 2015]
- Blind SQL injection in WooCommerce Abandon Cart Lite Plugin 1.7 [original report - Wed, 15 Jul 2015]
- Persistent XSS in Broken Link manager Ver 0.5.5 [original report - Thu, 16 Jul 2015]
- Blind SQL injections in Quiz Master Next 4.4.2 [original report - Thu, 16 Jul 2015]
- Cross-Site Scripting (XSS) in Role Scoper 1.3.64 [original report - Wed, 26 Aug 2015]
- Blind SQL injections in NEX-Forms 4.0 [original report - Thu, 16 Jul 2015]
- Cross-Site Scripting (XSS) in Ad Inserter 1.5.5 [original report - Thu, 13 Aug 2015]
- Cross-Site Scripting (XSS) in Olevmedia Shortcodes 1.1.8 [original report - Tue, 25 Aug 2015]
- Blind SQL injection in Booking System (+WooCommerce) 2.0 [original report - Tue, 7 Jul 2015]
- Blind SQL injections in Plgumatter Optin Feature Box 2.0.13 [original report - Thu, 16 Jul 2015]
- Blind SQL injection in WR ContactForm 1.1.9 [original report - Thu, 9 Jul 2015]
- Cross-Site Scripting (XSS) in Simple Fields 1.4.10 [original report - Tue, 25 Aug 2015]
- Blind SQL injections, XSS and more in wp live chat support 4.3.5 [original report - Mon, 6 Jul 2015]
Some notes:
Without magic_quotes_gpc (wordpress "emulate" it anyway when it's off) it will be some more exploitable SQLi vulnerabilities.
I'm also pretty sure that there are some vulnerabilities in reports that I missed, or just thought that they were unexploitable.
Because of low resources limits (like memory limit ~3GB) many plugins from top 1000 list didn't get 100% code coverage.
░▒▓█ What about CVE identifiers?
I've sent bulk request to cve-assign@mitre.org recently. No response so far.
░▒▓█ Static code analysis tool and some hilights
I used own PhpSourcerer static code analysis tool, which is still in development. You can try PhpSourcerer yourself on php-grinder.com (current limits: 2GB Ram, 15 minutes execution time). It has nice web-ui instead of raw text file reports:
I show you some details about reported vulnerabilities to demonstrate what this tool is capable of.
◼ Reflected Cross-Site Scripting (XSS) in Simple Fields 1.4.10
Description: Authenticated users (like subscribers) can inject html/js code (there is no CSRF protection!)
Some urls: Sent report | Original project sources | PhpSourcerer output
This is a simple example, so I guess it doesn't need explanation:
... function field_type_post_dialog_load() { global $sf; $arr_enabled_post_types = isset($_POST["arr_enabled_post_types"]) ? $_POST["arr_enabled_post_types"] : array(); $str_enabled_post_types = isset($_POST["str_enabled_post_types"]) ? $_POST["str_enabled_post_types"] : ""; $additional_arguments = isset($_POST["additional_arguments"]) ? $_POST["additional_arguments"] : ""; $existing_post_types = get_post_types(NULL, "objects"); $selected_post_type = isset($_POST["selected_post_type"]) ? (string) $_POST["selected_post_type"] : ""; if (empty($arr_enabled_post_types)) { $arr_enabled_post_types = explode(",", $str_enabled_post_types); } /*echo "<br>selected_post_type: $selected_post_type"; echo "<br>str_enabled_post_types: $str_enabled_post_types"; echo "<br>enabled post types:"; print_r($arr_enabled_post_types);*/ // If no post type is selected then don't show any posts if (empty($arr_enabled_post_types)) { _e("<p>No post type is selected. Please at at least one post type in Simple Fields.</p>", "simple-fields"); exit; } ?> <?php if (count($arr_enabled_post_types) > 1) { ?> <p>Show posts of type:</p> <ul class="simple-fields-meta-box-field-group-field-type-post-dialog-post-types"> <?php $loopnum = 0; foreach ($existing_post_types as $key => $val) { if (!in_array($key, $arr_enabled_post_types)) { continue; } if (empty($selected_post_type) && $loopnum == 0) { $selected_post_type = $key; } $class = ""; if ($selected_post_type == $key) { $class = "selected"; } printf("\n<li class='%s'><a href='%s'>%s</a></li>", $class, "$key", $val->labels->name); $loopnum++; } ?> </ul> <?php } else { $selected_post_type = $arr_enabled_post_types[0]; ?> <p>Showing posts of type: <a href="<?php echo $selected_post_type; ?>"><?php echo $existing_post_types[$selected_post_type]->labels->name; ?></a></p> <?php } ?> ...
◼ Blind SQL injection in smart manager for wp e commerce 3.9.6
Description: Unauthenticated remote attackers can execute arbitrary SQL commands.
Some urls: Sent report | Original project sources | PhpSourcerer output
/smart-manager-for-wp-e-commerce/sm/woo-json.php:
<?php ob_start(); ... // For insert updating product in woo. if (isset ( $_POST ['cmd'] ) && $_POST ['cmd'] == 'saveData') { ... if ( $_POST['active_module'] == "Coupons" ) { ... } else { $result = woo_insert_update_data ( $_POST ); } ...woo_insert_update_data() :
... function woo_insert_update_data($post) { global $wpdb,$woocommerce; $_POST = $post; // Fix: PHP 5.4 $editable_fields = array( '_billing_first_name' , '_billing_last_name' , '_billing_email', '_billing_address_1', '_billing_address_2', '_billing_city', '_billing_state', '_billing_country','_billing_postcode', '_billing_phone', '_shipping_first_name', '_shipping_last_name', '_shipping_address_1', '_shipping_address_2', '_shipping_city', '_shipping_state', '_shipping_country','_shipping_postcode', 'order_status' ); $new_product = json_decode($_POST['edited']); $edited_prod_ids = array(); $edited_prod_slug = array(); if (!empty($new_product)) { foreach($new_product as $product) { $edited_prod_ids[] = $product->id; } } //Code for getting the product slugs if ( !empty($edited_prod_ids) ) { $query_prod_slug = "SELECT id, post_name FROM {$wpdb->prefix}posts WHERE id IN (".implode(",",$edited_prod_ids).")"; $results_prod_slug = $wpdb->get_results($query_prod_slug, 'ARRAY_A'); $prod_slug_rows = $wpdb->num_rows; ...So we can inject our payload in valid json variable, like in verification example in sent report:
curl --request POST --data "cmd=saveData&edited=[{\"id\":\" 1) union select sleep(10),2; -- -\"}]" http://localhost/wp-content/plugins/smart-manager-for-wp-e-commerce/sm/woo-json.php
◼ Reflected Cross-Site Scripting (XSS) in SEO Redirection 2.8
Description: Authenticated administrators can inject html/js code (there is no CSRF protection).
Some urls: Sent report | Original project sources | PhpSourcerer output
/options/option_page_post_redirection_list.php:
<?php global $wpdb,$table_prefix,$util; $table_name = $table_prefix . 'WP_SEO_Redirection'; if($util->get('del')!='') { $delid=intval($util->get('del')); $wpdb->query(" delete from $table_name where ID='$delid' "); if($util->there_is_cache()!='') $util->info_option_msg("You have a cache plugin installed <b>'" . $util->there_is_cache() . "'</b>, you have to clear cache after any changes to get the changes reflected immediately! "); $SR_redirect_cache = new clogica_SR_redirect_cache(); $SR_redirect_cache->free_cache(); } $rlink=$util->get_current_parameters(array('del','search','page_num','add','edit')); ?> <br/> <script type="text/javascript"> ... </script> <div class="link_buttons"> <table border="0" width="100%"> <tr> <td width="110"><a href="<?php echo $rlink?>&add=1"><div class="add_link">Add New</div></a></div></td> <td align="right"> <input onkeyup="if (event.keyCode == 13) go_search();" style="height: 30px;" id="search" type="text" name="search" value="<?php echo $util->get('search')?>" size="40"> <a onclick="go_search()" href="#"><div class="search_link">Search</div></a>$util is object of "clogica_util" class (seo-redirection.php):
<?php /* ... */ require_once ('common/controls.php'); require_once ('custom/controls.php'); require_once ('custom/controls/cf.SR_redirect_cache.class.php'); if(!defined('WP_SEO_REDIRECTION_OPTIONS')) { define( 'WP_SEO_REDIRECTION_OPTIONS', 'wp-seo-redirection-group' ); } if(!defined('WP_SEO_REDIRECTION_VERSION')) { define( 'WP_SEO_REDIRECTION_VERSION', '2.8'); } $util= new clogica_util(); $util->set_option_gruop(WP_SEO_REDIRECTION_OPTIONS); ...Finally lets look how clogica_util::get() looks like:
... public function get($key,$type='text') { if(array_key_exists($key,$_GET)) { $unsafe_val=$_GET[$key]; return $this->sanitize_req($unsafe_val,$type); } else { return ''; } } ... public function sanitize_req($unsafe_val,$type='text') { switch ($type) { case 'text': return sanitize_text_field($unsafe_val); break; case 'int': return intval($unsafe_val); break; case 'email': return sanitize_email($unsafe_val); break; case 'filename': return sanitize_file_name($unsafe_val); break; case 'title': return sanitize_title($unsafe_val); break; default: return sanitize_text_field($unsafe_val); } } ...Yep. Wordpress sanitize_text_field() doesn't prevent from succesful XSS exploitation in html tag attribute.
◼ Reflected Cross-Site Scripting (XSS) in Display Widgets 2.03
Description: Authenticated users (like subscribers) can inject html/js code (there is no CSRF protection!).
Some urls: Sent report | Original project sources | PhpSourcerer output
Lets look at the DWPlugin::show_widget_options() method (invokes by http://localhost/wp-admin/admin-ajax.php?action=dw_show_widget)
... function show_widget_options() { $instance = htmlspecialchars_decode(nl2br(stripslashes($_POST['opts']))); $instance = json_decode($instance, true); $this->id_base = $_POST['id_base']; $this->number = $_POST['widget_number']; $new_instance = array(); $prefix = 'widget-'. $this->id_base .'['. $this->number .']['; foreach ( $instance as $k => $v ) { $n = str_replace( array( $prefix, ']'), '', $v['name']); $new_instance[$n] = $v['value']; } self::show_hide_widget_options($this, '', $new_instance); die(); } ...As we can see we have two variables assigned to object properties, and then another method (show_hide_widget_options) is called with $this argument. DWPlugin::show_hide_widget_options():
... function show_hide_widget_options($widget, $return, $instance) { self::register_globals(); $wp_page_types = self::page_types(); $instance['dw_include'] = isset($instance['dw_include']) ? $instance['dw_include'] : 0; $instance['dw_logged'] = self::show_logged($instance); $instance['other_ids'] = isset($instance['other_ids']) ? $instance['other_ids'] : ''; ?> <p> <label for="<?php echo $widget->get_field_id('dw_include'); ?>"><?php _e('Show Widget for:', 'display-widgets') ?></label> <select name="<?php echo $widget->get_field_name('dw_logged'); ?>" id="<?php echo $widget->get_field_id('dw_logged'); ?>" class="widefat"> <option value=""><?php _e('Everyone', 'display-widgets') ?></option> <option value="out" <?php echo selected( $instance['dw_logged'], 'out' ) ?>><?php _e('Logged-out users', 'display-widgets') ?></option> <option value="in" <?php echo selected( $instance['dw_logged'], 'in' ) ?>><?php _e('Logged-in users', 'display-widgets') ?></option> </select> </p> <p> <select name="<?php echo $widget->get_field_name('dw_include'); ?>" id="<?php echo $widget->get_field_id('dw_include'); ?>" class="widefat"> ...And finally get_field_id method and get_field_name:
... function get_field_name($field_name) { return 'widget-' . $this->id_base . '[' . $this->number . '][' . $field_name . ']'; } function get_field_id($field_name) { return 'widget-' . $this->id_base . '-' . $this->number . '-' . $field_name; } ...$this->id_base and $this->number were assigned at the beggining in DWPlugin::show_widget_options method.
◼ Persistent Cross-Site Scripting (XSS) in FV Wordpress Flowplayer 6.0.3.3
Description: Authenticated administrators can store html/js code in plugin configuration values (there is no CSRF protection!)
Some urls: Sent report | Original project sources | PhpSourcerer output
First lets look at fv_wp_flowplayer_admin_init() function:
... function fv_wp_flowplayer_admin_init() { if( isset($_GET['type']) ) { if( $_GET['type'] == 'fvplayer_video' || $_GET['type'] == 'fvplayer_video_1' || $_GET['type'] == 'fvplayer_video_2' || $_GET['type'] == 'fvplayer_mobile' ) { $_GET['post_mime_type'] = 'video'; } else if( $_GET['type'] == 'fvplayer_splash' || $_GET['type'] == 'fvplayer_logo' ) { $_GET['post_mime_type'] = 'image'; } } if( isset($_POST['fv-wp-flowplayer-submit']) ) { global $fv_fp; if( method_exists($fv_fp,'_set_conf') ) { $fv_fp->_set_conf(); } else { echo 'Error saving FV Flowplayer options.'; } } global $fv_fp; ...As we can see we execute _set_conf() method on $fv_wp object when we send fv-wp-flowplayer-submit in POST. $fv_fp is global object of flowplayer_frontend class defined in flowplayer.php main file.
... $fv_wp_flowplayer_ver = '2.3.17'; $fv_wp_flowplayer_core_ver = '5.5.2'; include( dirname( __FILE__ ) . '/includes/extra-functions.php' ); if( file_exists( dirname( __FILE__ ) . '/includes/module.php' ) ) { include( dirname( __FILE__ ) . '/includes/module.php' ); } include( dirname( __FILE__ ) . '/models/checker.php' ); $FV_Player_Checker = new FV_Player_Checker(); include_once(dirname( __FILE__ ) . '/models/flowplayer.php'); include_once(dirname( __FILE__ ) . '/models/flowplayer-frontend.php'); $fv_fp = new flowplayer_frontend(); if( is_admin() ) { include( dirname( __FILE__ ) . '/controller/backend.php' ); register_deactivation_hook( __FILE__, 'flowplayer_deactivate' ); } include( dirname( __FILE__ ) . '/controller/frontend.php' ); require_once( dirname( __FILE__ ) . '/controller/shortcodes.php');$fv_fp->_set_conf() method is not really belongs to "flowplayer_frontend" class but to "flowplayer" that "flowplayer_frontend" is extending:
... class flowplayer_frontend extends flowplayer { ...flowplayer::_set_conf():
... public function _set_conf() { $aNewOptions = $_POST; $sKey = $aNewOptions['key']; foreach( $aNewOptions AS $key => $value ) { if( is_array($value) ) { $aNewOptions[$key] = $value; } else if( !in_array( $key, array('amazon_region', 'amazon_bucket', 'amazon_key', 'amazon_secret', 'font-face', 'ad', 'ad_css') ) ) { $aNewOptions[$key] = trim( preg_replace('/[^A-Za-z0-9.:\-_\/]/', '', $value) ); } else { $aNewOptions[$key] = stripslashes($value); } if( (strpos( $key, 'Color' ) !== FALSE )||(strpos( $key, 'canvas' ) !== FALSE)) { $aNewOptions[$key] = '#'.strtolower($aNewOptions[$key]); } } $aNewOptions['key'] = trim($sKey); $aOldOptions = is_array(get_option('fvwpflowplayer')) ? get_option('fvwpflowplayer') : array(); if( isset($aNewOptions['db_duration']) && $aNewOptions['db_duration'] == "true" && ( !isset($aOldOptions['db_duration']) || $aOldOptions['db_duration'] == "false" ) ) { global $FV_Player_Checker; $FV_Player_Checker->queue_add_all(); } if( !isset($aNewOptions['pro']) || !is_array($aNewOptions['pro']) ) { $aNewOptions['pro'] = array(); } if( !isset($aOldOptions['pro']) || !is_array($aOldOptions['pro']) ) { $aOldOptions['pro'] = array(); } $aNewOptions['pro'] = array_merge($aOldOptions['pro'],$aNewOptions['pro']); $aNewOptions = array_merge($aOldOptions,$aNewOptions); $aNewOptions = apply_filters( 'fv_flowplayer_settings_save', $aNewOptions, $aOldOptions ); update_option( 'fvwpflowplayer', $aNewOptions ); $this->conf = $aNewOptions; $this->css_writeout(); return true; } ...As we can see we override plugin options, that is why we can inject XSS payload in fv_flowplayer_admin_default_options():
... function fv_flowplayer_admin_default_options() { global $fv_fp; ?> <table class="form-table2"> ... <tr> <td><label for="width">Default video size [px]:</label></td> <td colspan="2"> <label for="width">W:</label> <input type="text" class="small" name="width" id="width" value="<?php echo trim($fv_fp->conf['width']); ?>" /> <label for="height">H:</label> <input type="text" class="small" name="height" id="height" value="<?php echo trim($fv_fp->conf['height']); ?>" /> </td> </tr> <tr> <td><label for="googleanalytics">Google Analytics ID:</label></td> <td colspan="3"><input type="text" name="googleanalytics" id="googleanalytics" value="<?php echo trim($fv_fp->conf['googleanalytics']); ?>" /></td> </tr> <tr> <td><label for="key">Commercial License Key:</label></td> <td colspan="3"><input type="text" name="key" id="key" value="<?php echo trim($fv_fp->conf['key']); ?>" /></td> </tr> ...
◼ Blind SQL injection in Master Slider 2.5.1
Description: Authenticated users (like editors) can execute arbitrary sql commands (there is no CSRF protection)
Some urls: Sent report | Original project sources | PhpSourcerer output
I'll just show you just the flow without commenting (pay attention to orderby parameter/variables). We start by looking at /master-slider/admin/views/slider-dashboard/list-sliders.php file:
<?php msp_get_panel_header(); // Display sliders list $slider_table_list = new MSP_List_Table(); $slider_table_list->prepare_items(); $slider_table_list->display();MSP_List_Table::prepare_items() :
... function prepare_items() { $columns = $this->get_columns(); $hidden = array(); $sortable = $this->get_sortable_columns(); $this->_column_headers = array( $columns, $hidden, $sortable ); $this->process_bulk_action(); $perpage = (int) apply_filters( 'masterslider_admin_sliders_per_page', 10 ); $current_page = $this->get_pagenum(); $orderby = 'ID'; $order = 'DESC'; $total_items = $this->get_total_count(); $this->items = $this->get_records( $perpage, $current_page, $orderby, $order ); // echo 'MSP_List_Table::get_total_count() :'; print_r( $this->items ); echo ''; // tell the class the total number of items and how many items to show on a page $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $perpage )); } ...
... function get_total_count(){ global $mspdb; $all_items = $this->get_records( 0 ); return count( $all_items ); } ...MSP_List_Table::get_records() :
... function get_records( $perpage = 20, $paged = 1, $orderby = 'ID', $order = 'DESC', $where = "status='published'" ){ global $mspdb; $offset = ( (int)$paged - 1 ) * $perpage; $orderby = isset( $_REQUEST['orderby'] ) ? $_REQUEST['orderby'] : 'ID'; $order = isset( $_REQUEST['order'] ) ? $_REQUEST['order'] : 'ASC'; $search = isset( $_REQUEST['s'] ) ? " AND title LIKE '%%" . $_REQUEST['s'] . "%%'" : ''; return $mspdb->get_sliders( $perpage, $offset, $orderby, $order, $where.$search ); } ...MSP_DB::get_sliders() :
... public function get_sliders( $perpage = 0, $offset = 0, $orderby = 'ID', $sort = 'DESC', $where = "status='published'" ) { // pull mulitple row results from sliders table if( ! $results = $this->get_sliders_list( $perpage, $offset, $orderby, $sort, $where ) ){ return; } // map through some fields and unserialize values if some data fields are serialized foreach ($results as $row_index => $row) { $results[$row_index] = $this->maybe_unserialize_fields($row); } return $results; } ...MSP_DB::get_sliders_list() :
... public function get_sliders_list( $perpage = 0, $offset = 0, $orderby = 'ID', $order = 'DESC', $where = "status='published'" ) { global $wpdb; $args = array( 'perpage' => $perpage, 'offset' => $offset, 'orderby' => $orderby, 'order' => $order, 'where' => $where ); return $this->ms_query( $args ); } ...And finally MSP_DB::ms_query() where actual SQL injection takes place:
... public function ms_query( $args = array() ) { global $wpdb; $default_args = array( 'perpage' => 0, 'offset' => 0, 'orderby' => 'ID', 'order' => 'DESC', 'where' => "status='published'", 'like' => '' ); $args = wp_parse_args( $args, $default_args ); // convert perpage type to number $limit_num = (int) $args['perpage']; // convert offset type to number $offset_num = (int) $args['offset']; // remove limit if limit number is set to 0 $limit = ( 1 > $limit_num ) ? '' : 'LIMIT '. $limit_num; // remove offect if offset number is set to 0 $offset = ( 0 == $offset_num )? '' : 'OFFSET '. $offset_num; // add LIKE if defined $like = empty( $args['like'] ) ? '' : 'LIKE '. $args['like']; $where = empty( $args['where'] ) ? '' : 'WHERE '. $args['where']; // sanitize sort type $order = strtolower( $args['order'] ) === 'desc' ? 'DESC' : 'ASC'; $orderby = $args['orderby']; $sql = " SELECT * FROM {$this->sliders} $where ORDER BY $orderby $order $limit $offset "; return $wpdb->get_results( $sql, ARRAY_A ); } ...
░▒▓█ Bonus: funny mistake in validation
Plugin: Visitor Maps and Who's Online 1.5.8.6
Idea was good, but something went wrong :-)
function view_whos_been_online() { ... $show = (isset($wo_prefs_arr['show'])) ? $wo_prefs_arr['show'] : 'none'; if ( isset($_GET['show']) && in_array($_GET['show'], array('none','all','bots','guests')) ) { $wo_prefs_arr['show'] = $_GET['show']; $show = $_GET['show']; } ... $sort_by = (isset($wo_prefs_arr['sort_by'])) ? $wo_prefs_arr['sort_by'] : 'time'; if ( isset($_GET['sort_by']) && array('who','visits','time','ip','location','url') ) { $wo_prefs_arr['sort_by'] = $_GET['sort_by']; $sort_by = $_GET['sort_by']; } ... $order = (isset($wo_prefs_arr['order'])) ? $wo_prefs_arr['order'] : 'desc'; if ( isset($_GET['order']) && array('desc','asc') ) { // bots $wo_prefs_arr['order'] = $_GET['order']; $order = $_GET['order']; } ...