Saturday, November 21, 2015

PHP static code analysis vs ~1000 top wordpress plugins = 103 vulnerable plugins found


░▒▓█ 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'];
  }
...