Concept

This may affect multiple language implementations of query structures. The idea is pretty simple. Typically an application will use find and replace to insert sanitized data into an SQL query by its type. Imagine the following scenario: Somewhere the values for :email and :password have to be translated to their proper values. If the database engine is not actual PDO and simply PDO-like, we may have exploitable software on our hands. Suppose these values are completely sanitized and wrapped in quotes afterwards by the query factory when they are translated.

A quick perl script to illustrate:

You can run this and see the queries it generates by typing something like:

Here is an example:

But what happens if we supply a field name that is yet to be processed? Here we go:

Notice in the above that the quotes in the command line were just there to get the spaces, they didn’t actually end up in the value of password. The quotes were added automatically by the program! Let’s look at a more practical or realistic example.

CVE-2014-3978: TomatoCart (Currently In the Wild)

Let’s walk through the confirmation of the presence of the vulnerability and then the search for an implementation that we could easily use. First, download and install tomatocart with its default settings and run:

You should see a line immediately that looks like:

In this case, we probably can’t exploit it, because :table_products_description is probably bound first and also a static value. We’ll need to confirm that the query engine is vulnerable as well, so lets see how variables are bound. We go to the file we just saw: We can determine that first it makes the query, then it uses bindTable() to bind something that has been define()‘d. After this, it trusts bindValue() to properly sanitize the $_GET variable. Additionally, there are no quotes wrapped around the :field in the database parameter, meaning the software probably inserts them on its own.

Let’s check this out further. Lets go look up the “bindValue” function:

So lets check out database.php line 404:

That’s interesting. It just calls bindValueMixed() and says the variable is a string. Let’s see what that function does. Our last grep just told us we’re looking for line 369 of the same file: It uses some kind of parseString() to sanitize along with trim(), then it wraps the value in quotes and runs bindReplace($place_holder,$value), kind of like our perl script. Let’s see what bindReplace() does (if you’re following along in the code, it’s already in front of you right under the lines we just mentioned): The first thing it does is grab the position of the placeholder in the string. Immediately after this, it checks to see what the character right after the placeholder is. It wants it to be a comma, a space, or a close-parenthesis, or not exist at all. Well, that’s no problem, because we’ll be able to comment that part out anyway. Read on! To find an exploitable implementation of bindValue() I ran the following search. It’ll bring a lot up, so be sure to use more or less as appropriate: And lo and behold, there’s one with bindValue() being called line after line sequentially; there are many, many more! Let’s check this file out to make a quick PoC. Woah! Thats a huge insert query there in address_book.php! Lets throw it into our perl with some line breaks for sanity! First let’s try a sleep query. This is just something to see if it works for sure or not. I tried putting :entry_lastname, into the first name on the address book, and then ,sleep(10),1,3,4,5,6,7,8,9,10)# into the last name field. Notice in the above that this ends up commenting out the comma we put after :entry_lastname anyway, “”#’,””. When I put this in, it actually made the page sleep for ten seconds. So, after I was “done” testing to see if sleep worked, I jumped into the mysql shell to see how the db was organized. Sure enough, there was an admins table: I noticed some of the numbers from my first query ended up displaying in the address book, so this gave me the idea to use two fields. Due to the size limitation on the last name field, I had to find another field to extract the password hash into. My final query in the last name field was: I left the first_name field as “:entry_lastname,” — leaving the comma to satisfy “bindReplace()”. Sure enough, I had my local admin password hash from when I installed the cart! It is important to note that this vulnerability is systemic. That is to say, any query in the application that receives multiple string inputs is vulnerable to this type of attack. It is not limited exclusively to the first and last name fields during contact creation; this was simply the easiest thing to PoC.

CVE-2014-5140: CRELoaded aka Loaded7

After investigating the code in Tomatocart, I was able to determine that the vulnerable database code in question (the bindReplace() function) actually came from an unstable version of OSCommerce’s dev tree. Using this information I was able to find that Loaded7 was also forked from the same version of OsCommerce. This development version of OSCommerce has been updated to use real PDO and thusly is not vulnerable to this attack. For some reason, the database library itself was taken from a snapshot in Loaded and Tomatocart, but not brought in from a repository. The library does not automatically update when the OsCommerce developers update it on their end (improper use of revision control). When we discovered that Loaded7 was in fact vulnerable to the same technique, we did a bit more digging. Sure enough, the same exploit works if you just change the table prefix like so, as the query was basically the exact same: In this particular instance, your contacts/address book never appears until you go to a checkout screen. You do NOT have to add a payment type. When you get to the checkout screen, the shipping address will contain your injection data. The loaded7 developers were notified on July 29th. They confirmed exploitability. We sent them a direct link to the patch for tomatocart on github, which both vendors as of the time of this writing appear to have ignored.

Taking it further

Non-standard table prefixes

In this instance, you will notice that successful exploitation to get an administrative credential requires that the table-prefix is default. Suppose it isn’t default, what do we do then? The answer is simple: we do an injection into the last name field that grabs a table name.

Now that a table name is obtained, doctoring the exploit up isn’t very difficult.

Weaponizing

Believe it or not, simply extracting an admin password hash isn’t very weaponized. A more serious weapon would involve something that was capable of extracting an administrative session ID or password reset token. Because we are a security company, we aren’t going to release this type of exploit into the wild, but simply inform the reader for educational purposes that such a thing is possible.

Patching

Let’s take another look at the bindreplace function: Looking carefully, this can be easily patched by html-encoding the colon character in the $value variable, like so:

Conclusion

To recap, we have several points of failure in the developers’ judgement when writing these applications. First, the failure in the OSCommerce development tree when trying to basically rewrite PDO themselves. All of this can be prevented by using the PDO libraries already written for PHP (or implementing our patchsets on github), which will properly sanitize input and prevent the attacker from overwriting variables in the address book form. The OSC developers have re-implemented this using PDO and still have not marked this tree as ‘stable’, so it’s hard to blame much of this on them, all things considered. Unfortunately, the fact that the code was developed to begin with leads us directly into the second point of failure: The second point of failure was in the decision made by the developers for both TomatoCart and LoadedCommerce to use another systems unstable code in their stable tree and discontinue the code’s development. The third point of failure is not using proper revision control. If the developers had simply implemented proper revision control, this vulnerability would have been fixed in a much more timely manner without needing independent analysis– as the OSCommerce code updates would have been implemented as soon as the OSCommerce code was modified and committed. In e-commerce applications specifically, these are more critical issues, as they can put entire customer databases (with payment information in many cases) at risk. When writing your own software, remember the lessons we learned from this experience:

Don’t try to reinvent the wheel if it is already round (PDO– The wheel is round!) When borrowing unstable (development mode) code, it is probably a bad idea to place it into your stable tree and discontinue its development. When borrowing code, use proper revision control so that borrowed code will be updated as the original authors update it unless you actually intend to modify it as part of your project.

It is always a good idea (but also many times impractical) to inspect the internals of any third-party libraries you use before trusting their external APIs and input handling. This applies any time you develop software using a library that you didn’t write. Failure to do so leads to many vulnerabilities going undetected for a very long time (including the Heartbleed bug.) The mantra of “it’s so popular a thousand eyes must have already looked at it” is exactly the reason no one has actually looked at many of the applications and libraries that the general community believes to be secure. If it were not so, Heartbleed would have very likely been discovered much, much sooner. And a final note- advisories inherently mandate being on time. Both vendors in this instance have had ample time to implement a 5 minute fix and have thus far not done so. In e-commerce applications, this is nearly unbelievable. When responsible for consumer data, one would think that new feature additions should be halted in favor of securing existing features the moment a serious vulnerability report is received. Please develop responsibly.

CVE-2014-3978 CVE-2014-5140 Originally Vulnerable OSCommerce Code PDO for php TomatoCart Pull Request (Patchset) Loaded7 Pull Request (Patchset) Breaking Technology’s Vulnerability Disclosure Policy