Как я могу предотвратить SQL-инъекцию в PHP?

Если вводимые пользователем данные вставляются в SQL-запрос без изменений, тогда приложение становится уязвимым для SQL-инъекция, как в следующем примере:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Это потому, что пользователь может ввести что-то вроде value'); DROP TABLE table;--, и запрос будет выглядеть следующим образом:

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

Что можно сделать, чтобы этого не произошло?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
2 773
0
1 863 014
27
Перейти к ответу Данный вопрос помечен как решенный

Ответы 27

Я бы рекомендовал использовать PDO (объекты данных PHP) для выполнения параметризованных запросов SQL.

Это не только защищает от SQL-инъекций, но также ускоряет запросы.

А используя PDO вместо функций mysql_, mysqli_ и pgsql_, вы делаете свое приложение немного более абстрагированным от базы данных, в редких случаях, когда вам приходится менять поставщиков базы данных.

этот ответ вводит в заблуждение. PDO - это не волшебная палочка, которая защищает ваши запросы простым присутствием. Вы должны замените каждую переменную в вашем запросе заполнителем получить защиту от PDO.

Your Common Sense 12.06.2020 06:41

Вы можете сделать что-нибудь базовое, например:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Это не решит всех проблем, но это очень хорошая ступенька. Я не учел очевидные вещи, такие как проверка существования переменной, ее формата (числа, буквы и т. д.).

Я пробовал ваш пример, и он отлично работает для меня. Не могли бы вы прояснить, что «это не решит все проблемы»

Chinook 23.04.2012 00:31

Если вы не укажете строку в кавычках, ее можно будет вводить. Возьмем, к примеру, $q = "SELECT col FROM tbl WHERE x = $safe_var";. Установка $safe_var на 1 UNION SELECT password FROM users работает в этом случае из-за отсутствия кавычек. Также возможно вставлять строки в запрос, используя CONCAT и CHR.

Polynomial 16.04.2013 22:06

@Polynomial Совершенно верно, но я бы счел это просто неправильным использованием. Пока вы используете его правильно, он обязательно будет работать.

glglgl 10.07.2013 11:30

Итак, если я напишу эти коды, db все равно будет незащищен? mysql_query ("ВСТАВИТЬ В таблицу (столбец) ЗНАЧЕНИЯ ('$ safe_variable')");

DjOnce 03.12.2013 17:30

Не предотвращает "1 ИЛИ 1 = 1"

Arvind Bhardwaj 25.03.2014 10:32
ПРЕДУПРЕЖДЕНИЕ!mysql_real_escape_string()не безупречный.
eggyal 25.04.2014 18:46
mysql_real_escape_string is now deprecated, so its no longer a viable option. It will be removed in the future from PHP. Its best to move onto what the PHP or MySQL folks recommend.
jww 08.04.2015 09:37

Пожалуйста, дайте мне пример перед mysqli_real_escape_string и после строки, которая будет сгенерирована. Поскольку это вызывает путаницу и не приводится никаких примеров.

Pratik 12.12.2015 17:18

Приведенный выше код не работает. mysqli_real_escape_string ожидает два параметра. проверить

Abhijeet Kambli 15.02.2019 15:52

Здравствуй! Не могли бы вы дать более точный ответ? Просьба охватить все возможные способы предотвращения инъекций sql.

YanDatsiuk 20.06.2019 21:23

Deprecated Warning: This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.

Security Warning: This answer is not in line with security best practices. Escaping is inadequate to prevent SQL injection, use prepared statements instead. Use the strategy outlined below at your own risk. (Also, mysql_real_escape_string() was removed in PHP 7.)

Если вы используете последнюю версию PHP, вариант mysql_real_escape_string, описанный ниже, больше не будет доступен (хотя mysqli::escape_string является современным эквивалентом). В наши дни опция mysql_real_escape_string имеет смысл только для устаревшего кода в старой версии PHP.


У вас есть два варианта: экранирование специальных символов в unsafe_variable или использование параметризованного запроса. Оба защитят вас от SQL-инъекций. Параметризованный запрос считается лучшей практикой, но для его использования потребуется перейти на более новое расширение MySQL в PHP.

Сначала мы рассмотрим нижнюю ударную струну, ускользнув от одной.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

См. Также подробности функции mysql_real_escape_string.

Чтобы использовать параметризованный запрос, вам нужно использовать MySQLi, а не функции MySQL. Чтобы переписать ваш пример, нам понадобится что-то вроде следующего.

<?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();
?>

Ключевая функция, о которой вы захотите прочитать, - это mysqli::prepare.

Кроме того, как предлагали другие, вам может быть полезно / проще повысить уровень абстракции с помощью чего-то вроде PDO.

Обратите внимание, что случай, о котором вы спрашивали, довольно простой и что более сложные случаи могут потребовать более сложных подходов. Особенно:

  • Если вы хотите изменить структуру SQL на основе пользовательского ввода, параметризованные запросы не помогут, а требуемое экранирование не покрывается mysql_real_escape_string. В этом случае лучше передать вводимые пользователем данные через белый список, чтобы обеспечить пропуск только «безопасных» значений.
  • Если вы используете целые числа из пользовательского ввода в условии и применяете подход mysql_real_escape_string, вы столкнетесь с проблемой, описанной Полиномиальный в комментариях ниже. Этот случай сложнее, потому что целые числа не будут заключаться в кавычки, поэтому вы можете справиться, проверив, что вводимые пользователем данные содержат только цифры.
  • Вероятно, есть и другие случаи, о которых я не знаю. Вы можете найти это - полезный ресурс по некоторым из более тонких проблем, с которыми вы можете столкнуться.

достаточно использовать mysql_real_escape_string или я тоже должен использовать параметризованный?

peiman F. 09.03.2018 00:29

@peimanF. придерживайтесь хорошей практики использования параметризованных запросов даже в локальном проекте. С параметризованными запросами вы гарантированный, что не будет SQL-инъекции. Но имейте в виду, что вы должны дезинфицировать данные, чтобы избежать ложного извлечения (например, внедрения XSS, например, помещения HTML-кода в текст), например, с помощью htmlentities.

Goufalite 09.03.2018 11:02

@peimanF. Хорошая практика параметризованных запросов и привязки значений, но настоящая escape-строка пока хороша

Richard 04.04.2018 21:03

Я понимаю включение mysql_real_escape_string() для полноты картины, но мне не нравится перечислять сначала наиболее подверженный ошибкам подход. Читатель может быстро схватить первый пример. Хорошо, что сейчас он устарел :)

Steen Schütt 05.12.2018 03:13

@peimanF. - Не делайте и того, и другого; это будет дважды убегать от вещей! Выберите любую параметризацию (предпочтительно) или жеmysqli_real_escape_string

Rick James 01.12.2019 04:19

@ SteenSchütt - Все функции mysql_* устарели. Они были заменены функциями похожийmysqli_*, такими как mysqli_real_escape_string.

Rick James 01.12.2019 04:21

@RickJames Я хочу сказать, что вы с большей вероятностью ошибетесь при использовании старого подхода, когда параметры указываются вручную. Я знаю, что старые функции MySQL устарели / удалены и что расширение mysqli делает нечто подобное, но мы должны сначала обучать подготовленным операторам, а затем «старым способам» для полноты.

Steen Schütt 02.12.2019 12:07
Ответ принят как подходящий

Используйте подготовленные операторы и параметризованные запросы. Это операторы SQL, которые отправляются и анализируются сервером базы данных отдельно от любых параметров. Таким образом, злоумышленник не сможет внедрить вредоносный SQL.

В основном у вас есть два варианта достижения этого:

  1. Использование PDO (для любого поддерживаемого драйвера базы данных):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
    
  2. Использование MySQLi (для 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
    }
    

Если вы подключаетесь к базе данных, отличной от MySQL, вы можете использовать второй вариант для конкретного драйвера (например, pg_prepare() и pg_execute() для PostgreSQL). PDO - универсальный вариант.


Правильная настройка подключения

Обратите внимание, что при использовании PDO для доступа к базе данных MySQL подготовленные операторы настоящий - это не используется по умолчанию. Чтобы исправить это, вам нужно отключить эмуляцию подготовленных операторов. Пример создания соединения с использованием PDO:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

В приведенном выше примере режим ошибки не является строго необходимым, но рекомендуется добавить его. Таким образом, сценарий не остановится на Fatal Error, если что-то пойдет не так. И это дает разработчику возможность catch исправить любую ошибку (ошибки), которые являются thrown как PDOException.

Однако обязательный - это первая строка setAttribute(), которая сообщает PDO отключить эмулируемые подготовленные операторы и использовать подготовленные операторы настоящий. Это гарантирует, что оператор и значения не будут проанализированы PHP перед его отправкой на сервер MySQL (что не дает возможному злоумышленнику возможности внедрить вредоносный SQL).

Хотя вы можете установить charset в параметрах конструктора, важно отметить, что «старые» версии PHP (до 5.3.6) молча проигнорировал параметр кодировки в DSN.


Объяснение

Оператор SQL, который вы передаете prepare, анализируется и компилируется сервером базы данных. Задавая параметры (либо ?, либо именованный параметр, например :name в приведенном выше примере), вы указываете движку базы данных, где вы хотите выполнить фильтрацию. Затем, когда вы вызываете execute, подготовленный оператор объединяется с указанными вами значениями параметров.

Здесь важно то, что значения параметров объединяются со скомпилированным оператором, а не со строкой SQL. Внедрение SQL работает путем обмана сценария, который включает вредоносные строки при создании SQL для отправки в базу данных. Таким образом, отправляя фактический SQL отдельно от параметров, вы ограничиваете риск получить то, чего не планировали.

Любые параметры, которые вы отправляете при использовании подготовленного оператора, будут обрабатываться просто как строки (хотя ядро ​​базы данных может выполнять некоторую оптимизацию, поэтому параметры, конечно же, могут оказаться числами). В приведенном выше примере, если переменная $name содержит 'Sarah'; DELETE FROM employees, результатом будет просто поиск строки "'Sarah'; DELETE FROM employees", и вы не получите пустой стол.

Еще одно преимущество использования подготовленных операторов заключается в том, что если вы выполняете один и тот же оператор много раз в одном сеансе, он будет проанализирован и скомпилирован только один раз, что даст вам некоторый прирост скорости.

Да, и поскольку вы спросили, как это сделать для вставки, вот пример (с использованием PDO):

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

$preparedStatement->execute([ 'column' => $unsafeValue ]);

Можно ли использовать подготовленные операторы для динамических запросов?

Хотя вы по-прежнему можете использовать подготовленные операторы для параметров запроса, структура самого динамического запроса не может быть параметризована, а некоторые функции запроса не могут быть параметризованы.

Для этих конкретных сценариев лучше всего использовать фильтр белого списка, который ограничивает возможные значения.

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

Кроме того, официальная документация mysql_query позволяет выполнять только один запрос, поэтому любой другой запрос кроме; игнорируется. Даже если это уже устарело, существует множество систем под PHP 5.5.0, которые могут использовать эту функцию. php.net/manual/en/function.mysql-query.php

Randall Valenciano 19.01.2016 20:40

Это плохая привычка, но это решение после проблемы: не только для SQL-инъекций, но и для любого типа инъекций (например, в F3 framework v2 было отверстие для инъекции шаблона представления), если у вас есть готовый старый веб-сайт или приложение, страдающее из-за дефектов внедрения одно из решений - переназначить значения ваших предопределенных переменных supperglobal, таких как $ _POST, с экранированными значениями при начальной загрузке. По PDO все еще можно избежать (также для сегодняшних фреймворков): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)

Alix 24.01.2016 18:08

В этом ответе отсутствует объяснение того, что такое подготовленный оператор - одно - это снижение производительности, если вы используете много подготовленных операторов во время своего запроса, и иногда это приводит к 10-кратному снижению производительности. Лучше было бы использовать PDO с отключенной привязкой параметров, но с отключенной подготовкой оператора.

donis 18.11.2016 11:54

Лучше использовать PDO, если вы используете прямой запрос, убедитесь, что вы используете mysqli :: escape_string

Kassem Itani 06.11.2017 11:17

@Alix это звучит как хорошая идея в теории, но иногда значениям требуются другие типы экранирования, например, для SQL и HTML.

p0358 19.11.2018 02:24

Я думал, что вы не можете запускать несколько запросов в одном mysql_query. Рассмотрим ответ это.

niCk cAMel 16.11.2020 00:15

Я просто хотел бы указать на кое-что, чего совершенно не хватало в первые пять минут этой беседы, но стоит упомянуть здесь. Это действительно относится к вопросу OP. Подавляющему большинству пользователей не требуется учетная запись с чем-либо, кроме привилегий INSERT SELECT и UPDATE. В некоторых таблицах есть несколько столбцов, начинающихся с удаленного. Это сразу решает проблему Столы Бобби.

user138720 01.03.2021 18:17

Используйте PDO и подготовленные запросы.

($conn - это объект PDO)

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

Независимо от того, что вы в конечном итоге используете, убедитесь, что вы проверяете, что ваш ввод еще не был искажен magic_quotes или другим благонамеренным мусором, и, если необходимо, пропустите его через stripslashes или что-то еще, чтобы очистить его.

В самом деле; работа с включенной magic_quotes просто способствует плохой практике. Однако иногда вы не всегда можете контролировать среду до этого уровня - либо у вас нет доступа для управления сервером, либо ваше приложение должно сосуществовать с приложениями, которые (дрожь) зависят от такой конфигурации. По этим причинам хорошо писать переносимые приложения - хотя очевидно, что усилия будут потрачены впустую, если вы все же контролируете среду развертывания, например потому что это внутреннее приложение или будет использоваться только в вашей конкретной среде.

Rob 24.04.2011 21:04

Начиная с PHP 5.4 мерзостью, известной как «волшебные кавычки», была убит мертвым. И избавление от плохого хлама.

BryanH 17.01.2013 02:45

Я предпочитаю хранимые процедуры (MySQL поддерживает хранимые процедуры с 5.0) с точки зрения безопасности - преимущества:

  1. Большинство баз данных (включая MySQL) позволяют ограничить доступ пользователей к выполнению хранимых процедур. Детальный контроль доступа к системе безопасности полезен для предотвращения атак, связанных с повышением привилегий. Это не позволяет скомпрометированным приложениям запускать SQL непосредственно в базе данных.
  2. Они абстрагируют необработанный SQL-запрос от приложения, поэтому приложению доступно меньше информации о структуре базы данных. Это затрудняет понимание базовой структуры базы данных и разработку подходящих атак.
  3. Они принимают только параметры, поэтому преимущества параметризованных запросов есть. Конечно - IMO вам все равно нужно дезинфицировать свой ввод, особенно если вы используете динамический SQL внутри хранимой процедуры.

Недостатки -

  1. Их (хранимые процедуры) сложно поддерживать, и они имеют тенденцию очень быстро размножаться. Это делает управление ими проблемой.
  2. Они не очень подходят для динамических запросов - если они построены так, чтобы принимать динамический код в качестве параметров, многие преимущества теряются.

Deprecated Warning: This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.

Security Warning: This answer is not in line with security best practices. Escaping is inadequate to prevent SQL injection, use prepared statements instead. Use the strategy outlined below at your own risk. (Also, mysql_real_escape_string() was removed in PHP 7.)

IMPORTANT

The best way to prevent SQL Injection is to use Prepared Statementsinstead of escaping, as the accepted answer demonstrates.

There are libraries such as Aura.Sql and EasyDB that allow developers to use prepared statements easier. To learn more about why prepared statements are better at stopping SQL injection, refer to this mysql_real_escape_string() bypass and recently fixed Unicode SQL Injection vulnerabilities in WordPress.

Предотвращение впрыска - mysql_real_escape_string ()

В PHP есть специальная функция для предотвращения этих атак. Все, что вам нужно сделать, это использовать функцию mysql_real_escape_string.

mysql_real_escape_string принимает строку, которая будет использоваться в запросе MySQL, и возвращает ту же строку с безопасным экранированием всех попыток внедрения SQL. По сути, он заменит те вызывающие беспокойство кавычки ('), которые пользователь может ввести, на безопасную для MySQL замену, экранированную кавычку \'.

ПРИМЕЧАНИЕ: вы должны быть подключены к базе данных, чтобы использовать эту функцию!

// Подключаемся к 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;

Вы можете найти более подробную информацию в MySQL - предотвращение инъекций SQL.

Это лучшее, что вы можете сделать с устаревшим расширением mysql. Для нового кода рекомендуется переключиться на mysqli или PDO.

Álvaro González 26.02.2013 16:42

Я не согласен с этой «специально созданной функцией для предотвращения этих атак». Я думаю, что цель mysql_real_escape_string - позволить построить правильный SQL-запрос для каждой входной строки данных. Предотвращение sql-инъекций является побочным эффектом этой функции.

sectus 09.07.2013 09:01

вы не используете функции для записи правильных строк входных данных. Вы просто пишете правильные, которые не нужно экранировать или которые уже были экранированы. mysql_real_escape_string () может быть разработан с той целью, о которой вы говорите, но ее единственное значение - предотвращение внедрения.

Nazca 13.03.2014 02:38
ПРЕДУПРЕЖДЕНИЕ!mysql_real_escape_string()не безупречный.
eggyal 25.04.2014 18:50

@eggyal Особенно, если вы возитесь с разными кодировками.

Wayne Whitty 16.06.2014 18:58
mysql_real_escape_string is now deprecated, so its no longer a viable option. It will be removed in the future from PHP. Its best to move onto what the PHP or MySQL folks recommend.
jww 08.04.2015 09:41

@rahularyansharma Я хотел бы попросить изменить это с помощью заявления об отказе от ответственности: подготовленные операторы проще в использовании и более безопасны с инженерной точки зрения: параметры и строка запроса отправляются отдельными пакетами, что предотвращает изменение параметров Строка запроса. Также: stackoverflow.com/questions/5741187/…

Scott Arciszewski 29.05.2015 22:54

Хорошо, в обходе ничего не говорится о «Чтобы узнать больше о том, почему подготовленные операторы лучше останавливают SQL-инъекцию, обратитесь к этому обходу mysql_real_escape_string ()». Напротив, подготовленные операторы страдают теми же проблемами, и PDO не является исключением. «Становится хуже. PDO по умолчанию эмулирует подготовленные операторы с MySQL. Это означает, что на стороне клиента он в основном выполняет sprintf через mysql_real_escape_string () (в библиотеке C), что означает, что следующее приведет к успешной инъекции:»

cjohansson 03.03.2016 14:00

Deprecated Warning: This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.

Security Warning: This answer is not in line with security best practices. Escaping is inadequate to prevent SQL injection, use prepared statements instead. Use the strategy outlined below at your own risk. (Also, mysql_real_escape_string() was removed in PHP 7.)

Параметризованный запрос И проверка ввода - это правильный путь. Существует множество сценариев, при которых может произойти SQL-инъекция, даже если использовался mysql_real_escape_string().

Эти примеры уязвимы для 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");

или же

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

В обоих случаях нельзя использовать ' для защиты инкапсуляции.

Источник: Неожиданная SQL-инъекция (когда выхода недостаточно)

Вы можете предотвратить внедрение SQL-кода, если примените метод проверки ввода, при котором пользовательский ввод аутентифицируется по набору определенных правил для длины, типа и синтаксиса, а также по бизнес-правилам.

Josip Ivic 15.09.2015 11:07

Каждый ответ здесь охватывает только часть проблемы. Фактически, существуют различные части запроса четыре, которые мы можем динамически добавлять в SQL: -

  • строка
  • число
  • идентификатор
  • ключевое слово синтаксиса

И подготовленные заявления охватывают только два из них.

Но иногда нам приходится делать наш запрос еще более динамичным, добавляя в него операторы или идентификаторы. Итак, нам потребуются разные техники защиты.

В общем, такой подход к защите основан на белый список.

В этом случае каждый динамический параметр должен быть жестко задан в вашем скрипте и выбран из этого набора. Например, чтобы сделать динамический заказ:

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

Чтобы упростить процесс, я написал вспомогательная функция белого списка, который выполняет всю работу в одной строке:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

Есть еще один способ защитить идентификаторы - избежать, но я предпочитаю использовать белый список как более надежный и явный подход. Тем не менее, если у вас есть идентификатор в кавычках, вы можете избежать символа кавычки, чтобы сделать его безопасным. Например, по умолчанию для mysql вам нужно удвойте символ кавычки, чтобы его избежать. Для других СУБД правила экранирования будут другими.

Тем не менее, существует проблема с ключевыми словами синтаксиса SQL (такими как AND, DESC и т. д.), Но белый список кажется единственным подходом в этом случае.

Итак, общая рекомендация может быть сформулирована как

  • Any variable that represents an SQL data literal, (or, to put it simply - an SQL string, or a number) must be added through a prepared statement. No Exceptions.
  • Any other query part, such as an SQL keyword, a table or a field name, or an operator - must be filtered through a white list.

Обновлять

Хотя существует общее соглашение о лучших практиках защиты от SQL-инъекций, есть по-прежнему много плохих практик., и некоторые из них слишком глубоко укоренились в умах пользователей PHP. Например, на этой самой странице есть (хотя и невидимые для большинства посетителей) более 80 удаленных ответов - все они удалены сообществом из-за плохого качества или продвижения плохих и устаревших методов. Что еще хуже, некоторые из плохих ответов не удаляются, а скорее процветают.

Например, там (1)являются (2)еще (3)много (4)ответы (5), включая второй по количеству голосов ответ, предлагающий вам экранирование строки вручную - устаревший подход, который оказался небезопасным.

Или есть немного лучший ответ, который предлагает просто другой метод форматирования строк и даже может похвастаться им как окончательной панацеей. Хотя, конечно, это не так. Этот метод не лучше обычного форматирования строк, но при этом сохраняет все свои недостатки: он применим только к строкам и, как и любое другое ручное форматирование, является необязательной, необязательной мерой, подверженной человеческим ошибкам любого рода.

Я думаю, что все это из-за одного очень старого суеверия, поддерживаемого такими авторитетами, как OWASP или руководство по PHP, которое провозглашает равенство между любым «бегством» и защитой от SQL-инъекций.

Независимо от того, что было сказано в руководстве по PHP на протяжении веков, *_escape_string никоим образом не делает данные безопасными никогда не предназначался для этого. Помимо бесполезности для какой-либо части SQL, кроме строки, экранирование вручную неверно, потому что оно выполняется вручную, а не автоматизировано.

И OWASP делает это еще хуже, делая упор на избежание пользовательский ввод, что является полной ерундой: таких слов не должно быть в контексте защиты от инъекций. Каждая переменная потенциально опасна - независимо от источника! Или, другими словами, каждая переменная должна быть правильно отформатирована, чтобы быть помещенной в запрос, независимо от источника. Главное - место назначения. В тот момент, когда разработчик начинает отделять овец от коз (думая, является ли какая-то конкретная переменная «безопасной» или нет), он / она делает свой первый шаг к катастрофе. Не говоря уже о том, что даже формулировка предполагает массовое экранирование в точке входа, напоминая ту самую волшебную функцию кавычек - уже презираемую, устаревшую и удаленную.

Итак, в отличие от любого «экранирования», подготовленные операторы является - это мера, которая действительно защищает от SQL-инъекций (если применимо).

Если возможно, приведите типы ваших параметров. Но он работает только с простыми типами, такими как int, bool и float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Это один из немногих случаев, когда я использовал бы «экранированное значение» вместо подготовленного оператора. А преобразование целочисленного типа чрезвычайно эффективно.

HoldOffHunger 14.03.2016 01:29

На мой взгляд, лучший способ вообще предотвратить SQL-инъекцию в вашем приложении PHP (или любом другом веб-приложении, если на то пошло) - это подумать об архитектуре вашего приложения. Если единственный способ защититься от SQL-инъекций - не забывать использовать специальный метод или функцию, которая делает все правильно каждый раз, когда вы разговариваете с базой данных, вы делаете это неправильно. Таким образом, когда вы забудете правильно отформатировать запрос в какой-то момент кода, это всего лишь вопрос времени.

Принятие шаблона MVC и фреймворка, такого как CakePHP или CodeIgniter, вероятно, является правильным путем: общие задачи, такие как создание защищенных запросов к базе данных, были решены и централизованно реализованы в таких фреймворках. Они помогают разумно организовать ваше веб-приложение и заставляют думать больше о загрузке и сохранении объектов, чем о безопасном построении отдельных запросов SQL.

Я думаю, что ваш первый абзац важен. Понимание - ключ к успеху. Кроме того, не все работают в компании. Для большого количества людей фреймворки фактически противоречат идее понимание. Приобщение к основам, возможно, не ценится при работе с соблюдением крайних сроков, но тем, кто занимается своими делами, нравится пачкать руки. Разработчики фреймворков не настолько привилегированы, что все остальные должны преклоняться и полагать, что они никогда не ошибаются. Право принимать решения по-прежнему важно. Кто может сказать, что мой фреймворк не вытеснит какую-то другую схему в будущем?

Anthony Rutledge 04.01.2017 19:32

@AnthonyRutledge Вы абсолютно правы. Для понимать очень важно, что происходит и почему. Тем не менее, вероятность того, что проверенный и активно используемый и развиваемый фреймворк столкнется и решит множество проблем и исправит множество дыр в безопасности, довольно высок. Хорошая идея - взглянуть на исходный код, чтобы оценить качество кода. Если это непроверенный беспорядок, это, вероятно, небезопасно.

Johannes Fahrenkrug 04.01.2017 21:38

Здесь. Здесь. Хорошие моменты. Однако согласитесь, что многие люди могут изучить и научиться применять систему MVC, но не все могут воспроизвести ее вручную (контроллеры и сервер). С этим можно зайти слишком далеко. Нужно ли мне разбираться в своей микроволновой печи, прежде чем я разогреваю печенье с орехами и пеканом, которое приготовила мне моя подруга? ;-)

Anthony Rutledge 04.01.2017 22:30

@AnthonyRutledge Я согласен! Я думаю, что вариант использования тоже имеет значение: создаю ли я фотогалерею для своей личной домашней страницы или создаю веб-приложение для онлайн-банкинга? В последнем случае очень важно понимать детали безопасности и то, как фреймворк, который я использую, решает эти проблемы.

Johannes Fahrenkrug 04.01.2017 23:35

Ах, исключение безопасности из следствия «сделай сам». Видите ли, я склонен рисковать всем и идти ва-банк. :-) Шутя. Имея достаточно времени, люди могут научиться создавать чертовски безопасное приложение. Слишком много людей спешат. Они поднимают руки и предполагают, что это безопаснее. В конце концов, им не хватает времени, чтобы все проверить и разобраться. Более того, безопасность - это область, требующая специального изучения. Это не то, что простые программисты хорошо знают в силу понимания алгоритмов и шаблонов проектирования.

Anthony Rutledge 04.01.2017 23:48

Кроме того, в php есть возможности для внедрения команд, о которых стоит подумать. Один из способов обойти это - заблокировать запросы, содержащие специальные символы или запросы, которые выглядят неоправданно долго на балансировщике нагрузки / веб-сервере, прежде чем они дойдут до PHP ... gracefulsecurity.com/…owasp.org/www-community/vulnerabilities/PHP_Object_Injection

andrew pate 30.10.2020 19:31

Есть много способов предотвратить SQL-инъекции и другие взломы SQL. Вы можете легко найти его в Интернете (поиск в Google). Конечно PDO - одно из хороших решений. Но я хотел бы предложить вам хорошую защиту ссылок от SQL-инъекции.

Что такое SQL-инъекция и как предотвратить

Руководство по PHP для SQL-инъекций

Объяснение Microsoft SQL-инъекций и предотвращения в PHP

И некоторые другие, например Предотвращение SQL-инъекций с помощью MySQL и PHP.

Теперь почему вам нужно предотвратить ваш запрос от SQL-инъекции?

Я хотел бы сообщить вам: почему мы пытаемся предотвратить SQL-инъекцию с помощью небольшого примера ниже:

Запрос на совпадение аутентификации при входе:

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

Теперь, если кто-то (хакер) поставит

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

и пароль ничего ....

Запрос будет проанализирован в системе только до:

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

Другая часть будет отброшена. Итак, что будет? Неавторизованный пользователь (хакер) сможет войти в систему как администратор, не имея своего пароля. Теперь он / она может делать все, что может сделать администратор / адресат электронной почты. Видите ли, это очень опасно, если не предотвратить SQL-инъекцию.

Используя эту функцию PHP mysql_escape_string(), вы можете быстро получить хорошую профилактику.

Например:

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

mysql_escape_string - экранирует строку для использования в mysql_query

Для большей профилактики вы можете добавить в конце ...

wHERE 1=1   or  LIMIT 1

В итоге вы получите:

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

Security Warning: This answer is not in line with security best practices. Escaping is inadequate to prevent SQL injection, use prepared statements instead. Use the strategy outlined below at your own risk. (Also, mysql_real_escape_string() was removed in PHP 7.)

Deprecated Warning: The mysql extension is deprecated at this time. we recommend using the PDO extension

Я использую три разных способа защиты моего веб-приложения от SQL-инъекции.

  1. Использование mysql_real_escape_string(), предопределенной функции в PHP, и этот код добавляет обратную косую черту к следующим символам: \x00, \n, \r, \, ', " и \x1a. Передайте входные значения в качестве параметров, чтобы минимизировать вероятность SQL-инъекции.
  2. Самый продвинутый способ - использовать PDO.

Я надеюсь, что это поможет вам.

Рассмотрим следующий запрос:

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

mysql_real_escape_string () здесь не защищает. Если вы используете одинарные кавычки ('') вокруг ваших переменных внутри вашего запроса, это то, что защищает вас от этого. Вот решение для этого ниже:

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

В этом вопрос есть несколько хороших ответов по этому поводу.

Я предполагаю, что использование PDO - лучший вариант.

Редактировать:

mysql_real_escape_string() устарел с PHP 5.5.0. Используйте mysqli или PDO.

Альтернативой mysql_real_escape_string () является

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Пример:

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

Для тех, кто не уверен, как использовать PDO (исходящий из функций mysql_), я сделал очень и очень простая оболочка PDO, который является одним файлом. Он существует для того, чтобы показать, насколько легко делать все, что нужно приложению. Работает с PostgreSQL, MySQL и SQLite.

По сути, прочтите его пока вы читаете руководство, чтобы увидеть, как использовать функции PDO в реальной жизни, чтобы упростить сохранение и получение значений в формате, который нужен ты.

I want a single column

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

I want an array(key => value) results (i.e. for making a selectbox)

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

I want a single row result

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

I want an array of results

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

Как видите, люди предлагают использовать в лучшем случае заранее подготовленные операторы. В этом нет ничего плохого, но когда ваш запрос выполняется только раз для каждого процесса, будет небольшое снижение производительности.

Я столкнулся с этой проблемой, но думаю, что решил ее изощренным способом очень - способом, который хакеры используют, чтобы избежать использования кавычек. Я использовал это вместе с эмулированными подготовленными операторами. Я использую его для предотвращения возможных атак типа SQL-инъекций типа все.

Мой подход:

  • Если вы ожидаете, что ввод будет целым числом, убедитесь, что это целое число В самом деле. В языке с переменным типом, таком как PHP, этот параметр очень важен. Вы можете использовать, например, очень простое, но мощное решение: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);.

  • Если вы ожидаете чего-либо еще от целого числа проклятие. Если вы заколдите его, вы полностью избежите ввода. В C / C++ есть функция mysql_hex_string(), в PHP вы можете использовать bin2hex().

    Не беспокойтесь о том, что экранированная строка будет иметь размер в 2 раза больше ее исходной длины, потому что даже если вы используете mysql_real_escape_string, PHP должен выделить ту же емкость ((2*input_length)+1), что то же самое.

  • Этот шестнадцатеричный метод часто используется при передаче двоичных данных, но я не вижу причин, почему бы не использовать его для всех данных, чтобы предотвратить атаки SQL-инъекций. Обратите внимание, что вам нужно добавить данные с помощью 0x или вместо этого использовать функцию MySQL UNHEX.

Так, например, запрос:

SELECT password FROM users WHERE name = 'root';

Станет:

SELECT password FROM users WHERE name = 0x726f6f74;

или же

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

Hex - идеальный побег. Нет возможности уколоть.

Разница между функцией UNHEX и префиксом 0x

В комментариях было некоторое обсуждение, поэтому я наконец хочу прояснить это. Эти два подхода очень похожи, но в некоторых отношениях они немного отличаются:

Префикс 0x может использоваться только для столбцов данных, таких как char, varchar, text, block, binary и т. д.
Кроме того, его использование немного сложнее, если вы собираетесь вставить пустую строку. Вам придется полностью заменить его на '', иначе вы получите ошибку.

UNHEX() работает со столбцом любой; вам не нужно беспокоиться о пустой строке.


Hex-методы часто используются как атаки

Обратите внимание, что этот шестнадцатеричный метод часто используется в качестве атаки SQL-инъекции, когда целые числа похожи на строки и экранируются только с помощью mysql_real_escape_string. Тогда вы сможете избежать использования кавычек.

Например, если вы просто сделаете что-то вроде этого:

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

атака может ввести вам очень с легкостью. Рассмотрим следующий внедренный код, возвращенный вашим скриптом:

SELECT ... WHERE id = -1 UNION ALL SELECT table_name FROM information_schema.tables;

а теперь просто извлеките структуру таблицы:

SELECT ... WHERE id = -1 UNION ALL SELECT column_name FROM information_schema.column WHERE table_name = __0x61727469636c65__;

А затем просто выберите те данные, которые вам нужны. Разве это не круто?

Но если бы кодировщик инъекционного сайта заколдовал бы его, внедрение было бы невозможно, потому что запрос будет выглядеть так:

SELECT ... WHERE id = UNHEX('2d312075...3635');

@Zaffy, мне нравится идея, но как насчет производительности, я имею в виду, если у вас есть 1 миллион записей и 1000 пользователей ищут, замедляется ли это по сравнению с подготовкой решения?

Sumit Gupta 01.06.2013 16:48

Я просто тестирую SELECT * FROM tblproducts WHERE product_code LIKE ('% 42%') действительно находит запись, но SELECT * FROM tblproducts WHERE product_code LIKE ('%' + 0x3432 + '%') нет, поэтому он просто не работает или я что то не так сделал?

Sumit Gupta 01.06.2013 16:53

@SumitGupta Да, это так. MySQL не соединяется с +, но с CONCAT. И к производительности: я не думаю, что это влияет на производительность, потому что mysql должен анализировать данные, и не имеет значения, является ли происхождение строкой или шестнадцатеричным.

Zaffy 02.06.2013 03:49

@YourCommonSense Ваш 0x . $_GET["id"] для идентификатора 42 будет иметь байт с кодом ascii 42. Попробуйте SELECT 0x42, и вы увидите, что у вас получилось.

Zaffy 01.07.2013 17:59

@YourCommonSense Вы не понимаете концепцию ... Если вы хотите иметь строку в mysql, вы цитируете ее, как этот 'root', или вы можете шестнадцатеричное значение 0x726f6f74, НО если вы хотите число и отправить его в виде строки, вы, вероятно, напишите '42', а не CHAR (42) ... '42' в шестнадцатеричном формате будет 0x3432, а не 0x42

Zaffy 01.07.2013 18:07

@YourCommonSense Мне нечего сказать ... просто смех ... если вы все еще хотите попробовать шестнадцатеричный код в числовых полях, см. Второй комментарий. Бьюсь об заклад, это сработает.

Zaffy 01.07.2013 18:24

В ответе четко указано, что с целочисленными значениями это не сработает, причина в том, что bin2hex преобразует переданное значение в строку (и, таким образом, bin2hex (0) равно 0x30, а не 0x03) - это, вероятно, та часть, которая вас смущает. . Если вы последуете этому, он работает отлично (по крайней мере, на моем сайте, протестированном с 4 разными версиями mysql на машинах debian, от 5.1.x до 5.6.x). В конце концов, шестнадцатеричное - это только способ представления, а не значение;)

griffin 01.08.2013 13:06

@YourCommonSense, ты все еще не понимаешь? Вы не можете использовать 0x и concat, потому что, если строка пуста, вы получите ошибку. Если вам нужна простая альтернатива вашему запросу, попробуйте этот SELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')

Zaffy 01.08.2013 16:33

@YourCommonSense. Если вы не хотите этого делать, вам не нужно этого делать. Я тебя не заставляю :)

Zaffy 01.08.2013 16:51

@YourCommonSense читает "(фиксированный размер записи) -таблицы" - каждая запись имеет фиксированный размер. И речь идет о преобразовании uint8_t в uint16_t, который можно легко распараллелить, используя (фиксированный размер записи) -таблицы на языке программирования, таком как c. Кроме того, как я уже сказал, настоящая причина использовать это - просто сделать что-то другое, поскольку любая разница в производительности разных методов будет настолько мала, что (вероятно) не будет иметь никакого значения в действительности.

griffin 01.08.2013 18:01

Этот излишне усложненный подход абсолютно бесполезен. Можно было бы использовать простую функцию цитирования "'".$mysqli->escape_string($_GET["id"])."'" вместо этих шестнадцатеричных / не шестнадцатеричных атрибутов. Но он будет в равной степени ограничен, оставляя ваше приложение уязвимым для SQL-инъекции в тех случаях, когда оно неприменимо.

Your Common Sense 12.06.2020 06:53

@Zaffy, спасибо, это очень помогает. Я тестировал сам, и ваша "формула" hex / unhex предотвращает наиболее распространенные атаки SQL-инъекций. Возможно это сломалось, утечка в процессе или что-то в этом роде? По крайней мере, так, как вы знаете ...

John Lemon 27.01.2021 19:05

Думаю, если кто-то захочет использовать PHP и MySQL или какой-нибудь другой сервер базы данных:

  1. Подумайте об изучении PDO (объекты данных PHP) - это уровень доступа к базе данных, обеспечивающий единый метод доступа к нескольким базам данных.
  2. Подумайте об изучении MySQLi
  3. Используйте собственные функции PHP, такие как: strip_tags, mysql_real_escape_string или, если числовая переменная, просто (int)$foo. Подробнее о типах переменных в PHP здесь. Если вы используете такие библиотеки, как PDO или MySQLi, всегда используйте PDO :: quote () и mysqli_real_escape_string ().

Примеры библиотек:

---- PDO

----- No placeholders - ripe for SQL injection! It's bad

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

----- Unnamed placeholders

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

----- Named placeholders

$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();

P.S:

PDO с легкостью выигрывает эту битву. При поддержке двенадцати различные драйверы базы данных и именованные параметры, мы можем игнорировать небольшая потеря производительности, и привыкните к его API. Из ценной бумаги с точки зрения безопасности, оба они безопасны, пока разработчик их использует. как они должны использоваться

Но хотя и PDO, и MySQLi довольно быстры, MySQLi выполняет незначительно быстрее в тестах - ~ 2,5% для неподготовленных выписки и ~ 6.5% на подготовленные.

И, пожалуйста, проверяйте каждый запрос к своей базе данных - это лучший способ предотвратить внедрение.

что mysqli неверен. Первый параметр выражает типы данных.

mickmackusa 29.05.2020 12:05

Если вы хотите использовать механизмы кэширования, такие как Redis или Memcached, возможно, вам подойдет DALMP. Он использует чистый MySQLi. Проверьте это: Уровень абстракции базы данных DALMP для MySQL с использованием PHP.

Кроме того, вы можете «подготовить» свои аргументы перед подготовкой вашего запроса, чтобы вы могли строить динамические запросы и в конце иметь полностью подготовленный запрос операторов. Уровень абстракции базы данных DALMP для MySQL с использованием PHP.

Простая альтернатива этой проблеме может быть решена путем предоставления соответствующих разрешений в самой базе данных. Например: если вы используете базу данных MySQL, войдите в базу данных через терминал или предоставленный пользовательский интерфейс и просто выполните эту команду:

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

Это ограничит доступ пользователя только к указанному запросу. Удалите разрешение на удаление, и данные никогда не будут удалены из запроса, запущенного со страницы PHP. Второе, что нужно сделать, - сбросить привилегии, чтобы MySQL обновил разрешения и обновления.

FLUSH PRIVILEGES; 

больше информации о румянец.

Чтобы увидеть текущие привилегии пользователя, выполните следующий запрос.

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

Узнать больше о ГРАНТ.

Этот ответ - по сути неправильно, так как он не помогает предотвратить инъекцию, а просто пытается смягчить последствия. Напрасно.

Your Common Sense 20.05.2016 14:00

Да, это не решение, но это то, что вы можете сделать заранее, чтобы избежать неприятностей.

Apurv Nerlekar 25.05.2016 21:25

@Apurv Если моя цель - прочитать личную информацию из вашей базы данных, то отсутствие разрешения DELETE ничего не значит.

Alex Holsgrove 05.10.2016 17:03

@AlexHolsgrove: Успокойтесь, я просто предлагал хорошие методы смягчения последствий.

Apurv Nerlekar 14.10.2016 23:59

@Apurv Вы не хотите «смягчать последствия», вы хотите сделать все возможное, чтобы от этого защититься. Честно говоря, установка правильного доступа пользователей важна, но не совсем то, о чем просит OP.

Alex Holsgrove 15.10.2016 00:08

Пользователь1: вставить, обновить, Пользователь2: выбрать, Пользователь3: удалить. Сложен в обслуживании и может повлиять на производительность при подключении трех пользователей, но определенно заблокирует то, что можно сделать в данной ситуации. Пользователь 1 не может удалять или читать, но, добавив столбец «удалить», вы можете обновить его там, где вам нужно что-то удалить, Удаляет ли из задания cron, используя User3 проверку вашего флага. Укажите, что я делаю, если выполняется вставка / обновление, конфиденциальная информация не может быть извлечена, если сделан выбор, удаление или запись / обновление не могут быть выполнены и т. д. Не устраняет все проблемы, но должно помочь?

Chris 23.01.2017 08:38

@Chris, вы ошибаетесь, он ничего не делает, кроме дополнительной боли в ягодицах. Например, у нас есть запрос select id, name from dropdown_table where status = '$YOUR_STATUS_VARIABLE$';. Теперь передайте 1' union all select id, email from users -- как $ YOUR_STATUS_VARIABLE $, и вы увидите, как это работает.

degr 14.06.2017 10:00

Я не утверждал, что он решил все проблемы, это вопрос правильного кодирования, но пользователь только с выбором не может обновить или удалить, пользователь с одним только обновлением не может выбрать удаление и т. д. И, как я сказал, удаление может быть выполнено с помощью задания cron, отметьте строки для удаления и удаления при следующем запуске cron. Это абсолютно не устранит все атаки. но это усложнит жизнь злоумышленникам.

Chris 14.06.2017 20:08

Предупреждение: подход, описанный в этом ответе, применим только к очень конкретным сценариям и небезопасен, поскольку атаки SQL-инъекций не зависят только от возможности внедрить X=Y.

Если злоумышленники пытаются взломать форму через переменную PHP $_GET или с помощью строки запроса URL-адреса, вы сможете поймать их, если они небезопасны.

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

Потому что 1=1, 2=2, 1=2, 2=1, 1+1=2 и т. д. Являются частыми вопросами к базе данных SQL злоумышленника. Возможно, он также используется многими хакерскими приложениями.

Но вы должны быть осторожны, вы не должны переписывать безопасный запрос со своего сайта. Приведенный выше код дает вам подсказку, как переписать или перенаправить (это зависит от тебя) эту специфичную для взлома динамическую строку запроса на страницу, которая будет хранить айпи адрес злоумышленника или ДАЖЕ ИХ COOKIES, историю, браузер или любую другую конфиденциальную информацию, чтобы вы могли иметь дело с ними позже, заблокировав их учетную запись или связавшись с властями.

Что случилось с 1-1=0? :)

Rápli András 01.10.2019 13:32

@ RápliAndrás Какой-то ([0-9\-]+)=([0-9]+).

5ervant 02.10.2019 15:12

Несколько рекомендаций по экранированию специальных символов в операторах SQL.

Не используйте MySQL. Это расширение устарело. Вместо этого используйте MySQLi или PDO.

MySQLi

Чтобы вручную экранировать специальные символы в строке, вы можете использовать функцию mysqli_real_escape_string. Функция не будет работать должным образом, если правильный набор символов не будет установлен с помощью mysqli_set_charset.

Пример:

$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')");

Для автоматического экранирования значений с помощью подготовленных операторов используйте mysqli_prepare и mysqli_stmt_bind_param, где типы для соответствующих переменных связывания должны быть предоставлены для соответствующего преобразования:

Пример:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

Независимо от того, используете ли вы подготовленные операторы или mysqli_real_escape_string, вам всегда нужно знать тип входных данных, с которыми вы работаете.

Поэтому, если вы используете подготовленный оператор, вы должны указать типы переменных для функции mysqli_stmt_bind_param.

И использование mysqli_real_escape_string для, как следует из названия, экранирования специальных символов в строке, поэтому это не сделает целые числа безопасными. Цель этой функции - предотвратить разрыв строк в операторах SQL и повреждение базы данных, которое это может вызвать. mysqli_real_escape_string - полезная функция при правильном использовании, особенно в сочетании с sprintf.

Пример:

$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

Вопрос очень общий. Некоторые отличные ответы выше, но большинство предлагает заранее подготовленные утверждения. MySQLi async не поддерживает подготовленные операторы, поэтому sprintf выглядит отличным вариантом для этой ситуации.

Dustin Graham 24.04.2016 01:33

Простым способом было бы использовать фреймворк PHP, такой как CodeIgniter или Laravel, который имеет встроенные функции, такие как фильтрация и активная запись, так что вам не нужно беспокоиться об этих нюансах.

Я думаю, что весь вопрос в том, чтобы сделать это без использования такого фреймворка.

Sanke 02.01.2018 19:16

Deprecated Warning: This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.

Security Warning: This answer is not in line with security best practices. Escaping is inadequate to prevent SQL injection, use prepared statements instead. Use the strategy outlined below at your own risk. (Also, mysql_real_escape_string() was removed in PHP 7.)

Использование PDO и MYSQLi является хорошей практикой для предотвращения инъекций SQL, но если вы действительно хотите работать с функциями и запросами MySQL, было бы лучше использовать

mysql_real_escape_string

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

Есть и другие возможности предотвратить это: например, определить - если входные данные являются строкой, числом, символом или массивом, существует множество встроенных функций для обнаружения этого. Также было бы лучше использовать эти функции для проверки входных данных.

is_string

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

is_numeric

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

И гораздо лучше использовать эти функции для проверки входных данных с помощью mysql_real_escape_string.

Также нет никакого смысла проверять элементы массива $ _POST с помощью is_string ()

Your Common Sense 18.01.2014 11:06
ПРЕДУПРЕЖДЕНИЕ!mysql_real_escape_string()не безупречный.
eggyal 25.04.2014 18:54
mysql_real_escape_string is now deprecated, so its no longer a viable option. It will be removed from PHP in the future. Its best to move onto what the PHP or MySQL folks recommend.
jww 08.04.2015 09:53

Тема: Не доверяйте предоставленным пользователем данным. Все, что вы ожидаете, - это данные мусора со специальными символами или логической логикой, которые должны сами стать частью SQL-запроса, который вы, возможно, выполняете. Храните значения $ _POST только как данные, а не как часть SQL.

Bimal Poudel 02.12.2017 10:39

Существует так много ответов на PHP и MySQL, но вот код для PHP и Oracle для предотвращения SQL-инъекций, а также регулярного использования драйверов 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);

Пожалуйста, объясните параметры oci_bind_by_name.

Jahanzeb Awan 01.01.2019 07:10

Я написал эту маленькую функцию несколько лет назад:

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;
}

Это позволяет запускать операторы в однострочном C# -ish String.Format, например:

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

Ускользает, учитывая тип переменной. Если вы попытаетесь параметризовать таблицу и имена столбцов, это не удастся, поскольку каждая строка помещается в кавычки, что является недопустимым синтаксисом.

ОБНОВЛЕНИЕ БЕЗОПАСНОСТИ: предыдущая версия str_replace допускала инъекции путем добавления токенов {#} в пользовательские данные. Эта версия preg_replace_callback не вызывает проблем, если замена содержит эти токены.

Хорошая идея - использовать объектно-реляционный преобразователь, например Идиорма:

$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;
}

Это избавит вас не только от SQL-инъекций, но и от синтаксических ошибок! Он также поддерживает коллекции моделей с цепочкой методов для фильтрации или применения действий сразу к нескольким результатам и нескольким соединениям.

Я честно не согласен с вашим предложением. Это может привести к ложному положительному чувству безопасности, добавляемому в любой ORM. Конечно, большинство из них заботятся о подготовленных операторах и параметризованных запросах. Новичок, приходящий на этот пост, может чувствовать себя в безопасности, выбирая любой ORM - доверяя им всем. В целом ORM упрощает работу, скрывая / абстрагируя детали реализации. Вы действительно ХОТИТЕ проверить (или слепо поверить), как это делается. Эмпирическое правило: чем больше за этим стоит сообщество (поддержка) открытого исходного кода, тем меньше он провален;)

pocketrocket 09.10.2020 18:25

Честно говоря, это не самая плохая идея, карманная ракета. В зависимости от ORM существует очень-очень высокая вероятность того, что авторы ORM разбираются в SQL лучше, чем кодировщик. Это похоже на то старое правило шифрования, что, если ваше имя не указано в исследовательских работах в этой области, не используйте свое собственное, потому что есть вероятность, что злоумышленник ДЕЙСТВИТЕЛЬНО имеет свое имя в статьях в этой области. Тем не менее, если это ORM, требующий от вас предоставления всего или части запроса (например, Model.filter ('where foo =?', Bar), вам может быть лучше свернуть ручной SQL

Shayne 28.10.2020 04:43

Другие вопросы по теме