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

avatar
Andrew G. Johnson
12 сентября 2008 в 23:55
1926114
28
2773

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

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

Источник

Ответы (28)

avatar
PeeHaa
28 июня 2021 в 10:43
9294

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

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

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


Пояснение

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

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

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

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

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

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

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

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

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

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

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}
Randall Valenciano
19 января 2016 в 17:40
47

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

Alix
24 января 2016 в 15:08
15

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

donis
18 ноября 2016 в 08:54
20

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

Kassem Itani
6 ноября 2017 в 08:17
10

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

p0358
18 ноября 2018 в 23:24
5

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

niCk cAMel
15 ноября 2020 в 21:15
1

Я думал, что нельзя запускать несколько запросов в одном mysql_query. Обдумайте этот ответ.

user138720
1 марта 2021 в 15:17
1

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

Jack
20 апреля 2021 в 10:10
2

Есть ли причина, по которой вы тестируете на empty($dir)?

user7571491
4 июля 2021 в 04:54
0

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

ADJenks
4 августа 2021 в 20:12
0

Почему этот вопрос связан со всеми другими вопросами PHP? Я думаю, что алгоритм «связанных вопросов» должен придавать таким популярным вопросам меньший вес. Вопрос, на который я смотрел, содержал теги «массивы» и «слияние массивов». Единственным подходящим тегом был php. Я думаю, что были гораздо более актуальные вопросы о php, массивах и слиянии массивов, которые не были этим вопросом. Что-то неправильно. Похоже, что этот вопрос достиг критической массы и становится черной дырой.

avatar
Imran
24 марта 2021 в 10:25
644

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

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

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();
avatar
Your Common Sense
20 июня 2020 в 09:12
1117

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

avatar
Zaffy
12 июня 2020 в 03:31
569

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

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

Мой подход:

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

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

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

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

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

SELECT password FROM users WHERE name = 'root';

станет:

SELECT password FROM users WHERE name = 0x726f6f74;

или

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

SELECT ... WHERE id = UNHEX('2d312075...3635');
Sumit Gupta
1 июня 2013 в 12:48
0

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

Sumit Gupta
1 июня 2013 в 12:53
0

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

Zaffy
1 июня 2013 в 23:49
8

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

Zaffy
1 июля 2013 в 13:59
0

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

Zaffy
1 июля 2013 в 14:07
14

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

Zaffy
1 июля 2013 в 14:24
9

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

griffin
1 августа 2013 в 09:06
3

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

Zaffy
1 августа 2013 в 12:33
7

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

Zaffy
1 августа 2013 в 12:51
0

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

griffin
1 августа 2013 в 14:01
1

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

Your Common Sense
12 июня 2020 в 03:53
0

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

Jaakko
27 января 2021 в 16:05
0

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

avatar
Thomas Ahle
29 марта 2020 в 15:35
131

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

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

pocketrocket
9 октября 2020 в 15:25
0

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

Shayne
28 октября 2020 в 01:43
0

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

avatar
Manish Shrivastava
22 февраля 2020 в 17:39
307

Есть много способов предотвратить 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-инъекцию.

avatar
Danijel
12 декабря 2019 в 06:36
223

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

Dustin Graham
23 апреля 2016 в 22:33
3

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

avatar
Matt Sheppard
21 ноября 2019 в 11:13
1696

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

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

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


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

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

//Connect

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

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

//Disconnect

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

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

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

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

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

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

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

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

  • Если вы хотите изменить структуру SQL на основе пользовательского ввода, параметризованные запросы не помогут, а требуемое экранирование не покрывается mysql_real_escape_string. В таком случае лучше передать вводимые пользователем данные через белый список, чтобы обеспечить пропуск только «безопасных» значений.
  • Если вы используете целые числа из пользовательского ввода в условии и применяете подход mysql_real_escape_string, вы столкнетесь с проблемой, описанной Polynomial в комментариях ниже. Этот случай сложнее, потому что целые числа не будут заключаться в кавычки, поэтому вы можете справиться, проверив, что вводимые пользователем данные содержат только цифры.
  • Вероятно, есть и другие случаи, о которых я не знаю. Вы можете найти этот полезный ресурс по некоторым из более тонких проблем, с которыми вы можете столкнуться.
peiman F.
8 марта 2018 в 21:29
1

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

Goufalite
9 марта 2018 в 08:02
8

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

Richard
4 апреля 2018 в 18:03
2

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

Steen Schütt
5 декабря 2018 в 00:13
0

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

Rick James
1 декабря 2019 в 01:19
0

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

Rick James
1 декабря 2019 в 01:21
2

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

Steen Schütt
2 декабря 2019 в 09:07
0

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

avatar
5ervant
15 июля 2019 в 13:40
149

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

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

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

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

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

5ervant - techintel.github.io
2 октября 2019 в 12:12
0

@ RápliAndrás Что-то вроде ([0-9\-]+)=([0-9]+).

avatar
15 июля 2019 в 13:31
178

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

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 SQL.

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

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

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

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

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

Неожиданное поведение в указанном выше пользовательском вводе: SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA и 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. Проверка данных
avatar
Kibbee
15 июля 2019 в 12:48
869

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

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

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

Your Common Sense
12 июня 2020 в 03:41
5

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

avatar
Kiran Maniya
18 июня 2019 в 10:23
475

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

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

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

Chinook
22 апреля 2012 в 20:31
30

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

Polynomial
16 апреля 2013 в 18:06
17

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

glglgl
10 июля 2013 в 07:30
4

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

DjOnce
3 декабря 2013 в 13:30
0

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

Arvind Bhardwaj
25 марта 2014 в 06:32
0

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

eggyal
25 апреля 2014 в 14:46
25

ВНИМАНИЕ! mysql_real_escape_string() не является непогрешимым.

jww
8 апреля 2015 в 06:37
8

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

Pratik
12 декабря 2015 в 14:18
0

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

Abhijeet Kambli
15 февраля 2019 в 12:52
0

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

YanDatsiuk
20 июня 2019 в 18:23
0

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

avatar
rahularyansharma
9 мая 2019 в 03:05
509

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

Álvaro González
26 февраля 2013 в 12:42
32

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

sectus
9 июля 2013 в 05:01
7

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

Nazca
12 марта 2014 в 22:38
4

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

eggyal
25 апреля 2014 в 14:50
20

ВНИМАНИЕ! mysql_real_escape_string() не является непогрешимым.

Wayne Whitty
16 июня 2014 в 14:58
0

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

jww
8 апреля 2015 в 06:41
10

mysql_real_escape_string теперь устарел, поэтому его больше не использовать. В будущем он будет удален из PHP. Лучше перейти к тому, что рекомендуют специалисты по PHP или MySQL.

Scott Arciszewski
29 мая 2015 в 19:54
0

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

cjohansson
3 марта 2016 в 11:00
1

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

avatar
Rakesh Sharma
9 мая 2019 в 03:00
124

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

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

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

Your Common Sense
18 января 2014 в 07:06
10

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

eggyal
25 апреля 2014 в 14:54
21

ВНИМАНИЕ! mysql_real_escape_string() не является непогрешимым.

jww
8 апреля 2015 в 06:53
10

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

Bimal Poudel
2 декабря 2017 в 07:39
2

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

avatar
Soumalya Banerjee
9 мая 2019 в 03:00
175

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

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

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

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

Надеюсь, это вам поможет.

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

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

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

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

На этот вопрос есть хорошие ответы по этому поводу.

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

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

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

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

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Пример :

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");
avatar
Cedric
9 мая 2019 в 02:59
372

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

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

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

Эти примеры уязвимы для SQL-инъекции:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

или

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

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

Источник: Неожиданное внедрение SQL (когда экранирования недостаточно)

Josip Ivic
15 сентября 2015 в 08:07
2

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

avatar
Apurv Nerlekar
19 ноября 2018 в 13:25
183

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

Your Common Sense
20 мая 2016 в 11:00
26

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

Apurv Nerlekar
25 мая 2016 в 18:25
1

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

Alexander Holsgrove
5 октября 2016 в 14:03
2

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

Apurv Nerlekar
14 октября 2016 в 20:59
1

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

Alexander Holsgrove
14 октября 2016 в 21:08
3

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

Chris
23 января 2017 в 05:38
0

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

degr
14 июня 2017 в 07:00
0

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

Chris
14 июня 2017 в 17:08
1

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

avatar
devOp
1 мая 2018 в 16:51
262

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

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
HoldOffHunger
13 марта 2016 в 22:29
3

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

avatar
Calmarius
25 декабря 2017 в 14:55
87

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

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

avatar
Deepak Thomas
25 декабря 2017 в 14:50
170

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

Sanke
2 января 2018 в 16:16
8

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

avatar
Xeoncross
25 декабря 2017 в 14:47
227

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

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

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

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

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

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

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

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

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

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));
avatar
Nicolas Finelli
25 декабря 2017 в 14:46
223

Используя эту функцию 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
avatar
Peter Mortensen
25 декабря 2017 в 14:45
235

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

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

avatar
Nikhil
25 декабря 2017 в 14:43
308

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

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

Недостатки -

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

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

Rob
24 апреля 2011 в 17:04
11

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

BryanH
16 января 2013 в 22:45
26

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

avatar
Chintan Gor
25 мая 2016 в 13:51
129

Есть так много ответов для 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);
Jahanzeb Awan
1 января 2019 в 04:10
0

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

avatar
16 июля 2014 в 02:27
272

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

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

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

---- PDO

----- Заполнителей нет - готово для внедрения SQL! Это плохо

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

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

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

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

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

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

P.S :

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

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

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

mickmackusa
29 мая 2020 в 09:05
0

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

avatar
16 июля 2014 в 02:05
318

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

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

Anthony Rutledge
4 января 2017 в 16:32
6

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

Johannes Fahrenkrug
4 января 2017 в 18:38
0

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

Anthony Rutledge
4 января 2017 в 19:30
3

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

Johannes Fahrenkrug
4 января 2017 в 20:35
3

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

Anthony Rutledge
4 января 2017 в 20:48
3

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