Aug
14
2011

Hacked: CSS & JS Compression on GoDaddy

Let me welcome you to the first post on our new blog.


Here is “The Situation”:

You have a website hosted on a shared hosting environment like GoDaddy or any other Apache environment that doesn’t support mod_gzip and mod_deflate and you wanted to serve those big CSS and JS resources as compressed, that way your users get a faster and more efficient experience.

The following is a very popular method of transparently compressing and sending requested CSS and JS resources. Because this method is transparent and very easy to implement it has become a popular method among WordPress and Joomla users. But heed this WARNING. Using this method comes at a price. The way it is implemented is very insecure and leaves a large open hole in your website. If you really want to use this method, add some additional logic in order to make sure that you are not at risk.


What This Article Will Explain:

First we will explain the solution that we are talking about, then we will explain how it can be exploited and finally we will give examples how it can be fixed if you still choose to use this method.

 

The Common Implementation:

The most common implementation is to create 2 PHP files, one specifically for CSS file requests and one for JS requests. Both PHP files will accept a GET parameter that is supposed to contain the requested resource file location. The PHP file will create a buffer that will append headers for cache and expiration info, read the requested file to the buffer and then flush everything to the browser using GZIP for compression.

The 2 PHP files are generally created and saved on the root of the website.

In order to redirect all requests for CSS and JS files, a rewrite rule is added to the htaccess file that will redirect all requested files with a js or css file extension to their appropriate PHP files.

 

The Code:

The file “compress-css.php” is created and saved on the root:

 PHP |  copy code |? 
1
2
<?php
3
ob_start("ob_gzhandler");
4
header("content-type: text/css; charset: UTF-8");
5
header ("expires: " . gmdate ("D, d M Y H:i:s", time() + 604800) . " GMT");
6
header("Cache-Control: max-age=302400, public, must-revalidate", true);
7
echo file_get_contents($_GET['file']);
8
?>
9

The file “compress-js.php” is created and saved on the root:

 PHP |  copy code |? 
1
2
<?php
3
ob_start(ob_gzhandler);
4
header(“content-type: text/javascript; charset: UTF-8);
5
header (“expires:. gmdate (“D, d M Y H:i:s”, time() + 604800) . ” GMT”);
6
header(“Cache-Control: max-age=302400, private, must-revalidate”, true);
7
echo file_get_contents($_GET['file']);
8
?>
9

Add the following to the .htaccess file on the root:

 Apache configuration |  copy code |? 
1
<ifModule mod_rewrite.c>
2
RewriteEngine on
3
RewriteRule ^(.*\.(css))$ compress-css.php?file=$1
4
RewriteRule ^(.*\.(js))$ compress-js.php?file=$1
5
</ifModule>

 

The exploit:

Any programmer should be able to immediately see where the exploit exists in this solution.

If I know that a site is using this solution, I could easily navigate to the URL www.domain.com/compress-css.php?file=.htaccess and read the contents of the htaccess file. Or if it were a WordPress blog I could navigate to www.domain.com/compress-css.php?file=wp-config.php and read all the database and configuration settings.

This is because the solution assumes that no one is aware of the compress-***.php files, so they assume no one will never request them directly. It also assumes that all redirects to those files will be trusted redirects.

This violates the most basic concepts of development, never trust data that is accessible by the end-user.

You think the odds are in your favor? I went out to see how common this method is. I targeted WordPress blogs because the original solution was written for them in mind when their blog is hosted using shared hosting. 

I didn’t have to look far. The second blog I looked up had this solution implemented. I have no idea who the blog author is. This is a taste of the blog’s front page (edited for privacy).

 

Now let’s see if we can view the .htaccess file:

 

 Nice! Not much going on here though. 

 

Let’s get some useful information:

You can see how this is dangerous. Not only do I have the credentials to access the WordPress MySQL database, I also wouldn’t doubt it if the password used is also used in other places by this blog author. GoDadddy account? Email? Bank? If someone wanted to be malicious, this definitely puts them on the right path.

 

The fix:

Besides simply not using it, the way to make this solution secure is to add some validation in the php files.

We can start by verifying that the requested file actually exists. This will make sure that an error is not thrown to the end-user in case a file is requested that does not exist. Note: It’s always good practice to prevent or trap errors. Allowing the end-user to see system produced error messages gives them a window in how your application/website works.

Finally, we check the extension of the file requested. If it matches the type of file that we want to process, we perform our logic and send the file back. If not, we respond with a 404 error.

The following is how we implement this in code: Note: The example is for the “compress-css.php” file, the logic would just have to be modified to “JS” to change the “compress-js.php” file

 PHP |  copy code |? 
01
<?php
02
$file = $_GET['file']; 
03
 
04
if (file_exists($file) && strtoupper(pathinfo($file,PATHINFO_EXTENSION))=="CSS"){
05
    ob_start("ob_gzhandler");
06
    header("content-type: text/css; charset: UTF-8");
07
    header ("expires: " . gmdate ("D, d M Y H:i:s", time() + 604800) . " GMT");
08
    header("Cache-Control: max-age=604800, public, must-revalidate", true);
09
    header("Last-Modified: ". gmdate("D, d M Y H:i:s", filemtime($file))." GMT"); 
10
 
11
    echo file_get_contents($file);
12
}
13
else
14
    header('HTTP/1.1 404 Not Found');
15
?>

Related Posts

About the Author: Jared Miller

Leave a comment