Monday, March 17, 2008

All User Input Is Malicious!

If you, like me nowadays, obide by the above statement, you have a much better chance to avoid monday morning calls telling you someone "hacked" your website.

I just got off the phone with someone that has been developing websites for years and years, have multinational corporations as customers, and with serious problems with input validation. In just 5 minutes over the phone, I could access data from several of the websites he had made in a way their data was not supposed to be accessed, simply by inputing malicious data in a few form fields. He was ofcourse chocked, not the monday morning he had expected, but nevertheless he learnt his lesson and started working through the code of his most important customers.

All of this made me think: "How many web site developers with 10 years or more
in the business has the same problem". Normal reasoning and multiplication made
this thought send chills down my spline..

So, for what it's worth, I give you my contribution to safe-up the web a little
bit. It's a variation of the code I have used for several projects to validate
input in PHP. I hope you find it useful, and that you implement it, or
something similar in your projects.


<?php

// Defines used as $method parameter to getPP()
define('PP_GET'1);
define('PP_POST'2);
define('PP_GET_POST'3);
define('PP_POST_GET'4);

function 
getPP($name$format$method PP_GET)
{
  unset(
$first);
  unset(
$second);

  switch (
$method) {
  case 
PP_GET:
    
$first $_GET;
    break;
  case 
PP_POST:
    
$first $_POST;
    break;
  case 
PP_GET_POST:
    
$first $_GET;
    
$second $_POST;
    break;
  case 
PP_POST_GET:
    
$first $_POST;
    
$second $_GET;
    break;
  default:
    
// This function should LOG (& Send Email)
    
internalError("getPP(): Invalid parameter method: $method");
    break;
  }
  if (!isset(
$first))
    
internalError("getPP(): Sanity check failed");

  if (isset(
$first[$name]))
    
$var $first[$name];
  else if (isset(
$second) && isset($second[$name]))
    
$var $second[$name];
  if(isset(
$var) && $format) {
    if(!
checkAttribute($format$var))
      
$var false;
  }

  if(!isset(
$var) || $var == "")
    unset(
$var);

  return @
$var;
}

function 
checkAttribute($name$value)
{
  
// List of known attribute types ($name)
  
$allowedAttributes =
    Array(
"username"        => '^([+~!#\"\ 0-9a-zA-Z_-])*$',
      
"parameter" => '^[A-Za-z 0-9_\.\-]+$',
      
"page"            => '^[a-z_0-9]+$',
      
"email"           => '^[A-Za-z0-9]+[A-Za-z0-9_\.-]*@([a-z0-9]+([\.-][a-z0-9]+)*)\.[a-z]{2,4}$',
      
"common"          => '^[A-Za-z 0-9åäöÅÄÖ\.,/\-_]+$',
      
"password"        => '^([A-Za-z0-9_@!\*&#?\.,-_]){3,}$',
    );
  
$regexp "~".str_replace("~""\\~"$allowedAttributes[$name]) . "~";
  
$regexp utf8_encode($regexp);
  if(
preg_match($regexp$value)!=0) {
    return 
TRUE;
  } else {
    return 
FALSE;
  }
}
?>