views:

808

answers:

7

I am new to using prepared statements in mysql with php. I need some help creating a prepared statement to retrieve columns.

I need to get information from different columns. Currently for a test file, I use the completely unsecure SQL statement:

$qry = "SELECT * FROM mytable where userid='{$_GET['userid']}' AND category='{$_GET['category']}'ORDER BY id DESC"
$result = mysql_query($qry) or die(mysql_error());

Can someone help me create a secure mysql statement using input from url parameters (as above) that is prepared?

BONUS: Prepared statements are suppose to increase speed as well. Will it increase overall speed if I only use a prepared statement three or four times on a page?

A: 

$sql_userid = mysql_real_escape_string($_GET['userid']);
$sql_category = mysql_real_escape_string($_GET['category']);

qry = "SELECT * FROM mytable where userid=$sql_userid AND category=$sql_category ORDER BY id DESC";

$result = mysql_query($qry) or die(mysql_error());
Havenard
I want to avoid using mysql_real_escape_string. I hear that prepared mysqli statements are better.
chris
Yes, there are MUCH better solutions, but you are using the mysql_* methods so its all you got.
Havenard
Check this out: http://www.php.net/manual/en/class.pdo.phpIts not better because its safer, but its better because its portable. You may change your database server (from MySQL to PGSQL for instance) without changing anything but the connection string.
Havenard
@chris. Yes, bound-parameters instead of string filtering, all the way.@Havenard - PDO is also safer, as it implements bound parameters properly (as does mysqli)
Cheekysoft
+1  A: 

Security with MySQL in PHP (or any other language for that matter) is a largely discussed issue. Here are a few places for you to pick up some great tips:

The two most major items in my opinion are:

  • SQL Injection: Be sure to escape all of your query variables with PHP's mysql_real_escape_string() function (or something similar).
  • Input Validation: Never trust the user's input. See this for a tutorial on how to properly sanitize and validation your inputs.
James Skidmore
mysql_real_escape_string is not great and must only be used in a string context. Never use it to try and protect a non-string field. Please, please, please use bound parameters in preference to this blacklist string-filtering band-aid.
Cheekysoft
@James: the input validation link is very useful. thanks!
chris
+5  A: 

You can write this instead:

$qry = "SELECT * FROM mytable where userid='";
$qry.= mysql_real_escape_string($_GET['userid'])."' AND category='";
$qry.= mysql_real_escape_string($_GET['category'])."' ORDER BY id DESC";

But to use prepared statements you better use a generic library, like PDO

<?php
/* Execute a prepared statement by passing an array of values */
$sth = $dbh->prepare('SELECT * FROM mytable where userid=? and category=? 
                      order by id DESC');
$sth->execute(array($_GET['userid'],$_GET['category']));
//Consider a while and $sth->fetch() to fetch rows one by one
$allRows = $sth->fetchAll(); 
?>

Or, using mysqli

<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "world");

/* check connection */
if (mysqli_connect_errno()) {
    printf("Connect failed: %s\n", mysqli_connect_error());
    exit();
}

$category = $_GET['category'];
$userid = $_GET['userid'];

/* create a prepared statement */
if ($stmt = mysqli_prepare($link, 'SELECT col1, col2 FROM mytable where 
                      userid=? and category=? order by id DESC')) {
    /* bind parameters for markers */
    /* Assumes userid is integer and category is string */
    mysqli_stmt_bind_param($stmt, "is", $userid, $category);  
    /* execute query */
    mysqli_stmt_execute($stmt);
    /* bind result variables */
    mysqli_stmt_bind_result($stmt, $col1, $col2);
    /* fetch value */
    mysqli_stmt_fetch($stmt);
    /* Alternative, use a while:
    while (mysqli_stmt_fetch($stmt)) {
        // use $col1 and $col2 
    }
    */
    /* use $col1 and $col2 */
    echo "COL1: $col1 COL2: $col2\n";
    /* close statement */
    mysqli_stmt_close($stmt);
}

/* close connection */
mysqli_close($link);
?>
Vinko Vrsalovic
+1  A: 

Prepared statements aren't supported by the plain old Mysql/PHP interface. You'll need PDO or mysqli. But if you just want to substitute placeholders, check this comment on php's mysql_query manual page.

inerte
+4  A: 

Here's an example using mysqli (object-syntax - fairly easy to translate to function syntax if you desire):

$db = new mysqli("host","user","pw","database");
$stmt = $db->prepare("SELECT * FROM mytable where userid=? AND category=? ORDER BY id DESC");
$stmt->bind_param('ii', intval($_GET['userid']), intval($_GET['category']));
$stmt->execute();

$stmt->store_result();
$stmt->bind_result($column1, $column2, $column3);

while($stmt->fetch())
{
    echo "col1=$column1, col2=$column2, col3=$column3 \n";
}

$stmt->close();

Also, if you want an easy way to grab associative arrays (for use with SELECT *) instead of having to specify exactly what variables to bind to, here's a handy function:

function stmt_bind_assoc (&$stmt, &$out) {
    $data = mysqli_stmt_result_metadata($stmt);
    $fields = array();
    $out = array();

    $fields[0] = $stmt;
    $count = 1;

    while($field = mysqli_fetch_field($data)) {
        $fields[$count] = &$out[$field->name];
        $count++;
    }
    call_user_func_array(mysqli_stmt_bind_result, $fields);
}

To use it, just invoke it instead of calling bind_result:

$stmt->store_result();

$resultrow = array();
stmt_bind_assoc($stmt, $resultrow);

while($stmt->fetch())
{
    print_r($resultrow);
}
Amber
thanks. How do I display the results from this statement?Generally I use mysql_fetch_row, does that work here?
chris
Updated to add an example of how to get the results.
Amber
Also note that my example assumes that both of your paramters are integers - if they're strings, you'd need to change the arguments to bind_param accordingly.
Amber
+1  A: 

If you're going to use mysqli - which seems the best solution to me - I highly recommend downloading a copy of the codesense_mysqli class.

It's a neat little class that wraps up and hides most of the cruft that accumulates when using raw mysqli such that using prepared statements only takes a line or two extra over the old mysql/php interface

Cruachan
That class is extremely easy to use. I wonder why something like this isn't more popular.
chris
+3  A: 

I agree with several other answers:

  • PHP's ext/mysql has no support for parameterized SQL statements.
  • Query parameters are considered more reliable in protecting against SQL injection issues.
  • mysql_real_escape_string() can also be effective if you use it correctly, but it's more verbose to code.
  • In some versions, international character sets have cases of characters that are not escaped properly, leaving subtle vulnerabilities. Using query parameters avoids these cases.

You should also note that you still have to be cautious about SQL injection even if you use query parameters, because parameters only take the place of literal values in SQL queries. If you build SQL queries dynamically and use PHP variables for the table name, column name, or any other part of SQL syntax, neither query parameters nor mysql_real_escape_string() help in this case. For example:

$query = "SELECT * FROM $the_table ORDER BY $some_column";

Regarding performance:

  • The performance benefit comes when you execute a prepared query multiple times with different parameter values. You avoid the overhead of parsing and preparing the query. But how often do you need to execute the same SQL query many times in the same PHP request?
  • Even when you can take advantage of this performance benefit, it is usually only a slight improvement compared to many other things you could do to address performance, like using opcode caching or data caching effectively.
  • There are even some cases where a prepared query harms performance. For example in the following case, the optimizer can't assume it can use an index for the search, because it must assume the parameter value might begin with a wildcard:

    SELECT * FROM mytable WHERE textfield LIKE ?
    
Bill Karwin
excellent answer. How would you suggest I handle a statement like "SELECT * FROM mytable WHERE textfield LIKE ?". Should I use a prepared statement or something else?
chris
In that specific case, you have to interpolate the pattern into the query string: "`SELECT * FROM mytable WHERE textfield LIKE 'word%'`". That way the optimizer can tell that it can use an index (of course if your pattern begins with a wildcard, an index gives no benefit anyway).
Bill Karwin