动态语句是创建为字符串的 SQL 语句,其中插入/连接了从某个来源(通常来自用户)获得的值,如果不对其进行清理,这会使它们容易受到 SQL 注入的影响。输入,例如:
$id_usuario = $_POST["id"];
mysql_query("SELECT * FROM usuarios WHERE id = $id_usuario");
这是应用程序(无论是否为 Web)安全性中的一个严重漏洞示例,因为如果用户输入这样的值1; DROP TABLE usuarios;--
,我们会发现执行的语句将是:
SELECT * FROM usuarios WHERE id = 1; DROP TABLE usuarios;--
并且包含其中所有数据的用户表将被删除。
如何防止在 PHP 中发生 SQL 注入?
使用准备好的语句和参数化查询
尽管可以使用诸如 等方法对输入进行清理
mysqli_real_escape_string
,但更建议使用准备好的或参数化的语句。准备好的语句将允许您高效地执行相同的语句。在 PHP 中,您有两个主要选择:PDO和 MySQLi。两者之间有几个区别,但主要的区别在于 PDO 可以用于不同类型的数据库(取决于使用的驱动程序),而 MySQLi 专门用于 MySQL 数据库。这就是为什么我会推荐 PDO 而不是 MySQLi。
PDO
占位符(指示字符串将在何处替换其值)可以通过使用问号 (
?
) 或使用名称(通常以 开头:
)来定义。我个人更喜欢使用名称,因为这可以帮助我在有多个变量的情况下找到可能的错误。这是问题代码的示例:
在这种情况下,
:idusuario
它将被替换为$_POST["id"]
安全的值,并且当它执行绑定时,表明该变量是整数(PDO::PARAM_INT
)类型。如果 SQL 语句中包含多个变量,则语句中使用的每个值都必须包含一个参数。从上面的示例中,
:idusuario
只能在正在准备的查询中使用一次。如果需要在查询中再次使用“userid”,则必须创建另一个参数,其值为$id_usuario
。我的 MySQL
该方法有两个接口:一个过程接口和一个面向对象接口。程序界面与 非常相似
mysql_*
,因此迁移过来的人可能会被它提供mysql_*
的易用性所吸引。mysqli_*
虽然,我个人还是会选择 OOP 版本。问题中的示例在其面向对象的接口中使用 MySQLi:
正如你所看到的,它与 PDO 非常相似(它改变了值类型的指定方式,
i
对于整数和s
字符串,但想法是相似的)。在 MySQLi 的程序版本中,等效代码为:
更多西班牙语信息的来源和参考书目:
我唯一的优点是翻译,也许在某些方面可以改进。我们知道,没有什么是 100% 安全的,但是 PDO 或 MySQLi 的简单使用必须与第 5 点中解释的其他策略相结合,以确保与我们的数据一样有价值的东西。
1. 介绍性说明
SQL 注入是一种控制数据库查询的技术,通常会导致机密性受损。在某些情况下,它可能会导致服务器完全接管。
例如:
由于代码注入(包括 SQL、LDAP、Command OS 和XPath 注入技术)一直保持在OWASP的 10 大漏洞之首,因此对于试图涉足应用程序安全领域的博主来说,这是一个热门话题。
不幸的是,在 Internet 上流传的许多建议(尤其是在搜索引擎中排名靠前的旧博客上)已经过时、无意中误导,而且往往很危险。
2.一个很常见的错误:错误配置的PDO
如果您是一名希望充分利用 PDO 的 PHP 开发人员,我们建议您更改两个默认值:
PDOStatement::execute()
并使您的代码减少冗余。要做到这两点:
由于它
PDO::ATTR_EMULATE_PREPARES
被定义为false
,我们得到了实际的准备好的语句。因为我们已经定义PDO::ATTR_ERRMORE
为PDO::ERRMODE_EXCEPTION
。而不是这个:
您可以像这样编写代码:
更高的安全性、简洁性和更好的易读性。你还能想要什么?
3. 另一个错误:没有完全理解 PDO 的用途
一个非常常见的错误是没有完全理解 PDO 存在的目的,并最终将其用作简单的更新助手。
Mientras juegan con PDO, casi todos los usuarios de PHP terminan considerándolo como una especie de mejora para la consulta
INSERT
/UPDATE
, que acepta una matriz asociativa y crea una consulta con marcadores dinámicos, que básicamente se parece a esto (un código real tomado de una pregunta sobre Stack Overflow):Se ve bastante bien, ya que puede producir rápidamente una consulta dentro de una matriz o arreglo en la que las claves representan nombres de columna y los valores representan los valores que se utilizarán en la consulta. Dado que los nombres de campos en formato HTML son los mismos que los nombres de columna de tabla, puede automatizar en gran medida el procesamiento de formularios, permitiéndole utilizar el mismo código para editar información en cualquier tabla. Muy conveniente. Pero catastróficamente vulnerable.
"¿Cómo?" - usted diría probablemente - "los marcadores de posición se utilizan y los datos se enlazan con seguridad, por lo tanto nuestra consulta es segura". Sí, los datos son seguros. Pero, de hecho, lo que hace este código es tomar la entrada del usuario y agregarlo directamente a la consulta. Sí, es una variable
$key
que va a la derecha en su consulta sin ningún tratamiento.Usar PDO de forma ingenua y mal configurado es una puerta abierta a la inyección SQL.
4. Ejemplos de inyección posibles con PDO mal configurado y desmontando algunos mitos sobre protección de la base de datos
Aquí hay una pequeña prueba de código de concepto que, siempre y cuando tenga una tabla
usuarios
y una fila conid = 1
, cambiará el nombre de usuario a un resultado de la consultaSELECT
. Y esta consulta podría ser cualquier cosa:Este código producirá una consulta como esta
Donde todo lo pasado después de
#
será tratado como un comentario.Puedes probarlo en casa.
¿Qué sucede en este código?
Estamos forjando un formulario, añadiendo otro campo a él y escribiendo un código SQL en el atributo
name
. La función auxiliar mencionada tomará este SQL forjado y lo insertará en la consulta construida. Como resultado, en lugar deJoe
el nombre se establecerá enhackeado!
. ¿No asusta demasiado, eh? Pero usted tiene que entender que lo que es peligroso aquí es el hecho de la inyección. Mientras que sea posible, la cantidad de crear consultas mal intencionadas es infinita. Y no todas son tan inofensivas. A continuación veremos una vulnerabilidad más peligrosa.PDO::quote() - El movimiento equivocado
Ok, necesitamos proteger nuestro código útil (así como cualquier otro código que no pueda ser protegido con declaraciones preparadas).
Lo primero que un usuario promedio de PHP probablemente pensaría es una función incorporada de PDO::quote(), que aparentemente hace lo que necesitamos: proteger los datos de la inyección de SQL. Pero pronto estará claro que una función destinada al formato de
cadena
es inaplicable para los identificadores. Ésta agregará comillas simples alrededor del valor devuelto y hará que nuestro código produzca una secuencia como esta:Lo que resultará en un error de sintaxis, ya que las comillas simples son ilegales como delimitadores de nombre de campo en MySQL.
No pienses nunca en recortar comillas simples de la cadena resultante, ya que fue sugerido hace algún tiempo en un comentario bajo la entrada de PDO::quote() en el manual de PHP: el propósito de esta función es hacer un formateo completo haciendo escape de ambos caracteres y agregando comillas. Saca una de estas dos acciones y tendrás una inyección.
En otras palabras, si tiramos las comillas simples circundantes del resultado de
PDO::quote()
, será como si solo aplicáramos la picadura regular escapando. Y veamos por qué está mal:Cadena de escape (mysql_real_escape_string) - un desastre
Otra cosa que un usuario de PHP podría pensar es una función de escape familiar que "hace que sus datos sean seguros" como falsamente se declaró durante mucho tiempo en el manual de PHP. Desafortunadamente, solo ayudará si el código de inyección contiene comillas simples u otros caracteres con esta función. La mala noticia es que todos esos caracteres son innecesarios para la inyección, demostrando esta función inútil para el caso (y refutando las habilidades ficticias de protección de esta función):
Como se puede ver, no hay una sola cita en la consulta maliciosa y por lo tanto ni
addslashes()
ni varios*_escape_string()
puede hacer nada aquí.Esta inyección es mucho más peligrosa ya que revelará la contraseña del administrador a cualquier persona.
Adición de Operadores de ejecución o comillas invertidas `` (aún vulnerable)
Ok, como ya hemos aprendido, las comillas simples no ayudarán. ¿Quizá los operadores de ejecución o comillas invertidas (``) ayudarían? Veamos si ayudan:
Como se puede ver, añadir comillas invertidas no nos ayudó en absoluto: un atacante sólo añade otra comilla para cerrar prematuramente el identificador y luego proceder con la consulta malintencionada. Sucede porque agregar cualquier delimitador alrededor de algún literal es inútil si no escapamos de estos delimitadores dentro - es la lección que aprendimos duramente de las inyecciones convencionales basadas en cadenas. ¡Así que escaparemos nuestras comillas invertidas!
Escapando las comillas invertidas
Entonces, ¿qué se puede hacer para conservar una función tan útil, pero sin una violación terrible?
Como se dijo antes, escapar delimitadores ayudaría. Por lo tanto, su primer nivel de defensa debe ser escapar delimitadores (comillas invertidas) duplicándolos.
Construyendo nuestra consulta de esta manera:
Usted conseguirá eliminar la inyección.
Si tratamos de inyectar utilizando el método anterior resultará en un error de ejecución de la consulta, que, aunque es seguramente mejor que una inyección exitosa, todavía no es un comportamiento bastante deseable.
Esta es la razón por la que citar/escapar nombres de campo aún no es suficiente. Y esta es la razón por la que usted debe utilizar otro nivel de defensa:
Solución sólida en todo
Hay una cosa más a tener en cuenta: aunque citar/escapar nombres de campo eliminará la inyección de SQL clásica, hay otro vector de ataque posible.
Como no controlamos qué nombres de campo se utilizan para actualizar, es posible que el usuario modifique el valor al que no debería tener acceso. Por ejemplo, imagine que hay un campo en una tabla de usuarios como
admin
que se establece en1
para admins y0
para usuarios regulares. La función en cuestión permitirá a cualquier manipulador de código aumentar el nivel de privilegio de su cuenta.Suponiendo todo lo anterior, una función tan útil debe estar siempre aceptando un parámetro adicional con una lista de nombres de campos permitidos:
¡Con tal mejora nuestro código se convertirá en sólido en todo!
Por supuesto, los ejemplos anteriores (punto 4) sólo funcionarán si el modo de emulación está activado. Pero usted tiene que entender que si la inyección del SQL puede ser explotada con éxito o no es una pregunta diferente y completamente irrelevante. Usted no debe dejar una inyección en el primer lugar o la manera de explotarlo se encontrará un día u otro.
5. Entonces, las consultas preparadas sirven o no?
Sí, pero a condición de que nuestro PDO esté configurado como se indica en el apartado 2
5.1 Cómo prevenir inyección de SQL (casi) siempre* garantizado
Utilice las instrucciones preparadas , también conocidas como consultas parametrizadas. Por ejemplo:
Las declaraciones preparadas eliminan cualquier posibilidad de inyección de SQL en su aplicación web. No importa lo que se pasa a las variables
$_GET
aquí, la estructura de la consulta SQL no puede ser cambiada por un atacante (a menos que, por supuesto, tengaPDO::ATTR_EMULATE_PREPARES
activado, lo que significaría que no está usando auténticas declaraciones).Nota: Si usted intenta desactivar
PDO::ATTR_EMULATE_PREPARES
es posible que algunas versiones de controladores de bases de datos ignoren este intento. Para tomar precauciones extras, establezca explícitamente el conjunto de caracteres en el DSN a uno que su aplicación y base de datos utilizan (por ejemploUTF-8
, el cual, si está usando MySQL, se llama confusamenteutf8mb4
).Los estados preparados solucionan un problema fundamental de la seguridad de la aplicación: Separan los datos que se van a procesar de las instrucciones que operan sobre dichos datos enviándolos en paquetes completamente separados. Este es el mismo problema fundamental que hace provoca los desbordamientos de pila.
Siempre y cuando nunca concatenes las variables proporcionadas por el usuario o el entorno con la sentencia SQL (y asegúrandote de no usar preparaciones emuladas) puedes, para todos los propósitos, descartar la inyección de SQL de tu lista de preocupaciones para siempre.
Advertencia Importante y Clarificación
Las sentencias preparadas protegen las interacciones entre su aplicación web y su servidor de base de datos (si están en máquinas separadas, también deben comunicarse a través de TLS). Todavía es posible que un atacante pueda almacenar una carga útil en un campo que podría ser peligroso, por ejemplo, en un procedimiento almacenado. Llamamos a esto una inyección SQL de orden superior (la respuesta de Stack Overflow vinculada se refiere a ellos como de "segundo orden", pero cualquier cosa que se ejecute después la consulta inicial debería ser objeto de análisis).
En esta situación, nuestro consejo sería no escribir procedimientos almacenados de tal manera que creen puntos de inyección SQL de orden superior.
5.2 ¿Qué pasa con el saneamiento de las entradas (Sanitizing Input)?
Si bien es posible prevenir ataques mediante la reescritura del flujo de datos entrantes antes de enviarlo al controlador de la base de datos, esta práctica está llena de matices peligrosos y oscuros... (Ambos enlaces en la oración anterior son muy recomendables.)
A menos que desee tomar el tiempo para investigar y lograr un dominio completo sobre todos los formatos Unicode que su aplicación use o acepte, es mejor que no intente desinfectar sus entradas. Las sentencias preparadas son más eficaces en la prevención de la inyección de SQL que las cadenas de escape.
Además, alterar el flujo de datos entrantes puede causar daños en los datos, especialmente si se trata de bloques binarios crudos (por ejemplo, imágenes o mensajes cifrados).
Las sentencias preparadas son más fáciles y pueden garantizar la prevención de la inyección de SQL.
Si la entrada del usuario nunca tiene la oportunidad de alterar la cadena de consulta, nunca puede conducir a la ejecución de código. Las sentencias preparadas separan completamente el código de los datos.
5.3 La entrada debe ser validada
La validación no es lo mismo que el saneamiento.
Las sentencias preparadas pueden impedir la inyección de SQL pero no pueden guardarlos de datos incorrectos. Para la mayoría de los casos filter_var() es útil aquí.
Nota:
filter_var()
valida que la cadena de correo electrónico dada se ajusta a la especificación RFC. No garantiza que haya una bandeja de entrada abierta en esa dirección ni compruebe que el nombre de dominio esté registrado. Una dirección de correo electrónico válida todavía no es segura para usar en consultas sin procesar, ni para mostrar en una página web sin filtrar para evitar ataques XSS .5.4 ¿Qué pasa con los identificadores de columnas y tablas?
Dado que los identificadores de columna y tabla forman parte de la estructura de consulta, no es posible parametrizarlos. Por lo tanto, si la aplicación que está desarrollando requiere una estructura de consulta dinámica donde las tablas o columnas son seleccionadas por el usuario, debe optar por una lista blanca.
Una lista blanca es una estrategia de lógica de aplicación que explícitamente sólo permite unos cuantos valores aceptados y rechaza el resto o utiliza un predeterminado sano. Contrasta con una lista negra que sólo prohíbe las entradas mal conocidas. En la mayoría de los casos las listas blancas son mejores para la seguridad que las listas negras.
Si permite que el usuario final proporcione los nombres de la tabla y/o de las columnas, ya que los identificadores no pueden parametrizarse, debe recurrir a escapar. En estas situaciones, recomendamos lo siguiente:
No : Simplemente escriba los meta caracteres SQL (por ejemplo ' )
Sí : Filtre todos los caracteres que no están permitidos.
El siguiente fragmento de código sólo permitirá nombres de tablas que comiencen con una letra en mayúscula o minúscula seguida de cualquier número de caracteres alfanuméricos y subrayados.
5.6 ¿Qué pasa si el uso de declaraciones preparadas parece demasiado engorroso?
La primera vez que un desarrollador encuentra sentencias preparadas puede sentirse frustrado por la perspectiva de verse forzado a escribir mucho código redundante (preparar, ejecutar, buscar, preparar, ejecutar, buscar, ad nauseam ).
Existe una biblioteca de PHP llamada EasyDB, que puede servir como alternativa al uso de PDO.
6. Enlaces
接受的答案是一个很好的补充,总是很感激。前段时间我也在网上看了一篇我觉得挺有意思的文章,想分享一下,可惜是英文的,我尽量把文章的一部分翻译一下,因为非常广泛(The Hitchhiker's Guide to SQL Injection Prevention)。
如何防止 [xxx] 类型的注入。
您可能会受到不同类型的注射的攻击—— “盲注”、“经期延迟”、“二阶”和数以千计的其他类型。人们必须明白,这些并不是执行注入的所有不同方式,而只是利用它的不同方式。虽然只有一种方法可以执行注入:破坏查询的完整性。因此,如果您可以保持查询的完整性,您就可以安全地同时避免数千种不同类型的注入。
为了保持查询完整性,只需正确格式化查询文字。
结论。
总之,我们可以制定两个简单的规则:
即使是动态创建的,SQL 查询也必须仅包含 2 种可能的数据类型:
遵守这些规则将保证 100% 的保护。
1)什么是SQL注入?
问题的根源在于
inyección SQL
代码和数据的混合。实际上,我们的查询SQL
是一个程序。一个合法且完整的程序,就像我们PHP
熟悉的脚本一样。所以碰巧我们正在动态构建这个应用程序,在我们进行的过程中添加一些数据。因此,这些数据可能会干扰我们的代码并对其进行更改。这样的改变将是自己的inyección
。这只有在我们不以无懈可击的方式格式化查询部分时才会发生。
它被编译成恶意序列。
他们叫它
inyección
吗?不正确。这是字符串文字的不正确格式。只要格式正确,就不会伤害任何人:
危害较小的结果:
再给他打电话
inyección
?又一次坏了。这是格式错误的数字文字。只要格式正确,诚实该声明肯定是无害的。
2) 格式化规则是什么?
事实上,格式化规则并不那么容易,也不能用一个命令式来表达。对于 MySQL,它将是:
1. 链条
2. 数字
3. 标识符
4. 运算符和关键字
如您所见,有四组不同的规则,而不仅仅是一个语句。
3) 准备好的报表
原生的想法
sentencia preparada
是聪明而简单的:查询和数据是分开发送到服务器的,因此它们没有干扰的可能性。是什么让不可能的inyección
。但同时,原生实现也有其局限性,因为它只支持两种类型的文字(即字符串和数字),这使得它们在实际使用中不足且不安全。在这里我们来到要点:
SQL
从常量部分和占位符创建查询的一般想法,用真实数据替换,自动格式化确实是我们正在寻找的圣杯。主要和最本质的好处
declaraciones preparadas
是消除了手动格式化的所有危险:这就是为什么今天如此鄙视手动格式化并且如此尊重准备好的语句的原因。
4)一些错误的测量和不良做法
1. 转义用户输入。
这是一个国王。一个严重的错觉,几乎所有 PHP 用户(甚至 OWASP,如您所见)仍然共享。它由“转义”和“用户输入”两部分组成:
转义:如上所述,它仅完成部分工作,仅用于一种文字类型。而当单独使用或在错误的地方使用时,肯定会带来灾难。
用户输入:在注射保护的上下文中不应该有这样的词。每个变量都有潜在的危险 - 无论来源如何!或者,换句话说,所有变量都必须正确格式化才能放入查询中——不要再介意源了。重要的是目的地。当开发人员开始将绵羊与山羊分开的那一刻,他们就迈出了走向灾难的第一步。
2. 数据验证。
必须理解,输入(在用户输入的意义上)数据验证与 SQL 完全无关。真的。如果允许自由格式的文本,则没有任何验证规则可以帮助防止 SQL 注入。然而,尽管有任何验证,我们还是必须格式化我们的 SQL - 提醒 Sarah O'Hara,它采用的名称从用户输入的角度来看是完全有效的。还要记住,验证规则是可以改变的。
3. Htmlspecialchars(还有filter_var()、strip_tags()等)。
朋友们。这是 HTML 编码,如果您还没有注意到的话。它与 SQL 完全无关。它根本无济于事,永远不应该在 SQL 注入保护的上下文中使用。它绝对不适用于 SQL,即使用作字符串转义函数也无法保护您的查询。将其留给应用程序的其他部分。另外,请理解,SQL 格式不应该接触数据。所以你把你的首饰放在一个保险箱里,你想保持它完好无损,而不是一些修改或更换的零件!同样在这里。数据库旨在存储您的数据,而不是“保护”它。并且必须存储您想要重用的确切数据(这意味着您愚蠢的 base64 尝试也是错误的,顺便说一句)。
除了最佳答案之外的另一个建议
建议为 url 添加掩码并创建执行 2 个功能的友好 url: 1- 它们隐藏 get 发送的参数,也有助于用户更清楚地查看信息。
例子
标准网址:http
://stackoverflow.com?id=100 友好网址:如何避免 PHP 中的 SQL 注入?
注意:第一个url不对应友好的url,只是一个例子。
如果攻击者想要攻击标准 url,他已经有一个参数来启动“id”,而在友好 url 的情况下,它就没有那么简单了,他必须进一步分析向 Web 服务器发出的请求并找到网址。
基本测量
如何屏蔽网址
一个例子是修改你的 web 服务器上的 .htaccess 文件,如果你使用 Apache,则将它与 mod_rewrite 绑定。
更多关于这里的信息:http ://www.emenia.es/como-crear-urls-amigables-con-htaccess/
如果你觉得还不够
建议结合使用登录、cookie 跟踪、标头、网络代理。
还添加了验证码系统,以避免机器人生成多个请求。
Mi respuesta simple, vamos al grano, lo que debes hacer ademas de un sistema de login y sesión de usuarios, es "validaciones" (especialmente en el login), crear un pattern y compararlo con lo que viene del post, (ademas de las validaciones básicas disponibles con html ) si es válido que lo deje pasar, algo así:
Este filtro determina si la cadena de texto contiene caracteres que no están en tu lista de permitidos, después si pasa por el filtro, el resto del código que seria lo que haría, guardar actualizar etc.
luego capturas el $da(datos del return) y lo muestras en el front como variable, para que si existe algo en el return, despliegue un mensaje de error en el formulario.
Por otro lado también deshabilita que puedan copiar y pegar en los inputs del formulario.
En resumen: debes evitar que ingresen caracteres especiales que se usan en las consultas sql, como * ; etc.
Interesante post. Actualmente estoy desarrollando un panel de administrador para un sitio web y el formulario de la página de inicio tiene en total, hasta ahora, 46 campos para texto (incluyendo rutas a imágenes). Primero creo la conexión a la base de datos:
Cuando el usuario le da click al botón "guardar cambios", se ejecuta el siguiente código:
Y luego tengo que realizar el mismo proceso que hice para el slogan 45 veces mas, y eso solo para la página de inicio. Mi pregunta es si estoy previniendo bien la inyección SQL y además previniendo que se puedan ejecutar scripts de Javascript (por el condicional que tiene strpos buscando por "script" para aplicar htmlspecialchars si lo encuentra).
Soy bastante novato en cuanto a PHP y MySql así que agradezco cualquier orientación que me puedan brindar. Saludos.