CodeWatch

Finally, we reach a more glamorous class of vulnerability. Injection attacks can take many forms; SQL injection, XPath/XML/SOAP injection, LDAP injection, and Command injection are just a few types. This post will cover some PHP countermeasures to the more common forms of injection, starting with SQL injection:

SQL Injection Mitigations:

The first step in preventing SQL injections is to perform input validation on all user supplied data. Review our coverage of input validation in the OWASP A2 – Cross-Site Scripting (XSS) series here, here, and here.

The next step is to take advantage of PHP’s support for prepared statements, also known as parameterized queries. Prepared statements utilize the database driver to pass user supplied data as a parameter in a query. This causes the database to interpret the data as a single value for a particular portion of the query. The database will interpret the parameter as data, even if SQL commands were input as part of the data, instead of interpreting the data as SQL statements.

Prepared statements, when used properly, prevent known forms of SQL injection attacks. Here is an example of a prepared statement with PHP:

  // This example makes the assumption that the 
  // database handle ($dbh) has already been setup.
  // It also assumes that input validation has already
  // been performed and the three values that were 
  // input for the query were assigned to $param1,
  // $param2, and $param3.
  $qry = $dbh->prepare("SELECT * FROM customers 
      WHERE User_Name = ? AND User_Pass = ? AND User_ID = ?");
  $qry->bindValue(1, $param1, PDO::PARAM_STR);
  $qry->bindValue(2, $param2, PDO::PARAM_STR);
  $qry->bindValue(3, $param3, PDO::PARAM_INT);
  $qry->execute();

 
In the above example, we are running a SELECT statement against the database with three parameters. The next three lines bind user supplied data to the three parameters and the final statement executes the query. The question marks are placeholders for the user supplied data. They are referenced in order of appearance, so the “1” value in the first bindValue statement binds $param1 to the User_Name question mark placeholder.

We have taken this a step further and explicitly defined the data type for added security. The bindValue statements set the data type with the third value, PDO::PARAM_STR and PDO::PARAM_INT. A list of the different data types that can be set can be found here. Where possible, it is always best to validate the input data, the input data type, and the input length/size for appropriate values.

There is also a WRONG WAY to use prepared statements. It is fairly common to see the following (THIS IS AN INVALID WAY OF USING PREPARED STATEMENTS):

  $qry = $dbh->prepare("SELECT * FROM customers 
      WHERE User_Name = $param1 AND User_Pass = $param2 AND User_ID = $param3");

 
The above example concatenates user supplied data in with the query string. This is just as vulnerable as running a regular query. This example does not bind user supplied data to a query parameter. DO NOT DO THIS.

Prepared statements also have the benefit of improving database query performance. Running multiple queries with varying parameters without using prepared statements causes the database to analyze, compile, and optimize each query. Prepared statements only need to be analyzed, compiled, and optimized once.

LDAP Injection Mitigations:

Unfortunately, the PHP drivers that support LDAP do not have prepared statement/parameterized query support. This means we must rely on input validation for effective injection mitigation. Whitelist validation is always the preferred method, but sometimes a blacklist is the only approach for a given application.

The following characters should be filtered out of user supplied data passed to an LDAP query:

  • &
  • |
  • =
  • *
  • (
  • )
  • ;
  • !
  • <
  • >
  • ~
  • ,

If using a whitelist approach, make sure these characters are omitted from the acceptable characters filter. If using a blacklist approach, make sure these characters are included in the denied characters filter.

SOAP/XML/XPath Injection Mitigations:

As with LDAP, the PHP SOAP/XML/XPath drivers do not have prepared statement/parameterized query support. This means we must rely on input validation and encoding for effective injection mitigation. Whitelist validation is always the preferred method, but sometimes a blacklist is the only approach for a given application.

The following characters should be filtered out of user supplied data passed to a SOAP/XML/XPath query:

  • <
  • >
  • /
  • [
  • ]
  • !
  • @
  • =
  • (
  • )
  • *
  • :
  • *

If using a whitelist approach, make sure these characters are omitted from the acceptable characters filter. If using a blacklist approach, make sure these characters are included in the denied characters filter.

In addition, encode user supplied data in case there is a special character that you have missed in one of your filter lists. This can be performed by forcing the data type (found in our ASVS 5.4 solution here) and then using a PHP function to encode the data (found in examples here).

Some example code as a reminder:

  // Encode the data.  Encode both single and double
  // quote characters, use UTF-8 as the character set,
  // and do not double encode ("false" parameter).
  function outputEncode($data) {
    $encodedData = htmlentities($data, ENT_QUOTES, "UTF-8", false);
    return $encodedData;
  }

  // Convert the data to the UTF-8 character set and
  // silently drop any characters that can't be converted.
  // Pass the value to the output encoder above.
  function utfEncode($data) {
    $utfEncoded = iconv("UTF-8", "UTF-8//IGNORE", (string)$data);
    return outputEncode($utfEncoded);
  }

  // Take a POST'ed parameter and encode it
  $uservalue = utfEncode($_POST['param1']);

 

These two steps should to a long way towards preventing SOAP/XML/XPath related injection attacks.

Command Injection

The best way to avoid command injection is to not pass user supplied data to a function that calls an operating system level command. If users must be able to run commands through PHP, then I suggest indirectly mapping specific commands and their parameters to values that the user can select. We covered indirect object mapping here. If this is not an option, split the request into two sections; the command to be run and the user data to be supplied to it. Create a whitelist to deny requests to commands that aren’t on the approved list. Create a blacklist to filter out the following characters at a minimum:

  • |
  • &
  • `
  • !
  • *
  • ;

If possible, utilize a whitelist that limits input data to the commonly used characters for command line executable parameters:

  • a-zA-Z0-9
  • =
  • /
  • .

That wraps up our post on PHP injection mitigations. We are down to one final OWASP TOP 10 issue, A8 – Failure to Restrict URL Access. See you next time.

Tagged with:
 

Leave a Reply

Your email address will not be published. Required fields are marked *