views:

3270

answers:

8

My website was recently attacked by, what seemed to me as, an innocent code:

<?php
  if ( isset( $ _GET['page'] ) ) {
    include( $ _GET['page'] . ".php" );
  } else {
    include("home.php");
  }
?>

There where no SQL calls, so I wasn't afraid for SQL Injection. But, apparently, SQL isn't the only kind of injection.

This website has an explanation and a few examples of avoiding code injection: http://www.theserverpages.com/articles/webmasters/php/security/Code_Injection_Vulnerabilities_Explained.html

How would you protect this code from code injection?

+3  A: 

I'm assuming you deal with files in the same directory:

<?php
if (isset($_GET['page']) && !empty($_GET['page'])) {
  $page = urldecode($_GET['page']);
  $page = basename($page);
  $file = dirname(__FILE__) . "/{$page}.php";
  if (!file_exists($file)) {
    $file = dirname(__FILE__) . '/home.php';
  }
} else {
  $file = dirname(__FILE__) . '/home.php';
}
include $file;
?>

This is not too pretty, but should fix your issue.

Till
1. You don't need `urldecode` $_GET. PHP always decodes it for you. You should make it clear that `basename` is crucial thing in this code. Without it attacker could read sensitive files from parent directories.
porneL
+4  A: 

The #1 rule when accepting user input is always sanitize it. Here, you're not sanitizing your page GET variable before you're passing it into include. You should perform a basic check to see if the file exists on your server before you include it.

Kyle Cronin
That still doesn't solve a =n injection attack! To sanitize it, you need to be sure the input is a safe, allowed file. Only a whitelist will suffice.
DGM
+22  A: 

Use a whitelist and make sure the page is in the whitelist:

  $whitelist = array('home', 'page');

  if (in_array($_GET['page'], $whitelist)) {
        include($_GET['page'].'.php');
  } else {
        include('home.php');
  }
yjerem
If possible, avoid including files dynamically altogether. `include`, as you have experienced, is almost as dangerous as `eval`.
tdammers
+9  A: 

Another way to sanitize the input is to make sure that only allowed characters (no "/", ".", ":", ...) are in it. However don't use a blacklist for bad characters, but a whitelist for allowed characters:

$page = preg_replace('[^a-zA-Z0-9]', '', $page);

... followed by a file_exists.

That way you can make sure that only scripts you want to be executed are executed (for example this would rule out a "blabla.inc.php", because "." is not allowed).

Note: This is kind of a "hack", because then the user could execute "h.o.m.e" and it would give the "home" page, because all it does is removing all prohibited characters. It's not intended to stop "smartasses" who want to cute stuff with your page, but it will stop people doing really bad things.

BTW: Another thing you could do in you .htaccess file is to prevent obvious attack attempts:

RewriteEngine on
RewriteCond %{QUERY_STRING} http[:%] [NC]
RewriteRule .* /–http– [F,NC]
RewriteRule http: /–http– [F,NC]

That way all page accesses with "http:" url (and query string) result in an "Forbidden" error message, not even reaching the php script. That results in less server load.

However keep in mind that no "http" is allowed in the query string. You website might MIGHT require it in some cases (maybe when filling out a form).

BTW: If you can read german: I also have a blog post on that topic.

BlaM
I now little about htaccess files. I've something that looks like that, for a routing system that could use special characters and spaces. Could you post some examples of forbidden url, blocked by your config file? Thanks
Fábio Antunes
+3  A: 

pek, for a short term fix apply one of the solutions suggested by other users. For a mid to long term plan you should consider migrating to one of existing web frameworks. They handle all low-level stuff like routing and files inclusion in reliable, secure way, so you can focus on core functionalities.

Do not reinvent the wheel. Use a framework. Any of them is better than none. The initial time investment in learning it pays back almost instantly.

Michał Rudnicki
+3  A: 

Pek, there are many things to worry about an addition to sql injection, or even different types of code injection. Now might be a good time to look a little further into web application security in general.

From a previous question on moving from desktop to web development, I wrote:

The OWASP Guide to Building Secure Web Applications and Web Services should be compulsory reading for any web developer that wishes to take security seriously (which should be all web developers). There are many principles to follow that help with the mindset required when thinking about security.

If reading a big fat document is not for you, then have a look at the video of the seminar Mike Andrews gave at Google a couple years back about How To Break Web Software.

Cheekysoft
+1  A: 

Some good answers so far, also worth pointing out a couple of PHP specifics:

The file open functions use wrappers to support different protocols. This includes the ability to open files over a local windows network, HTTP and FTP, amongst others. Thus in a default configuration, the code in the original question can easily be used to open any arbitrary file on the internet and beyond; including, of course, all files on the server's local disks (that the webbserver user may read). /etc/passwd is always a fun one.

Safe mode and open_basedir can be used to restrict files outside of a specific directory from being accessed.

Also useful is the config setting allow_url_fopen, which can disable URL access to files, when using the file open functions. ini-set can be used to set and unset this value at runtime.

These are all nice fall-back safety guards, but please use a whitelist for file inclusion.

Cheekysoft
A: 

@pek - That won't work, as your array keys are 0 and 1, not 'home' and 'page'.

This code should do the trick, I believe:

<?php

$whitelist = array(
  'home',
  'page',
);

if(in_array($_GET['page'], $whitelist)) {
  include($_GET['page'] . '.php');
} else {
  include('home.php');
}

?>

As you've a whitelist, there shouldn't be a need for file_exists() either.

ceejayoz