PHP, variable variables, oh my!
By Romain Tuesday, September 20 2011 - 18:50 UTC - Vulnerabilities - Permalink
By Romain Tuesday, September 20 2011 - 18:50 UTC - Vulnerabilities - Permalink
I was just looking at some PHP code for one of our clients, and found a case I haven't seen many times before. I thought I should share it here.
The code I was looking at looks like this:
<?php
// Init the PHP array with some SQL code to start the query
$declareSQLArray = InitializedArray('stuff');
// Use a strong enough validation routine for do the input
// validation of POST variables
while(list($name, $value) = each($_POST)) {
if(!is_array($value))
$$name = StrongValidation($value);
else
$$name = $value;
}
// Do something with my variables and always do a proper
// validation when I use the data
// Eventually, build my SQL command, and send this to the DB
$sql_command = join(' ', $declareSQLArray);
mysql_query($sql_command);
?>
The code, even if horribly constructed, does not seem to show important
weaknesses, but the usual case of submitting a POST variable as an array, and
bypassing the StrongValidation. Then, in that case, it would have
failed every other validation routines in the code.
Even if experienced with PHP, you might not have encountered variable variables before. In short, this allows to dynamically declare named variables. Here is a simple example:
hubert:~ Romain$ php -r '$name="foo"; $$name="Hello World!\n"; echo $foo;' Hello World!
Here, the variable $foo gets declared, and assigned using PHP's
variable variables capabilities.
Getting back to our code example, I'm sure the reader will spot the issue,
and what an attacker can do to exploit such scenario to trigger, in that case,
a SQL injection. Since the variable $declareSQLArray is defined
and initialized before the POST variables lookup, it is possible to reassign it
using the variable variables. In that case, no validation is performed when we
submit an array, and this is exactly what we want to do!
To exploit the SQL injection, you only need to submit POST variables to
overwrite the $declareSQLArray, and add the content that we want
in it!
POST /code_example.php HTTP/1.1 Host: example.com ... declareSQLArray%5B%5D=SELECT...;&declareSQLArray%5B%5D=--&whatever...
Job done! The resulting SQL query will start with the payload that was
submitted as part of $declareSQLArray. You've got your SQL injection.
Update: While driving back home, I was wondering if I could overwrite values from the SESSION using this technique. A couple of lines of code, and POST request after the answer is short: YES.
Imagine that you have an isadmin variable as part of the
session (which is an associative array). This variable would be set in a code
like this:
if ($user->isNotAdmin())
$_SESSION['isadmin'] = 0;
else
$_SESSION['isadmin'] = 1;
Exploiting the previous weakness of the code example, we are able to
overwrite the $_SESSION['isadmin'] content, only by supplying what
will be interpreted as an associative array by PHP:
POST /code_example.php HTTP/1.1 Host: example.com ... _SESSION%5Bisadmin%5D=1&whatever...
I'm sure you're thinking, as I do, that this is getting more interesting!
Anyways, this issue is not new at all, it is known as Dynamic Variable Evaluation (thanks to Steve Christey).
The interesting part of it is that DAST won't be able to detect it (or maybe
if you are lucky enough), and it is very hard for a SAST to deal with it
(actually, I doubt any SAST vendor who supports PHP handles this case, but it's
not impossible since they have all they need to solve the problem).
Update 2: Based on the comments, I did some testing and observed that even if we can overwrite data from the session, this data does not get persisted in the session. This means that you can still control a value from a super global for the remaining execution of the script, but cannot persist the data.
Comments
All that besides the quite ugly DOS. Egads - I've always hated PHP but this is off the charts nauseating.
You're right the code example shows a vulnerability, but what you're not right about is that it's not immediately visible to anyone experienced with PHP. Additionally you're incorrect about PHP interpreting arrays in variable variables as in your $_SESSION example. Try to make it work. You won't be able to.
Of course, you can always fail if you demand to, say $_SESSION[$_POST['something']] but that's blatantly obviously bad.
Wouldn't exploiting the first example require very intimate knowledge of the executed code? First you'd need to know variable variables are being used, and then somehow you'd have to know to overwrite $declareSQLArray.
Once I encountered a similar bug on a PHP application. The way I exploited it though was different.
The location of the variable overwriting code was at the top of the file and only few variables could get overwritten. Among them was mysql_host and mysql_user/pass.
The rest of the code didn't have any authentication and all the other variables were properly initialized so one couldn't exploit an SQL injection or change the code flow to get admin access. I spend a couple days trying to figure out how to exploit this vulnerability...
I did an nmap and found that the server had the mysql server bound on 0.0.0.0 and without any host restrictions. So I overwrote the mysql_host variable and made it connect to my computer. On my computer I was running mysql proxy and had it setup to connect to the remote server.
Tada! I had complete MySQL access... without knowing the database credentials (the PHP client did the authentication for me). I wrote a few LUA scripts (this is what MySQL proxy uses) and was able to access the whole DB.
These vulnerabilities can be very tricky to exploit some times and are hard to detect with SAST. During a manual code review though one could do a grep for '$$' to find any dynamic variable assignments.
Overall great post and a great idea for some PHP challenges :)
So, you are saying that php.net is bullshitting?
http://php.net/manual/en/language.v...
More exactly:
Warning
Please note that variable variables cannot be used with PHP's Superglobal arrays within functions or class methods. The variable $this is also a special variable that cannot be referenced dynamically.
Superglobals:
http://www.php.net/manual/en/langua...
If approching as a black box - not so vulnerable... But still, variable variables never should have been born :)
@Tokya Kite, @Trikisatan,
Interesting, I just read this part actually that it was not supposed to overwrite the session. However, my quick testing shows this is actually possible to change the values inside the session, those do not get persisted though. That means that the changes are only available for the remaining of the script.
Here is the script (rw.php) I use:
When you use something like:
http://example.com/rw.php?_SESSION[admin]=1
The trace will show:
Before: Admin? - 0
After: Admin? - 1
However, the value isadmin=1 is not persisted in the session itself. It's only dynamically set during the context of the running script.
Thanks Romain, your reply was very useful as I needed a proper code example to fully understand how this applies.
Hi, Romain.
You know what, wow.
You're right. This has to be a bug. Writing to the SESSION should persist, or not work at all. Can you please file this code example with the details on bugs.php.net?
Then please give us here the bug ID (or link) so we can vote it up.
Correction: you set the session var to 0, overwriting it in the session (if set), so it doesn't persist. I wasn't careful. But arrays shouldn't work in var vars, so I believe a bug should be filed so we can see what comes out of it.
Thanks.
To me, the main problem is not the ability to write arrays in a var vars. The PHP docs claim that super globals cannot be overwritten (which is eventually true), but we see that we can temporarily modify them.
The problem is mostly about the access to the super globals through var vars. I'll file a bug report soon.
Thanks.