This week saw the inaugural Click Frenzy sale generate a huge amount of interest in Australian eCommerce, and we are proud to have been the developer responsible for delivering the website for the event based on a Magento platform.
The site stood up well after a challenging start on Tuesday evening, but there was a configuration issue with the webserver environment at one point which left a number of private application files exposed for some time. During this period the Magento directory was inadvertently left with directory listings on and without private directories and their contents protected from access. These private files being accessible did not result in a security breach of any kind, and there was never any sensitive personal data stored on any of the website's servers, but the cause of the disclosure does highlight a risk of Magento’s design. Currently, the entire application is designed to be located in the webserver’s document root (docroot). To address this risk and eliminate the possibility of a misconfiguration having this effect, the Magento application needs to be restructured to make it possible to relocate most parts outside of the docroot. This is an approach we’ll be adopting in future and which we document in this post. We also include an accompanying patch that we are releasing to the Magento community.
So, what happened? The issue stemmed from a fundamental aspect of Magento’s design, where the entire application is intended to be located within the webserver’s document root. Part of Magento’s success and broad installed base can be attributed to the wide availability of LAMP hosting services, and this design does allow for an easier installation on shared hosts and environments where custom configuration is not possible - just unpack the files and get started with the web-based installer. It ships with a number of Apache .htaccess files included which are designed to disable access to sensitive directories. However, should the Apache webserver be misconfigured in such a way that directory listings are enabled and/or the files intended to disable access are ignored (Options Indexes
and AllowOverride None
), the contents of the entire application are then at risk of being disclosed. It is worth noting that this design is the current state of both of the latest official Magento Community Edition and Enterprise Edition releases (version 1.7.0.2 and 1.12.0.2 respectively). Magento 2 versions published on github so far don’t appear to indicate this will be changed significantly for the next major release.
An alternative design adopted by many other popular web applications and frameworks is to separate as much of the private application files from the public content as possible. The private files are moved to a location outside the docroot. Such a design protects against any server misconfiguration since it doesn't rely upon placing blocks on certain paths - the private parts of the application exist in a location from which the webserver won’t directly serve files. Some other examples from the Open Source MVC PHP world that have already adopted this approach spring to mind:
- Laravel - includes a "public" directory, which ships containing only:
- a single .htaccess file to handle rewrites
- index.php to bootstrap the application
- subdirectories for css, js, images and the default information pages
- favicon.ico
- plus a location for laravel plugin’s assets
- Fuel PHP - includes a "public" directory, containing:
- a single .htaccess file to handle rewrites
- index.php for bootstrapping and routing
- an "assets" folder for images,css and js
- CakePHP - includes a "webroot" folder, containing:
- a single .htaccess file to handle rewrites
- subdirectories for css, js, images and a files folder for publicly downloadable content
- index.php and test.php used to bootstrap the application from its protected location
- favicon.ico
While catering to “lowest common denominator” hosting may have done wonders for Magento’s initial adoption, when configuring a customised Magento installation and hosting environment, we feel the risks presented by the monolithic directory structure outweigh any of the potential benefits. Therefore it is worthwhile restructuring and modifying Magento to allow for the application files to be moved. Using Mage+ (e61ac08729) as a base, our approach moves the contents of the following directories outside of the document root:
app
includes
lib
pkginfo
shell
var
We also move files such as cron.sh, install.php and mage, while we leave the directories js, media, skin, downloader and of course the index.php file public. There are a number of other, lesser used files such as get.php and api.php that we leave public too.
Our changes to get.php and api.php have not yet been exhaustively tested, however we would question whether the get.php file functions correctly in recent releases and whether it is safe to be used in a production environment. We wonder whether anyone actually uses this this feature though (leave a comment and let us know if you do). The api.php file is designed to improve the speed of API calls by using a light Magento stack to service the request. However, there appears to be an issue in the latest release where the WSDL includes an incorrect URL, which renders the SOAP API useless when you're using this method regardless of our changes. Making a request to magento/api?wsdl
and using the SOAP API as normal still functions correctly and has been tested with the changes.
Restructuring
To set up Mage+ or Magento to use our new, safer structure with an existing Magento installation, there are a few manual file changes required before applying our patch. It goes without saying that changes like these should always be tested on a development version of a site first, and that backups of all files and the databases should be taken prior to making any changes of this magnitude and scope.
The first step is to create a new directory in the root of the magento folder called ‘public’. This directory will become the docroot of the magento installation, so that everything inside it will be public, and everything outside it will be private. Next, move the following files and folders into this new public directory:
downloader/
errors/
js/
media/
skin/
.htaccess
.htaccess.sample
api.php
cron.php
favicon.ico
get.php
index.php
index.php.sample
The resulting file structure should look something like the following:
Now that the basic file structure is in place, the
app/Mage.php
app/code/core/Mage/Core/Model/Config/Options.php
app/code/core/Mage/Install/etc/install.xml
public/api.php
public/cron.php
public/get.php
public/index.php
Finally, we’ve deleted the .htaccess files in the app, downloader, includes, lib, pkginfo, shell and var directories, since they should now be located outside the docroot and are not required.
Once all of the above changes have been made, the webserver configuration should be updated so that the site’s docroot is magento/public rather than magento. We’re still working on the patch and conducting further testing with Mage+, and would welcome any feedback or improvements.
With these changes in place, we can rest easy knowing that as long as our docroot is correctly set to the public directory, the rest of our application files will be safe from access in the event of a configuration error - something we don’t intend to get caught out by again! There aren’t many reasons not to adopt this change, so we’d strongly recommend other Magento users consider whether it might also be appropriate for their installation.