Last week i’ve been searching for security issues on eBay websites. This time I found a controller which was prone to remote-code-execution due to a type-cast issue in combination with complex curly syntax. Since this techniques are less known and less discussed I found it interesting enough to blog about it. The vulnerable subdomain was the same where I found an exploitable SQL injection last year which is located at http://sea.ebay.com . I will introduce you to some cool techniques, so let’s stop wasting time….

A legitimate user request look like that:

legitimate request
1
https://sea.ebay.com/search/?q=david&catidd=1

One of the very first tests I perform against php web applications is to look for type-cast issues because php is known to cause problems when the value of a param is expected to be string but an array was supplied as user-input instead. So obviously my next step was to perform the above request with arrays this time.

array rather than string
1
https://sea.ebay.com/search/?q[]=david&catidd=1

The web application served me the same response as in the prior request which surprised me a bit, but i will come to this point again later.

Now comes the interesting part. From my experience I know that php has several ways to handle strings. For example if the string is enclosed in double-quotes(” instead of ‘), php will interpret more escape sequences for special characters which allows code evaluation if some circumstances are given.

at least one of these requirements
1
2
3
4
5
user-supplied values are stored in double-quotes
eval() function is used on user-supplied values
create_function() is used on user-supplied values
preg_replace() function is used on user-supplied values (with /e modifier)
string comparison (heredoc syntax) is made with user-supplied values

Which of these had eBay in use? Since it’s been a blackbox test I assumed that eBay was using preg_replace() for filtering badwords in combination with eval() afterwords.

Why? Because of 2 observations i made:
1) they were using a spellchecker. (i have seen a bunch of spellchecker in webapps working with eval() function in the past)
2) they are using some filter which I guess to be a blacklist of words that are being replaced with preg_replace() function. For example when I submitted my handle ‘secalert’ it was stripped and as a result it returned ‘sec’ in the response of the search query. So obviously they are cutting words like “alert” from the user-supplied string, maybe in hope to prevent XSS, which is a very bad idea!


But before we can use that code evaluation from attackers perspective we need to get some facts about php internals.
Variable parsing
1
2
"When a string is specified in double quotes, variables are parsed within it."
- from: http://www.php.net/manual/en/language.types.string.php

Sounds easy, right?
Well, if we use php’s complex curly syntax we could possibly have some success.
Never heard of complex syntax?

Complex syntax
1
2
3
4
5
"This is called complex because it allows for the use of complex expressions.
The complex syntax can be recognised by the curly braces surrounding the expression.
Functions, method calls, static call variables and class constants inside {$} 
will be interpreted as the name of the variable in the scope in which the string is defined." 
- from: http://www.php.net/manual/en/language.types.string.php

How can we exploit that? Let’s give it a first try:

first try with complex syntax
1
https://sea.ebay.com/search/?q={${phpinfo()}}&catidd=1

It didn’t work. Okay, seems like they are not using user-supplied values within double-quotes. So what can we do now?


How does php internally handle strings?
internally php strings are byte arrays
1
2
3
the php documentation on strings says:
"internally php strings are byte arrays. As a result accessing or modifying a string using array brackets will trick the parser into evaluating arbitrary php code in the scope of the variable if the prior mentioned requirements are met."
- from: http://www.php.net/manual/en/language.types.string.php

So let’s try to submit an array rather than a string and try to echo the values of the param “q” by accessing the array indices.

array indices
1
https://sea.ebay.com/search/?q[0]=david&q[1]=sec&catidd=1

It works. The search controller parsed that request and I got the last instance as part of the result, in this particular case it returned valid entries which matched to the keyword “sec”.


But why?

As mentioned prior I was assuming that eBay is using preg_replace() for filtering bad words and afterwards doing some eval() stuff with that return values. So what happens here could be that they are trying to enforce that user-supplied values are always form string type. That means if it’s not a string they try to make a string out of it, i.e. they try to cast the values of the array into a string before doing the string-comparison for the list containing bad words.

pseudo-code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// please note that the following lines are meant to be pseudo-code
...
$cat = $_GET['catidd'];
$q = $_GET['q'];
if(is_string($q)) {$q=filter_str($q);}
else {
$orig_value = array( 0 => "$q" );
$str_value = array_shift($orig_value);
$user_supplied = filter_str($str_value);
}
...
function filter_str($string){
  return preg_match("[regex]", "somefilter", $string);
}
...
// assuming some eval() afterwords for spellchecker or similar
// of course they also could have used the /e modifier instead

Okay, good. But how can we exploit that?


We will put all this stuff together and submit an array with 2 indices containing arbitrary values, one of them will be supplied in complex curly syntax to trick the parser.
interesting part - PoC 1
1
https://sea.ebay.com/search/?q[0]=david&q[1]=sec{${phpinfo()}}&catidd=1

Yes, ladies and gentlemen! Success!

Now let’s verify this by submitting two more requests.

interesting part - PoC 2 & PoC 3
1
2
https://sea.ebay.com/search/?q[0]=david&q[1]=sec{${phpcredits()}}&catidd=1
https://sea.ebay.com/search/?q[0]=david&q[1]=sec{${ini_get_all()}}&catidd=1

Verified! We can evaluate arbitrary php code in context of the ebay website.
From my point of view that was enough to prove the existence of this vulnerabilty to ebay security team and I don’t wanted to cause any harm. What could an evil hacker have done? He could for example investigate further and also try things like {${`ls -al`}} or other OS commands and would have managed to compromise the whole webserver.

How could you avoid such attacks within your own code? See some recommendations from my side in the following box.

mitigation recommendation
1
2
3
don't use eval() function or even create_function() with user-supplied values
use preg_match_callback() instead of preg_match() function with /e modifier
do not allow escape sequences within user-supplied values

Conclusion: don’t forget to read the manuals and language references :P

references
1
2
http://www.php.net/manual/en/language.types.string.php
by @i0n1c: http://www.suspekt.org/downloads/DPC_PHP_Security_Crash_Course_06_IncludeAndEval.pdf
timeline
1
2
3
4
December,  6th 2013: vulnerability discovered and reported to eBay (securityresearch@eBay.com)
December,  6th 2013: recorded a screencast as proof-of-concept
December,  9th 2013: eBay solved the issue and deployed it on production server
December, 13th 2013: I have published this write-up