proteger inyeccion injection evitar ejemplos contra como php mysql sql security sql-injection

inyeccion - proteger php contra sql injection



¿Cómo puedo prevenir la inyección de SQL en PHP? (28)

Advertencia de seguridad : esta respuesta no está en línea con las mejores prácticas de seguridad. El escape no es adecuado para evitar la inyección de SQL ; en su lugar, utilice instrucciones preparadas . Utilice la estrategia que se describe a continuación bajo su propio riesgo. (Además, mysql_real_escape_string() se eliminó en PHP 7.)

Podrías hacer algo básico como este:

$safe_variable = mysql_real_escape_string($_POST["user-input"]); mysql_query("INSERT INTO table (column) VALUES (''" . $safe_variable . "'')");

Esto no resolverá todos los problemas, pero es un buen paso. Dejé de lado elementos obvios como verificar la existencia de la variable, el formato (números, letras, etc.).

Si la entrada del usuario se inserta sin modificación en una consulta SQL, la aplicación se vuelve vulnerable a la inyección de SQL , como en el siguiente ejemplo:

$unsafe_variable = $_POST[''user_input'']; mysql_query("INSERT INTO `table` (`column`) VALUES (''$unsafe_variable'')");

Eso es porque el usuario puede ingresar algo como value''); DROP TABLE table;-- value''); DROP TABLE table;-- , y la consulta se convierte en:

INSERT INTO `table` (`column`) VALUES(''value''); DROP TABLE table;--'')

¿Qué se puede hacer para evitar que esto suceda?


Advertencia: el código de muestra de esta respuesta (como el código de muestra de la pregunta) utiliza la extensión mysql de PHP, que se desaprobó en PHP 5.5.0 y se eliminó por completo en PHP 7.0.0.

Si está utilizando una versión reciente de PHP, la opción mysql_real_escape_string describe a continuación ya no estará disponible (aunque mysqli::escape_string es un equivalente moderno). En estos días, la opción mysql_real_escape_string solo tendría sentido para el código heredado en una versión antigua de PHP.

Tiene dos opciones: escapar de los caracteres especiales en su unsafe_variable o usar una consulta parametrizada. Ambos te protegerían de la inyección de SQL. La consulta parametrizada se considera la mejor práctica, pero requerirá cambiar a una extensión mysql más nueva en PHP antes de poder usarla.

Cubriremos la cuerda de menor impacto que se escapa primero.

//Connect $unsafe_variable = $_POST["user-input"]; $safe_variable = mysql_real_escape_string($unsafe_variable); mysql_query("INSERT INTO table (column) VALUES (''" . $safe_variable . "'')"); //Disconnect

Vea también, los detalles de la función mysql_real_escape_string .

Para usar la consulta parametrizada, necesita usar MySQLi lugar de las funciones MySQL . Para volver a escribir su ejemplo, necesitaríamos algo como lo siguiente.

<?php $mysqli = new mysqli("server", "username", "password", "database_name"); // TODO - Check that connection was successful. $unsafe_variable = $_POST["user-input"]; $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)"); // TODO check that $stmt creation succeeded // "s" means the database expects a string $stmt->bind_param("s", $unsafe_variable); $stmt->execute(); $stmt->close(); $mysqli->close(); ?>

La función clave que querrás leer allí sería mysqli::prepare .

Además, como otros han sugerido, puede resultarle útil / más fácil intensificar una capa de abstracción con algo como PDO .

Tenga en cuenta que el caso que preguntó es bastante simple y que los casos más complejos pueden requerir enfoques más complejos. En particular:

  • Si desea modificar la estructura del SQL en función de la entrada del usuario, las consultas parametrizadas no ayudarán, y mysql_real_escape_string no cubrirá el escape requerido. En este tipo de caso, sería mejor pasar la entrada del usuario a través de una lista blanca para garantizar que solo se permiten valores "seguros".
  • Si usa enteros de la entrada del usuario en una condición y toma el enfoque mysql_real_escape_string , sufrirá el problema descrito por Polynomial en los comentarios a continuación. Este caso es más complicado porque los enteros no estarían rodeados de comillas, por lo que se podría tratar al validar que la entrada del usuario solo contiene dígitos.
  • Probablemente hay otros casos que no conozco. Podría encontrar que this es un recurso útil en algunos de los problemas más sutiles que puede encontrar.

IMPORTANTE

La mejor manera de prevenir la inyección SQL es usar declaraciones preparadas en lugar de escapar , como lo demuestra la respuesta aceptada .

Hay bibliotecas como Aura.Sql y EasyDB que permiten a los desarrolladores usar declaraciones preparadas más fácilmente. Para obtener más información acerca de por qué las declaraciones preparadas son mejores para detener la inyección de SQL , consulte esta mysql_real_escape_string() y corrigió recientemente las vulnerabilidades de inyección de SQL de Unicode en WordPress .

Prevención de la inyección - mysql_real_escape_string()

PHP tiene una función especial para prevenir estos ataques. Todo lo que necesitas hacer es usar la boca llena de una función, mysql_real_escape_string .

mysql_real_escape_string toma una cadena que se usará en una consulta de MySQL y devuelve la misma cadena con todos los intentos de inyección de SQL escapados de forma segura. Básicamente, reemplazará esas citas problemáticas ('') que un usuario podría ingresar con un sustituto seguro para MySQL, una cita escapada''.

NOTA: debe estar conectado a la base de datos para utilizar esta función.

// Conectar a MySQL

$name_bad = "'' OR 1''"; $name_bad = mysql_real_escape_string($name_bad); $query_bad = "SELECT * FROM customers WHERE username = ''$name_bad''"; echo "Escaped Bad Injection: <br />" . $query_bad . "<br />"; $name_evil = "''; DELETE FROM customers WHERE 1 or username = ''"; $name_evil = mysql_real_escape_string($name_evil); $query_evil = "SELECT * FROM customers WHERE username = ''$name_evil''"; echo "Escaped Evil Injection: <br />" . $query_evil;

Puede encontrar más detalles en MySQL - Prevención de inyección de SQL .


Cada respuesta aquí cubre solo una parte del problema. De hecho, hay cuatro partes de consulta diferentes que podemos agregar dinámicamente:

  • una cuerda
  • un número
  • un identificador
  • una palabra clave de sintaxis.

Y las declaraciones preparadas cubren solo dos de ellas.

Pero a veces tenemos que hacer nuestra consulta aún más dinámica, agregando operadores o identificadores también. Entonces, necesitaremos diferentes técnicas de protección.

En general, este enfoque de protección se basa en la inclusión en listas blancas .

En este caso, todos los parámetros dinámicos deberían estar codificados en su script y elegidos de ese conjunto. Por ejemplo, para hacer pedidos dinámicos:

$orders = array("name", "price", "qty"); // Field names $key = array_search($_GET[''sort''], $orders)); // See if we have such a name $orderby = $orders[$key]; // If not, first one will be set automatically. smart enuf :) $query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

Sin embargo, hay otra forma de asegurar los identificadores: escapar. Siempre que tenga un identificador citado, puede escapar de backticks dentro duplicándolos.

Como paso adicional, podemos tomar prestada una idea realmente brillante de usar algún marcador de posición (un proxy para representar el valor real en la consulta) de las declaraciones preparadas e inventar un marcador de posición de otro tipo: un marcador de posición identificador.

Por lo tanto, para resumir la historia larga: es un marcador de posición , una declaración no preparada puede considerarse como una bala de plata.

Por lo tanto, una recomendación general puede expresarse en la medida en que siempre que agregue partes dinámicas a la consulta utilizando marcadores de posición (y estos marcadores de posición procesados ​​correctamente, por supuesto), puede estar seguro de que su consulta es segura .

Aún así, hay un problema con las palabras clave de sintaxis SQL (como AND , DESC y similares), pero la lista blanca parece ser el único enfoque en este caso.

Actualizar

Aunque existe un acuerdo general sobre las mejores prácticas con respecto a la protección de inyección SQL, también existen muchas malas prácticas. Y algunos de ellos están demasiado arraigados en la mente de los usuarios de PHP. Por ejemplo, en esta misma página hay (aunque invisibles para la mayoría de los visitantes) más de 80 respuestas eliminadas , todas eliminadas por la comunidad debido a la mala calidad o la promoción de prácticas malas y obsoletas. Peor aún, algunas de las malas respuestas no se eliminan, sino que prosperan.

Por ejemplo, there(1) are(2) still(3) many(4) answers(5) , incluida la share sugiere que se escape manualmente la cadena, un enfoque obsoleto que se ha demostrado que es inseguro.

O hay una respuesta ligeramente mejor que sugiere solo otro método de formateo de cadenas e incluso lo presenta como la panacea definitiva. Mientras que por supuesto, no lo es. Este método no es mejor que el formato de cadena normal, pero mantiene todos sus inconvenientes: es aplicable solo a cadenas y, como cualquier otro formato manual, es una medida esencialmente obligatoria, no obligatoria, propensa a errores humanos de cualquier tipo.

Creo que todo esto se debe a una superstición muy antigua, apoyada por autoridades como OWASP o el manual de PHP , que proclama la igualdad entre cualquier "escape" y la protección de las inyecciones de SQL.

Independientemente de lo que el manual de PHP haya dicho durante años, *_escape_string de ninguna manera hace que los datos sean seguros y nunca ha sido intencionado. Además de ser inútil para cualquier parte de SQL que no sea una cadena, el escape manual es incorrecto, porque es un manual opuesto a automatizado.

Y OWASP lo empeora aún más, insistiendo en escapar de la información del usuario, lo cual es una completa tontería: no debería haber tales palabras en el contexto de la protección de la inyección. ¡Cada variable es potencialmente peligrosa, sin importar la fuente! O, en otras palabras, todas las variables deben tener el formato correcto para ser colocadas en una consulta, sin importar la fuente nuevamente. Es el destino lo que importa. En el momento en que un desarrollador comienza a separar las ovejas de las cabras (pensando si alguna variable en particular es "segura" o no), él / ella da su primer paso hacia el desastre. Sin mencionar que incluso la redacción sugiere un escape masivo en el punto de entrada, que se asemeja a la característica de citas muy mágicas: ya despreciada, desaprobada y eliminada.

Por lo tanto, a diferencia de cualquier "escape", las declaraciones preparadas son la medida que protege de la inyección de SQL (cuando corresponda).

Si aún no está convencido, aquí hay una explicación paso a paso que escribí, La guía del autostopista para la prevención de la inyección de SQL , donde expliqué todos estos asuntos en detalle e incluso compilé una sección completamente dedicada a las malas prácticas y su divulgación.


Como puede ver, la gente sugiere que use declaraciones preparadas a lo sumo. No está mal, pero cuando su consulta se ejecuta solo una vez por proceso, habrá una leve penalización en el rendimiento.

Me enfrentaba a este problema, pero creo que lo resolví de una manera muy sofisticada, la forma en que los hackers utilizan para evitar el uso de comillas. Utilicé esto junto con las declaraciones preparadas emuladas. Lo uso para prevenir todo tipo de posibles ataques de inyección SQL.

Mi acercamiento:

  • Si espera que la entrada sea un número entero, asegúrese de que sea realmente entero. En un lenguaje de tipo variable como PHP es esto muy importante. Puede usar, por ejemplo, esta solución simple pero poderosa: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Si esperas algo más del entero hexadecimal . Si lo usas, escaparás perfectamente de todas las entradas. En C / C ++ hay una función llamada mysql_hex_string() , en PHP puedes usar bin2hex() .

    No se preocupe acerca de que la cadena escapada tendrá un tamaño 2x de su longitud original porque incluso si usa mysql_real_escape_string , PHP tiene que asignar la misma capacidad ((2*input_length)+1) , que es la misma.

  • Este método hexadecimal se usa a menudo cuando transfieres datos binarios, pero no veo ninguna razón para no usarlos en todos los datos para evitar ataques de inyección de SQL. Tenga en cuenta que debe anteponer los datos con 0x o usar la función MySQL UNHEX en UNHEX lugar.

Así, por ejemplo, la consulta:

SELECT password FROM users WHERE name = ''root''

Se convertirá:

SELECT password FROM users WHERE name = 0x726f6f74

o

SELECT password FROM users WHERE name = UNHEX(''726f6f74'')

Hex es el escape perfecto. No hay forma de inyectar.

Diferencia entre la función UNHEX y el prefijo 0x

Hubo un poco de discusión en los comentarios, así que finalmente quiero dejarlo claro. Estos dos enfoques son muy similares, pero son un poco diferentes en algunos aspectos:

El prefijo ** 0x ** solo se puede utilizar para columnas de datos como char, varchar, text, block, binary, etc.
Además, su uso es un poco complicado si está a punto de insertar una cadena vacía. Tendrá que reemplazarlo por completo con '''' , o obtendrá un error.

UNHEX () funciona en cualquier columna; usted no tiene que preocuparse por la cadena vacía.

Los métodos hexagonales se utilizan a menudo como ataques.

Tenga en cuenta que este método hexadecimal se usa a menudo como un ataque de inyección SQL donde los enteros son como cadenas y se escapan solo con mysql_real_escape_string . Entonces puedes evitar el uso de citas.

Por ejemplo, si solo haces algo como esto:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

Un ataque puede inyectarte muy fácilmente . Considere el siguiente código inyectado devuelto desde su script:

SELECCIONE ... DONDE id = -1 union all select table_name from information_schema.tables

y ahora solo extraiga la estructura de la tabla:

SELECCIONE ... DONDE id = -1 union all seleccione column_name de information_schema.column donde table_name = 0x61727469636c65

Y luego simplemente seleccione los datos que desee. ¿No es genial?

Pero si el codificador de un sitio inyectable lo hechizaría, no sería posible una inyección porque la consulta tendría este aspecto: SELECT ... WHERE id = UNHEX(''2d312075...3635'')


En mi opinión, la mejor manera de prevenir generalmente la inyección de SQL en su aplicación PHP (o en cualquier aplicación web, en este caso) es pensar en la arquitectura de su aplicación. Si la única forma de protegerse contra la inyección de SQL es acordarse de usar un método o función especial que haga lo correcto cada vez que hable con la base de datos, lo está haciendo mal. De esa manera, es solo cuestión de tiempo hasta que se olvide de formatear correctamente su consulta en algún punto de su código.

Adoptar el patrón MVC y un marco como CakePHP o CodeIgniter es probablemente el camino correcto: las tareas comunes como la creación de consultas de bases de datos seguras se han resuelto y se han implementado centralmente en dichos marcos. Le ayudan a organizar su aplicación web de una manera sensata y lo hacen pensar más en cargar y guardar objetos que en construir de forma segura consultas SQL únicas.


La validación de consultas Y entradas parametrizadas es el camino a seguir. Hay muchos escenarios en los que se puede producir la inyección de SQL, aunque se haya utilizado mysql_real_escape_string() .

Esos ejemplos son vulnerables a la inyección de SQL:

$offset = isset($_GET[''o'']) ? $_GET[''o''] : 0; $offset = mysql_real_escape_string($offset); RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

o

$order = isset($_GET[''o'']) ? $_GET[''o''] : ''userid''; $order = mysql_real_escape_string($order); RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

En ambos casos, no puede usar '' para proteger la encapsulación.

Source : La inyección SQL inesperada (cuando el escape no es suficiente)


Lo que sea que magic_quotes usando, asegúrate de verificar que tu entrada no haya sido magic_quotes por magic_quotes o alguna otra basura bien intencionada, y si es necesario, stripslashes través de stripslashes o lo que sea para desinfectarla.


Recomiendo usar PDO (objetos de datos PHP) para ejecutar consultas SQL parametrizadas.

Esto no solo protege contra la inyección de SQL, sino que también acelera las consultas.

Y al usar las mysql_ PDO en lugar de mysql_ , mysqli_ y pgsql_ , hace que su aplicación sea un poco más abstracta de la base de datos, en el raro caso de que tenga que cambiar de proveedor de base de datos.


Utilizar PDO y consultas preparadas.

( $conn es un objeto PDO )

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)"); $stmt->bindValue('':id'', $id); $stmt->bindValue('':name'', $name); $stmt->execute();


Utilice declaraciones preparadas y consultas parametrizadas. Estas son declaraciones de SQL que el servidor de la base de datos envía y analiza por separado de cualquier parámetro. De esta manera es imposible para un atacante inyectar SQL malicioso.

Básicamente tienes dos opciones para lograr esto:

  1. Utilizando PDO (para cualquier controlador de base de datos compatible):

    $stmt = $pdo->prepare(''SELECT * FROM employees WHERE name = :name''); $stmt->execute(array(''name'' => $name)); foreach ($stmt as $row) { // do something with $row }

  2. Usando MySQLi (para MySQL):

    $stmt = $dbConnection->prepare(''SELECT * FROM employees WHERE name = ?''); $stmt->bind_param(''s'', $name); // ''s'' specifies the variable type => ''string'' $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // do something with $row }

Si se está conectando a una base de datos que no sea MySQL, hay una segunda opción específica del controlador que puede consultar (por ejemplo, pg_prepare() y pg_execute() para PostgreSQL). DOP es la opción universal.

Configuración correcta de la conexión

Tenga en cuenta que cuando se usa PDO para acceder a una base de datos MySQL, las declaraciones preparadas no se utilizan de forma predeterminada . Para arreglar esto tienes que deshabilitar la emulación de declaraciones preparadas. Un ejemplo de crear una conexión usando DOP es:

$dbConnection = new PDO(''mysql:dbname=dbtest;host=127.0.0.1;charset=utf8'', ''user'', ''pass''); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

En el ejemplo anterior, el modo de error no es estrictamente necesario, pero se recomienda agregarlo . De esta manera, el script no se detendrá con un Fatal Error cuando algo salga mal. Y le da al desarrollador la oportunidad de catch cualquier error (s) que se throw como PDOException s.

Sin embargo, lo que es obligatorio es la primera línea setAttribute() , que le dice a PDO que deshabilite las declaraciones preparadas emuladas y use declaraciones preparadas reales . Esto asegura que la declaración y los valores no sean analizados por PHP antes de enviarlos al servidor MySQL (lo que le da al posible atacante ninguna posibilidad de inyectar SQL malicioso).

Aunque puede configurar el conjunto de charset en las opciones del constructor, es importante tener en cuenta que las versiones "antiguas" de PHP (<5.3.6) ignoraron silenciosamente el parámetro del conjunto de caracteres en el DSN.

Explicación

Lo que sucede es que el servidor de la base de datos analiza y compila la declaración SQL que pasa para prepare . Al especificar parámetros (ya sea un ? O un parámetro con nombre como :name en el ejemplo anterior) le indica al motor de la base de datos dónde desea filtrar. Luego, cuando llama a execute , la instrucción preparada se combina con los valores de los parámetros que especifique.

Lo importante aquí es que los valores de los parámetros se combinan con la declaración compilada, no con una cadena SQL. La inyección SQL funciona engañando al script para que incluya cadenas maliciosas cuando crea SQL para enviar a la base de datos. Por lo tanto, al enviar el SQL real por separado de los parámetros, limita el riesgo de terminar con algo que no tenía la intención. Cualquier parámetro que envíe cuando usa una declaración preparada solo se tratará como cadenas (aunque el motor de la base de datos puede hacer una optimización, por lo que los parámetros también pueden terminar como números). En el ejemplo anterior, si la variable $name contiene ''Sarah''; DELETE FROM employees ''Sarah''; DELETE FROM employees el resultado sería simplemente una búsqueda de la cadena "''Sarah''; DELETE FROM employees" , y no terminará con una tabla vacía .

Otro beneficio del uso de declaraciones preparadas es que si ejecuta la misma instrucción muchas veces en la misma sesión, solo se analizará y compilará una vez, lo que le dará algunas ganancias de velocidad.

Ah, y ya que ha preguntado cómo hacerlo para un inserto, aquí hay un ejemplo (usando PDO):

$preparedStatement = $db->prepare(''INSERT INTO table (column) VALUES (:column)''); $preparedStatement->execute(array(''column'' => $unsafeValue));

¿Se pueden usar declaraciones preparadas para consultas dinámicas?

Si bien aún puede usar sentencias preparadas para los parámetros de consulta, la estructura de la consulta dinámica en sí misma no puede parametrizarse y ciertas características de consulta no pueden parametrizarse.

Para estos escenarios específicos, lo mejor es usar un filtro de lista blanca que restrinja los valores posibles.

// Value whitelist // $dir can only be ''DESC'' otherwise it will be ''ASC'' if (empty($dir) || $dir !== ''DESC'') { $dir = ''ASC''; }


Algunas pautas para escapar de caracteres especiales en sentencias de SQL.

No use MySQL , esta extensión está en desuso, use MySQLi o PDO .

MySQLi

Para escapar manualmente caracteres especiales en una cadena, puede usar la función mysqli_real_escape_string . La función no funcionará correctamente a menos que el conjunto de caracteres correcto esté configurado con mysqli_set_charset .

Ejemplo:

$mysqli = new mysqli( ''host'', ''user'', ''password'', ''database'' ); $mysqli->set_charset( ''charset''); $string = $mysqli->real_escape_string( $string ); $mysqli->query( "INSERT INTO table (column) VALUES (''$string'')" );

Para el escape automático de valores con sentencias preparadas, use mysqli_prepare y mysqli_stmt_bind_param donde se deben proporcionar los tipos para las variables de enlace correspondientes para una conversión apropiada:

Ejemplo:

$stmt = $mysqli->prepare( "INSERT INTO table ( column1, column2 ) VALUES (?,?)" ); $stmt->bind_param( "is", $integer, $string ); $stmt->execute();

No importa si usa sentencias preparadas o mysqli_real_escape_string, siempre debe saber el tipo de datos de entrada con los que está trabajando.

Entonces, si usa una declaración preparada, debe especificar los tipos de variables para la función mysqli_stmt_bind_param.

Y el uso de mysqli_real_escape_string es para, como su nombre indica, escapar de caracteres especiales en una cadena, por lo que no hará que los enteros sean seguros. El propósito de esta función es evitar que se rompan las cadenas en las declaraciones SQL y el daño a la base de datos que podría causar. mysqli_real_escape_string es una función útil cuando se usa correctamente, especialmente cuando se combina con sprintf.

Ejemplo:

$string = "x'' OR name LIKE ''%John%"; $integer = ''5 OR id != 0''; $query = sprintf( "SELECT id, email, pass, name FROM members WHERE email =''%s'' AND id = %d", $mysqli->real_escape_string( $string ), $integer ); echo $query; // SELECT id, email, pass, name FROM members WHERE email =''x/' OR name LIKE /'%John%'' AND id = 5 $integer = ''99999999999999999999''; $query = sprintf( "SELECT id, email, pass, name FROM members WHERE email =''%s'' AND id = %d", $mysqli->real_escape_string( $string ), $integer ); echo $query; // SELECT id, email, pass, name FROM members WHERE email =''x/' OR name LIKE /'%John%'' AND id = 2147483647


Creo que si alguien quiere usar PHP y MySQL o algún otro servidor de base de datos:

  1. Piense en aprender el PDO (objetos de datos PHP): es una capa de acceso a la base de datos que proporciona un método uniforme de acceso a múltiples bases de datos.
  2. Piensa en aprender MySQLi
  3. Use funciones nativas de PHP como: strip_tags , mysql_real_escape_string() o si es variable numérica, solo (int)$foo. Lea más sobre el tipo de variables en PHP here . Si está utilizando bibliotecas como PDO o MySQLi, use siempre PDO::quote() y mysqli_real_escape_string() .

Ejemplos de bibliotecas:

---- DOP

----- ¡No hay marcadores de posición - maduros para la inyección de SQL! Es malo

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- Marcadores de posición sin nombre

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Marcadores de posición con nombre

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('' SELECT * FROM trainers WHERE name = ? AND email = ? AND last_login > ?''); $query->bind_param(''first_param'', ''second_param'', $mail, time() - 3600); $query->execute();

PS :

DOP gana esta batalla con facilidad. Con soporte para doce controladores de base de datos diferentes y parámetros con nombre, podemos ignorar la pequeña pérdida de rendimiento y acostumbrarnos a su API. Desde un punto de vista de seguridad, ambos están a salvo siempre que el desarrollador los use de la forma en que se supone que deben ser utilizados.

Pero mientras tanto PDO y MySQLi son bastante rápidos, MySQLi se desempeña de manera insignificantemente más rápida en los puntos de referencia: ~ 2.5% para declaraciones no preparadas y ~ 6.5% para las preparadas.

Y, por favor, pruebe cada consulta en su base de datos: es una mejor manera de evitar la inyección.


He escrito esta pequeña función hace varios años:

function sqlvprintf($query, $args) { global $DB_LINK; $ctr = 0; ensureConnection(); // Connect to database if not connected already. $values = array(); foreach ($args as $value) { if (is_string($value)) { $value = "''" . mysqli_real_escape_string($DB_LINK, $value) . "''"; } else if (is_null($value)) { $value = ''NULL''; } else if (!is_int($value) && !is_float($value)) { die(''Only numeric, string, array and NULL arguments allowed in a query. Argument ''.($ctr+1).'' is not a basic type, it/'s type is ''. gettype($value). ''.''); } $values[] = $value; $ctr++; } $query = preg_replace_callback( ''/{(//d+)}/'', function($match) use ($values) { if (isset($values[$match[1]])) { return $values[$match[1]]; } else { return $match[0]; } }, $query ); return $query; } function runEscapedQuery($preparedQuery /*, ...*/) { $params = array_slice(func_get_args(), 1); $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results. return $results; }

Esto permite ejecutar sentencias en una cadena C # -ish String.Format como:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Se escapa teniendo en cuenta el tipo de variable. Si intenta parametrizar la tabla, los nombres de las columnas, fallaría, ya que pone cada cadena entre comillas, lo que es una sintaxis no válida.

ACTUALIZACIÓN DE SEGURIDAD: La str_replaceversión anterior permitió inyecciones al agregar tokens {#} en los datos del usuario. Esta preg_replace_callbackversión no causa problemas si el reemplazo contiene estos tokens.


Utilizo tres formas diferentes para evitar que mi aplicación web sea vulnerable a la inyección de SQL.

  1. El uso de mysql_real_escape_string(), que es una función predefinida en PHP , y este código barras invertidas añaden a los siguientes caracteres: /x00, /n, /r, /, '', "y /x1a. Pase los valores de entrada como parámetros para minimizar la posibilidad de inyección de SQL.
  2. La forma más avanzada es usar DOP.

Espero que esto ayude.

Considere la siguiente consulta:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string () no protegerá aquí. Si usa comillas simples ('''') alrededor de sus variables dentro de su consulta es lo que lo protege contra esto. Aquí hay una solución para esto:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

Esta question tiene algunas buenas respuestas sobre esto.

Sugiero, utilizando la DOP es la mejor opción.

Editar:

mysql_real_escape_string()está en desuso a partir de PHP 5.5.0. Utilice mysqli o DOP.

Una alternativa a mysql_real_escape_string () es

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Ejemplo:

$iId = $mysqli->real_escape_string("1 OR 1=1"); $mysqli->query("SELECT * FROM table WHERE id = $iId");


** Advertencia: el enfoque descrito en esta respuesta solo se aplica a escenarios muy específicos y no es seguro ya que los ataques de inyección de SQL no solo confían en la posibilidad de inyectar X=Y. **

Si los atacantes intentan piratear el formulario a través de la $_GETvariable de PHP o con la cadena de consulta de la URL, podría capturarlos si no están seguros.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+) RewriteRule ^(.*) ^/track.php

Porque 1=1, 2=2, 1=2, 2=1, 1+1=2, etc ... son las preguntas comunes a una base de datos SQL de un atacante. Tal vez también sea utilizado por muchas aplicaciones de hacking.

Pero debe tener cuidado de no volver a escribir una consulta segura desde su sitio. El código anterior le da una sugerencia para reescribir o redirigir (depende de usted) esa cadena de consulta dinámica específica de piratería a una página que almacenará la dirección IP del atacante , o AUN SUS COOKIES, el historial, el navegador o cualquier otra información confidencial. información, por lo que puede tratarlos más tarde prohibiendo su cuenta o contactando a las autoridades.


Con respecto a muchas respuestas útiles, espero agregar algunos valores a este hilo. La inyección de SQL es un ataque que se puede realizar a través de las entradas del usuario (entradas que el usuario rellena y luego utiliza dentro de las consultas). Los patrones de inyección de SQL son la sintaxis de consulta correcta, aunque podemos llamarlo: consultas erróneas por razones erróneas, asumimos que podría Ser una mala persona que intenta obtener información secreta (sin pasar por el control de acceso) que afecta los tres principios de seguridad (confidencialidad, integridad, disponibilidad).

Ahora, nuestro punto es prevenir amenazas de seguridad como los ataques de inyección de SQL, la pregunta (Cómo prevenir un ataque de inyección de SQL usando PHP), ser más realistas, filtrar datos o borrar datos de entrada es el caso cuando se usan datos de entrada de usuario dentro de tales consultar, usar PHP o cualquier otro lenguaje de programación no es el caso, o según lo recomendado por más personas para usar tecnología moderna como una declaración preparada o cualquier otra herramienta que actualmente admita la prevención de inyección SQL, ¿considera que estas herramientas ya no están disponibles? ¿Cómo asegurar su aplicación?

Mi enfoque contra la inyección de SQL es: borrar los datos de entrada del usuario antes de enviarlos a la base de datos (antes de usarlos dentro de cualquier consulta).

Filtrado de datos para (Convertir datos no seguros en datos seguros) Considere que PDO y MySQLi no están disponibles, ¿cómo puede proteger su aplicación? ¿Me obligas a usarlos? ¿Qué pasa con otros idiomas distintos de PHP? Prefiero proporcionar ideas generales, ya que pueden usarse para un borde más amplio, no solo para un idioma específico.

  1. Usuario de SQL (límite de privilegio de usuario): las operaciones de SQL más comunes son (SELECCIONAR, ACTUALIZAR, INSERTAR), entonces, ¿por qué otorgar el privilegio de ACTUALIZACIÓN a un usuario que no lo requiere? Por ejemplo , el inicio de sesión y las páginas de búsqueda solo usan SELECT, entonces, ¿por qué usar usuarios de DB en estas páginas con altos privilegios? REGLA: no cree un usuario de base de datos para todos los privilegios, para todas las operaciones de SQL, puede crear su esquema como (deluser, selectuser, updateuser) como nombres de usuario para facilitar su uso.

Ver Principio de privilegio mínimo.

  1. Filtrado de datos: antes de construir cualquier consulta, la entrada del usuario debe validarse y filtrarse, para los programadores, es importante definir algunas propiedades para cada variable de entrada del usuario: tipo de datos, patrón de datos y longitud de los datos . un campo que es un número entre (x e y) debe ser validado exactamente usando la regla exacta, para un campo que es una cadena (texto): el patrón es el caso, por ejemplo, el nombre de usuario debe contener solo algunos caracteres, digamos [a- zA-Z0-9_-.] la longitud varía entre (x y n) donde x y n (enteros, x <= n). Regla: la creación de filtros exactos y las reglas de validación son las mejores prácticas para mí.

  2. Use otras herramientas: aquí, también estaré de acuerdo con usted en que la declaración preparada (consulta parametrizada) y los procedimientos almacenados, las desventajas aquí son que estas habilidades requieren habilidades avanzadas que no existen para la mayoría de los usuarios; la idea básica aquí es distinguir entre consultas SQL y los datos que se utilizan en el interior, ambos enfoques pueden usarse incluso con datos no seguros, porque los datos de entrada del usuario aquí no agregan nada a la consulta original como (cualquiera o x = x). Para obtener más información, lea la Hoja de referencia de OWASP SQL Injection Prevention .

Ahora, si es un usuario avanzado, comience a usar esta defensa como quiera, pero, para los principiantes, si no pueden implementar rápidamente el procedimiento almacenado y prepararon la declaración, es mejor filtrar los datos de entrada tanto como puedan.

Finalmente, consideremos que el usuario envía este texto a continuación en lugar de ingresar su nombre de usuario:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)=''2'',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = ''root''

Esta entrada puede verificarse temprano sin ninguna declaración preparada ni procedimientos almacenados, pero para estar seguro, su uso comienza después del filtrado y la validación de los datos del usuario.

El último punto es detectar un comportamiento inesperado que requiere más esfuerzo y complejidad; No se recomienda para aplicaciones web normales. El comportamiento inesperado en la entrada anterior del usuario es SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, root una vez detectadas estas palabras, puede evitar la entrada.

ACTUALIZACIÓN1:

Un usuario comentó que esta publicación es inútil, ¡OK! Aquí está lo que OWASP.ORG proporcionó:

Defensas primarias:

Opción # 1: El uso de sentencias preparadas (consultas parametrizadas)
Opción # 2: El uso de procedimientos almacenados
Opción # 3: escapar de todas suministrada por el usuario de entrada

defensas adicionales:

También Forzar: Privilegio
también llevan a cabo: Lista Blanca de validación de entrada

Como usted sabe, reclamar sobre un artículo debe estar respaldado por un argumento válido, ¡al menos una referencia! De lo contrario, se considera como un ataque y mal reclamo!

Actualización2:

Del manual de PHP, PHP: Declaraciones preparadas - Manual :

Escape e inyección SQL.

Las variables enlazadas serán escapadas automáticamente por el servidor. El servidor inserta sus valores de escape en los lugares apropiados en la plantilla de declaración antes de la ejecución. Se debe proporcionar una sugerencia al servidor para el tipo de variable vinculada, para crear una conversión adecuada. Consulte la función mysqli_stmt_bind_param () para obtener más información.

El escape automático de valores dentro del servidor a veces se considera una característica de seguridad para evitar la inyección de SQL. El mismo grado de seguridad se puede lograr con declaraciones no preparadas si los valores de entrada se escapan correctamente.

Update3:

Creé casos de prueba para saber cómo PDO y MySQLi envían la consulta al servidor MySQL cuando uso una declaración preparada:

DOP:

$user = "''''1''''"; //Malicious keyword $sql = ''SELECT * FROM awa_user WHERE userame =:username''; $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $sth->execute(array('':username'' => $user));

Registro de consultas:

189 Query SELECT * FROM awa_user WHERE userame =''/'/'1/'/''' 189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) { $stmt->bind_param("s", $user); $user = "''''1''''"; $stmt->execute();

Registro de consultas:

188 Prepare SELECT * FROM awa_user WHERE username =? 188 Execute SELECT * FROM awa_user WHERE username =''/'/'1/'/''' 188 Quit

Está claro que una declaración preparada también está escapando a los datos, nada más.

Como también se mencionó en la declaración anterior The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly, por lo tanto, esto prueba que la validación de los datos, por ejemplo, intval()es una buena idea para valores enteros antes de enviar cualquier consulta, además, la prevención de datos maliciosos del usuario antes de enviar la consulta es un enfoque correcto y válido .

Consulte esta pregunta para obtener más detalles: PDO envía una consulta sin formato a MySQL mientras Mysqli envía una consulta preparada, ambas producen el mismo resultado

Referencias:

  1. Hoja de trucos de inyección SQL
  2. Inyección SQL
  3. Seguridad de información
  4. Principios de seguridad
  5. Validación de datos

Estoy a favor de los procedimientos almacenados ( MySQL ha tenido soporte de procedimientos almacenados desde 5.0 ) desde un punto de vista de seguridad, las ventajas son:

  1. La mayoría de las bases de datos (incluido MySQL ) permiten que el acceso de los usuarios esté restringido a la ejecución de procedimientos almacenados. El control de acceso de seguridad detallado es útil para evitar la escalada de ataques de privilegios. Esto evita que las aplicaciones comprometidas puedan ejecutar SQL directamente contra la base de datos.
  2. Abstraen la consulta de SQL sin procesar de la aplicación, por lo que hay menos información disponible sobre la estructura de la base de datos. Esto hace que sea más difícil para las personas comprender la estructura subyacente de la base de datos y diseñar ataques adecuados.
  3. Solo aceptan parámetros, por lo que las ventajas de las consultas parametrizadas están ahí. Por supuesto: en la OMI, aún necesita sanear su entrada, especialmente si está utilizando SQL dinámico dentro del procedimiento almacenado.

Las desventajas son -

  1. Ellos (procedimientos almacenados) son difíciles de mantener y tienden a multiplicarse muy rápidamente. Esto hace que gestionarlos sea un problema.
  2. No son muy adecuados para consultas dinámicas: si están diseñadas para aceptar código dinámico como parámetros, se niegan muchas de las ventajas.

Hay muchas maneras de prevenir las inyecciones de SQL y otros hacks de SQL. Puede encontrarlo fácilmente en Internet (Búsqueda de Google). Por supuesto que la DOP es una de las buenas soluciones. Pero me gustaría sugerirle algunos buenos enlaces de prevención de inyección de SQL.

¿Qué es la inyección SQL y cómo prevenirla?

Manual de PHP para inyección SQL.

Explicación de Microsoft sobre inyección y prevención de SQL en PHP.

y algunos otros como Prevenir la inyección de SQL con MySQL y PHP

Ahora, ¿por qué necesita evitar su consulta de inyección de SQL?

Me gustaría hacerle saber: ¿Por qué intentamos prevenir la inyección de SQL con un breve ejemplo a continuación:

Consulta para la coincidencia de autenticación de inicio de sesión:

$query="select * from users where email=''".$_POST[''email'']."'' and password=''".$_POST[''password'']."'' ";

Ahora, si alguien (un hacker) pone

$_POST[''email'']= [email protected]'' OR ''1=1

y contraseña de nada ....

La consulta se analizará en el sistema solo hasta:

$query="select * from users where email=''[email protected]'' OR ''1=1'';

La otra parte será descartada. Entonces, ¿qué pasará? Un usuario no autorizado (hacker) podrá iniciar sesión como administrador sin tener su contraseña. Ahora, él puede hacer cualquier cosa que el administrador / correo electrónico pueda hacer. Ver, es muy peligroso si la inyección de SQL no se evita.


Hay tantas respuestas para PHP y MySQL , pero aquí hay un código para PHP y Oracle para prevenir la inyección de SQL, así como el uso regular de los controladores oci8:

$conn = oci_connect($username, $password, $connection_string); $stmt = oci_parse($conn, ''UPDATE table SET field = :xx WHERE ID = 123''); oci_bind_by_name($stmt, '':xx'', $fieldval); oci_execute($stmt);


La alternativa simple a este problema podría resolverse otorgando los permisos apropiados en la propia base de datos. Por ejemplo: si está utilizando una base de datos mysql, ingrese a la base de datos a través del terminal o la IU proporcionada y simplemente siga este comando:

GRANT SELECT, INSERT, DELETE ON database TO username@''localhost'' IDENTIFIED BY ''password'';

Esto restringirá al usuario para que se limite solo a las consultas especificadas. Elimine el permiso de eliminación y, por lo tanto, los datos nunca se eliminarán de la consulta activada desde la página php. Lo segundo que debe hacer es vaciar los privilegios para que mysql actualice los permisos y las actualizaciones.

FLUSH PRIVILEGES;

Más información sobre la flush .

Para ver los privilegios actuales para el usuario, active la siguiente consulta.

select * from mysql.user where User=''username'';

Aprenda más sobre GRANT .


Para aquellos que no están seguros de cómo usar PDO (proveniente de las mysql_funciones), hice un contenedor de PDO muy, muy simple que es un solo archivo. Existe para mostrar lo fácil que es hacer todas las cosas comunes que deben hacerse las aplicaciones. Funciona con PostgreSQL, MySQL y SQLite.

Básicamente, léalo PDO para ver cómo hacer que las funciones de la DOP se usen en la vida real para que sea más fácil almacenar y recuperar valores en el formato que desee.

Quiero una sola columna

$count = DB::column(''SELECT COUNT(*) FROM `user`);

Quiero una matriz (clave => valor) resultados (es decir, para hacer un cuadro de selección)

$pairs = DB::pairs(''SELECT `id`, `username` FROM `user`);

Quiero un resultado de una sola fila

$user = DB::row(''SELECT * FROM `user` WHERE `id` = ?'', array($user_id));

Quiero una serie de resultados

$banned_users = DB::fetch(''SELECT * FROM `user` WHERE `banned` = ?'', array(TRUE));



Si es posible, emitir los tipos de sus parámetros. Pero solo funciona en tipos simples como int, bool y float.

$unsafe_variable = $_POST[''user_id'']; $safe_variable = (int)$unsafe_variable ; mysqli_query($conn, "INSERT INTO table (column) VALUES (''" . $safe_variable . "'')");


Una buena idea es usar un ''mapeador objeto-relacional'' como Idiorm :

$user = ORM::for_table(''user'') ->where_equal(''username'', ''j4mie'') ->find_one(); $user->first_name = ''Jamie''; $user->save(); $tweets = ORM::for_table(''tweet'') ->select(''tweet.*'') ->join(''user'', array( ''user.id'', ''='', ''tweet.user_id'' )) ->where_equal(''user.username'', ''j4mie'') ->find_many(); foreach ($tweets as $tweet) { echo $tweet->text; }

No solo te salva de las inyecciones de SQL, sino también de los errores de sintaxis. También admite colecciones de modelos con encadenamiento de métodos para filtrar o aplicar acciones a múltiples resultados a la vez y múltiples conexiones.


Una forma sencilla sería utilizar un marco PHP como CodeIgniter o Laravel que tenga características integradas como el filtrado y el registro activo para que no tenga que preocuparse por estos matices.


Usando esta función PHP mysql_escape_string()puedes obtener una buena prevención de una manera rápida.

Por ejemplo:

SELECT * FROM users WHERE name = ''".mysql_escape_string($name_from_html_form)."''

mysql_escape_string - Escapa de una cadena para usar en mysql_query

Para más prevención, puedes añadir al final ...

wHERE 1=1 or LIMIT 1

Finalmente obtienes:

SELECT * FROM users WHERE name = ''".mysql_escape_string($name_from_html_form)."'' LIMIT 1


Usar PDO y MYSQLi es una buena práctica para evitar inyecciones de SQL, pero si realmente desea trabajar con las funciones y consultas de MySQL, sería mejor usar

mysql_real_escape_string()

$unsafe_variable = mysql_real_escape_string($_POST[''user_input'']);

Hay más habilidades para evitar esto: como identificar: si la entrada es una cadena, un número, un carácter o una matriz, hay muchas funciones incorporadas para detectar esto. Además, sería mejor usar estas funciones para verificar los datos de entrada.

is_string

$unsafe_variable = (is_string($_POST[''user_input'']) ? $_POST[''user_input''] : '''');

is_numeric

$unsafe_variable = (is_numeric($_POST[''user_input'']) ? $_POST[''user_input''] : '''');

Y es mucho mejor usar esas funciones para verificar los datos de entrada mysql_real_escape_string.