Concrete5 security considerations
A few weeks ago I had to do an auditing job for a large concrete5 website. It’s fun to do, as you really deep dive into everything. In this article I will outline a few security things worthwhile sharing.
Add-ons often include translation files. They are always located in the same directory so concrete5 can ‘autoload’ them. The path to these files could be, for example, /packages/pkg_handle/languages/nl_NL/LC_MESSAGES/messages.mo. The Zend Translate library parses these mo-files to translate strings. Mo-files can be generated via tools like poEdit. Packages often also include a messages.po file in the same directory. Po-files contain the actual translations, and, often, paths to the source code. This is where things get tricky. You don’t want to expose paths to all your source files that contain translatable texts. For a slider add-on it’s not a big deal, but if you’re building API’s or booking systems, it is a big deal. Developers should be aware of this and should consider securing the translation files via their web server configuration. E.g. via .htaccess.
Compiled CSS assets
Concrete5 can combine CSS assets automatically. However, the data-source attribute on the link-tag still shows the source of these files. Somebody who looks at the HTML source could get an idea of which add-ons and blocks are used. It’s possible that a specific version of a package contains a bug or a security hole. Strictly speaking you won’t want to expose information like this so easily. Although security through obscurity is not a best practice, you shouldn’t make it too easy either...
Exposing the concrete5 version
By default concrete5 outputs a meta tag with the version number of the installed concrete5. This is not a big deal if you keep your installations up to date, but what if your client doesn’t care about a subscription to keep the CMS up to date? Are you going to do it for free? I doubt it ;) My point is, it’s likely that the installation gets out of date and that over time security holes become known for specific versions of concrete5. It’s not too difficult to get a list of concrete5 websites, parse their HTML and store the URL + CMS version in a database somewhere. My advise, hide the version number by setting the config value concrete.misc.app_version_display_in_header to false.
If you buy, use, or develop add-ons, I’d recommend to check possible custom routes. Check if they are accessible as a non-authorized user. Do you have a route that returns JSON data for some back-end API call? Then you probably need to do authentication. As pointed out before, PHP files can easily be looked up via translation files… Basically the idea of ‘nobody doesn’t know the URL to that resource’ isn’t a valid point to not secure a request.
Concrete5 provides a download file route. This makes it possible to force download a file, It also gives the possibility to add authentication, file versions, etc. on a resource. It’s perfectly OK to use this URL to point to a PDF file for example via /download_file/-/view/2222/, where 2222 is the File ID. However… it’s easy to create a script that just requests all ID’s from 1 to >999 and downloads all files to a dedicated folder. Are you sure there are no files that contain sensitive information? They might look secure, because nobody might know the exact path to the resource, but they could easily be retrieved via a download link. To prevent this, you should secure the files via the file manager.
Have you thought of files like db.xml, content.xml, readme.txt? >90% change they are publicly accessible. If one knows you’ve built a custom package or block, it’s easy to just request the db.xml file to see what kind of database structure you’ve used. Do you want this information exposed? I doubt it… Also, a content.xml could contain private data. Readme files could contain information meant for developers that use a package. If it’s a custom add-on built in house, you probably don’t want such information to be public. Instead, store them in a wiki, secure the files via an .htaccess file, or put them in a PHP files and add comments around the test.
The concept is that all files go through the index.php to boot up concrete5 and do authentication, autoloading, etc. Because one could easily go to a file manually, concrete5 added a constant. It’s recommended to verify this constant is set to make sure the request went through the index.php file. Use this snippet to do so:
defined('C5_EXECUTE') or die("Access Denied."); This check is not necessary in controllers or models, or other files that contain code that doesn’t run / instantiate on itself. However, it is necessary in templates files like view.php, but also in mail templates and element files. If a view file doesn’t run the check, a file can be accessed directly and the code will execute. In most scenarios this will lead to an error because certain variables are not set. No big deal, but if you show detailed errors on your production server, it exposes information about your code. I mean, what if you’ve hardcoded that API-key on line 22 ;) (not a smart thing of course, but to illustrate the example).
CSS source maps
This article is not complete in any regard. Please leave a comment if you have suggestions. Also, if you’re looking for an in-depth code review or website / package audit, contact me.