Hardening Your MODX Site

Some thoughts about making your MODX site more secure

I don't claim to be an expert on hardening web sites in general, but I thought I'd write a little concerning what I've learned about how MODX protects your web site from malicious code, and how and when it doesn't.

When a MODX security problem is discovered, you want to know about it right away. Subscribe to this MODX mailing list to get an immediate warning when there are steps to take to protect your site.

MODX Security at Work

All standard MODX pages are delivered via index.php.

The code in index.php does this:

/* At the beginning */
if (!defined('MODX_API_MODE')) {
    define('MODX_API_MODE', false);

/* At the end */

That means that for every request to your site (with an exception we'll discuss in a bit), the first thing that handleRequest() does (after loading the error handler) is call sanitizeRequest(). Here is the whole sanitizeRequest() function:

    public function sanitizeRequest() {
        $modxtags = array_values($this->modx->sanitizePatterns);
        modX :: sanitize($_GET, $modxtags);
        if ($this->modx->getOption('allow_tags_in_post',null,true)) {
            modX :: sanitize($_POST);
        } else {
            modX :: sanitize($_POST, $modxtags);
        modX :: sanitize($_COOKIE, $modxtags);
        modX :: sanitize($_REQUEST, $modxtags);
        $rAlias = $this->modx->getOption('request_param_alias', null, 'q');
        if (isset ($_GET[$rAlias])) {
            $_GET[$rAlias] = preg_replace("/[^A-Za-z0-9_\-\.\/]/", "", $_GET[$rAlias]);

The code above sanitizes the $_GET, $_POST, $_COOKIE, and $_REQUEST, arrays by removing any script tags, and any MODX tags, assuming that you have the allow_tags_in_post System Setting off — (and you definitely should).

When it Doesn't Happen

If you have Friendly URLs on, which almost all of us do, the rules in the .htaccess file specify that if the page being requested isn't found (which it generally won't be, because the pages are in the database, not on the disk), index.php is loaded. The code above will execute, then MODX will look for the requested page in the database, prepare it, and send it off to the browser.

What if the file *is* found, though? If you have a .php or .html file on your site that is available via web browser, a request for that file will bypass MODX altogether. If the file presents a form and posts to itself, malicious code will not be sanitized unless you do it yourself in the code that processes the form.

Even if you sanitize the user input before processing it, you may still be vulnerable. If your form validates the user input and re-displays the form if there is an invalid entry (without sanitizing), malicious code in the form may still execute. This is generally only a problem with text input, not checkboxes or lists, but most forms have at least some text input.

The best way, by far, to make sure you are protected is to move the MODX core above the web root and put any executable .php files or .html files that contain PHP code under the core directory. Properly written extras put all executable PHP files under core/components/componentname. Moving the core is strongly recommended by MODX and you can see why. Once you move the core, no files in that directory, including all the MODX core files, can be executed from a browser.

An added protection is to use a host that has suPHP or suExec running and set the folder and file permissions to 755 and 644. This makes the files executable by you or by MODX, but leaves them as read-only for everyone else. If you look at your folders and files in PhpMyAdmin's File Manager and see that their permissions are set to 755 and 644, the odds are good that suExec or suPHP is running.

Of course, that won't help you with a web form that's in a PHP file, or processed by a PHP file. Or a form processed with PHP that's in an .html file. Usually, those situations are the result of importing code from outside of MODX. There are three possible solutions. One is to call $modx->sanitizeRequest() a the top of the processing code, but that only works if the $modx object has been instantiated in your code. The second is to copy the sanitizeRequest() function above to the top of your code, modify it so it works outside of MODX, and call it before doing any processing. The third, and probably best, method is to convert the code to a MODX snippet and let MODX handle the sanitizing. This can be a challenge if the code has mixed PHP and HTML, but it's usually worth the effort.

Renaming the MODX Directories

Renaming the main MODX directories also helps protect your site. Remember that the MODX core code is public. Anyone can download it, study it, and test attack trategies against it. If a malicious person finds a vulnerability in some code in the MODX manager directory, renaming that directory (and others) will make it very difficult for them to attack your site.

There are instructions for moving the core and renaming the directories, here

Renaming the Manager directory will also prevent people from launching brute-force attacks that attempt to log in over and over with generated usernames and passwords. If you have a strong username and password, they are unlikely to succeed, but they're putting an unnecessary load on the server. If you rename the Manager directory, they'll get a 404 page instead of the Manager login page. They'll most likely go away after a single try rather than hammering away at the site.

Renaming the connectors directory is also important. The connectors directory is the gateway to the MODX processors and much of the code of MODX extras. If a MODX extra has a vulneribility, it can be exploited through the connectors directory.

Another Potential Vulnerability

A few of my extras were found to have a somewhat exotic vulnerabilty (now fixed) because they recorded URLs and information about the user's visit (e.g., the user agent) which could be viewed later in a report. If you use xPDO to save your data, xPDO sanitizes it against MySQL injection attacks, but it doesn't remove script tags, since MODX resources, templates, and chunks might contain legitimate script tags. The problem was compounded by the fact that the sanitizeRequest() code above doesn't sanitize the URL or any other $_SERVER variables like $_SERVER['HTTP_USER_AGENT'].

Saving malicious code that was part of the URL or user agent string to the database caused no trouble. But when user's view the report in their browsers, the malicious code could execute.

My mistake was in a) assuming that MODX's sanitization of the request protected against this, and b) failing to think of this data as user input. It usually isn't, because it's set automatically by the browser and the server, but it can be.

Foreign Data

If you use a web service (e.g., Mandrill, MailChimp, a real estate database), or import data (e.g., importing WordPress users), the site you get your data from may have been compromised. In that case, the data could contain malicious code. Always sanitize it before saving or displaying it.

Using .htaccess to Protect Directories

If you have directories with sensitive files, you can keep them from being viewed or executed by putting an .htaccess file in the directory with code like this:

IndexIgnore */*
<Files *.php>
    Order Deny,Allow
    Deny from all

<Files *.log>
    Order Deny,Allow
    Deny from all

The example above prevents visitors from viewing or executing the index file, PHP files, and any files ending in .log. Your PHP code and MODX will be able to read and execute the files, but they won't be available for anyone visiting with a browser.

Final Notes

Here's a quick checklist of things to do to make your site more secure:

  • Move the MODX core above the web root and rename it.
  • Rename the main MODX directories (core, manager, connectors, assets).
  • Don't use anything like admin, root, manager, or any username that people could guess (e.g. BobRay) as your admin username.
  • Don't use words that might be in the dictionary or common names for your username. Make up combination of non-words like PleenkFinbrun, or (better) use a random string of characters.
  • Use a strong, long password — again, no dictionary words or common names.
  • Use a different username and password for your Manager, cPanel, database, and FTP credentials.
  • Use a MODX-Friendly host that runs suPHP or suExec, and make sure all folder/file permissions are 755/644.
  • Never, ever, trust user input! This includes data from any source outside your site.

Important: When you move and rename directories, Back up your site first. Also, don't try to rename them on the Files tab of the Manager! The Manager will stop working and your site will be down. Do it in cPanel's File Manager (or the equivalent). Delete all files in the core/cache directory before leaving File Manager.

Comments (1)

  1. Susan OttwellMay 05, 2015 at 10:24 PM

    There are sites that generate passwords, and these can be used for usernames as well. A good password-keeper application is important. Many of these actually store your passwords on their own server, so you can access your passwords from anywhere. Convenient, perhaps, but I'm not at all comfortable about some third party having my passwords. I use KeePassX. I also have a Mac dashboard widget that generates passwords. Or this https://strongpasswordgenerator.com/

Please login to comment.