
░▒▓█ 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 ''; 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
));
}
...
MSP_List_Table::get_total_count() :
...
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'];
}
...
Another "sprint code review" resulted in many vulnerabilities in *.php.net sites.