<

Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x

Update and Secure Legacy PHP Scripts wih filter_input_array and PDO Prepared Statements

Published on
4,222 Points
1,222 Views
Last Modified:
John Kawakami
Wake up and code.
Many old projects have bad code, but the budget doesn't exist to rewrite the codebase. You can update this code to be safer by introducing contemporary input validation, sanitation, and safer database queries.

I’ve been a real nut for filter_var() for years, and have come up with concise ways to use it, but totally missed this other function, which, at first look, seemed a little too specialized.

filter_input_array()

I was so wrong. This is a great way to filter inputs.  I figured this out while updating some code because it was throwing a zillion “Notice, index foobar not defined.” in the error logs.


Legacy Superglobal $_GET


That’s what happens when your legacy code looks like this:

$bar = $_GET['bar'];

all over the place, and the index “bar” is not defined.  One way to fix this is with filter input:

$bar = filter_input(INPUT_GET, 'bar', FILTER_SANITIZE_STRING);

filter_input() checks for the existence of the value, first, before returning it.  It returns false if the value is unset.


I'll describe filter_input's sibling, filter_input_array later.


To make your scripts safer, you need to stop using string interpolation and concatenation to write database queries.


Eliminate Legacy String Interpolation in Queries


There’s a certain irony here – few articles explain and advocate using filter_input() because many programmers are now using frameworks like Laravel that come with object-relational mappers, Request and Response objects. These encapsulate the superglobals $_GET and $_POST.  


They also encapsulate the database access, so there’s less need to know about quoting SQL input.


Consequently, there’s going to be a lot of existing legacy code with some “worst practices”.


They should be fixed.


Remember when people wrote code like this:


mysql_query("SELECT * FROM users WHERE pass='$_POST[password]'");


In case you didn’t know, you could easily hack this via a “SQL injection attack.”  Pass the page input with password set to:


' OR ''='


The concatenated string would look like this:


SELECT * FROM users WHERE pass='' OR ''=''

If you didn’t already know this, stop right now and read up about SQL injection attacks.  Just search. There’s a lot of info.  You should not write web apps if you don’t know about SQL injection attacks.


Avoiding the SQL Injection Attack


The quick fix to avoid these injection attacks is to use PHP Data Objects (PDO) prepared statements.


Here’s PDO code:

$db = new PDO ( $dsn, $user, $password, $options ) ;
$sth = $db->prepare("SELECT * FROM users WHERE pass=?");
$sth->execute($password);

It’s one extra line, to prepare() then execute(), but this pattern eliminates risks from SQL injection.


The execute method escapes $password correctly, and then inserts into the SQL string, surrounded by quotes.  SQL injection is averted.


Using filter_input to coerce values to correct type


Filtering input can help avoid some logic errors you can make in PHP.  All data comes in as strings, but you sometimes want numbers.  In legacy PHP, it’s common to see this pattern:


$foo = $_GET['foo'];
if ($foo == 1) ...

It is comparing a string to an int.  It works, because PHP converts strings to numbers if they look like numbers, but it makes us lazy about data types.


What if you had a function like this:


function f($d) {
 if (is_string($d)) { ... }
 else if (is_int($d)) { ... }
}

Then passing a value of “1” as $d causes one branch of code to execute, while passing the number 1 causes the other branch to execute.


With filter_input, you can validate the input and convert it to the correct type:


$foo = filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT);


Then, if foo’s value is a string that looks like an int, the filter_input() return value will be the int value of the string representation.


filter_input_array Saves Lines of Code


filter_input_array() is even better than filter_input().  This code below shows not only savings in lines of code, but a nice convention to use the output of filter_input_array() directly into PDO prepared statements.


Imagine you have foo, the int, and bar, the string.  You can filter for both at once:


$get = filter_input_array(INPUT_GET, [
 'foo' => FILTER_VALIDATE_INT,
 'bar' => FILTER_SANITIZE_STRING
]);


Look at that!  You can take all your inputs, and validate them in one place, right at the top of your code.

You should put this code right at the top of any method that uses the input.  If the old code is OO, you can go the next step and separate out sanitizing and loading values into a method.


Then, later, you eliminate all uses of $_GET, replacing it with $get.


$foo = $_GET['foo'];


Becomes:


$foo = $get['foo'];


Or, in OO code:


$foo = $this->get('foo');


Then, further down,  you can fix up the database code with PDO. PDO has a nice feature where you can name parameters, like this:


$sth = $db->prepare("SELECT * FROM users WHERE foo=:foo");
$sth->execute(['foo'=>$password]);


Let’s try this with foo and bar, and we’ll use $get instead of $_GET.


$sth = $db->prepare("INSERT INTO glorp COLUMNS(foo,bar) VALUES(:foo,:bar)");
$sth->execute($get);


Well, look at that!


The code isn’t just safer, it’s also shorter. Concatenate the two parts together, and you have:


$get = filter_input_array(INPUT_GET, [
 'foo' => FILTER_VALIDATE_INT,
 'bar' => FILTER_SANITIZE_STRING
]);
$sth = $db->prepare("INSERT INTO glorp COLUMNS(foo,bar) VALUES(:foo,:bar)");
$sth->execute($get);


The db link setup isn’t there, the FILTER_* are not strict enough, and there are no views, but you get the idea.  This is an easy, quick way to take old scripts and make them safer.

0
Comment
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
0 Comments

Featured Post

2017 Webroot Threat Report

MSPs: Get the facts you need to protect your clients.
The 2017 Webroot Threat Report provides a uniquely insightful global view into the analysis and discoveries made by the Webroot® Threat Intelligence Platform to provide insights on key trends and risks as seen by our users.

Join & Write a Comment

The viewer will learn how to dynamically set the form action using jQuery.
This video Micro Tutorial shows how to password-protect PDF files with free software. Many software products can do this, such as Adobe Acrobat (but not Adobe Reader), Nuance PaperPort, and Nuance Power PDF, but they are not free products. This vide…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month