Вопрос

Если пользовательский ввод вставляется в 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;--')

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

Это было полезно?

Решение

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

По сути, у вас есть два варианта добиться этого:

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

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('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', 'pass');

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

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

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

Хотя вы можете установить charset в опциях конструктора важно отметить, что «старые» версии PHP (< ​​5.3.6) молча проигнорировал параметр charset в ДСН.

Объяснение

Происходит следующее: оператор 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(array('column' => $unsafeValue));

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

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

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

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

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

Устаревшее предупреждение:Пример кода этого ответа (как и пример кода вопроса) использует PHP MySQL расширение, которое было объявлено устаревшим в PHP 5.5.0 и полностью удалено в PHP 7.0.0.

Предупреждение безопасности:Этот ответ не соответствует лучшим практикам безопасности. Экранирование недостаточно для предотвращения SQL-инъекции., использовать подготовленные заявления вместо.Используйте стратегию, изложенную ниже, на свой страх и риск.(Также, mysql_real_escape_string() был удален в 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.

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

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

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

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

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

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

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

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

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

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

Однако есть другой способ обезопасить идентификаторы — экранирование.Если у вас есть идентификатор в кавычках, вы можете избежать обратных кавычек внутри, удвоив их.

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

Итак, если вкратце:это заполнитель, нет подготовленное заявление можно рассматривать как серебряную пулю.

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

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

Обновлять

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мой подход:

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

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

    Не беспокойтесь о том, что экранированная строка будет иметь размер, в два раза превышающий ее первоначальную длину, потому что даже если вы используете 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, текст, блок, двоичный файл и т. д..
Кроме того, его использование немного сложнее, если вы собираетесь вставить пустую строку.Вам придется полностью заменить его на '', иначе вы получите сообщение об ошибке.

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


Шестнадцатеричные методы часто используются в качестве атак.

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

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

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

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

ВЫБИРАТЬ ...ГДЕ идентификатор = -1 объединение всех выберите имя_таблицы из Information_schema.tables

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

ВЫБИРАТЬ ...ГДЕ id = -1 объединение всех выбирает имя_столбца из информационной_схемы.столбец, где имя_таблицы = 0x61727469636c65

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

Но если кодировщик инъекционного сайта воспользуется шестнадцатеричным кодом, инъекция будет невозможна, потому что запрос будет выглядеть так: SELECT ... WHERE id = UNHEX('2d312075...3635')

Устаревшее предупреждение:Пример кода этого ответа (как и пример кода вопроса) использует PHP MySQL расширение, которое было объявлено устаревшим в PHP 5.5.0 и полностью удалено в PHP 7.0.0.

Предупреждение безопасности:Этот ответ не соответствует лучшим практикам безопасности. Экранирование недостаточно для предотвращения SQL-инъекции., использовать подготовленные заявления вместо.Используйте стратегию, изложенную ниже, на свой страх и риск.(Также, mysql_real_escape_string() был удален в PHP 7.)

ВАЖНЫЙ

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

Есть такие библиотеки, как Аура.Sql и EasyDB которые позволяют разработчикам проще использовать подготовленные операторы.Чтобы узнать больше о том, почему подготовленные операторы лучше остановка SQL-инъекции, Ссылаться на этот mysql_real_escape_string() обойти и недавно исправлены уязвимости Unicode SQL Injection в 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-инъекций.

Устаревшее предупреждение:Пример кода этого ответа (как и пример кода вопроса) использует PHP MySQL расширение, которое было объявлено устаревшим в PHP 5.5.0 и полностью удалено в PHP 7.0.0.

Предупреждение безопасности:Этот ответ не соответствует лучшим практикам безопасности. Экранирование недостаточно для предотвращения SQL-инъекции., использовать подготовленные заявления вместо.Используйте стратегию, изложенную ниже, на свой страх и риск.(Также, mysql_real_escape_string() был удален в PHP 7.)

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

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

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

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

Устаревшее предупреждение:Пример кода этого ответа (как и пример кода вопроса) использует PHP MySQL расширение, которое было объявлено устаревшим в PHP 5.5.0 и полностью удалено в PHP 7.0.0.

Предупреждение безопасности:Этот ответ не соответствует лучшим практикам безопасности. Экранирование недостаточно для предотвращения SQL-инъекции., использовать подготовленные заявления вместо.Используйте стратегию, изложенную ниже, на свой страх и риск.(Также, mysql_real_escape_string() был удален в 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-кода в ваше PHP-приложение (или любое веб-приложение, если на то пошло) — это подумать об архитектуре вашего приложения.Если единственный способ защититься от SQL-инъекций — это не забывать использовать специальный метод или функцию, которая выполняет «Правильные действия» каждый раз, когда вы обращаетесь к базе данных, вы делаете это неправильно.Таким образом, это всего лишь вопрос времени, когда вы забудете правильно отформатировать запрос в какой-то момент кода.

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

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

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

Недостатки -

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

Существует множество способов предотвращения 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']= admin@emali.com' OR '1=1

и пароль какой-нибудь....

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

$query="select * from users where email='admin@emali.com' OR '1=1';

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

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

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

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

---- ПДО

----- Никаких заполнителей – готово для SQL-инъекций! Это плохо

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

----- Безымянные заполнители

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

----- Именованные заполнители

$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% для приготовленных.

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

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

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

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

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

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

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

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

мне нужен один столбец

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

Мне нужны результаты массива (ключ => значение) (т.е.для создания поля выбора)

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

Мне нужен результат в одну строку

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

Мне нужен массив результатов

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

Использование этой функции 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

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

Не используйте MySQL, это расширение устарело, используйте MySQLi или ПДО.

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

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

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

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

FLUSH PRIVILEGES; 

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

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

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

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

Предупреждение безопасности:Этот ответ не соответствует лучшим практикам безопасности. Экранирование недостаточно для предотвращения SQL-инъекции., использовать подготовленные заявления вместо.Используйте стратегию, изложенную ниже, на свой страх и риск.(Также, mysql_real_escape_string() был удален в PHP 7.)

Устаревшее предупреждение:Расширение mysql в настоящее время устарело.мы рекомендуем использовать Расширение PDO

Я использую три разных способа предотвратить уязвимость моего веб-приложения к 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");

Что касается многих полезных ответов, я надеюсь добавить некоторые ценности в эту тему.SQL-инъекция — это атака, которая может быть осуществлена ​​с помощью пользовательских входных данных (входные данные, которые заполняются пользователем и затем используются внутри запросов). Шаблоны SQL-инъекций представляют собой правильный синтаксис запроса, хотя мы можем его назвать:плохие запросы по плохим причинам, мы предполагаем, что может быть плохой человек, который попытается получить секретную информацию (в обход контроля доступа), что влияет на три принципа безопасности (конфиденциальность, целостность, доступность).

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

Мой подход против SQL-инъекций:очистка введенных пользователем данных перед отправкой их в базу данных (перед их использованием внутри любого запроса).

Фильтрация данных (преобразование небезопасных данных в безопасные)Считают, что ПДО и MySQLi недоступен, как вы можете защитить свое приложение?Вы заставляете меня использовать их?А как насчет других языков, кроме PHP?Я предпочитаю излагать общие идеи, поскольку их можно использовать для более широких границ, а не только для конкретного языка.

  1. Пользователь SQL (ограничение привилегий пользователя):наиболее распространенными операциями SQL являются (SELECT, UPDATE, INSERT), тогда зачем давать привилегию UPDATE пользователю, которому она не нужна?Например вход и страницы поиска используют только SELECT, тогда зачем на этих страницах использовать пользователей БД с высокими привилегиями?ПРАВИЛО:не создавайте одного пользователя базы данных для всех привилегий, для всех операций SQL вы можете создать свою схему, например (deluser, selectuser, updateuser), в качестве имен пользователей для удобства использования.

видеть Принцип наименьших привилегий

  1. Фильтрация данных:перед созданием любого запроса пользовательский ввод должен быть проверен и отфильтрован. Для программистов важно определить некоторые свойства для каждой переменной пользовательского ввода:тип данных, шаблон данных и длина данных.поле, представляющее собой число между (x и y), должно быть точно проверено с использованием точного правила для поля, представляющего собой строку (текст):В этом случае используется шаблон, например, имя пользователя должно содержать только несколько символов, скажем, [a-zA-Z0-9_-.] длина варьируется между (x и n), где x и n (целые числа, x <=n ).Правило:Для меня лучшая практика — создание точных фильтров и правил проверки.

  2. Используйте другие инструменты:Здесь я также соглашусь с вами, что подготовленный оператор (параметризованный запрос) и хранимые процедуры, недостатки здесь в том, что эти способы требуют продвинутых навыков, которых нет у большинства пользователей. Основная идея здесь состоит в том, чтобы различать SQL-запрос и данные. который используется внутри, оба подхода могут использоваться даже с небезопасными данными, поскольку данные, вводимые пользователем, не добавляют ничего к исходному запросу, например (любой или x=x).Для получения дополнительной информации, пожалуйста, прочитайте Памятка OWASP по предотвращению SQL-инъекций.

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

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

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

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

Последний пункт — обнаружение неожиданного поведения, которое требует больше усилий и сложности;это не рекомендуется для обычных веб-приложений.Неожиданное поведение в приведенном выше пользовательском вводе: SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, корень, как только эти слова обнаружены, вы можете избежать ввода.

ОБНОВЛЕНИЕ1:

Пользователь прокомментировал, что этот пост бесполезен, ОК!Вот что OWASP.ORG предоставил:

Первичная защита:

Опция 1:Использование подготовленных операторов (параметризованных запросов)
Вариант №2:Использование хранимых процедур
Вариант №3:Экранирование всех вводимых пользователем данных

Дополнительная защита:

Также обеспечить соблюдение:Наименьшие привилегии
Также выполните:Проверка ввода белого списка

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

Обновление2:

Из руководства PHP, PHP:Подготовленные отчеты – Руководство:

Экранирование и SQL-инъекция

Связанные переменные будут автоматически экранированы сервером.Сервер вставляет свои сбежавшие значения в соответствующие места в шаблон оператора перед выполнением.Подсказка должна быть предоставлена ​​серверу для типа связанной переменной, чтобы создать соответствующее преобразование.См. Функцию mysqli_stmt_bind_param () для получения дополнительной информации.

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

Обновление3:

Я создал тестовые примеры, чтобы узнать, как PDO и MySQLi отправляют запрос на сервер MySQL при использовании подготовленного оператора:

ПДО:

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

Журнал запросов:

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

MySQLi:

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

Журнал запросов:

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

Понятно, что подготовленный оператор также экранирует данные, и ничего больше.

Как также упоминалось в приведенном выше заявлении The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly, следовательно, это доказывает, что проверка данных, такая как intval() рекомендуется использовать целочисленные значения перед отправкой любого запроса. Кроме того, предотвращение вредоносных пользовательских данных перед отправкой запроса является хорошей идеей. правильный и действенный подход.

Пожалуйста, посмотрите этот вопрос для более подробной информации: PDO отправляет необработанный запрос в MySQL, а Mysqli отправляет подготовленный запрос, оба дают один и тот же результат.

Использованная литература:

  1. Шпаргалка по SQL-инъекциям
  2. SQL-инъекция
  3. Информационная безопасность
  4. Принципы безопасности
  5. Валидация данных

Простым способом было бы использовать PHP-фреймворк, например КодИгнитер или Ларавел которые имеют встроенные функции, такие как фильтрация и активная запись, так что вам не придется беспокоиться об этих нюансах.

** Предупреждение:подход, описанный в этом ответе, применим только к очень конкретным сценариям и небезопасен, поскольку атаки с помощью 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 злоумышленника.Возможно, он также используется многими хакерскими приложениями.

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

Есть так много ответов на 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);

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

$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-инъекций, но и от синтаксических ошибок!Также поддерживает коллекции моделей с цепочками методов для фильтрации или применения действий к нескольким результатам одновременно и нескольким соединениям.

Устаревшее предупреждение:Пример кода этого ответа (как и пример кода вопроса) использует PHP MySQL расширение, которое было объявлено устаревшим в PHP 5.5.0 и полностью удалено в PHP 7.0.0.

Предупреждение безопасности:Этот ответ не соответствует лучшим практикам безопасности. Экранирование недостаточно для предотвращения SQL-инъекции., использовать подготовленные заявления вместо.Используйте стратегию, изложенную ниже, на свой страх и риск.(Также, mysql_real_escape_string() был удален в PHP 7.)

С использованием ПДО и 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.

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top