¿Tengo que protegerme de la inyección de SQL si utilicé un menú desplegable?

96

Entiendo que NUNCA debe confiar en la entrada del usuario desde un formulario, principalmente debido a la posibilidad de inyección SQL.

Sin embargo, ¿esto también se aplica a un formulario en el que la única entrada proviene de un menú desplegable (ver más abajo)?

Estoy guardando el $_POST['size']en una sesión que luego se usa en todo el sitio para consultar las diversas bases de datos (con una mysqliconsulta de selección) y cualquier inyección de SQL definitivamente las dañaría (posiblemente las eliminaría).

No hay un área para que el usuario ingrese la información para consultar las bases de datos, solo menús desplegables.

<form action="welcome.php" method="post">
<select name="size">
  <option value="All">Select Size</option> 
  <option value="Large">Large</option>
  <option value="Medium">Medium</option>
  <option value="Small">Small</option>
</select>
<input type="submit">
</form>
Andrajos
fuente
112
Si. Nada impide que un atacante envíe los valores que desee en su <select>entrada. De hecho, incluso un usuario ligeramente técnico podría agregar opciones adicionales utilizando la consola del navegador. si mantiene una lista blanca de matriz de valores disponibles y compara la entrada con ella, puede mitigar eso (y debería hacerlo porque evita valores no deseados)
Michael Berkowski
13
Debe comprender las cosas básicas de solicitud / respuesta y lo que no importa cómo se construye el front-end sobre la solicitud, es decir, en este caso, menú desplegable
Royal Bg
13
@YourCommonSense Porque es una buena pregunta. No todo el mundo se da cuenta de lo manipulable que es un cliente. Esto provocará respuestas muy valiosas a este sitio.
Cruncher
8
@Cruncher ya veo. Para un stackoverflowiano promedio, es una ciencia espacial de la que han oído hablar antes. Incluso a pesar de la pregunta más votada bajo la etiqueta PHP.
Tu sentido común
9
"Entiendo que NUNCA debe confiar en la entrada del usuario". Sin excepciones.
Prinzhorn

Respuestas:

69

Puede hacer algo tan simple como el siguiente ejemplo para asegurarse de que el tamaño de publicación sea el esperado.

$possibleOptions = array('All', 'Large', 'Medium', 'Small');

if(in_array($_POST['size'], $possibleOptions)) {
    // Expected
} else {
    // Not Expected
}

Luego use mysqli_ * si está usando una versión de php> = 5.3.0 que debería ser, para guardar su resultado. Si se usa correctamente, esto ayudará con la inyección de SQL.

Oliver Bayes-Shelton
fuente
¿Es esta una versión abreviada de un OliverBS de "lista blanca"?
Tatters
En realidad, no es solo una versión muy básica de una, puede agregar los valores a una base de datos para compararlos para que sea más fácil y reutilizable, o tal vez hacer una clase de lista blanca con un método específico para cada lista blanca para verificar. si no desea utilizar una base de datos, las propiedades de la lista blanca podrían estar dentro de una propiedad de matriz en su clase de lista blanca.
Oliver Bayes-Shelton
9
No obstante, debe utilizar Prepared Statements (recomendado) o mysqli_real_escape_string. También escape correctamente los valores al generarlos (por ejemplo, use htmlspecialchars () en un documento HTML).
ComFreek
4
Sugeriría establecer el tercer parámetro de in_arraya truepara una comparación estricta. No estoy seguro de qué podría salir mal, pero las comparaciones sueltas son bastante extravagantes.
Brilliand
1
Los valores de @OliverBS $ _POST también pueden ser matrices. Las cadenas numéricas se comparan como números ('5' == '05'). No creo que tenga un agujero de seguridad en su ejemplo específico, pero las reglas son complejas y los agujeros podrían abrirse por razones que ni siquiera entiendo. La comparación estricta es más fácil de razonar y, por lo tanto, más fácil de usar de forma segura.
Brilliand
195

Sí, necesitas protegerte contra esto.

Déjame mostrarte por qué, usando la consola de desarrollador de Firefox:

He editado uno de los valores en el menú desplegable para que sea una declaración de tabla desplegable

Si no limpia estos datos, su base de datos será destruida. (Puede que esta no sea una declaración SQL totalmente válida, pero espero haber entendido mi punto).

El hecho de que haya limitado las opciones disponibles en su menú desplegable no significa que haya limitado los datos que puedo enviar a su servidor.

Si trató de restringir aún más este comportamiento de uso en su página, mis opciones incluyen deshabilitar ese comportamiento o simplemente escribir una solicitud HTTP personalizada en su servidor que imita el envío de este formulario de todos modos. Hay una herramienta llamada curl que se usa exactamente para eso, y creo que el comando para enviar esta inyección SQL de todos modos se vería así:

curl --data "size=%27%29%3B%20DROP%20TABLE%20*%3B%20--"  http://www.example.com/profile/save

(Puede que este no sea un comando curl totalmente válido, pero nuevamente, espero haber entendido mi punto).

Entonces, reiteraré:

NUNCA confíe en la entrada del usuario. SIEMPRE protéjase.

No asuma que cualquier entrada del usuario es segura. Es potencialmente inseguro incluso si llega a través de algún medio que no sea un formulario. Ninguno de ellos es lo suficientemente confiable como para renunciar a protegerse de la inyección SQL.

doppelgreener
fuente
24
Sin mencionar la creación de una carga útil personalizada con curl. ¡SIEMPRE desinfecte la entrada del lado del servidor !
recursión.ninja
14
Una imagen dice más que mil palabras.
davidkonrad
4
Esta debería ser la respuesta predeterminada. También debe incluir algo sobre curl. La gente no entiende que puede enviar una solicitud HTTP desde cualquier lugar a cualquier lugar utilizando cualquier formato y pasando cualquier valor, depende del servidor asegurarse de que la solicitud sea válida antes de procesarla.
retrohacker
2
¡Que lindo! ¡Son las pequeñas mesas de Bobby!
Aura
45

Como esta pregunta fue etiquetada con , aquí hay una respuesta con respecto a este tipo de ataque en particular:

Como le han dicho en los comentarios, debe usar declaraciones preparadas para cada consulta que involucre datos variables, sin excepciones .

¡Independientemente de cualquier material HTML!
Es esencial comprender que las consultas SQL deben formatearse correctamente independientemente de cualquier factor externo, ya sea entrada HTML o cualquier otra cosa.

Aunque puede usar la lista blanca sugerida en otras respuestas para el propósito de validación de entrada, no debería afectar ninguna acción relacionada con SQL ; deben permanecer igual, sin importar si validó la entrada HTML o no. Significa que aún debe usar declaraciones preparadas al agregar cualquier variable a la consulta.

Aquí puede encontrar una explicación completa, por qué las declaraciones preparadas son imprescindibles y cómo usarlas correctamente y dónde no son aplicables y qué hacer en tal caso: La guía del autoestopista para la protección de inyección SQL

Además, esta pregunta fue etiquetada con . Sobre todo por accidente, supongo, pero de todos modos, tengo que advertirle que mysqli en bruto no es una sustitución adecuada para las antiguas funciones mysq_ * . Simplemente porque si se usa en el estilo antiguo, no agregará seguridad en absoluto. Si bien su soporte para las declaraciones preparadas es doloroso y problemático, hasta el punto de que el usuario promedio de PHP simplemente no puede intentarlo en absoluto. Por lo tanto, si no hay ORM o algún tipo de biblioteca de abstracción es una opción, entonces PDO es su única opción.

Tu sentido común
fuente
5
i = aleatorio (0, 15); // alguna consulta usando i. ¿Todavía necesito preparar una declaración aquí?
Cruncher
2
¿Qué es la implementación random?
Slicedpan
9
@YourCommonSense ¿vista estrecha? Para mí, "Haz siempre X, y no daré ningún razonamiento para ello" es una visión estrecha.
Cruncher
5
@Cruncher No puedo pensar en ninguna razón para no usar siempre declaraciones preparadas, cada vez, para todo, siempre. Me puedes decir una
Wesley Murch
3
@Cruncher Con eso estoy de acuerdo, parece que estás jugando a Devil's Advocate. La estipulación en la respuesta también es "involucra cualquier dato variable" . Hay algunos casos como PHP int casting, pero parece mejor usar declaraciones preparadas. Decirle a la gente (sin experiencia) que un "impulso" de rendimiento menor es más importante que la seguridad está muy lejos. La respuesta es "incompleta" pero envía un fuerte mensaje que los demás están ignorando. Descansaré mi caso.
Wesley Murch
12

Si.

Cualquiera puede falsificar cualquier cosa por los valores que realmente se envían:

Entonces, para validar los menús desplegables, puede verificar para asegurarse de que el valor con el que está trabajando estaba en el menú desplegable; algo como esto sería la mejor manera (más sanamente paranoica):

if(in_array($_POST['ddMenu'], $dropDownValues){

    $valueYouUseLaterInPDO = $dropDownValues[array_search("two", $arr)];

} else {

    die("effin h4x0rs! Keep off my LAMP!!"); 

}
rm-vanda
fuente
5
Esta respuesta es incompleta, además, que puedes usar comandos preparados, por lo que la inyección no es posible, independientemente de lo que el valor se establece en
Slicedpan
7
@Slicedpan ¿De verdad? Suena como si estuvieras subiéndote al tren sin saber por qué ... Si tengo un puñado de posibles entradas en una consulta, de modo que pueda verificar que todas están bien (lo que sé, porque las hice), entonces no acumula beneficios de seguridad adicionales al usar una declaración preparada
Cruncher
3
Excepto que hacer cumplir el uso de declaraciones preparadas como una convención lo protege de la introducción de vulnerabilidades de inyección de SQL en el futuro.
Slicedpan
1
@Cruncher Hacer una promesa de que "todos los valores del menú desplegable siempre estarán seguros" es una promesa muy insegura. Los valores cambian, el código no se actualiza necesariamente. ¡Es posible que el que actualice los valores ni siquiera sepa qué es un valor inseguro! Especialmente en el ámbito de la programación web, que está plagado de todo tipo de problemas de seguridad, escatimar en algo como esto es simplemente irresponsable (y otras palabras menos agradables).
hyde
1
@Cruncher Si maneja adecuadamente sus cosas de SQL, puede aceptar cualquier entrada sin interferir con el correcto funcionamiento de SQL. La parte SQL debe estar preparada y lista para aceptar cualquier cosa, sin importar de dónde venga. Todo lo demás es propenso a errores.
glglgl
8

Una forma de protegerse contra los usuarios que cambian sus menús desplegables usando la consola es usar solo valores enteros en ellos. Luego, puede validar que el valor POST contiene un número entero y usar una matriz para convertirlo en texto cuando sea necesario. P.ej:

<?php
// No, you don't need to specify the numbers in the array but as we're using them I always find having them visually there helpful.
$sizes = array(0 => 'All', 1 => 'Large', 2 => 'Medium', 3 => 'Small');
$size = filter_input(INPUT_POST, "size", FILTER_VALIDATE_INT);

echo '<select name="size">';
foreach($sizes as $i => $s) {
    echo '<option value="' . $i . '"' . ($i == $size ? ' selected' : '') . '>' . $s . '</option>';
}
echo '</select>';

Luego, puede usarlo $sizeen su consulta sabiendo que solo contendrá FALSEun número entero.

Styphon
fuente
2
@OliverBS ¿Qué es filter_inputsi no es un cheque en el lado del servidor? Nadie puede publicar nada excepto un número entero.
Styphon
Gracias Styphon, ¿Entonces esto reemplaza el formulario en el OP? ¿Y sería esto aplicable a formularios más grandes con múltiples menús desplegables? ¿Si agregué otro menú desplegable para decir 'color'?
Tatters
@SamuelTattersfield Sí y sí. Puede usar esto para tantos menús desplegables como desee, con tantas opciones como desee. Simplemente crea una nueva matriz para cada menú desplegable y coloca todas las opciones para el menú desplegable en la matriz.
Styphon
¡Agradable! Me gusta. Lo intentaré y veré qué obtengo en las pruebas.
Tatters
@SamuelTattersfield Genial, solo asegúrate de usar la filter_inputparte también para la validación, de lo contrario, es tan útil como una tetera de chocolate por seguridad.
Styphon
7

Las otras respuestas ya cubren lo que necesita saber. Pero quizás ayude aclarar un poco más:

Hay DOS COSAS que debe hacer:

1. Valide los datos del formulario.

Como muestra muy claramente la respuesta de Jonathan Hobbs , la elección del elemento html para la entrada del formulario no realiza ningún filtrado confiable por usted.

La validación generalmente se realiza de una manera que no altera los datos, pero que muestra el formulario nuevamente, con los campos marcados como "Corrija esto".

La mayoría de los frameworks y CMS tienen constructores de formularios que le ayudan con esta tarea. Y no solo eso, también ayudan contra CSRF (o "XSRF"), que es otra forma de ataque.

2. Desinfectar / escapar variables en sentencias SQL.

.. o deje que las declaraciones preparadas hagan el trabajo por usted.

Si crea una declaración (Mi) SQL con cualquier variable, proporcionada por el usuario o no, debe escapar y citar estas variables.

En general, cualquier variable que inserte en una declaración de MySQL debe ser una cadena o algo que PHP pueda convertir de manera confiable en una cadena que MySQL pueda digerir. Como números.

Para las cadenas, debe elegir uno de varios métodos para escapar de la cadena, es decir, reemplazar cualquier carácter que pueda tener efectos secundarios en MySQL.

  • En MySQL + PHP de la vieja escuela, mysql_real_escape_string () hace el trabajo. El problema es que es demasiado fácil de olvidar, por lo que debería utilizar absolutamente declaraciones preparadas o constructores de consultas.
  • En MySQLi, puede utilizar declaraciones preparadas.
  • La mayoría de los frameworks y CMS proporcionan generadores de consultas que le ayudan con esta tarea.

Si se trata de un número, puede omitir el escape y las comillas (es por eso que las declaraciones preparadas permiten especificar un tipo).

Es importante señalar que se escapan las variables para la declaración SQL y NO para la base de datos en sí . La base de datos almacenará la cadena original, pero la declaración necesita una versión de escape.

¿Qué sucede si omite uno de estos?

Si no usa la validación de formularios , pero desinfecta su entrada SQL, es posible que vea todo tipo de cosas malas, ¡pero no verá la inyección SQL! (*)

Primero, puede llevar su solicitud a un estado que no planeó. Por ejemplo, si desea calcular la edad promedio de todos los usuarios, pero un usuario dio "aljkdfaqer" para la edad, su cálculo fallará.

En segundo lugar, puede haber todo tipo de otros ataques de inyección que debe considerar: por ejemplo, la entrada del usuario podría contener javascript u otras cosas.

Aún puede haber problemas con la base de datos: por ejemplo, si un campo (columna de la tabla de la base de datos) tiene un límite de 255 caracteres y la cadena es más larga. O si el campo solo acepta números y, en su lugar, intenta guardar una cadena no numérica. Pero esto no es una "inyección", es simplemente "bloquear la aplicación".

Pero, incluso si tiene un campo de texto libre donde permite cualquier entrada sin validación en absoluto, aún puede guardar esto en la base de datos así, si lo escapa correctamente cuando va a una declaración de base de datos. El problema surge cuando quieres usar esta cadena en alguna parte.

(*) o esto sería algo realmente exótico.

Si no escapa de las variables para las declaraciones SQL , pero sí validó la entrada del formulario, aún puede ver que suceden cosas malas.

En primer lugar, corre el riesgo de que cuando guarde datos en la base de datos y los vuelva a cargar, ya no sean los mismos datos, "perdidos en la traducción".

En segundo lugar, puede generar declaraciones SQL no válidas y, por lo tanto, bloquear su aplicación. Por ejemplo, si alguna variable contiene una comilla o un carácter de comilla doble, según el tipo de cita que utilice, obtendrá una declaración MySQL no válida.

En tercer lugar, todavía puede provocar una inyección SQL.

Si la entrada de usuario de los formularios ya está filtrada / validada, la inyección intencional de SQl puede volverse menos probable, SI su entrada se reduce a una lista codificada de opciones o si está restringida a números. Pero cualquier entrada de texto libre se puede utilizar para la inyección de SQL, si no se escapan correctamente las variables en las declaraciones de SQL.

E incluso si no tiene ninguna entrada de formulario, aún podría tener cadenas de todo tipo de fuentes: leídas del sistema de archivos, extraídas de Internet, etc. Nadie puede garantizar que estas cadenas sean seguras.

Don Quijote
fuente
Ni los datos demasiado largos ni una cadena en un campo numérico bloqueará nada
su sentido común
hmm, acabo de intentar esto ahora, y de hecho muestra una advertencia en lugar de un error. Creo que es PDO lo que convierte esto en un error. Estoy seguro de que hablé en una docena de problemas en drupal.org donde alguna cadena es demasiado larga para un varchar.
donquixote
6

Su navegador web no "sabe" que está recibiendo una página de php, todo lo que ve es html. Y la capa http sabe incluso menos que eso. Debe poder manejar casi cualquier tipo de entrada que pueda cruzar la capa http (afortunadamente, para la mayoría de las entradas php ya dará un error). Si está tratando de evitar que las solicitudes maliciosas estropeen su base de datos, debe asumir que el tipo del otro extremo sabe lo que está haciendo y que no está limitado a lo que puede ver en su navegador en circunstancias normales ( sin mencionar lo que puedes jugar con las herramientas de desarrollo de un navegador). Entonces, sí, debe atender cualquier entrada de su menú desplegable, pero para la mayoría de las entradas puede dar un error.

Uno sin nombre
fuente
6

El hecho de que haya restringido al usuario para que solo use valores de una determinada lista desplegable es irrelevante. Un usuario técnico puede capturar la solicitud http enviada a su servidor antes de que salga de su red, modificarla usando una herramienta como un servidor proxy local y luego continuar en su camino. Usando la solicitud alterada, pueden enviar valores de parámetros que no son los que usted especificó en la lista desplegable. Los desarrolladores deben tener la mentalidad de que las restricciones del cliente a menudo no tienen sentido, ya que cualquier cosa en un cliente puede modificarse. Se requiere la validación del servidor en cada punto en el que ingresan los datos del cliente. Los atacantes confían en la ingenuidad de los desarrolladores en este único aspecto.

Jason Higgins
fuente
5

Es mejor utilizar una consulta parametrizada para asegurarse contra la inyección de SQL. En ese caso, el aspecto de la consulta sería este:

SELECT * FROM table WHERE size = ?

Cuando proporciona una consulta como la anterior con texto sin verificar su integridad (la entrada no está validada en el servidor) y contiene código de inyección SQL, se manejará correctamente. En otras palabras, la solicitud dará como resultado que suceda algo como esto en la capa de la base de datos:

SELECT * FROM table WHERE size = 'DROP table;'

Esto simplemente seleccionará 0 resultados a medida que regrese, lo que hará que la consulta sea ineficaz para causar daño a la base de datos sin la necesidad de una lista blanca, una verificación de verificación u otras técnicas. Tenga en cuenta que un programador responsable hará la seguridad en capas y, a menudo, validará además de parametrizar consultas. Sin embargo, hay muy pocas razones para no parametrizar sus consultas desde una perspectiva de rendimiento y la seguridad agregada por esta práctica es una buena razón para familiarizarse con las consultas parametrizadas.

Dan Smith
fuente
4

Todo lo que se envía desde su formulario llega a su servidor como texto a través de los cables. No hay nada que impida que alguien cree un bot para imitar al cliente o que lo escriba desde una terminal si así lo desea. Nunca asuma que debido a que programó al cliente, éste actuará como usted cree. Esto es muy fácil de falsificar.

Ejemplo de lo que puede suceder y sucederá cuando confíe en el cliente.

GenericJam
fuente
4

Un pirata informático puede omitir el navegador por completo, incluida la verificación de formularios de JavaScript, enviando una solicitud mediante Telnet. Por supuesto, mirará el código de su página html para obtener los nombres de campo que tiene que usar, pero a partir de ese momento es "todo vale" para él. Por lo tanto, debe verificar todos los valores enviados en el servidor como si no se originaran en su página html.

usuario1452686
fuente