I don't claim to be an expert on hardening websites, but I thought I'd write a little of what I've learned about how MODX protects your website from malicious code, and how and when it doesn't.
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 */
if (!MODX_API_MODE) {
$modx->handleRequest();
}
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).
What MODX Can't Protect
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 in the file system), 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 (which could contain PHP code) 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 submitted 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.
Store Executables in the core Directory
The best way, by far, to make sure you are protected is to 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]
. Assuming that the core directory is protected by its own .htaccess
file, no files in that directory, including all the MODX core files, can be executed from a browser.
Use a Secure Host
An added protection is to use a host that has PHP-FPM (as MODX Cloud does) or suExec (CGI) 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 your hosting File Manager and see that their permissions are set to 755 and 644, the odds are good that PHP-FPM or suExec 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.
Incorporate MODX Sanitization or Make a Snippet
One is to call $modx->sanitizeRequest()
at 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 to work 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.
At one time, it was recommended that you move the MODX core
directory above the web root, and modify your config.inc.php
file accordingly. This is no longer allowed for MODX 3 and beyond, since that would interfere with Composer.
Renaming the MODX Directories
If you're hosting at a host other than MODX Cloud, 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 strategies 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 suggestions for 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.
It's not necessary or recommended to rename the directories in MODX Cloud due to the additional layers of security and integrated functionality in MODX Cloud. Renaming the directories in MODX Cloud will lead to certain features to stop working such as upgrades, reinstalls, or back-up restores.
Another Potential Gotcha
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 (or Nginx Config) 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. This code protects files in the log/
directory:
IndexIgnore */*
<Files *.php>
Order Deny,Allow
Deny from all
</Files>
<Files *.log>
Order Deny,Allow
Deny from all
</Files>
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. Again, this is not required in MODX Cloud hosting.
Final Notes
Here's a quick checklist of things to do to make your site more secure:
- If not hosting in MODX Cloud, rename the main MODX directories (except for the core in MODX 3+): core, manager, connectors, assets),and modify the
core/config/config.inc.php
file to use the new names. Change theMODX_CONFIG_KEY
in the three config.core.php files (root, manager, and connectors directory) and rename the config directory under the core to match. Test heavily after making this change as some extras may not check the config key. - 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, reused passwords or common names.
- Use a different username and password for your Manager, Cloud Dashboard, cPanel, database, and SFTP.
- Use a MODX-friendly host that runs PHP-FPM or suExec such as MODX Cloud or others, and make sure all folder/file permissions are 755/644.
- Never, ever, trust user input! This includes data from any source outside your site.
Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.