2

Just a quick question: is the following PHP code secure? Also is there anything you think I could or should add?

    $post = $_GET['post'];

    if(is_numeric($post))
    {
        $post = mysql_real_escape_string($post);
    }
    else
    {
        die("NAUGHTY NAUGHTY");
    }

    mysql_select_db("****", $*****);

    $content = mysql_query("SELECT * FROM tbl_***** WHERE Id='" . $post . "'");
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oliver Bayes-Shelton
  • 6,135
  • 11
  • 52
  • 88

6 Answers6

9

In this particular case, I guess the is_numeric saves you from SQL injections (although you would still be able to break the SQL statement, cf Alex' answer). However, I really think you should consider using parameterized queries (aka. prepared statements) because:

  1. They will protect you even when using parameters of non-numeric types
  2. You do not risk forgetting input sanitation as you add more parameters
  3. Your code will be a lot easier to write and read

Here is an example (where $db is a PDO connection):

$stmt = $db->prepare('SELECT * FROM tbl_Persons WHERE Id = :id');
$stmt->execute(array(':id' => $_GET['post']));
$rows = $stmt->fetchAll();

For more information about parameterized SQL statements in PHP see:

Community
  • 1
  • 1
Jørn Schou-Rode
  • 37,718
  • 15
  • 88
  • 122
  • Sorry but what is a parameterized query ? – Oliver Bayes-Shelton Feb 18 '10 at 14:37
  • Check the line where $preparedStatement is defined with prepare(). Parameterized query is like a SQL template with placeholders, where you can fill the blanks by passing an array with the tags and values to the query with execute(). Finally you get the data with fetchAll(). It's PDO if i'm not mistaken. – Joel A. Villarreal Bertoldi Feb 18 '10 at 14:43
  • 1
    Added a few links that should help you get started if you chose to go this way - and you really should ;) – Jørn Schou-Rode Feb 18 '10 at 14:45
  • I totally support the use of prepared statements here (+1). Just to be nit-picky: the original code is not protected against sql injections by the is\_numeric() test. That's already been taken care of by using real\_escape\_string() and passing the argument as a string literal to MySQL: `Id='" . $post . "'"`. is\_numeric() (or any other kind of test on the "format"/type of $id) is an additional test with "additional" consequences (`die()` in the example) – VolkerK Feb 18 '10 at 16:05
2

It's a little rough, but I don't immediately see anything that will cause you any serious problems. You should note that hexidecimal notation is accepted within is_numeric() according to the documentation. You may want to use is_int() or cast it. And for clarity, I would suggest using parameterized queries:

$sql = sprintf("SELECT col1 
                FROM tbl 
                WHERE col2 = '%s'", mysql_real_escape_string($post));

In this case, $post would be passed in as the value of %s.

Sampson
  • 265,109
  • 74
  • 539
  • 565
2

is_numeric will return true for hexadecimal numbers such as '0xFF'.

EDIT: To get around this you can do something like:

sprintf('%d', mysql_real_escape_string($post, $conn));
//If $post is not an int, it will become 0 by sprintf

Look at the snippet here on php.net for more info.

AlexV
  • 22,658
  • 18
  • 85
  • 122
  • Ahh thanks I will investigate , Any advice on how to get around this ? – Oliver Bayes-Shelton Feb 18 '10 at 14:37
  • 1
    I would recommend using ctype_digit($string), it returns true if all the characters of $string are digits(regex pattern [0-9]* ), this would be especially good if your id column contains no negative values. – Kenzal Hunter Feb 18 '10 at 15:05
  • ..and I'd still recommend eliminating that test all together, unless there's an explanation why this is the best place to have such a constraint on the `id` field. The function(?) imposes its own idea of what an `id` is on the table layout and data insertion code. Might be necessary or a brilliant idea but it might also be an annoying limitation to anyone who must remove it when `id` becomes a more "relaxed" type in the database. The test comes with a (maybe small) price tag and you'd have to explain why it's worth it. (could be perfectly valid though, context matters) – VolkerK Feb 18 '10 at 15:58
2

You have the right idea but is_numeric() may not behave as you intended.

Try this test:

<?php
$tests = Array(
        "42", 
        1337, 
        "1e4", 
        "not numeric", 
        Array(), 
        9.1
        );

foreach($tests as $element)
{
    if(is_numeric($element))
    {
        echo "'{$element}' is numeric", PHP_EOL;
    }
    else
    {
        echo "'{$element}' is NOT numeric", PHP_EOL;
    }
}
?>

The result is:

'42' is numeric
'1337' is numeric
'1e4' is numeric
'not numeric' is NOT numeric
'Array' is NOT numeric
'9.1' is numeric

1e4 may not be something your sql server understands if you're looking for what is commonly referred to as a numeric value. From an SQL injection standpoint you're fine.

mercator
  • 28,290
  • 8
  • 63
  • 72
avirtuos
  • 129
  • 5
2

You're not passing the connection resource to mysql_real_escape_string() (but you seemingly do so with mysql_select_db()). The connection resource amongst other things stores the connection charset setting which might affect the behavior of real_escape_string().
Either don't pass the resource anywhere or (preferably) pass it always but don't make it even worse than not passing the resource by mixing both.

"Security" in my book also encompasses whether the code is readable, "comprehensible" and does "straight-forward" things. In the example you would at least have to explain to me why you have the !numeric -> die branch at all when you treat the id as a string in a SELECT query. My counter-argument (as the example stands; could be wrong in your context) would be "Why bother? The SELECT just will not return any record for a non-numeric id" which reduces the code to

if ( isset($_GET['post']) ) {
  $query = sprintf(
    "SELECT x,y,z FROM foo WHERE id='%s'",
    mysql_real_escape_string($_GET['post'], $mysqlconn) 
  );
   ...
}

That automagically eliminates the trouble you might run into because is_numeric() doesn't behave as you expect (as explained in other answers).

edit: And there's something to be said about using die() to often/to early in production code. It's fine for test/example code but in a live system you almost always want to give control back to the surrounding code instead of just exiting (so your application can handle the error gracefully). During the development phase you might want to bail out early or put more tests in. In that case take a look at http://docs.php.net/assert.
Your example might qualify for an assertion. It won't break if the assertion is deactivated but it might give a developer more information about why it's not working as intended (by this other developer) when a non-numeric argument is passed. But you have to be careful about separating necessary tests from assertions; they are not silver bullets.
If you feel is_numeric() to be an essential test your function(?) might return false, throw an exception or something to signal the condition. But to me an early die() is the easy way out, a bit like a clueless opossum, "I have no idea what to do now. If i play dead maybe no one will notice" ;-)

Obligatory hint to prepared statements: http://docs.php.net/pdo.prepared-statements

VolkerK
  • 95,432
  • 20
  • 163
  • 226
  • would it be betetr to use exit ? do you have a link to a article on this subject ? – Oliver Bayes-Shelton Feb 18 '10 at 15:03
  • It's not about using exit() instead of die(), one is an alias for the other. It's about giving back control. You might want to signal an error condition to a layer above this little snippet. die() simply terminates the script without (or almost without) giving another part of your application a chance to react. see edit. – VolkerK Feb 18 '10 at 15:22
1

I think it looks ok.

When accessing databases I always use query binding, this avoids problems if I forget to escape my strings.

bramp
  • 9,581
  • 5
  • 40
  • 46