Safe Dynamic Includes

From Hashphp.org
Revision as of 16:44, 10 October 2011 by TML (Talk | contribs) (Created page with "What you should know about: Dynamic Includes Introduction Welcome to my “What you should know about X“ series. Over the next few weeks/months, I will be writing more tutoria...")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

What you should know about: Dynamic Includes

Introduction Welcome to my “What you should know about X“ series. Over the next few weeks/months, I will be writing more tutorials on things that I’ve learned through my experiences with PHP on a daily basis that I think everyone else should know. This series will be a series of tutorials showing good and bad ways of doing whatever the subject is, and explaining why each is good and each is bad. I will also include tutorials that will show how to do some basic and advanced things.

This week we’re discussing a very important issue called “Dynamic Includes“ that I’ve come across a lot by using PHP and helping other users that use PHP to build their professional and personal websites. There’s a lot of bad tutorials out there that can mislead an inexperienced programmer to thinking that user input is safe and does not need to be checked. This, of course, is WRONG. User input should never be trusted and should always be vigorously checked no matter the situation. Not just because it can be malicious, but because people make mistakes. I might cover this in another article, but it is something to take into consideration when using dynamic includes that are based upon user input. Lets move on. The Bad and The Ugly … The Good is taking a break. First lets look at includes. I’m assuming you all know the basics of an include statement and the difference between a static include and a dynamic include. Just in case though, a static include is where a variable that cannot be changed is used in an include or require.

<?php include('page.php'); ?> A dynamic include is an include based upon input from a variable. The following code uses a dynamic include based on a variable’s value.

<?php $var = 'page.php'; include($var); ?> Now, this include statement isn’t all that dangerous since we define the variable right before the include, but what if it’s another situation where the variable is defined in another page or a section of the script where the variable might change before it gets to the include? This is where value checking becomes necessary. Now lets move on and take a look at our first example.

The most common use of dynamic includes is when you setup a home page where your links show up like http://example.com/?page=about.php or http://example.com/index.php?page=about.php. The most common way (and one of the worst ways of doing this, is by the code below…

<?php include($_GET['page']); ?> Now, you may argue that this works. It in fact will work, however it’s very insecure! What if someone wanting to do something malicious and say find all the users on your box? Well, if it’s Linux, all they would have to do is call http://example.com/index.php?page=/etc/passwd and it would show them the file. This of course can happen for any file on your system that apache can read. Most of the time the /etc/passwd file is in fact not world readable, so this wouldn’t be possible, but why risk it? Now lets go on to look at another situation.

Now that you’ve seen that, you’re probably thinking of other ways to try to make the script more secure. Some of you might ask “Why not put the path in front of the include to force the path?” Well, lets take a look and see! Again, we’re using the same URL example where http://example.com/?page=about.php or http://example.com/index.php?page=about.php

<?php include('/home/user/www/'.$_GET['page']); ?> OK, so we’ve appended the path to the include… safer right? WRONG! While this looks safer than before because we’ve appended the path, it’s actually just as insecure. We still have no input checking and malicious input can still be entered. This might detour some in experienced person, but others would know to put in a page that would never be found, for example http://example.com/index.php?page=hahayoucantfindmeever.php Which would then cause include to show this warning

Warning: include(/home/user/www/hahayoucantfindmeever.php) [function.include]: failed to open stream: No such file or directory in /home/user/www/index.php on line 2

  • Gasp* now you can see by the warning “include(/home/user/www/hahayoucantfindmeever.php)” that the path is “/home/user/www/” because for our input, we only used “hahayoucantfindmeever.php”. Now all the person has to do is use http://example.com/index.php?page=../../../etc/passwd and they have your /etc/passwd file again! So, as you can see, this is just as insecure as before. Now lets take a look at something a bit more secure.

The Ugly… very ugly. As I’ve said all along, user input needs to be checked against and should never be trusted! In our first two examples, our include was based upon user input and as we found, was very insecure. Now I’ll show you how to take that user input and validate it before using it in the include. We’re still not going to be doing it the best way, but I’m hoping by showing you as many examples as possible, it will give you some kind of idea of how to validate input first. Our URLs will now change to look something like http://example.com/?page=about and http://example.com/index.php?page=about OK, lets take an array of pages and check against that, here’s the code…

<?php /* this is our array of pages we can use */ $pages = array('home', 'about', 'contact');

/* Now lets test our user input */ if (!isset($_GET['page']) || !isset($pages[$_GET['page']])) {

   include('/home/user/www/'.$pages[0].'.php'); /* Grab the default page */

} else {

   include('/home/user/www/'.$_GET['page'].'.php');

} ?> This is much better than our previous examples because now we’re actually checking the input against something in our code now. While this will work and is a lot more secure, it should still never be trusted as we’re still using the user input in our include statement! While it would be much harder to bypass this, why risk it? Why leave the script open to possible attack on our include? We shouldn’t! Lets take a look at other examples on how we can secure this further. The Good … hooray, our hero has arrived! The previous array example can be expanded upon to make it a bit more secure, but since we would still be using the user input in the include statement, I won’t go into that. Lets take a look at a method that checks the user input, but in no way uses it in our include statement, thus removing user input from being used in a place where it shouldn’t. This example will use a simple switch statement to include each page based on the user input. Our example URL will still be http://example.com/?page=about or http://example.com/index.php?page=about

<?php switch ($_GET['page']) {

   case 'about':
       include('/home/user/www/about.php');
       break;
   case 'contact':
       include('/home/user/www/contact.php');
       break;
   default:
       include('/home/user/www/home.php');
       break;

} ?> There! Now we’ve removed the user input from our include statements, thus disallowing the user from getting something he isn’t supposed to! This can also be done using a if/elseif/else statement, but I prefer using switch. There are also other ways of keeping user input out of dynamic includes, but I feel this is the easiest way, and can be used in many situations. Since I’ve already shown a basic page, lets look at something more advanced. Say you want 404 error reporting if the page isn’t found, it’s not too hard to add in, lets look.

<?php $page = (isset($_GET['page']))? $_GET['page'] : ;

switch ($page) {

   case 'about':
       include('/home/user/www/about.php');
       break;
   case 'contact':
       include('/home/user/www/contact.php');
       break;
   case 'home':
   case :
       include('/home/user/www/home.php');
       break;
   default:
       include('/home/user/www/404.php');
       break;

} ?> Now you’ve got a page that displays your home page when no other page is defined, and a 404 page when the page defined isn’t found! We’ve also removed a possible notice that could have been shown by checking if $_GET['page'] was set first by using the ternary operator. All in all, this is a pretty decent example of how dynamic includes can be made safer so the user isn’t allowed to use malicious attacks against the page. Conclusion Well, we’ve looked at a lot of methods using include statements, The Good, The Bad, and there was plenty of The Ugly. I hope you learned something from this article, especially that you should always validate your input! Next article I think I’ll go a bit more in depth on validating input in general. There are many ways it can be done, and you should be aware of all of them! So, until next time! ~ Eric