Rob Vinson

Drupal SQLi CVE-2014-3704

Sure the SSLv3 CBC padding oracle attack dubbed POODLE is a big deal, any attacks that break SSL are for sure a big deal. However this attack as described requires MiTM. As such its probably not going to cause the instantaneous doom that the media is trumpeting. In fact it seems to be overshadowing another more tactile threat. Specifically the unauthenticated SQLi vulnerability in Drupal core that was announced yesterday (CVE-2014-3704 | SA-CORE-2014-005). Drupal is a pretty commonly deployed CMS, the amount of folks this could impact is probably pretty high. So naturally last night I started to look at the changes introduced to Drupal to patch this issue, and to figure out how it will be exploited. Though when coming back to it this evening I see an exploit has been posted on packetstorm. Early bird gets the worm and all that jazz… Anyway, since I took some time to investigate this vulnerability last night I may as well blog some details.

funky foreach

The commit just makes a slight change to the expandArguments() function. The comments for this function indicate that it expands out array arguments into a SQL query. The fix makes sure that a php foreach statement only iterates over the values of the array by using the array_values() function. That seems a bit odd until you realize that foreach has two forms. One iterates over values, and one that iterates over key –> values.

Boring iteration over values
1
2
3
4
5
// form one
$arr = array("one", "two", "three");
foreach ($arr as $value) {
      echo "Value: $value\n";
}

The above code will simply assign each array element in $arr in turn to the variable $value then print it out. You can try it yourself at [http://writecodeonline.com/php/] if you’d like. Nothing groundbreaking here… The next form is a bit more interesting.

Slightly less boring iteration
1
2
3
4
5
6
7
8
$arr = array("one", "two", "three");
foreach ($arr as $key => $value) {
      echo "Key: $key => Value: $value\n";
}
// Output: 
// Key: 0 => Value: one
// Key: 1 => Value: two 
// Key: 2 => Value: three 

Ok, so the above foreach will assign the array element’s index to $key and the value to $value, fair enough. This is the sort of array that the vulnerable expandArguments() function was expecting. Though as it turns out PHP arrays are really maps. So these “arrays” can have key > value elements like you would expect from a hash/dict/map in some other languge. This leads us to the final foreach example..

Last foreach example I promise
1
2
3
4
5
6
7
8
$arr = array("a" => "one", "b" => "two", "c" => "three");
foreach ($arr as $key => $value) {
      echo "Key: $key => Value: $value\n";
}
// Output: 
// Key: a => Value: one
// Key: b => Value: two 
// Key: c => Value: three 

Ah ha! So that’s why the fixed foreach statment added in array_values(). They want to make sure someone hasn’t slipped in a key value pair. This becomes more evident when you look through the rest of expandArguments() and see that the key actually makes its way into a query.

expandArguments snippet
1
2
3
4
5
6
7
// ...snip...
foreach ($data as $i => $value) {
// ...snip...
  $new_keys[$key . '_' . $i] = $value;
}
// ...snip...
$query = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $query);

So, now we know what we need to aim for, getting an array with a key value pair to this function, and putting our SQLi payload into the key.

Finding the path to vulnerable code

We know to exploit this we need to get an array we can control to expandArguments(). To investigate help this I:

  • installed Drupal 7.31 on a VM.
  • turned on MySQL query logging by adding the line “log = /var/log/mysql/all.log” to /etc/mysql/my.cnf.
  • instrumented the expandAruments() function by using fopen() to open a log file and fwrite() to write out the key/values being handled by the function.

After browsing through the Drupal code a bit to figure out how a HTTP GET/POST request could result in an appropriately formed array making its way to expandArguments() I found the function drupal_http_build_query. Reading this code and running a few test arrays through the function via [http://writecodeonline.com/php/] was sufficient to get a feel for the function.

arrays in URLs
1
2
3
4
5
6
7
8
9
10
11
12
$a = array("a", "b", array("x","y","z"));
foreach($a as $k => $v) {
  echo "$k => $v\n";
};
echo "encoded array: "
echo drupal_http_build_query($a);

// Output:
// 0 => a
// 1 => b
// 2 => Array
// encoded array: 0=a&1=b&2[0]=x&2[1]=y&2[2]=z

From the example above We now know that you can set a key in an array by using brackets in a Drupal URL. Using burp to capture a login attempt (it doesn’t matter that username and password are wrong) and then using burp repeater to edit the login request and replay it, forcing an error that showed SQLi didn’t take long. One could probably construct a request file to be used with sqlmap and go to town now.

Alas, since someone just released an exploit I’ve lost interest…