views:

508

answers:

1

Hi,

I am working on a directions service where users enter the from and to addresses and get the directions table ( that gives turn by turn information ) along with a map showing the route.

Below is the complete source code ( getdirections.php ):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
 <title>Directions</title>
 <style>
  * { font-family: Verdana; font-size: 96%; }
  label { width: 15em; float: left; }
  label.error { display: block; float: none; color: red; vertical-align: top; }
  p { clear: both; }
  .submit { margin-left: 12em; }
  em { font-weight: bold; padding-right: 1em; vertical-align: top; }
 </style>
 <script src="jquery-1.3.1.js" type="text/javascript">
 </script>
 <script src="http://maps.google.com/maps?file=api&amp;amp;v=2&amp;amp;sensor=false&amp; amp;key=[Your Key Here]" 
type="text/javascript">
 </script>
</head>
<body onunload="GUnload()">
<div id="container">    
<div id="directform">
    <form id="direct" action="getdirections.php" method="get">
<p><label for="loc1">From Here:</label>
<input id="loc1" type="text" name="location1" class="required" /></p>
<p><label for="loc2">To Here:</label>
<input id="loc2" type="text" name="location2" class="required" /></p>
<p><input type="submit" value="Search" /></p>
    </form>
</div>
<?php
function filterInput ( $input ) {
$replacement = ',';
$input = preg_replace('/(\n|\r)+/', $replacement, $input);
$replacement = " ";
$input = preg_replace('/(\t)+/', $replacement, $input);
$inputarray = explode(' ', $input);
foreach ( $inputarray as $i => $value ) {
     $ch = '';
     if ( $value[strlen($value)-1] == ',') {
 $ch = ',';
 $value = substr($value, 0, -1);
     }

$value = 
      preg_replace('/^(\&|\(|\)|\[|\]|\{|\}|\"|\.|\!|\?|\'|\:|\;)+/', "", $value);

$inputarray[$i] = 
      preg_replace('/(\&|\(|\)|\[|\]|\{|\}|\"|\.|\!|\?|\'|\:|\;)+$/', "", $value);
$inputarray[$i] = $inputarray[$i].$ch;
}
$filteredString = implode(" ", $inputarray);
return $filteredString;
}

?>
</div>    
<table class="directions">
 <tr>
 <td valign="top">
  <div id="directions" style="width: 100%"></div>
 </td>
</tr>
<tr>
 <td valign="top">
 <div id="map_canvas" style="width: 250px; height: 400px"></div>
 </td>
</tr>
<td valign="top">
 <div id="directions_url"></div>
</td>   
</table>

 <noscript><b>JavaScript must be enabled in order for you to use Google Maps.</b> 
  However, it seems JavaScript is either disabled or not supported by your browser. 
  To view Google Maps, enable JavaScript by changing your browser options, and then 
  try again.
 </noscript>
 <script type="text/javascript">

 // This programming pattern limits the number of global variables
 // Thus it does not pollute the global namespace
 // for_directions is the only global object here.
 for_directions = function(){

// The map is loaded into the div element having id specified by mapid
// private variable
var mapid = "map_canvas";

// The direction listing is loaded into the div element having id specified by directionsid.
// private variable
var directionsid = "directions";

// From here
// private variable
var location1;

// To here
// private variable
var location2;

// The functions ( init and addevent )  are public methods of for_directions object
return {
 // Called on loading of this page
 // public method
 init: function (){
  location1 = "<?= filterInput($_GET['location1']) ?>" || 0;
  location2 = "<?= filterInput($_GET['location2']) ?>" || 0;
  var directions = document.getElementById(directionsid);
  directions.innerHTML = "Please check the address and try again";

  if ( GBrowserIsCompatible() && location1 != 0  && location2 != 0){
   mapAddress(location1, location2);
         }
 },

 // This method is cross browser compliant and is used to add an event listener
 // public method
 addEvent:function(elm,evType,fn,useCapture){
  if(elm.addEventListener){
   elm.addEventListener(evType, fn, useCapture);
   return true;
  } else if (elm.attachEvent) {
   var r = elm.attachEvent('on' + evType, fn);
   return r;
  } else {
   elm['on' + evType] = fn;
  }
 }
};

// Called from init 
// private method
    function mapAddress ( address1, address2 ){
     var geocoder = new GClientGeocoder();
 var directions = document.getElementById(directionsid);
 var i = 0;

 geocoder.getLatLng( address1, function(point1){
     if (point1){
  geocoder.getLatLng ( address2, function(point2){
      if (point2){
   getDirections();
      } else {
   directions.innerHTML = "Please check the address and try again";
      }

  });
     } else {
  directions.innerHTML = "Please check the address and try again";
     }

 });
}

// Called from mapAddress to load the directions and map
// private method
function getDirections( ){
  var gmap = new GMap2(document.getElementById(mapid));
  var gdir = new GDirections(gmap,document.getElementById(directionsid));
  gdir.load("from: " + location1 + " to: " + location2,
      { "locale": "en_US" });
  generateURL();
}

function generateURL(){
 var url = "http://maps.google.com/maps?saddr=";
 url += location1;
 url += "&daddr=";
 url += location2;
 var a = $("<a></a>").attr('href',url);
 $(a).text("Google Maps");
 $("#directions_url").append(a);
}
}();
// The (); above results in the function being interpreted by the browser just before   the page is loaded.

// Make for_directions.init as the listener to load event
// Note that the init method is public that why its accessible outside the object scope
for_directions.addEvent(window, 'load', for_directions.init, false);
</script>
</body>
</html>

If you try out this code on your system name it as getdirections.php. The only thing you would need to change is the google maps api key. You can get the key here.

Once you generate your key put in the key parameter ( reproduced the line below for convenience ):

<script src="http://maps.google.com/maps?file=api&amp;amp;v=2&amp;amp;sensor=false&amp; amp;key=[Your key here]" 
type="text/javascript">

As is seen from the code above, I get the input through PHP and do the processing in Javascript. Now, I don't want users to get away with any kind of input ( javscript, dangerous HTML, etc ). I tried using the urlencode function in PHP. However, the encoded user input is not accepted by the javascript code and fails even on good input.

As a workaround to this problem I wrote a filterInput function in PHP that will replace/delete certain characters and thwart any attempt by the user to try and execute Javascript code through input.

This worked well. However, when the user did try give malicious input, say like "+alert("hello")+" with both the beginning and ending quotes included, the filterInput function trimmed the leading and tailing quotes and the resulting string is below:

+alert("hello")+

Now when code below is executed:

location1 = "<?= filterInput($_GET['location1']) ?>" || 0;

PHP substitues the function call with its return value like below:

location1 = "+alert("hello")+" || 0;

Execution of the script halts with the line above with an error ( missing ; before statement )

Note, had I not trimmed the quotes and used $_GET['location1'] directly I would get.

location1 = ""+alert("hello")+"" || 0;

alert("hello") would get executed!!

So, I am in a fix. If I filter input I get a javascript error on certain user input and if I don't filter input I allow users to execute any kind of javascript.

My questions then are:

  • What is a proper and secure way to handle input on the web?
  • Is this kind of user input crossing languages ( from PHP to Javascript ) ok?
  • Apart from the user being able to execute javascript what other kinds of security threats does is this piece of code vulnerable?

Thanks for reading!!

Please help.

+1  A: 

You can try json_encode in php and an eval in javascript.

If the JS is executed on the same machine where the input is done, I wouldn't be too concerned about security. A hacker can hack his own machine, it should be no problem.

SorinV