Details on the Privilege Escalation Vulnerability in Joomla

Yesterday, Joomla! 3.6.4 was released, patching a critical privilege escalation and arbitrary account creation vulnerability.

As we’ve seen some exploits attempts occurring in the wild, we feel it is a good time to describe what the issue is and how it was fixed.

Analyzing the Patch

It was fairly easy to figure out where the vulnerable code was, as pretty much all the patch does (with the exception of fixing an additional two factor authentication bug) is basically remove the register method from the UsersControllerUser class. So that’s where our investigation started.

Joomla register method removed in privilege escalation vulnerability code snippet
We removed some original code for improved readability

All in all, what this method does is it takes user input from the user POST parameter (which is intended to be an associative array) and validates whether specific parameters are properly formatted (email address, username, etc.). If it’s all good, it pushes the array to the register method from the UsersModelRegistration class.

Step #1 – Creating an Account

At this point, one question popped in our mind: “Why would they remove that method? Won’t that break sites that allows users to register an account?”

We quickly discovered that Joomla uses another controller class for registering users, UsersControllerRegistration, which also has a register method:

Joomla privilege escalation vulnerability snippet register method lacks checks

While the two look very similar, they present two very important differences:

  1. The script first checks if user registrations are enabled, something the other register method didn’t do. As the two methods are publicly accessible, it allows users to create accounts even if the option supposed to restrict this possibility is disabled.
  2. This method uses what returns from UsersModelRegistration‘s validate method to register the user. The former register method barely used this to validate if the data was successfully processed.

This very subtle difference is what caused the privilege escalation vulnerability to be possible.

Step #2 – Getting Higher Privileges

At this point, you might ask yourself why this seemingly innocent difference is causing such mayhem. The answer is pretty simple to understand when we take a look at what the validate method does internally.

Joomla privilege escalation vulnerability passes user input

Essentially, it passes the user input $data into the $form filter method, which has pretty much the same effect as PHP’s array_intersect_key function: it only keeps the parameters located in $data if they are also in the $form variable. This way, they can limit what’s being sent to their model classes. After processing the array, it checks if all of the values are properly formatted and then returns the modified $data array.

See where we are going with this? If you read step #1 carefully, you’ll remember that the vulnerable register method didn’t use validate‘s return to register the user.

What this means is that any additional parameters we send will go through the model’s user registration code unsanitized.

Joomla privilege escalation vulnerability unsanitized code passed
Joomla privilege escalation vulnerability unsanitized code passed

When looking at $model’s register method (located in the UsersModelRegistration class), we see that the data we send gets submitted to JUser‘s bind method – before saving the $user instance to the database.

The bind method basically takes every $key => $value pairs from our $data array and sets the corresponding $user instance property $key with its $value. This means that an attacker can override any properties present in the JUser class- which will be saved in the database not long after that as a new user.

So what property would we want to override? Good candidates would be any these (or a combination of them):

  • the $groups property, which contains an array with the numeric ID of the groups the user belongs to (such groups are managers, authors, and administrators)
  • the $id property, which contains the ID of the user we want to save (remember, we’re dealing with the JUser class here – which doesn’t know if we’re registering or updating a user)

From Arbitrary Account Creation to Remote Code Execution

Using all of these factors combined, we managed to craft an internal exploit to test our WAF that not only allows us to create an account even when account registration is disabled, but also changes the administrator’s username, password, and email address!

As administrators can install extension packages on their site, an attacker could use his freshly hacked administrator account to upload a remote shell on the site and further compromise the server.

Update As Soon As Possible!

To keep your site safe, you should update it as soon as possible. We’re seeing exploitation attempts in the wild so it’s only a matter of time until an exploit becomes public.

As we mentioned in our initial post, websites behind the Sucuri Firewall are automatically protected through our virtual patching technology. If you believe your website has already been compromised, you can follow our new DIY guide to fix your hacked Joomla site.

    1. Yes, that’s why we shared. Since the cat is out of the bag already, we might as well help the defenders understand how it work to get it patched/blocked.

      1. Your post is truly informative on the flaw itself. Maybe the key to success(in prevention) is more about “how to get to the user.register()” function, rather than “how the exploit works”.

        I understand that you don’t want to share this(at this point) , but I am grateful (and maybe all of us) for what you have already shared – good job !

  1. – first exploit attemps i saw came from a server in italy about 1 AM yesterday, but just checking for vulnerability
    – @ 3PM other servers joined (russia/china/ukraine)

    1. You’re right! Not sure why I linked array_filter in there, anyway. Thanks for pointing that out!

  2. Thank you for your post! I’m trying to reproduce your internal exploit using the modification of a Super User account, in order to install an extension package. I can change the account’s password and email, but it then requires activation. When I receive the email to active the account, the activation link failed with: Registration failed: Failed to save activation data: User not Super Administrator.
    How did you manage to bypass this, knowing we can’t replace “block” and “activation” variable in database? Thank you!

Comments are closed.

You May Also Like