Re: anti SQL injection method in php manual.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




----- Original Message ----- From: "Dotan Cohen" <dotancohen@xxxxxxxxx>
To: "Satyam" <Satyam@xxxxxxxxxxxxx>
Cc: "PHP General (E-mail)" <php-general@xxxxxxxxxxxxx>
Sent: Friday, May 26, 2006 6:36 PM
Subject: Re:  anti SQL injection method in php manual.


On 5/26/06, Satyam <Satyam@xxxxxxxxxxxxx> wrote:
----- Original Message -----
From: "Dotan Cohen" <dotancohen@xxxxxxxxx>


> // Make a safe query
> $query = sprintf("SELECT * FROM users WHERE user=%s AND password=%s",
>           quote_smart($_POST['username']),
>           quote_smart($_POST['password']));
>
> mysql_query($query);
> ?>
>
> What is the purpose of the sprintf?

Can't see any reason for that.

I don't know if I already posted this on this list or perhaps in the Spanish one, but below I'm copying my SQL building function, like an sprintf but for
SQL.

Notice it assumes a global variable $table_prefix that is meant for
customized table prefixes, to avoid conflicts with existing applications.

Depending on the font and line length, it will be broken in many parts. The arrows showing the different parts of the regular expresion point anywhere,
but should be fine if shown in a fixed pitch font.

Satyam


/**
 *  Builds a properly formatted and escaped SQL statement using an SQL
template and a list of arguments.
 *
* The function scans the template for query marks ? which are placeholders
for the arguments
 *  Query marks are to be followed by format descriptors.
 *
 *  The first argument, the template, is mandatory.  If the template
contains no query marks
 *  and no argument is given, the function does nothing.
 *
 *  Placeholders have the following format and are not case sensitive:
 *
 *  <b>?[nn][m]t</b>
 *
 *  Where:
 *
 *  <b>?</b> Begining of placeholder for argument
 *
 *  <b>nn - <i>position</i></b> number of the argument to be replaced.
 *      Argument 0 is the template itself and is not valid.
 *   The first argument after the SQL template is number 1
 *   If no number is given, arguments are taken sequentially.
 *  Numbered replacements do not move the sequential argument pointer.
 *  Arguments beyond the actual number present are considered null
 *
 *  <b>m - <i>modifier</i></b> [optional] indicates what to do if the
argument is null
 *  - m: mandatory, if the argument contains null, it will give a fatal
error.
* - z: null, if the argument is 0 or an empty string, it will be replaced
by null
 *
 *  <b>t - <i>data type</i></b> the placeholder will be replaced by the
argument as follows
 *  - s: string, if not null, it will be escaped and enclosed in quotes
 *  - i: integer, the integer value (intval() function) of the argument
 *  - f: float, the floating point value (floatval() function) of the
argument
* - d: date, the argument will be assumed to represent a timestamp and it
will be converted to yyyy-mm-dd and quoted
 *  - b: boolean, anything evaluated to false will be 0, otherwise 1
 *  - t: table prefix, the value of the global variable
<i>$table_prefix</i>, escaped and unquoted
 *       It takes no argument from the argument list
 *
 *  Example:
 *  <code>
 *  echo BuildSql('Insert into ?ttable
(?s,?ns,?mi,?d,?ni,?i)','Something','',5,time(),0,null);
 *  </code>
 *  will return:
 *  <pre>
 *  Insert into wp_table ('Something',null,5,'2006-05-15',null,0)
 *  </pre>
 *
* Note that placeholders do not need to be quoted, if quotes are required
(strings or dates) they will be provided
 *
 *  @param string $query Template of SQL statement
 *
 *
 *  @param mixed $value,... Values to be replaced into placeholders,
sequentially unless stated otherwise
 *
 *  @return string properly formated and escaped SQL statement
 *
 *  The function will trigger a fatal error if an unknown formatting
character is found.
 *  Unused arguments will produce warnings.
 *  Missing arguments will be assumed null and will trigger a fatal error
 *    if the placeholder has the mandatory modifier m.
 *  There is no provision to put a literal ? into the SQL statement since
the ? is not a valid SQL operator,
* the only valid place for query marks are in literal string constants,
which can be passed to this
 *    function in an argument
 */

 $table_prefix = 'wp_';

 echo BuildSql('Insert into ?ttable
(?s,?ns,?mi,?d,?ni,?i)','Something','',5,time(),0,null);

function BuildSql($query) {
 global $table_prefix;

 $num_args = func_num_args();  // number of arguments available
 $args_used = (1 << $num_args) -2; // bit mask to check if arguments are
used
    /*                      +---------Anything up to first query mark
| +------ query mark, start of placeholder
                            |      |    +---- position of argument
                            |      |    |     +----- modifier
                            |      |    |     |       +--- data type
       |      |    |     |       |                     */
 if
(preg_match_all('|([^\?]*)(\?(\d?\d)?([mn]?)([sifdbt])?)*|i',$query,$matches,PREG_SET_ORDER))
{
  $arg_pointer = 1; // sequential pointer to arguments
  $s = '';  // output SQL statement
  foreach($matches as $match) {
   $NullIfEmpty = false;
   $s .= $match[1];    //concatenate everything up to question mark
   $type = strtolower($match[5]);  // read datatype

   // read the value of the argument
   if ($type =='t') {
$value = $table_prefix; // t is a special case, it takes no argument
from the list
    $n = 0;
   } elseif (intval($match[3])) {  // if there is a nn position modifier,
read it
           // (null evaluates to 0, which is an invalid position anyhow)
    $n = intval($match[3]);
} else { // else, read the next sequential argument an increment the
argument pointer
    $n = $arg_pointer++;
   }
if ($n) { // if there was an argument to be read (notice t does not ask
for an argument)
    $args_used &= ~ (1 << $n);   // some bitwise magic to unset the bit
position representing the argument
    if ($n >= $num_args) $value = null;   // if argument requeste beyond
those available, assume it null
    else $value = func_get_arg($n);      // otherwise, read it
   }

   switch ($match[4]) {  // read modifier
    case 'm':   // mandatory?
    case 'M':
if (is_null($value)) trigger_error("Missing value for argument $n near:
... ${match[0]} ...",E_USER_ERROR);
     break;
    case 'n':  // null if empty?
    case 'N':
     $NullIfEmpty = true;  // set flag
     break;
   }
   switch($type) { // now we process $value according to datatype and
$NullIfEmpty flag
          case 's':
    case 'S':
     if ($NullIfEmpty and strlen($value) == 0) {
      $s .='null';
     } else {
$s .= "'" . mysql_real_escape_string($value) . "'"; // replace
with this->escape if within class wpdb
     }
           break;
          case 'i':
    case 'I':
     if ($NullIfEmpty and $value == 0) {
      $s .='null';
     } else {
            $s .= intval($value);
     }
           break;
          case 'f':
    case 'F':
     if ($NullIfEmpty and $value == 0) {
      $s .='null';
     } else {
            $s .= floatval($value);
     }
           break;
    case 'd':
    case 'D':
     if ($NullIfEmpty and empty($value)) {
      $s .='null';
     } else {
      $s .= "'" . date('Y-m-d', $value) . "'";
     }
     break;
    case 'b':
    case 'B':  // booleans cannot be null
     $s .= intval($value!=false);
     break;
    case 't':
    case 'T':
     $s .=  mysql_real_escape_string($value);
     break;
    case null:
     break;
          default:
trigger_error('Unknown formatting character in BuildSql: ' . $match[0]
. " en SQL <br />[$query]",E_USER_ERROR);
   }

  }
 }
 // if all arguments used, $args_used will be zero
 if ($args_used) {
  for($i=1;$i<$num_args;$i++) {
   $args_used >>=1; // bitmaks is shift right
   // if right most bit still 1, it means it has not been used.
   if ($args_used & 1) trigger_error("Argument $i not used");
  }
 }

 return $s;
}



Thanks, Satyam. I'm having a hard time following your code, however.
What is the advantage of it? I'm not criticizing, rather, trying to
learn. What types of attacks will it prevent that the code I copied
from the manual will not?

Thanks.

Dotan Cohen
http://gmail-com.com


The escaping of invalid characters is already included and beside, you can simplify your SQL statements such as the example (taken from the phpdocs header).

echo BuildSql('Insert into ?ttable (?s,?ns,?mi,?d,?ni,?i)','Something','',5,time(),0,null);

will return:

Insert into wp_table ('Something',null,5,'2006-05-15',null,0)

So, as you see, it will handle date conversion from internal PHP to SQL, it will quote and escape strings, it will either use null or 0 o '' according to formatting options and it will add the contents of the $table_prefix variable wherever you put a ?t. It spares you a lot of trouble on the whole SQL instruction, not just on escaping every individual field.

And you don't need to check the code, just read the comments, that's why I bothered putting them there.

Satyam

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php


[Index of Archives]     [PHP Home]     [Apache Users]     [PHP on Windows]     [Kernel Newbies]     [PHP Install]     [PHP Classes]     [Pear]     [Postgresql]     [Postgresql PHP]     [PHP on Windows]     [PHP Database Programming]     [PHP SOAP]

  Powered by Linux