Как я могу предотвратить внедрение 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;--')

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

12.09.2008 23:55:00
28 ОТВЕТОВ
РЕШЕНИЕ

Используйте подготовленные операторы и параметризованные запросы. Это операторы 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 как PDOExceptions.

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

Хотя вы можете установить charsetв параметрах конструктора, важно отметить, что «старые» версии PHP (до 5.3.6) молча игнорировали параметр charset в 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';
}
8896
19.04.2020 18:14:18
Просто добавьте, потому что я не видел этого нигде здесь, другая линия защиты - это брандмауэр веб-приложений (WAF), в котором могут быть установлены правила для поиска атак SQL-инъекций:
jkerak 29.12.2015 20:11:18
Кроме того, официальная документация mysql_query позволяет выполнять только один запрос, так что любой другой запрос, кроме; игнорируется Даже если это уже устарело, в PHP 5.5.0 есть много систем, которые могут использовать эту функцию. php.net/manual/en/function.mysql-query.php
Randall Valenciano 19.01.2016 17:40:02
Это плохая привычка, но это решение после проблем: не только для внедрения SQL, но и для любого типа внедрения (например, в F3 framework v2 была дыра внедрения шаблона представления), если у вас есть готовый старый веб-сайт или приложение страдает от дефектов внедрения, одно решение состоит в том, чтобы переназначить значения ваших предопределенных переменных supperglobal, таких как $ _POST, с экранированными значениями при начальной загрузке. С помощью PDO все еще возможно избежать (также на сегодняшний день фреймворков): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix 24.01.2016 15:08:30
В этом ответе отсутствует объяснение того, что представляет собой подготовленное утверждение - во-первых, это снижение производительности, если вы используете много подготовленных утверждений во время запроса, а иногда это приводит к 10-кратному снижению производительности. Лучше было бы использовать PDO с отключенной привязкой параметров, но без подготовки операторов.
donis 18.11.2016 08:54:29
Использование PDO лучше, в случае , если вы используете прямой сделайте запрос , что вы используете MySQLi :: escape_string
Kassem Itani 6.11.2017 08:17:19

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

Это не только защищает от внедрения SQL, но и ускоряет запросы.

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

845
15.07.2019 12:48:22

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

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

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

460
18.06.2019 10:23:01
Я попробовал ваш пример, и он отлично работает для меня. Не могли бы вы пояснить, «это не решит всех проблем»
Chinook 22.04.2012 20:31:05
Если вы не цитируете строку, она все еще может быть введена. Взять $q = "SELECT col FROM tbl WHERE x = $safe_var";к примеру. Установка $safe_varдля 1 UNION SELECT password FROM usersработы в этом случае из-за отсутствия котировок. Также возможно ввести строки в запрос, используя CONCATи CHR.
Polynomial 16.04.2013 18:06:15
@Polynomial Совершенно верно, но я бы воспринял это как неправильное использование. Пока вы используете его правильно, он обязательно будет работать.
glglgl 10.07.2013 07:30:38
ПРЕДУПРЕЖДЕНИЕ! mysql_real_escape_string() не является непогрешимым .
eggyal 25.04.2014 14:46:52
mysql_real_escape_stringтеперь считается устаревшим, поэтому его больше нельзя использовать. В будущем он будет удален из PHP. Лучше всего перейти на то, что рекомендуют PHP или MySQL.
jww 8.04.2015 06:37:06

Устаревшее предупреждение. В примере кода этого ответа (например, в примере кода вопроса) используется MySQLрасширение PHP , которое устарело в 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.

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

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

  • Если вы хотите изменить структуру SQL на основе пользовательского ввода, параметризованные запросы не помогут, и требуемое экранирование не покрывается mysql_real_escape_string. В этом случае вам лучше передать ввод пользователя через белый список, чтобы обеспечить пропуск только «безопасных» значений.
  • Если вы используете целые числа из пользовательского ввода в условии и применяете mysql_real_escape_stringподход, вы будете страдать от проблемы, описанной Полиномом в комментариях ниже. Этот случай сложнее, потому что целые числа не будут заключены в кавычки, так что вы могли бы справиться, проверив, что пользовательский ввод содержит только цифры.
  • Есть, вероятно, другие случаи, о которых я не знаю. Вы можете найти это полезным ресурсом по некоторым из более тонких проблем, с которыми вы можете столкнуться.
1645
21.11.2019 11:13:20
использования mysql_real_escape_stringдостаточно или я должен использовать параметризованный тоже?
peiman F. 8.03.2018 21:29:20
@peimanF. придерживайтесь хорошей практики использования параметризованных запросов даже в локальном проекте. При параметризованных запросах вам гарантировано, что SQL-инъекций не будет. Но имейте в виду, что вы должны санировать данные, чтобы избежать ложного поиска (например, XSS-инъекций, таких как размещение HTML-кода в тексте), htmlentitiesнапример
Goufalite 9.03.2018 08:02:53
@peimanF. Хорошая практика для параметризованных запросов и привязки значений, но настоящая escape-строка хороша
Richard 4.04.2018 18:03:04
Я понимаю включение mysql_real_escape_string()для полноты, но я не фанат перечисления наиболее подверженных ошибкам подхода в первую очередь. Читатель может просто быстро взять первый пример. Хорошо, что это устарело сейчас :)
Steen Schütt 5.12.2018 00:13:01
@ SteenSchütt - все mysql_*функции устарели. Они были заменены аналогичными mysqli_* функциями, такими как mysqli_real_escape_string.
Rick James 1.12.2019 01:21:34

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

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

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

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

379
25.12.2017 14:40:48
В самом деле; работа с включенными magic_quotes только поощряет плохую практику. Однако иногда вы не всегда можете контролировать среду до этого уровня - либо у вас нет доступа для управления сервером, либо ваше приложение должно сосуществовать с приложениями, которые (дрожат) зависят от такой конфигурации. По этим причинам хорошо писать переносимые приложения - хотя очевидно, что усилия будут потрачены впустую, если вы действительно контролируете среду развертывания, например, потому что это собственное приложение или будет использоваться только в вашей конкретной среде.
Rob 24.04.2011 17:04:56
Начиная с PHP 5.4, мерзость, известная как «магические кавычки», была убита мертвым . И хорошее избавление от плохого мусора.
BryanH 16.01.2013 22:45:58

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

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

Недостатки -

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

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

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

ВАЖНЫЙ

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

Существуют библиотеки, такие как Aura.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 Injection Prevention .

498
9.05.2019 03:05:39
Это лучшее, что вы можете сделать с устаревшим расширением MySQL. Для получения нового кода рекомендуется переключиться на mysqli или PDO.
Álvaro González 26.02.2013 12:42:46
Я не согласен с этой «специально сделанной функцией для предотвращения этих атак». Я думаю, что mysql_real_escape_stringцель состоит в том, чтобы построить правильный SQL-запрос для каждой входной строки данных. Профилактика sql-инъекций является побочным эффектом этой функции.
sectus 9.07.2013 05:01:27
Вы не используете функции для написания правильных строк входных данных. Вы просто пишете правильные, которые не нужно убегать или уже сбежали. Возможно, mysql_real_escape_string () была разработана с целью, о которой вы упомянули, но ее единственное значение - предотвращение внедрения.
Nazca 12.03.2014 22:38:22
ПРЕДУПРЕЖДЕНИЕ! mysql_real_escape_string() не является непогрешимым .
eggyal 25.04.2014 14:50:11
mysql_real_escape_stringтеперь считается устаревшим, поэтому его больше нельзя использовать. В будущем он будет удален из PHP. Лучше всего перейти на то, что рекомендуют PHP или MySQL.
jww 8.04.2015 06:41:25

Устаревшее предупреждение. В примере кода этого ответа (например, в примере кода вопроса) используется MySQLрасширение PHP , которое устарело в 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-инъекция (когда побега недостаточно)

362
9.05.2019 02:59:38
Вы можете предотвратить внедрение SQL, если примените метод проверки входных данных, в котором пользовательский ввод проходит проверку подлинности в соответствии с набором определенных правил для длины, типа и синтаксиса, а также в соответствии с бизнес-правилами.
Josip Ivic 15.09.2015 08:07:17

Каждый ответ здесь охватывает только часть проблемы. Фактически, есть четыре различных части запроса, которые мы можем динамически добавить в 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и тому подобное), но белый список кажется единственным подходом в этом случае.

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

  • Любая переменная, представляющая литерал данных SQL (или, проще говоря, строку SQL или число), должна быть добавлена ​​с помощью подготовленного оператора. Без исключений.
  • Любая другая часть запроса, такая как ключевое слово SQL, имя таблицы или поля или оператор, должна быть отфильтрована через белый список.

Обновить

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

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

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

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

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

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

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

1065
21.11.2019 11:37:32

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

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
256
1.05.2018 16:51:42
Это один из немногих случаев, когда я использовал бы «экранированное значение» вместо подготовленного выражения. И целочисленное преобразование типов чрезвычайно эффективно.
HoldOffHunger 13.03.2016 22:29:37

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

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

310
16.07.2014 02:05:53
Я думаю, что ваш первый абзац важен. Понимание является ключевым. Кроме того, не все работают на компанию. Для большого числа людей рамки фактически идут вразрез с идеей понимания . Быть близким к основам, возможно, не ценится при работе в сжатые сроки, но самодельные любители получают удовольствие от грязных рук. Разработчики фреймворка не настолько привилегированы, что все остальные должны поклониться и предположить, что они никогда не делают ошибок. Способность принимать решения по-прежнему важна. Кто сказал, что моя структура не заменит какую-то другую схему в будущем?
Anthony Rutledge 4.01.2017 16:32:46
@AnthonyRutledge Вы абсолютно правы. Очень важно понимать, что происходит и почему. Однако вероятность того, что действительно проверенная и активно используемая и разработанная инфраструктура натолкнулась на множество проблем и исправила множество дыр в безопасности, уже достаточно высока. Это хорошая идея, чтобы взглянуть на источник, чтобы почувствовать качество кода. Если это непроверенный беспорядок, это, вероятно, не безопасно.
Johannes Fahrenkrug 4.01.2017 18:38:17
Вот. Вот. Хорошие моменты. Однако согласитесь ли вы, что многие люди могут учиться и учиться внедрять систему MVC, но не каждый может воспроизвести ее вручную (контроллеры и сервер). С этим можно зайти слишком далеко. Нужно ли мне разбираться в микроволновке, прежде чем я разогрею печенье с орехами и арахисовым маслом, которое сделала моя подруга? ;-)
Anthony Rutledge 4.01.2017 19:30:19
@AnthonyRutledge Я согласен! Я думаю, что сценарий использования также имеет значение: создаю ли я фотогалерею для своей личной домашней страницы или веб-приложение для онлайн-банкинга? В последнем случае очень важно понять детали безопасности и то, как используемая мной структура решает эти проблемы.
Johannes Fahrenkrug 4.01.2017 20:35:02
Ах, исключение безопасности, сделайте это сами. Видите, я склонен рисковать всем этим и идти ва-банк. :-) Шучу. Со временем люди могут научиться делать чертовски безопасное приложение. Слишком много людей спешат. Они вскидывают руки и предполагают, что рамки безопаснее . В конце концов, у них нет достаточно времени, чтобы проверить и разобраться. Кроме того, безопасность является областью, которая требует специального изучения. Это не то, что простые программисты знают глубоко благодаря пониманию алгоритмов и шаблонов проектирования.
Anthony Rutledge 4.01.2017 20:48:10

Существует много способов предотвращения 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-инъекция не предотвращена.

295
22.02.2020 17:39:17

Используя эту функцию 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
221
25.12.2017 14:46:07

Предупреждение безопасности : этот ответ не соответствует рекомендациям по безопасности. Экранирование недостаточно для предотвращения внедрения 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. Используйте либо MySQL, либо 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");
174
9.05.2019 03:00:18

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

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

Я хочу одну колонку

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

Я хочу, чтобы массив (ключ => значение) результатов (т.е. для создания selectbox)

$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));
223
25.12.2017 14:47:00

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

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

Мой подход:

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

  • Если вы ожидаете что-то еще от целого числа, закройте его . Если вы это сделаете, вы полностью избежите ввода. В C / C ++ есть функция mysql_hex_string(), которую вы можете использовать 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')

Гекс это идеальный побег. Нет способа ввести.

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

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

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

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


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

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

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

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

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

SELECT ... WHERE id = -1 объединить все выбрать имя таблицы из таблицы information_schema.tables

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

SELECT ... WHERE id = -1 объединить все выбрать столбец_имя из information_schema.column, где имя_таблицы = 0x61727469636c65

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

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

549
25.12.2017 14:40:04
@SumitGupta Да, ты сделал. MySQL не соединяется с, +но с CONCAT. И на производительность: я не думаю, что это влияет на производительность, потому что mysql должен анализировать данные, и не имеет значения, является ли источник строковым или шестнадцатеричным
Zaffy 1.06.2013 23:49:49
@YourCommonSense Вы не понимаете концепцию ... Если вы хотите, чтобы в mysql была строка, вы должны заключить ее в кавычки, как это, 'root'или вы можете зашифровать ее, 0x726f6f74НО, если вы хотите получить число и отправить его в виде строки, вы, вероятно, напишите '42', а не CHAR (42 ) ... '42' в 0x34320x42
Zaffy 1.07.2013 14:07:02
@YourCommonSense Мне нечего сказать ... просто лол ... если вы все еще хотите попробовать hex в числовых полях, смотрите второй комментарий. Могу поспорить с тобой, что это сработает.
Zaffy 1.07.2013 14:24:52
В ответе четко сказано, что это не сработает с целочисленными значениями. Причина в том, что bin2hex преобразует переданное значение в строку (и, следовательно, bin2hex (0) составляет 0x30, а не 0x03) - возможно, это та часть, которая вас смущает , Если вы последуете этому, он отлично работает (по крайней мере, на моем сайте, протестирован с 4 разными версиями mysql на машинах Debian, с 5.1.x по 5.6.x). Ведь шестнадцатеричный - это только способ представления, а не значение;)
griffin 1.08.2013 09:06:18
@YourCommonSense вы все еще не понимаете? Вы не можете использовать 0x и concat, потому что если строка пуста, вы закончите с ошибкой. Если вы хотите простую альтернативу вашему запросу, попробуйте этотSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy 1.08.2013 12:33:59

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

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

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

---- PDO

----- Нет заполнителей - созрели для 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();

PS :

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

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

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

266
16.07.2014 02:27:44

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

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

232
25.12.2017 14:45:42

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

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

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

FLUSH PRIVILEGES; 

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

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

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

Узнайте больше о GRANT .

182
19.11.2018 13:25:25
Этот ответ по сути неправильный , так как он не помогает предотвратить профилактику инъекций, а просто пытается смягчить последствия. Напрасно.
Your Common Sense 20.05.2016 11:00:18
Правильно, это не дает решения, но это то, что вы можете сделать заранее, чтобы избежать вещей.
Apurv Nerlekar 25.05.2016 18:25:35
@Apurv Если моей целью является чтение личной информации из вашей базы данных, то отсутствие разрешения DELETE ничего не значит.
Alex Holsgrove 5.10.2016 14:03:41
@AlexHolsgrove: Полегче, я просто предлагал хорошие методы смягчения последствий.
Apurv Nerlekar 14.10.2016 20:59:30
@ Apurv Вы не хотите «смягчать последствия», вы хотите сделать все возможное, чтобы защититься от этого. Чтобы быть справедливым, установка правильного доступа пользователя важна, но не совсем то, что запрашивает OP.
Alex Holsgrove 14.10.2016 21:08:42

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

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

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

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

Фильтрация данных для (преобразования небезопасных данных в безопасные данные)

Учтите, что PDO и 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-запрос и данные, которые используются внутри. Оба подхода могут использоваться даже с небезопасными данными, потому что вводимые пользователем данные здесь ничего не добавляют к исходному запросу, например (any или x = x).

Для получения дополнительной информации, пожалуйста, прочтите OWASP Шпаргалку по предотвращению инъекций .

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

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

[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 и root. Как только эти слова обнаружены, вы можете избежать ввода.

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

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

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

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

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

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

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

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

Из руководства по PHP, PHP: Подготовленные заявления - Руководство :

Экранирование и внедрение SQL

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

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

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

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

PDO:

$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

Понятно, что подготовленное заявление также ускользает от данных, ничего больше.

Как также упоминалось в приведенном выше заявлении,

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

Следовательно, это доказывает, что проверка данных, например, intval()является хорошей идеей для целочисленных значений перед отправкой любого запроса. Кроме того, предотвращение злонамеренных пользовательских данных перед отправкой запроса является правильным и корректным подходом .

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

Ссылки:

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

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

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

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

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

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

146
15.07.2019 13:40:48
Что с тобой 1-1=0? :)
Rápli András 1.10.2019 10:32:31
@ RápliAndrás Какой-то ([0-9\-]+)=([0-9]+).
5ervant 2.10.2019 12:12:55

Несколько рекомендаций по экранированию специальных символов в операторах 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
217
12.12.2019 06:36:05
Вопрос очень общий. Некоторые отличные ответы выше, но большинство предлагают готовые заявления. MySQLi async не поддерживает подготовленные операторы, поэтому sprintf выглядит как отличный вариант для этой ситуации.
Dustin Graham 23.04.2016 22:33:00

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

168
25.12.2017 14:50:57
Я думаю, что весь вопрос заключается в том, чтобы сделать это без использования такой основы.
Sanke 2.01.2018 16:16:23

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

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

124
9.05.2019 03:00:59
Кроме того, нет абсолютно никакого смысла проверять члены массива $ _POST с помощью is_string ()
Your Common Sense 18.01.2014 07:06:05
ПРЕДУПРЕЖДЕНИЕ! mysql_real_escape_string() не является непогрешимым .
eggyal 25.04.2014 14:54:01
mysql_real_escape_stringтеперь считается устаревшим, поэтому его больше нельзя использовать. Он будет удален из PHP в будущем. Лучше всего перейти на то, что рекомендуют PHP или MySQL.
jww 8.04.2015 06:53:46
Тема: Не доверяйте предоставленным пользователем данным. Все, что вы ожидаете, это мусорные данные со специальными символами или логической логикой, которые сами должны стать частью SQL-запроса, который вы, возможно, выполняете. Сохраняйте значения $ _POST только как данные, а не как часть SQL.
Bimal Poudel 2.12.2017 07:39:58

Существует множество ответов на 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);
127
25.05.2016 13:51:10
Пожалуйста, объясните параметры oci_bind_by_name.
Jahanzeb Awan 1.01.2019 04:10:16

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

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версия не вызывает проблем, если замена содержит эти токены.

88
25.12.2017 14:55:27

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

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

125
29.03.2020 15:35:46