Tuesday, February 20, 2007

Gotcha!: A PHP Oddity (with contrived security implications)

A while ago, I was look over some code a friend of mine (who doesn't write much PHP) had written, which 'worked', but really shouldn't have.

It looked something like this:

if (strpos ($string, "needle") == "needle") {
    print "Is Valid";
}


And if you know PHP, you'll know that strpos always returns an integer or Boolean false (i.e. it should never return a string), so how the hell could this work?

Well, skipping my usual anecdote; it turns out that PHP casts string to integers when doing comparisons between strings and integers (not the other way around as I would have expected), and so "needle" got cast to an integer, and it was equal to 0. (And since strpos was returning 0 the above code worked).

<Edit> (21/02/07): I realise that there should never be a double dollar vulnerability anywhere in your code, but mistakes are made, this is just what I thought was a curiosity which would interest people; clearly I was wrong. Also, while this uses a double dollar vuln, it is the only way I could come up with to get a string to be compared to an integer (rather than a decimal string) you control.
</Edit>

Now, for the very contrived security issue:

<?php

$authenticated = 0;
session_start();

if (isset($_SESSION['password'])) {
    if ($_SESSION['password'] == "password removed from backup") {
        $authenticated = 1;
    }
} elseif (isset ($_GET['password'])) {
    if ($$_GET['password'] == "password removed from backup") {
        $authenticated = 1;
    }
}

if ($authenticated == 1) {
    print "You Win!";
} else {
    print "You Fail!";
}

?>


You'll see the code above has a double dollar vulnerability; which would be unexploitable in this scenario because the password string is not stored in a variable; but rather is hard coded. But since; at this point, the variable $authenticated is equal to zero, we can have $_GET['password'] equal to "authenticated", and $$_GET['password'] is equal to zero, and the comparison works.

Note: The double dollar vuln is needed rather than just passing 0 to a normal comparison because all variables sent through http are strings.

Note2: It doesn't matter in what order the arguments are typed in the comparison, i.e. the following would also be vulnerable:
if ("password removed from backup" == $$_GET['password']) {

10 comments:

John said...

Explain $$ a little please?

Anonymous said...

Using a $$ make no sense in that context. Only mad people will do that.
Sorry.

to john:

$foo='bar';
$myvar='foo';
echo $$myvar;

output:
bar


ok?

Anonymous said...

Well it's not question of vulnerability here, but only human stupidity

luigi said...

"f you know PHP, you'll know that strpos always returns an integer"

that false :
http://php.net/strpos

int strpos ( string $haystack, mixed $needle [, int $offset] )

Returns the numeric position of the first occurrence of needle in the haystack string. Unlike the strrpos() before PHP 5, this function can take a full string as the needle parameter and the entire string will be used.

If needle is not found, strpos() will return boolean FALSE.

kuza55 said...

@anonymous #1

Yes, using $$ makes no sense, its a double dollar vulnerability in itself, my point was merely that, barring what I mentioned in the post, there is no way (that I know of) to exploit that fact for any gain.

@anynymous #2

Ok, I realise that this should never exist because its a mistake, but mistakes happen, and I just thought this would be an interesting snippet.

@wluigi

Sorry, you're completely correct, as you can see, typos and mindblanks happen. *shrug* I'll fix it up now.

kuza55 said...

@john:

If you want a more detailed explanation than anonymous #1 gave of the $$ issue, you can read PHP's section on variable variables: http://au.php.net/language.variables.variable

luigi said...

It should be writed this way:

if (strpos ($string, "needle") === "needle") {
print "Is Valid";
}

And so it would print "Is Valid"

kuza55 said...

@wluigi

Try running that code you posted. It doesn't work (or at least not on my install).

Try it:

if (strpos ("needle", "needle") === "needle") {
print "Is Valid";
} else {
print "This doesn't actually work.";
}

It won't work because strpos returns an integer, boolean false or 'a non-Boolean value which evaluates to FALSE, such as 0 or ""' (as the manual states).

P.S. If it is working, there is no reason (according to the documentation, or my tests) that it should work, so if you could tell me what version of PHP you're running I'd like to have a look and find out why it is.

Doctorrock said...

Don't forget :
If you compare an integer with a string, the string is converted to a number. If you compare two numerical strings, they are compared as integers.
http://www.php.net/manual/en/language.operators.comparison.php

Anonymous said...

$string = "needle"
if (strpos ($string, "needle") === 0) {
print "Is Valid";
} else {
print "Not Valid";
}

This would print "Is Valid" because the string is found at the 0 position (first letter) however it it was it $string was equal to "blahblah" if would print "Not Valid". Keep in mind, if you change === to ==, then both would appear valid.