Chapter 6. Simplifying Sessions and Security

Enough theory! Now let's begin to write our own application. In this chapter, we'll outline an application that we're going to build, and we'll look at one of the basic questions affecting any website i.e. session management and security.

In this chapter, we'll see:

  • How to make your pages secure

  • How to use CI's sessions class

Starting to Design a Practical Site with CI

We've looked at the CI welcome page and seen how it's built up by a controller file and a view file. That's the equivalent of 'hello world'.

Once upon a time, hobby sites written by amateurs used open-source code and were often regarded as inferior to large 'enterprise' sites written by teams of programmers using complex procedures.

The landscape has changed. Major companies now use open-source technology. For example, NASA and Associated Press use MySQL, the US GAO and even Yahoo are using PHP for certain applications. And I believe that the market for the flexible 'medium sized' application is growing, as large companies realize that their legacy apps can't handle new tasks. It's sometimes easier to build a small, flexible programme than to revise the old one.

CI offers a bridge between 'home-made' code and the structured reliability of 'enterprise' sites. It holds your hand and helps you to program better and produce more consistent and reliable results.

To demonstrate the flexibility of CI, let's build our own application.

To re-state my requirements, I want to build something for a specific purpose. I run several websites, some of them for myself, and some for quite demanding clients. I need to maintain these sites, test them, and generally keep a track of them. Mostly, this is routine stuff. I could hire someone to do it for me, but it would be cheaper to write a website to automate as much of the process as possible.

So my requirements are:

  1. To manage one or more remote websites with a minimum of human intervention

  2. To run regular tests on the remote sites

  3. To generate reports on demand, giving details of the site and of tests conducted

I want to be able to set the site to run on a Cron, if my ISP allows it; if not, to run it myself twice a day or once an hour (as I please), and let it conduct a pre-arranged pattern of tests.

I don't want to know the details, unless something goes wrong (then I'd like an email telling me exactly what happened and exactly where), but I do want to be able to print out management reports for my clients to impress them with the regular and comprehensive checks I'm doing, and the (hopefully!) flawless performance of their sites.

Note

To avoid making the code too long and repetitive the code in this book is not very secure, so please bear that in mind if you use it for real. This chapter covers a basic means of securing your site's pages against unauthorized users, but other PHP security issues, which aren't unique to CodeIgniter, are outside the scope of this book.

At this stage, we're going to look at CI's approach to things that are generic to most dynamic websites. So we'll leave the detailed design of our site until later. Let's start with some of the very basic items.

Moving Around the Site

Any website is a collection of separate programmes, and it's essential that they are able to talk to each other. As we saw in Chapter 3, CI links them by their URLs. Typically, URLs take the pattern:

base url

http://www.mysite.com. This is the plain vanilla address everyone uses to access your site. Readers don't need to know all the rest of the URL structures because the site builds them up as it needs them.

index file

Segment 1: index.php This is the main file that CI starts off with every time the site is hit.

class (or controller)

Segment 2: start If no controller is set, CI defaults to the controller you specified in the config file see below.

method (or function)

Segment 3: assessme If no method is set, CI defaults to the index function of the controller, if there is one. If not, you get a '404' page.

plus any parameters

Segment 4: fred (and Segment 5: 12345, Segment 6: hello, etc.)

So, to call the assessme method in the start controller with the parameters fred and 12345, your URL will be:

http://www.mysite.com.index.php/start/assessme/fred/12345.

This code expects your site to contain a controller called start.php that includes a method assessme, which expects two parameters. A URL like this will call any function in any controller on your site. So it's ideal for a menu-based page.

For a practical example of how this can work, let's set up the first page the user sees on our site. We'll set up a controller called start, and make it our default controller.

Well, first of all, CI, as it comes out of the box, is set to go to the welcome controller by default, so I need to change this. CI's default route is held in the /system/application/config/routes file. At the moment this reads:

$route['default_controller'] = "welcome";

So I'll change it to:

$route['default_controller'] = "start";

(Just remember, from the table above, if your default controller doesn't have a default index method, you'll get a 404 error every time anyone logs on to your plain vanilla base URL, which is not a good idea!)

Now I need to write my new start controller. Remember the basic format:

<?php
class Start extends Controller
{
function Start()
{
parent::Controller();
}
function assessme($name, $password)
{
: if($name == 'fred' && $password == '12345')
{$this->mainpage();}
}
}
?>

Save this in the /system/application/controllers folder as start.php. (Note the cases: Start has an upper-case letter in the class name and constructor function, but not in the saved file name.)

The second line tells you that this is a controller. Then the constructor function starts and loads the parent controller class methods. The assessme function expects two variables $name and $password. CI (from version 1.5 onwards) automatically assigns any URL segments after the second as parameters, so fred and 12345 will become the $name and $password parameters, respectively.

So, if I type in the URL on the previous page, I'll be re-directed to the mainpage() function. We'll set this up later in the start controller. (If not, then the code will just die.)

For those more used to procedural PHP than OO classes, please note a function within a class has to be addressed as $this->xxxx. So, if I'm calling the mainpage() function of the start controller from another function within the start controller, I have to call it $this->mainpage(). Otherwise, CI won't be able to find it.

Of course, it's unlikely that anyone would type in a URL like:

http://www.mysite.com.index.php/start/assessme/fred/12345.

Mostly, they will just enter

http://www.mysite.com

and expect the site to sort out all the internal navigation. So let's start that now.

Often, the first thing you see on a site is a log-in form. So let's prepare one of those. First, I add a new function to my start controller. I want the site to default to this function, so I'll call it index():

function index()
{
$data['mytitle'] = "My site";
$data['base'] = $this->config->item('base_url'),
$data['css'] = $this->config->item('css'),
$data['mytitle'] = "A website to monitor other websites";
$data['text'] = "Please log in here!";
$this->load->view('entrypage', $data);
}

This is calling a view, entrypage. The view includes a form, and the form allows the user to submit a password and username. HTML forms must point to a page that will handle the data in the $_POST array. We've already written the function in our start controller to receive this: it's assessme(). In plain old HTML, the form on our view should begin:

<form method="post" action="http:/www.mysite.com/index.php/start/assessme" />

I've explained the assessme function a little. There's not much point in a function that only has one username/password combination. I need some way to look it up in a database. To make the structure more modular, I'm going to hand that over to another function, checkme().

So, as you will see, assessme() calls checkme(). Checkme() does some sort of test on the password and username (we haven't written that yet) and returns 'yes' or 'no' to assessme(). If it's yes, assessme() calls another function, mainpage(), which returns a view.

Notice the advantage of the modular approach. Each function has a role. If I need to alter the way the system checks a password, I only have to alter the checkme() function. If I need to alter the page it displays on a correct response, then I go to the mainpage() function.

Let's just look at the structure of the code and the way the sections interact. (Note that in order to make the example simpler to follow, we are not 'cleaning' the input from our form here. Of course, this leaves your code open to problems. CI's form class automatically sanitizes entered data.

/*receives the username and password from the POST array*/
function assessme()
{
$username = $_POST['username'];
$password = $_POST['password'];
/*calls the checkme function to see if the inputs are OK*/
if($this->checkme($username, $password)=='yes')
{
/*if the inputs are OK, calls the mainpage function*/
$this->mainpage();
}
/*if they are not OK, goes back to the index function, which re-presents the log-in screen */
else{
$this->index();
}
}
/*called with a u/n and p/w, this checks them against some list. For the moment, there's just one option. Returns 'yes' or 'no'*/
function checkme($username='', $password='')
{
if($username == 'fred' && $password == '12345')
{return 'yes';
else{return 'no';}
}

On lines 5-6, assessme() receives the output of the form from the $_POST array. This will contain something like:

[username] => fred [password] => 12345

The assessme() function passes these two variables to another function, checkme(). This simply tests if they are fred and 12345, respectively, and if they are, it returns 'yes'. Obviously, on a real site this would be more complex. You would probably do a database lookup for valid username/password pairs. Making it a separate function means I can test the rest of my code now, and improve the checkme() function later, at my leisure.

If the username and password are a valid combination, the assessme() function calls another function, mainpage(), which lets me into the site. Otherwise, it goes back to showing me the index() function that is, the log-in form again.

The next problem we have is how to manage state. In other words, how to recognize the logged-in user when (s)he makes another page request.

Security/Sessions: Using Another CI Library Class

If I want to build a session mechanism that will keep unwanted users from accessing my files, how many lines of code will it take?

The Internet works by a series of requests. Your browser makes a request to my server to see a particular page. My browser passes the page back to your server. You look at it, and perhaps need to make another request, so you click on a hyperlink, which makes a request to my server. And so on.

The Internet is 'stateless' that is, each request made by your browser to my website is treated as a separate event, and the HTTP protocol, which underlies the Internet, has no direct way of linking your request to any other requests (that you may have made). It's as if you were in a restaurant, the waiter takes your order, and brings you your meal, but then forgets all about you. That's fine, until he needs to bring you a bill, or to remember that you are entitled to a special discount, or simply remember that you wanted him to ring for a taxi for you after you've finished your meal.

If you want your website to connect one page request with another, you have to manage the 'state' of the relationship: somehow to let the website know that some requests are coming from the same browser, and should be treated specially.

PHP offers two ways of managing state: using cookies, or a specially generated session ID. The PHP session function automatically checks if the website is accepting cookies; if not, it uses the session ID method which is passed via the URL.

Note

Cookies are small strings of data that websites pass back to any browser that accesses the site. The browser automatically stores it away. Once the cookie is there, the website can check for it when the browser next attempts to access the site. If it finds the right cookie, it can use the information in it to configure itself appropriately. This may mean closing off certain pages to unauthorized users, or adding personal information. In our restaurant analogy, the waiter would leave your bill on the table, and next time he saw you, that would remind him that you were entitled to 15% discount, so he could take that into account when working out your bill.

Because some people set their browsers not to accept cookies, PHP offers an alternative approach. Each time a browser requests access, the site generates a random string called the 'session ID', and returns it to the browser. Browsers then add this to the URL when they make their next request, so that the site can recognize the browser. (Instead of the waiter leaving the bill on your table, you make him carry it back and forth with him to the kitchen.)

CI has a session class that handles much of the same stuff. In fact, it reduces a lot of coding to one line. We saw in the last chapter that CI has a wide range of 'library classes', which simplify most of the common tasks that a website deals with. These are the core of frameworks: pre-written chunks of highly abstracted code, which perform essential functions for you. All you need to know is where they are, how to address them and use their methods, and what parameters they expect. In return, you get to use professional code without having to write it!

If you want to use the functionality inside a class from within your controller or model, you must remember to first load the class into the controller or model. (A few classes, such as config are always automatically loaded, which is why we haven't loaded it in any of our code so far.)

You load a library class simply:

$this->load->library('newclass'),

Normally, put this line in the constructor of your controller or model.

If you think you will use a library class in every controller, you can have it load automatically just as the config class does. Go to the /system/application/config/autoload file, and add the name of the class you want into the line:

$autoload['libraries'] = array();

So that it looks like this:

$autoload['libraries'] = array('newclass','oldclass'),

The library class that we're going to use first is the session class, which helps you to maintain state, and to identify users. It's quite simple to do this. Here's our enlarged assessme() function from our start controller with the new lines highlighted:

function assessme()
{
$this->load->library('session'),
$username = $_POST['username'];
$password = $_POST['password'];
if($this->checkme($username, $password)=='yes')
{
$this->mainpage();
}
else{$this->index();}
}

(I've loaded the session library at the start of the function so you can see it, but normally, I'd load it in the controller's constructor, so it is loaded for all the other functions in this class.)

Just loading the session class immediately gives you a huge chunk of functionality in exchange for the one line of code. It will automatically read, create, and update sessions.

Well, to be frank, it's not quite one line of code. You have to make some changes to the config file first, to tell the session class what you want it to do.

Check your system/applications/config/config.php file, and you'll find a section like this:

--------------------------------------------------------------------------
| Session Variables
|--------------------------------------------------------------------------
|
| 'session_cookie_name' = the name you want for the cookie
| 'encrypt_sess_cookie' = TRUE/FALSE (boolean). Whether to encrypt the cookie
| 'session_expiration' = the number of SECONDS you want the session to last.
| by default sessions last 7200 seconds (two hours). Set to zero for no expiration.
|
*/
$config['sess_cookie_name'] = 'ci_session';
$config['sess_expiration'] = 7200;
$config['sess_encrypt_cookie'] = FALSE;
$config['sess_use_database'] = FALSE;
$config['sess_table_name'] = 'ci_sessions';
$config['sess_match_ip'] = FALSE;
$config['sess_match_useragent'] = FALSE;

For now, make sure sess_use_database is set to FALSE.

Now, every time your users connect, the site will save a cookie on your machine, containing the following information:

  • A unique Session ID generated by CI (not to be confused with a PHP session ID string, which isn't generated in this instance). This is a random string created by CI for this session.

  • The user's IP Address

  • The user's User Agent data (the first 50 characters of the browser data string)

  • Timestamps for "last activity"

If you set sess_encrypt_cookie to FALSE, you can read the cookie on your browser and see what has been saved (it's partly encoded, but you can make it out) e.g.:

ip_address%22%3Bs%3A9%3A%22127.0.0.1%22%3Bs%3A10%3A%22

includes the user's URL in this case, 127.0.0.1). If you set it to TRUE, the cookie is encrypted, just a string of random gunk. Your browser can't even distinguish separate sections of the cookie, which means that the user can't meaningfully alter it without invalidating it.

When the user makes another page request, the site can then check whether the session ID has been saved on the user's browser as part of the cookie. If it has, you know they are part of an existing session. If not, you know they are a new session. Provided I remember to load the CI session class on all my other controllers as well, CI will make the same checks for them too. All I have to do is tell each controller how to behave if there isn't a cookie.

Turning Sessions into Security

This in itself doesn't make a security system. Anyone who visits the site starts a session. The code just records whether they are a new visitor or not. One way of preventing unauthorized access to some pages involves adding something else to their cookie if they are 'logged in', so that I can test for that. Then, if they enter the correct username and password once, that will be recorded in the cookie, and the session mechanism will find it when it checks for cookies as each new request comes through. I can then test for that, and if I find it, the site will let them into protected pages for the rest of the session. They won't have to keep on logging in.

Adding something to the cookie is easy. In my assessme() controller, once I have decided if the password and username are acceptable, I add:

if($this->checkme($username, $password)=='yes')
{
$newdata = array(
'status' => 'OK',
);
$this->session->set_userdata($newdata);
$this->mainpage();
}

That takes the contents of my $newdata array just one variable in this case and adds it to the cookie string. Now, whenever the password/username combination is acceptable, assessme() will add'OK' to the cookie, and I can start each controller with this code:

/*remember to load the library!*/
$this->load->library('session'),
/*look for a 'status' variable in the contents of the session cookie*/
$status = $this->session->userdata('status'),
/*if it's not there, make the user log in*/
if(!isset($status) || $status != 'OK')
{ /*function to present a login page again…*/}
/*otherwise, go ahead with the code*/

Here, you have the basis of a security fence around your site. You can easily make it more elaborate. For instance, if some of your users have higher access levels than others, you can store a level in the status variable rather than'OK' then you can use this in conditional tests to control access to functions.

Saving this sort of data in a cookie is frowned upon because the user can easily rewrite the cookie on their machine between visits to your site. Given that CI's session class encrypts it, you're fairly safe. However, the alternative is to create a database of users, and after one has logged in, to write the'OK' to the database against that session ID. Then, for subsequent accesses, you check the session ID (in the cookie) against the database, to see whether it has'OK' or a level against it.

It is very simple to save the session data in your database. First, create the database table. If you're using MySQL, use this SQL query:

CREATE TABLE IF NOT EXISTS `ci_sessions` (
session_id varchar(40) DEFAULT '0' NOT NULL,
ip_address varchar(16) DEFAULT '0' NOT NULL,
user_agent varchar(50) NOT NULL,
last_activity int(10) unsigned DEFAULT 0 NOT NULL,
status varchar(5) DEFAULT 'no',
PRIMARY KEY (session_id)
);

Then, alter the connection parameters in the system/application/config/database.php file to tell CI where the database is. See Chapter 4 for more details on databases.

If all works, you will see the data build up in your database table as you connect and disconnect. If you have sessions stored in a database table, as each user connects to your site, the site tests for a cookie. If it finds one, you can then have it read the session id, and match this against the session ids stored in the database.

You now have a robust session mechanism. And all of this came from one line of code!

Just one caveat. The native PHP session class can cope with users who turn off cookies on their browsers. (Instead of storing a cookie, it adds session data to the URL string.) The CI class does not do this. If the user has turned off cookies, then (s)he can't log on to your site. Whether this is a problem for you depends on the people you expect to use your site. This is one enhancement I hope Rick Ellis will soon make to CI.

Security

Notice that the session class automatically stores information about the IP address and user agent of the user making a page request. You can use these to give additional security.

There are two settings you can change in your config file for additional security:

  • sess_match_ip: If you set this to true, CI will attempt to match the user's IP address when it reads the session data. This is to prevent users from 'hijacking' a log-in. However, some servers (both ISPs and large corporate servers) may issue requests by the same end user over different IP addresses. If you set this value to true, you may exclude them unintentionally.

  • sess_match_useragent: If you set this to true, CI will try to match the User Agent when reading the session data. This means that someone who tried to hijack a session would have to ensure that the 'user agent' setting returned by his or her system matched that of the genuine user. It makes hijacking a little more difficult.

CI also has a user_agent class, which you load like this:

$this->load->library('user_agent'),

Once loaded, you can ask it to return various information about any agent browsing your site, for instance, the type of browser and operating system, and in particular whether it is a browser, mobile, or robot. If you want to list the robots that visit your site, you might do it like this:

$fred = $this->agent->is_robot();
if ($fred == TRUE)
{$agent = $this->agent->agent_string();
/*add code here to store or analyse the name the user agent is returning*/
}

The class works by loading, and comparing against, the array of user agents, browsers, and robots contained in another of the config files: system/application/config/user_agents.

If you wished, you could easily develop this to enable your site to lock out certain types of browser or certain robots. However, remember that it is easy for an attacker to write robot user agents, and have them return whatever user_agent string you want, so they can easily masquerade as common browsers. Many robots, including ones like the Googlebot listed in CI's user_agents array, are 'well-behaved'. This means that if you set your robots.txt file to exclude them, they won't trespass. There is no easy way of excluding robots that don't obey this file, unless you know their names in advance!

In CI, the session mechanism stores the IP of the requesting site, so you could use this to operate a black-list of sites. Retrieve the IP from the session like this:

/*remember to load the library!*/
$this->load->library('session'),
/*look for an 'ip_address' variable in the contents of the session cookie*/
$ip = $this->session->userdata('ip_address'),

Then you can test the $ip variable against a blacklist.

You could also develop CI's session mechanism to limit the damage from repeated requests such as denial of service attacks where a robot is set to overload your site by repeatedly asking for pages. Or you could use this mechanism to handle 'dictionary' attacks, where a robot is set up to call your log-in form repeatedly, and try hundreds or thousands of password/username combinations until it finds the right one.

You can do this because CI's sessions class stores the last_activity time for each session. Each time a page is requested, you can check how long ago the last request was made by this user. While one time interval doesn't tell you very much, you can set the system to store more data and to develop usage patterns. A dictionary attack relies on getting a speedy reply, otherwise it will take too long. If you detected too many requests in rapid succession, you could either end the session, or slow down the response.

Summary

We've outlined an application that we'd like to build, and attacked the first issue that almost any application raises: session management and (if we want to protect parts of our site from unauthorized users) security.

To do this, we've looked at the CI sessions class in some detail, and seen how it creates session records and leaves cookies on the visitor's browser.

It can then look for cookies when subsequent requests are made, and you can use the response to control the way your site responds.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.55.14