2009-08-17 19 views
25

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

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

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

Puede alguien me ayude a crear una declaración seguro MySQL mediante la entrada de parámetros de URL (como el anterior) que se prepara?

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

+1

posible duplicado de [ La mejor manera de detener la inyección de SQL en PHP] (http://stackoverflow.com/questions/60174/best-way-to-stop-sql-injection-in-php) – outis

Respuesta

45

He aquí un ejemplo utilizando mysqli (objeto-sintaxis - bastante fácil de traducir a funcionar sintaxis 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 se desea una manera fácil de agarrar matrices asociativas (para usar con ciertos *) en lugar de tener que especificar exactamente cuáles son las variables que se unen a, aquí es una función muy ú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 invocar que en lugar de llamar bind_result:

$stmt->store_result(); 

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

while($stmt->fetch()) 
{ 
    print_r($resultrow); 
} 
+0

gracias. ¿Cómo se muestran los resultados de esta declaración? Generalmente uso mysql_fetch_row, ¿funciona eso aquí? – chris

+0

Actualizado para agregar un ejemplo de cómo obtener los resultados. – Amber

+0

También tenga en cuenta que mi ejemplo asume que ambos parámetros son enteros: si son cadenas, deberá cambiar los argumentos para bind_param en consecuencia. – Amber

2

La seguridad con MySQL en PHP (o cualquier otro idioma para el caso) es un tema ampliamente discutido. Aquí hay algunos lugares para que usted pueda recoger algunos consejos:

Los dos elementos más importantes en mi opinión son:

  • Inyección de SQL: Asegúrese de escapar todas sus variables de consulta con la función mysql_real_escape_string() de PHP (o algo similar).
  • Validación de entrada: Nunca confíes en la entrada del usuario. Consulte this para obtener un tutorial sobre cómo desinfectar y validar correctamente sus entradas.
+1

mysql_real_escape_string no es excelente y solo se debe usar en un contexto de cadena Nunca lo use para tratar de proteger un campo que no sea de cuerda. Por favor, por favor, use los parámetros vinculados con preferencia a esta banda negra de filtro de cuerdas. – Cheekysoft

+1

@James: el enlace de validación de entrada es muy útil. ¡Gracias! – chris

11

Usted puede 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 usar comandos preparados es mejor utilizar 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); 
?> 
1

Declaraciones preparadas aren ' Compatible con la antigua interfaz simple de Mysql/PHP. Necesitará PDO o mysqli. Pero si solo quiere sustituir marcadores de posición, check this comment en la página del manual mysql_query de php.

1

Si vas a utilizar mysqli, que me parece la mejor solución, te recomiendo que descargues una copia de la clase codesense_mysqli.

Es un poco de clase aseado que envuelve y oculta la mayor parte de la costra que se acumula al usar mysqli prima de tal manera que el uso de declaraciones preparadas sólo toma una o dos líneas extra sobre la antigua interfaz de MySQL/PHP

+0

Esa clase es extremadamente fácil de usar. Me pregunto por qué algo como esto no es más popular. – chris

7

Estoy de acuerdo con varias otras respuestas:

  • PHP ext/mysql no tiene soporte para sentencias de SQL parametrizadas.
  • Los parámetros de consulta se consideran más confiables para proteger contra problemas de inyección de SQL.
  • mysql_real_escape_string() también puede ser efectivo si lo usa correctamente, pero es más detallado para codificar.
  • En algunas versiones, los conjuntos de caracteres internacionales tienen casos de caracteres que no se escapan correctamente, dejando sutiles vulnerabilidades. El uso de parámetros de consulta evita estos casos.

También debe tener en cuenta que todavía debe tener cuidado con la inyección de SQL, incluso si utiliza parámetros de consulta, porque los parámetros solo toman el lugar de 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 mysql_real_escape_string() ayudan en este caso. Por ejemplo:

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

En cuanto al rendimiento:

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

    SELECT * FROM mytable WHERE textfield LIKE ? 
    
+0

excelente respuesta. ¿Cómo sugeriría que manejara una declaración como "SELECCIONAR * FROM mytable WHERE textfield LIKE?". ¿Debo usar una declaración preparada o algo más? – chris

+0

En ese caso específico, debe interpolar el patrón en la cadena de consulta: "' SELECT * FROM mytable WHERE textfield LIKE 'word%' '". De esta forma, el optimizador puede decir que puede usar un índice (por supuesto, si su patrón comienza con un comodín, un índice no da ningún beneficio). –

Cuestiones relacionadas