Backdoor found in OpenX open source version 2.8.10 – some thoughts

Two days ago, Heise (the German news and IT firm) reported that a backdoor was found in the prominent advertising platform OpenX.  (German link here, and I leave translation to the reader.)  The backdoor itself was injected into a somewhat obscure flowplayer javascript file, with obfuscated php hiding the fact that it was a remote code execution bit of php.  With this code there, it would be trivial for a solid hacker to build a shell for the server and completely compromise it.

The question which immediately came up for me was, Huh?  Since when is a webserver interpreting a javascript file as a php file?  I doubted that it is typical for web servers to be configured to have .js files interpreted by the .php interpreter first, then sent to the browser.  However, none of the technology reporting said anything about how the OpenX backdoor was getting the .js file to be executed as .php.

A couple examples of the simple explanation that .js files just obviously might house some .php and invoke it:

But web servers don’t do that.  So there must be something else in the OpenX code which allows javascript to be interpreted as php.  One clue was that the original Heise article said:

Die Datei wird durch einen Aufruf von require_once() aktiviert

which means, basically, that the rogue PHP function in the flowplayer js would be called and activated by a require_once() call.  But where is that call?

The second clue is that there were actually three files involved in the intrusion, not one, and all three are critical to the intrusion.  The somewhat unhelpful post from OpenX says to md5sum the following files:

558c80e601fb996e5f6bbc99a9ee0051  plugins/deliveryLog/vastServeVideoPlayer/flowplayer/3.1.1/flowplayer-3.1.1.min.js
fa4991d5fd3bf4a947b6ab0b15ce10b2  plugins/deliveryLog/vastServeVideoPlayer/
5014c31b479094c0b32221ae1f1473ac  lib/max/Delivery/common.php

The question then is: what is special about the other two files, and why would changing them be related to closing the backdoor? ver 2.8.10 has this line:

MAX_commonReadFile( $pwd . ‘/’ . $file_to_serve);

whereas vers 2.8.9 and 2.8.11 have:

echo file_get_contents( $pwd . ‘/’ . $file_to_serve);

The latter would simply send the javascript without interpreting it.  But… what does MAX_commonReadFile do?

Cue lib/max/Delivery/common.php:

Versions 2.8.9 and 2.8.11 don’t have this function, so it was also injected.  Here is the function in 2.8.10, nestled between MAX_commonConvertEncoding and MAX_commonSendContentTypeHeader:

* A function to read a contents of SWF file
* @param string $file The path to SWF file to read
function MAX_commonReadFile($arg)
echo file_get_contents($arg);


Bingo.  There is the invoker of the backdoor: require_once($arg), which in this case would be to the path.  All this stuff about “SWF file” is obfuscation – the attacker was actually doing a javascript read, and frankly, there never would be a reason to require_once a file if it is a non-php asset like a SWF.

I’m puzzled currently about the [‘debug’][‘read’] item – it seems to be a way of throwing admins who are trying to trace problems with the site and thus who are in debug mode.  Anyone else have an idea?

So, a few final thoughts:

  • Currently we have no idea how the code got in there.  I am looking forward to the article which traces the evolution of the code tree and speculates about the account(s) involved in these three code updates.
  • Javascript doesn’t invoke PHP, but it might be a convenient place to hide invokeable code in another language.  Think about all the other files which might contain backdoor code – an .HTML template?  A README file, or a documentation file in a usually uninteresting directory?  An image which is unused in the site but isn’t deleted, and so no one knows it is not displaying properly?  This case is of a static asset which was turned into backdoor code elsewhere by a sink (require_once).
  • Heise articles suggest that OpenX might be involved in some issues for a long time, including before this event.  This backdoor was discovered only after 7 months.  +1 for the person who finds any more problems in the codebase.



Submit a Comment

Your email address will not be published. Required fields are marked *