Динамические операторы — это операторы SQL, которые создаются в виде строк и в которые вставляются/объединяются значения, полученные из какого-либо источника (обычно от пользователя), что может сделать их уязвимыми для SQL-инъекций, если они не продезинфицированы.
$id_usuario = $_POST["id"];
mysql_query("SELECT * FROM usuarios WHERE id = $id_usuario");
Это пример серьезной уязвимости в безопасности приложения (веб-сайта или нет), потому что, если пользователь введет такое значение 1; DROP TABLE usuarios;--
, мы обнаружим, что выполненный оператор будет выглядеть так:
SELECT * FROM usuarios WHERE id = 1; DROP TABLE usuarios;--
И таблица Users со всеми содержащимися в ней данными будет удалена.
Как я могу предотвратить внедрение SQL в PHP?
Используйте подготовленные операторы и параметризованные запросы
Хотя входные данные можно очистить с помощью таких методов, как
mysqli_real_escape_string
, более целесообразно использовать подготовленные или параметризованные операторы. Подготовленные операторы позволят вам выполнить один и тот же оператор с большой эффективностью.В PHP у вас есть две основные альтернативы: PDO и MySQLi . Между ними есть несколько различий, но главное из них заключается в том, что PDO можно использовать с разными типами баз данных ( в зависимости от используемого драйвера ), в то время как MySQLi предназначен исключительно для баз данных MySQL. Вот почему я бы рекомендовал PDO вместо MySQLi.
ЗОП
Заполнители ( указывающие, где строка будет заменена на ее значение) могут быть определены либо с помощью вопросительного знака (
?
), либо с помощью имени (обычно начинающегося с:
). Я лично предпочитаю использовать имя, потому что это помогает мне найти возможные ошибки в случае наличия нескольких переменных.Вот пример кода вопроса:
В этом случае
:idusuario
она будет подставлена на значение$_POST["id"]
safe, а при выполнении привязки будет указано, что переменная имеет тип integer (PDO::PARAM_INT
).Если в оператор SQL нужно включить несколько переменных, для каждого значения, используемого в операторе, должен быть включен один параметр. Из приведенного выше примера
:idusuario
можно использовать только один раз в подготавливаемом запросе. Если в запросе необходимо снова использовать «userid», необходимо создать еще один параметр со значением$id_usuario
.MySQL я
Этот метод имеет два интерфейса : процедурный и объектно-ориентированный. Процедурный интерфейс очень похож на
mysql_*
, и поэтому людей, переходящих сmysql_*
него, может привлечь простота, которую онmysqli_*
предлагает. Хотя, опять же, лично я бы выбрал ООП-версию.Пример в вопросе будет выглядеть так с MySQLi в его объектно-ориентированном интерфейсе:
Как видите, он очень похож на PDO (немного меняется способ указания типа значения для
i
целых чисел иs
для строк, но идея аналогична).В процедурной версии MySQLi эквивалентный код будет таким:
Источник и библиография для получения дополнительной информации на испанском языке:
Моей единственной заслугой был перевод, возможно, в некоторых моментах улучшенный. Мы знаем, что ничто не является безопасным на 100%, но простое использование PDO или MySQLi должно сочетаться с другими стратегиями, описанными в пункте 5 , чтобы обеспечить нечто столь же ценное, как наши данные.
1. Введение
Внедрение SQL — это метод управления запросом к базе данных, который часто приводит к нарушению конфиденциальности. В некоторых случаях это может привести к полному захвату сервера.
Например:
Поскольку внедрение кода (охватывающее методы SQL, LDAP , Command OS и XPath Injection ) постоянно остается в топ -10 уязвимостей OWASP , это популярная тема для блоггеров, пытающихся разобраться в области безопасности приложений.
К сожалению, большая часть советов, циркулирующих в Интернете (особенно в старых блогах, занимающих высокие позиции в поисковых системах) , устарела, непреднамеренно вводит в заблуждение и часто опасна .
2. Очень распространенная ошибка: неправильно сконфигурированный PDO
Если вы разработчик PHP и хотите получить максимальную отдачу от PDO, мы рекомендуем вам изменить два значения по умолчанию:
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
Принятый ответ - отличное дополнение, которое всегда приветствуется. Некоторое время назад я также прочитал в Интернете статью, которая, на мой взгляд, довольно интересна, и я хотел бы поделиться ею, жаль, что она на английском языке, я собираюсь перевести часть статьи, насколько это возможно, так как это очень обширен ( Автостопом по предотвращению SQL-инъекций ).
Как защититься от инъекции типа [xxx].
Вас могут атаковать разные виды инъекций - «слепые» , «задержка менструации» , «второго порядка» и тысячи других. Нужно понимать, что это не все разные способы выполнения инъекции, а просто разные способы ее использования. Хотя есть только один способ выполнить инъекцию: нарушить целостность запроса . Таким образом, если вы можете поддерживать целостность запроса, вы защищены от всех тысяч различных типов инъекций одновременно.
А для поддержания целостности запроса просто правильно форматируйте литералы запроса.
Вывод.
Подводя итог, можно сформулировать два простых правила:
Даже динамически созданный SQL-запрос должен состоять только из двух возможных типов данных:
При соблюдении этих правил гарантируется 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, несмотря на любую проверку - напомните Саре О'Хара, что он принимает имя, которое совершенно правильно с точки зрения пользовательского ввода. Также помните, что правила проверки могут меняться.
3. Htmlspecialchars (а также filter_var() , strip_tags() и тому подобное).
Друзья. Это кодировка HTML, если вы еще не заметили. Это не имеет абсолютно никакого отношения к SQL. Это совсем не помогает и никогда не должно использоваться в контексте защиты от SQL-инъекций. Это абсолютно неприменимо для SQL и не может защитить ваш запрос, даже если он используется как функция экранирования строки. Оставьте это для других частей вашего приложения. Кроме того, пожалуйста, поймите, что формат SQL никогда не должен касаться данных. Итак, вы кладете свои драгоценности в сейф, вы хотите, чтобы они были целыми, а не модифицированными или замененными частями! То же самое. База данных предназначена для хранения ваших данных, а не для их «защиты». И важно хранить точные данные, которые вы хотите использовать повторно (означает, что ваша глупая попытка base64 тоже неверна, кстати).
Еще одна рекомендация помимо лучшего хорошего ответа
Рекомендуется добавлять маску к url и создавать дружественные url, которые выполняют 2 функции: 1- скрывают параметры, отправляемые get, а также помогают пользователю более четко видеть информацию.
Пример
стандартный URL-адрес: http://stackoverflow.com?id=100
дружественный URL-адрес: как избежать инъекции SQL в PHP?
Примечание. Первый URL-адрес не соответствует понятному URL-адресу и является просто примером.
В случае, если атакующий хотел атаковать стандартный url, у него уже был бы параметр для запуска «id», а в случае с дружественным url все не так просто и он должен дополнительно анализировать сделанный запрос к веб-серверу и найти URL.
Основные измерения
Как скрыть URL
Примером является изменение файла .htaccess на вашем веб-сервере и его привязка с помощью mod_rewrite, если вы используете Apache.
Подробнее об этом здесь: 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.