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




защита от sql инъекций php (19)

Предупреждение безопасности : этот ответ не соответствует рекомендациям по безопасности. Экранирование не подходит для предотвращения внедрения 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";

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

Я предлагаю использовать 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");

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

$unsafe_variable = $_POST['user_input']; 

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

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

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

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


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


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

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

Source : Неожиданный SQL-инъекция (когда побега недостаточно)


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


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

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

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

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

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


Несколько рекомендаций по экранированию специальных символов в операторах 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

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


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

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

Руководство по PHP для внедрения SQL

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

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

Теперь, почему вам нужно предотвратить ваш запрос от внедрения SQL?

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

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

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

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

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

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

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

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

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


Существует множество ответов на 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);

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

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: Высвобождение все Пользователь Поставляется Input

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

Также Принудительно: Наименее Privilege
также выполнять: Белый список проверки ввода

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

Обновление 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. Проверка данных

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

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

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


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

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

Недостатки -

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Мой подход:

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

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

    Не беспокойтесь о том, что экранированная строка будет иметь двукратный размер по mysql_real_escape_string первоначальной длиной, потому что даже если вы используете mysql_real_escape_string , PHP должен выделять mysql_real_escape_string же емкость ((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 объединить все выбрать столбец_имя из информационного_схемы.column, где имя_таблицы = 0x61727469636c65

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

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


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


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

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

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


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

У вас есть два варианта для достижения этой цели:

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

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

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

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

Однако обязательна первая 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 результатом будет просто поиск строки "'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';
}




sql-injection