FreeBSD Programming Primer – Part 10

}

November 30, 2013

In the tenth part of our series on programming, we will improve the login process, add more security, and keep spam robots under control.

In the previous article we put in place a very crude login system that allowed anyone to login to our CMS and add content. We assume that the user has been correctly authenticated by comparing their password against a hashed password stored in the CMS database, then writing a cookie at the client side. It is then a simple matter of checking that authorization has been granted prior to carrying out sensitive actions (e.g. adding a user or amending content). Unfortunately, exposing any login system on the World Wide Web leaves us open to undesirable elements. Brute force attacks (repeatedly attempting a login using dictionary attacks) and spambots that want to add advertising or phishing spam are commonplace, and our basic login system needs to defend against this. We also need to add logout functionality to every page that requires it.

The logout functionality
As the parameters passed to the cookie that is set when we are logged in, it makes sense to hold the logout function as part of the login.php page. We can then detect a logout post event to login.php and delete the cookie by setting the expire date to a time in the past. Add the following code at the end of login.php

(Listing 1). Now we need to check for a post event that carries the value logout. Add the following elseif branch between append and the closing else
(Listing 2). We now need a logoutform() function that will provide a logout button whenever a user is logged in to the system.  If we check whether or not the user is logged in we can place this in the footer of all pages where login / logout functionality is required, and the button will be displayed only if the user is logged in. Add this to the end of core.inc
(Listing 3). We need to add a function call to check if the user is logged in or not, and redirect them to the login page if they are not. Add this at the end of core.inc (Listing 4). As we cannot guarantee that the user does not spoof HTTP headers for the redirect, define our CMS Domain in cms.inc. Replace 192.168.0.118 with either the IP address or domain name of your server (if accessible via DNS).
(Listing 5) Add the ifnotloggedin() function call to the beginning of amendcontent.php and replace phpinfo.php with the content in Listing 7
(Listing 6 & 7). Add the logoutform(); after every occurrence after echo BODY; in login.php and amendcontent.php
(Listing 8).  Add the following to global.css to highlight and position the logout button
(Listing 9). With Firebug enabled in Firefox, check that a cookie called gpl9867fghlls is created when a user is logged in. The logout button should appear on all pages except the second time login.php is called and we arrive at the welcome page. Now this is a problem, as we should be able to logout immediately after we login. Subsequent calls to login.php will show the logout button. So what is happening here? The problem lies in the validatelogin() function (Listing 10).

We must set the cookie prior to creating the page header, but as the cookie data is generated at the client browser side when the page is loaded, as far as the PHP code running at the server side is concerned the cookie is not present yet. We can fool buildheader() by passing a parameter to force the display of the logout button (Listing 11 – 13).  This will result in login.php working as desired (Figure 7).

Spambots and robots
While we could use the very effective Apache MOD_SECURITY module to trap bad behaviour, this can be tricky to set up. What we will do here is monitor behaviour in two ways.  First, we will create a hidden field that a normal user will not see under normal circumstances, which most spam-robots will fill in assuming it is a genuine field. On completing the field, our CMS will automatically ban all connections from that IP address to login.php permanently. We will also check that no more than 3 invalid attempts are made to the login.php page, and if that is exceeded, that IP address will be banned as well.

First create another testuser by changing login.php as follows and visit login.php anew to create another user (e.g. Test, Test, Auth = 1). Don’t worry about the error messages – we will fix them later. Once you have created the new user, go back and comment out createnewlogin(); and check that you can login as the test user (Listing 14 and 15).

If you visit login.php you should be able to login as Test (Ignore the Email field), then Logout. (Figure 8). Now create our access table in MySQL to hold our banlist (Listing 16). Now create the file sqlstatements.inc in our includes directory (Listing 17). Replace the mysql_select() function call in mysql.inc with the following code (Listing 18).  This fixes a bug where a PHP error is raised when no results are returned. Add the following function calls to core.inc (Listing 19). Replace validatelogin() in login.php with the following code (Listing 20). Modify buildheader() in login.php to call loginsecurity() (Listing 21).

Testing
It is recommended that you run Firebug to view the cookies and PHP sessions generated during this test. Clear all cookies etc. from you browser and visit login.php:
• Login as Test with the correct password. You should be able to login. Logout.
• Login as Test with the correct password and an email address. You should be redirected to google.com. Any visits to login.php will cause a redirect to google.com.
• Use Adminer to clear all the entries from the access table.
• Visit login.php and click on the submit button 3 times without making any input. You should be redirected on the 4th attempt.
• Use Adminer to clear all the entries from the access table.
• Visit login.php and login and logout as normal. Your access attempts should be logged correctly with IP address and date.
• Login with a mixture of bad username and good password, good username and bad password. You should be banned on your 4th login attempt.

CSS modification

Finally, add the following code to global.css and refresh your browser with Ctrl F5 a couple of times to clear the cache. The email field should now be invisible to human visitors, but available to robots etc. (Listing 22).

Next steps

It might be a good idea to add the banlist functionality to all pages on a failed login etc. and keep a tally of what pages are accessed etc. legitimately. We also need to add the facility to add a user rather than manually editing code each time. Our CMS is getting quite large, with over 2,100 lines of code (excluding the Jquery libraries) so we will look at refactoring some of this code in the next article.

About the Author

Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager.  He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.

This article was re-published with the permission of BSD Magazine.  To Learn More about iXsystem’s commitment to open source check us out here:   https://www.ixsystems.com/about-ix/

Join iX Newsletter

iXsystems values privacy for all visitors. Learn more about how we use cookies and how you can control them by reading our Privacy Policy.
π