Hardening Concrete5 and Enhancing Security

29 January 2018

Concrete5 is our chosen CMS for the development of content or brochure websites. Security of any website is always of paramount importance and that's no different with C5. Here we look at how to make your Concrete5 site ultra secure.

Hardening is the process of enhancing security in a system environment by adding or increasing security functionality around possible access points.

System Hardening should be reviewed constantly and consistently. Otherwise overtime as your systems software get more and more out of date, your system will become more and more vulnerable to attacks.

While Concrete5 sounds hard enough. We can help make it even harder.

Change the admin username

Very straight forward, but you’d be surprised how many sites stand behind weak flimsy usernames.

When installing concrete5 you don’t get a choice of the username (which is a bit of an oversight on their part really) as it’s automatically set to admin.

But all you have to do is log into the dashboard after the installation is complete and navigate to the members page. There you will select the admin user and pick a less common username.

Something like DoYouEvenAdmin would be much better... or a combination of your company name if you don’t wanna live life quite to the fullest.

Try and steer clear of the following that were sourced from The Attacker's Dictionary by Rapid7 in 2015-2016. Worth a glance.

username count percent
administrator 77125 34.87%
Administrator 53427 24.15%
user1 8575 3.88%
admin 4935 2.23%
alex 4051 1.83%
pos 2321 1.05%
demo 1920 0.87%
db2admin 1654 0.75%
Admin 1378 0.62%
sql 1354 0.61%

Nice to see that Alexs around the world are getting into web development!

It should go without saying, but a strong username is only as strong as the password paired with it. So please use some sort of password manager like LastPass or 1Password to create and store strong passwords. Make your password 12-20 characters long with uppercase characters, lowercase characters, numbers, special characters and a stylish hat for good measure.

More things to avoid - the top ten passwords that were uncovered in The Attacker's Dictionary.

password count percent
x 11865 5.36%
Zz 10591 4.79%
St@rt123 8014 3.62%
1 5679 2.57%
P@ssw0rd 5630 2.55%
bl4ck4ndwhite 5128 2.32%
admin 4810 2.17%
alex 4032 1.82%
....... 2672 1.21%
administrator 2243 1.01%

Hmmmm, I sure hope the Alexs of the world aren’t also using their name for their password.
Come on Alex, get your head in the game!

Protect your custom files from direct access

Another simple one, but easily missed when you’re working hard on bespoke code.

If you are developing custom code for a Concrete5 site you may be very familiar with the core system controllers and views. You may have noticed the following line at the top of many of the php files:

defined('C5_EXECUTE') or die("Access Denied.");

Basically this will deny access to a file if the Concrete5 application hasn’t been executed. This happens when a request hits the index.php file in the web root and is routed to the appropriate files.

Concrete5 doesn’t want any old mate sticking its nose into its bits and bobs. So be a dear and close the blinds by including the above at the top or under any namespace in every custom php file you create.

IP banning

Concrete5 comes with some basic IP banning built in. Handy for stopping many brute force attempts from guessing what by now should be a herculean style username and password combo... right?

You can find these under System & Settings > Permissions & Access > IP Blacklist.

Adjusting the settings on this page is good practice. By default banned IP addresses are only banned for 10 minutes. This setting would be better if it was set to ban for a couple hours at least.

While I trust that your username and password are practically uncrackable at this stage, it’s a good idea to not make them work too hard.

You might want to go a step further on an apache server and include something in the .htaccess file to restrict access to the dashboard and login pages to certain IP addresses.

# Restrict access to login and dashboard page
RewriteCond %{REMOTE_ADDR} !127\.0\.0\.1 [OR]
RewriteCond %{REMOTE_ADDR} !123\.1\.12\.123
RewriteCond %{REQUEST_URI} ^/login(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^/dashboard(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^/index\.php/dashboard(.*)$
RewriteRule ^(.*)$ / [R=301,L]

Here’s what’s happening in the above code.

First we check that the current user is using an acceptable IP address from one or more selected by us.

RewriteCond %{REMOTE_ADDR} !127\.0\.0\.1 [OR]
RewriteCond %{REMOTE_ADDR} !123\.1\.12\.123

Then check if they’re trying to access a restricted URI

RewriteCond %{REQUEST_URI} ^/login(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^/dashboard(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^/index\.php/dashboard(.*)$

Finally if the above conditions pass, we redirect them somewhere, like the homepage.

RewriteRule ^(.*)$ / [R=301,L]

Change the Login URI

With all the IP banning and Strong username/passwords your site has in play you may not care about changing Concrete5’s super lax “/login” URI.

But really, if you had a super secure locked door. Would you really feel comfortable knowing that every man, his dog and that dogs extended family could find your door, and start gnawing on its hinges?

Perhaps your account is secure and no amount of gnawing on its delicates will yield any results for the growing pack of “bot dogs” outside your site. But, you have a client who hasn’t read this article and their colleague needs admin access.

So the client remembers back to that training session they had with you and think, “I’m pretty sure I can setup admin accounts myself!”. Unbeknownst to you or your company there is now an admin account named “alex” (you can probably guess what the password is) and your security door isn’t quite as robust as it was 2 seconds ago.

The login page in Concrete5 is controlled by… a controller. Located at
/concrete/controllers/single_page/login.php

Which displays a view located at
/concrete/single_pages/login.php

If we are going to disable the /login route then we’ll need to make another login route available. Let’s say we’d like to make the login route “/securelogin”. Of course you could pick anything you want, just be sure to replace the word “securelogin” with your own in the examples below.

First we create a controller at
/application/controllers/single_page/securelogin.php
(note the singular single_page directory).

Which has the following code:

(Note that the controller is extending the Core Login controller, so it will have the ability to use the core login methods. Plus if the core login controller is updated we’ll inherit the changes.)

<?php
namespace Application\Controller\SinglePage;
defined('C5_EXECUTE') or die(_("Access Denied."));
use PageController;
use User;
use View;
use Loader;
class Securelogin extends \Concrete\Controller\SinglePage\Login {
   public function on_start()
   {
       parent::on_start();
       $u = new User();
       $this->set('user', $u);
       if ( ! $u->checkLogin())
       {
           // The login form should submit to the new "securelogin" path
           $this->set('loginAction', View::url('/securelogin', 'authenticate', 'concrete'));
           $this->set('validationToken', Loader::helper('validation/token')->output('login_concrete', true));
       }
       else
       {
           $logoutToken = Loader::helper('validation/token')->generate('logout');
           // The logout path should alo use our new controller path
           $this->set('logoutPath', URL::to('/securelogin', 'logout', $logoutToken));
       }
   }
   public function logout($token = false)
   {
       if ($this->app->make('token')->validate('logout', $token)) {
           $u = new User();
           $u->logout();
           // When we loggout, send the user to the new "securelogin" path
           $this->redirect('/securelogin');
       }
   }
}

Then create the view file at
/application/single_pages/securelogin.php
(note the plural single_pages directory)

This file might look something like the following:

<?php defined('C5_EXECUTE') or die('Access denied.'); ?>
<style>
   .login_block_container {
       background-color: #fafafa;
       padding-top: 30px;
       padding-bottom: 30px;
       margin-top: 30px;
       margin-bottom: 30px;
       box-shadow: 0 0 5px rgba(0,0,0,0.2);
   }
   .button {
       background-color: #ededed;
       color: #242424;
   }
   .ccm-error {
       background-color: #ce3426;
       color: white;
       list-style: none;
       padding: 10px 15px;
   }
</style>
<div class="row">
   <div class="col-sm-6 col-sm-push-3 login_block_container">
       <div class="row ">
           <?php if($error): ?>
               <div class="col-sm-12">
                   <?php echo $error->output(); ?>
               </div>
           <?php endif ?>
           <?php if ( ! $user->checkLogin()): ?>
               <form class="clearfix" method="post" action="<?php echo $loginAction; ?>">
                   <div class="col-sm-12">
                       <h1 class="login_block_title"><?php echo t('User login') ?></h1>
                   </div>
                   <div class="col-sm-6">
                       <p><input type="text" name="uName"
                                 placeholder="<?php echo Config::get('concrete.user.registration.email_registration') ? t('Email Address') : t('Username') ?>"
                                 value="<?php echo $_POST['username']; ?>"/></p>
                   </div>
                   <div class="col-sm-6">
                       <p><input type="password" name="uPassword" placeholder="<?php echo t('Password'); ?>" value=""/></p>
                   </div>
                   <div class="col-sm-12">
                       <input type="checkbox" name="uMaintainLogin" value="1"><label
                           for="uMaintailLogin"> <?php echo t('Stay signed in for two weeks'); ?></label>
                   </div>
                   <div class="col-sm-12">
                       <?php echo $validationToken; ?>
                       <button class="button"><?php echo t('Submit') ?></button>
                   </div>
               </form>
           <?php else: ?>
               <div class="col-sm-12">
                   <h1>Welcome</h1>
                   <p>You are logged in as <b><?php echo $user->uName ?></b></p>
                   <a class="button" href="<?php echo $logoutPath ?>"><?php echo t('Logout') ?></a>
               </div>
           <?php endif; ?>
       </div>
   </div>
</div>

Now we need to login to the dashboard and navigate to Pages & Themes > Single Pages.

Here we can add the new page securelogin to the single pages list.

Lastly we can either deny access to the old /login path via the .htaccess file like so:

# remove access to /login paths
RewriteCond %{REQUEST_URI} ^/login(.*)$
RewriteRule ^(.*)$ / [R=301,L]

Or you could login to the dashboard sitemap and take away the guest permission to view the Login page. You’ll need to tick the box that includes System Pages in the sitemap to see the Login page in the list.

You should now be able to view your new /securelogin page and login using the form there.

If viewing the new /securelogin page while logged in you should see a button that will allow you to log out.

Extras

There is another logout link in the admin at the bottom of the site wide settings sidebar.

This will still be pointing to /login/logout/{token}.

You can override this file by copying it from:
/concrete/elements/panels/dashboard.php
To:
/application/elements/panels/dashboard.php

Then change the /login url at the bottom of the new file to point to your /securelogin path.

Also, if you followed the advice earlier in the article and restricted IP access to the login page, make sure you add your custom one as well.

# Restrict access to login and dashboard page
RewriteCond %{REMOTE_ADDR} !127\.0\.0\.1 [OR]
RewriteCond %{REMOTE_ADDR} !123\.1\.12\.123
RewriteCond %{REQUEST_URI} ^/securelogin(.*)$ [OR] # <<< Updated
RewriteCond %{REQUEST_URI} ^/dashboard(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^/index\.php/dashboard(.*)$
RewriteRule ^(.*)$ / [R=301,L]

INSIGHTS

CONTACT

Are you excited to get your next project up and running? Or are you unsure what is dragging you down?

Contact Us to discuss how we can help increase sales and boost your online performance!
files/ContactUsBlockDesktop_fede44d1-525a-48b1-9790-5fa1703ec11c.jpg

Enter your email address to sign up to our newsletter, featuring case studies, insights, industry news and much more.

If this is something you would like help with, please get in touch.