¿Cómo crear una declaración preparada de MySQL segura en PHP?

Resuelto chris asked hace 54 años • 6 respuestas

Soy nuevo en el uso de declaraciones preparadas en mysql con php. Necesito ayuda para crear una declaración preparada para recuperar columnas.

Necesito obtener información de diferentes columnas. Actualmente, para un archivo de prueba, uso la declaración SQL completamente insegura :

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

¿Alguien puede ayudarme a crear una declaración MySQL segura utilizando la entrada de los parámetros de URL (como arriba) que esté preparada?

BONIFICACIÓN: Se supone que las declaraciones preparadas también aumentan la velocidad. ¿Aumentará la velocidad general si sólo uso una declaración preparada tres o cuatro veces en una página?

chris avatar Jan 01 '70 08:01 chris
Aceptado

Aquí hay un ejemplo usando mysqli (sintaxis de objeto - bastante fácil de traducir a sintaxis de función si lo desea):

$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();

Además, si desea una manera fácil de capturar matrices asociativas (para usar con SELECT *) en lugar de tener que especificar exactamente a qué variables vincularse, aquí tiene una función útil:

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);
}

Para usarlo, simplemente invocalo en lugar de llamar a bind_result:

$stmt->store_result();

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

while($stmt->fetch())
{
    print_r($resultrow);
}
Amber avatar Aug 17 '2009 23:08 Amber

Puedes escribir esto en su lugar:

$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";

Pero para usar declaraciones preparadas es mejor que uses una biblioteca genérica, como 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(); 
?>

O usando 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 avatar Aug 17 '2009 23:08 Vinko Vrsalovic

Estoy de acuerdo con varias otras respuestas:

  • PHP ext/mysqlno admite declaraciones SQL parametrizadas.
  • Los parámetros de consulta se consideran más confiables para proteger contra problemas de inyección SQL.
  • mysql_real_escape_string()También puede ser efectivo si lo usas correctamente, pero el código es más detallado.
  • En algunas versiones, los conjuntos de caracteres internacionales tienen casos de caracteres a los que no se les escapa correctamente, lo que deja vulnerabilidades sutiles. El uso de parámetros de consulta evita estos casos.

También debe tener en cuenta que aún debe tener cuidado con la inyección SQL incluso si usa parámetros de consulta, porque los parámetros solo reemplazan los valores literales en las consultas SQL. Si crea consultas SQL dinámicamente y utiliza variables PHP para el nombre de la tabla, el nombre de la columna o cualquier otra parte de la sintaxis SQL, ni los parámetros de consulta ni la mysql_real_escape_string()ayuda en este caso. Por ejemplo:

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

En cuanto al rendimiento:

  • El beneficio de rendimiento se obtiene cuando ejecuta una consulta preparada varias veces con diferentes valores de parámetros. Evita la sobrecarga de analizar y preparar la consulta. Pero, ¿con qué frecuencia es necesario ejecutar la misma consulta SQL muchas veces en la misma solicitud PHP?
  • Incluso cuando puede aprovechar este beneficio de rendimiento, generalmente es solo una ligera mejora en comparación con muchas otras cosas que podría hacer para mejorar el rendimiento, como usar el almacenamiento en caché de código de operación o el almacenamiento en caché de datos de manera efectiva.
  • Incluso hay algunos casos en los que una consulta preparada perjudica el rendimiento. Por ejemplo, en el siguiente caso, el optimizador no puede asumir que puede usar un índice para la búsqueda, porque debe asumir que el valor del parámetro podría comenzar con un comodín:

    SELECT * FROM mytable WHERE textfield LIKE ?
    
Bill Karwin avatar Aug 17 '2009 23:08 Bill Karwin