Итак, вы думаете, что ваша база данных SQL эффективна и защищена от мгновенного уничтожения? Ну, SQL Injection не согласен!
Да, речь идет именно о мгновенном уничтожении, потому что я не хочу начинать эту статью с привычной хромой терминологии «усиление безопасности» и «предотвращение злонамеренного доступа». SQL-инъекция — это такой старый прием, о котором знает каждый разработчик и прекрасно знает, как его предотвратить. За исключением того странного случая, когда они ошибаются, и результаты могут быть не чем иным, как катастрофическими.
Если вы уже знаете, что такое SQL Injection, смело переходите ко второй половине статьи. Но для тех, кто только начинает заниматься веб-разработкой и мечтает занять более высокие должности, необходимо ввести некоторые сведения.
Оглавление
Что такое SQL-инъекция?
Ключ к пониманию SQL Injection кроется в его названии: SQL + Injection. Слово «инъекция» здесь не имеет никакого медицинского значения, а скорее является употреблением глагола «вводить». Вместе эти два слова передают идею использования SQL в веб-приложении.
Внедрение SQL в веб-приложение. . . хм . . . Разве это не то, что мы делаем в любом случае? Да, но мы не хотим, чтобы злоумышленник управлял нашей базой данных. Давайте разберемся в этом на примере.
Допустим, вы создаете типичный веб-сайт PHP для местного интернет-магазина, поэтому вы решили добавить контактную форму, подобную этой:
<form action="record_message.php" method="POST"> <label>Your name</label> <input type="text" name="name"> <label>Your message</label> <textarea name="message" rows="5"></textarea> <input type="submit" value="Send"> </form>
И давайте предположим, что файл send_message.php хранит все в базе данных, чтобы владельцы магазина могли позже прочитать пользовательские сообщения. У него может быть такой код:
<?php $name = $_POST['name']; $message = $_POST['message']; // check if this user already has a message mysqli_query($conn, "SELECT * from messages where name = $name"); // Other code here
Итак, сначала вы пытаетесь узнать, есть ли у этого пользователя непрочитанное сообщение. Запрос SELECT * from messages where name = $name кажется достаточно простым, не так ли?
НЕПРАВИЛЬНЫЙ!
В своей невинности мы открыли двери для мгновенного уничтожения нашей базы данных. Для этого злоумышленнику необходимо выполнить следующие условия:
- Приложение работает на базе данных SQL (сегодня почти каждое приложение)
- Текущее подключение к базе данных имеет права «редактировать» и «удалять» в базе данных.
- Названия важных таблиц можно угадать
Третий пункт означает, что теперь, когда злоумышленник знает, что вы управляете интернет-магазином, вы, скорее всего, храните данные о заказах в таблице заказов. Вооружившись всем этим, все, что нужно сделать злоумышленнику, это указать это как свое имя:
Джо; урезать заказы;? Да сэр! Давайте посмотрим, во что превратится запрос, когда он будет выполнен PHP-скриптом:
SELECT * FROM messages WHERE name = Joe; урезать заказы;
Хорошо, в первой части запроса есть синтаксическая ошибка (без кавычек вокруг «Джо»), но точка с запятой заставляет движок MySQL начать интерпретацию новой: обрезать заказы. Вот так, одним махом вся история заказов пропала!
Теперь, когда вы знаете, как работает SQL-инъекция, пришло время посмотреть, как ее остановить. Два условия, которые необходимо выполнить для успешного внедрения SQL:
Предотвращение внедрения SQL в PHP
Теперь, учитывая, что подключения к базе данных, запросы и пользовательский ввод являются частью жизни, как мы можем предотвратить внедрение SQL? К счастью, это довольно просто, и есть два способа сделать это: 1) очистить пользовательский ввод и 2) использовать подготовленные операторы.
Дезинфекция пользовательского ввода
Если вы используете более старую версию PHP (5.5 или ниже, а это часто происходит на виртуальном хостинге), целесообразно запускать весь пользовательский ввод через функцию mysql_real_escape_string(). По сути, что он делает, так это удаляет все специальные символы в строке, чтобы они теряли свое значение при использовании базой данных.
Например, если у вас есть строка типа I’m a string, злоумышленник может использовать символ одинарной кавычки (‘) для управления создаваемым запросом к базе данных и выполнения SQL-инъекции. Запустив его через mysql_real_escape_string(), я получаю строку, которая добавляет обратную косую черту к одинарной кавычке, экранируя ее. В результате вся строка теперь передается в базу данных как безвредная строка, вместо того, чтобы участвовать в манипулировании запросом.
У этого подхода есть один недостаток: это очень, очень старый метод, который сочетается со старыми формами доступа к базе данных в PHP. Начиная с PHP 7, этой функции больше не существует, что подводит нас к следующему решению.
Используйте подготовленные операторы
Подготовленные операторы — это способ сделать запросы к базе данных более безопасными и надежными. Идея состоит в том, что вместо того, чтобы отправлять необработанный запрос в базу данных, мы сначала сообщаем базе данных структуру запроса, который мы будем отправлять. Вот что мы подразумеваем под «подготовкой» заявления. Как только оператор подготовлен, мы передаем информацию в виде параметризованных входных данных, чтобы база данных могла «заполнить пробелы», вставив входные данные в структуру запроса, которую мы отправили ранее. Это лишает входные данные какой-либо особой силы, заставляя их рассматриваться как простые переменные (или полезные нагрузки, если хотите) во всем процессе. Вот как выглядят подготовленные заявления:
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // prepare and bind $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // set parameters and execute $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute(); echo "New records created successfully"; $stmt->close(); $conn->close(); ?>
Я знаю, что процесс кажется излишне сложным, если вы новичок в подготовленных заявлениях, но концепция стоит затраченных усилий. Вот хорошее введение в него.
Для тех, кто уже знаком с расширением PHP PDO и использует его для создания готовых операторов, у меня есть небольшой совет.
Предупреждение: будьте осторожны при настройке PDO
При использовании PDO для доступа к базе данных у нас может возникнуть ложное чувство безопасности. «Ну, я использую PDO. Теперь мне не нужно думать ни о чем другом» — так обычно идет наше мышление. Это правда, что PDO (или подготовленных операторов MySQLi) достаточно, чтобы предотвратить все виды атак SQL-инъекций, но вы должны быть осторожны при его настройке. Обычно просто копируют и вставляют код из руководств или из ваших предыдущих проектов и идут дальше, но этот параметр может отменить все:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
Этот параметр указывает PDO эмулировать подготовленные операторы, а не фактически использовать функцию подготовленных операторов базы данных. Следовательно, PHP отправляет простые строки запроса в базу данных, даже если ваш код выглядит так, как будто он создает подготовленные операторы, устанавливает параметры и все такое. Другими словами, вы так же уязвимы для SQL-инъекций, как и раньше. 🙂
Решение простое: убедитесь, что для этой эмуляции установлено значение false.
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Теперь скрипт PHP вынужден использовать подготовленные операторы на уровне базы данных, предотвращая все виды SQL-инъекций.
Предотвращение использования WAF
Знаете ли вы, что вы также можете защитить веб-приложения от SQL-инъекций с помощью WAF (брандмауэра веб-приложений)?
Ну, не только SQL-инъекция, но и многие другие уязвимости уровня 7, такие как межсайтовый скриптинг, нарушенная аутентификация, межсайтовая подделка, раскрытие данных и т. д. Либо вы можете использовать самостоятельный хостинг, такой как Mod Security, либо облачный, как показано ниже.
SQL-инъекция и современные PHP-фреймворки
SQL-инъекция настолько распространена, настолько проста, настолько разочаровывает и настолько опасна, что все современные веб-фреймворки PHP имеют встроенные меры противодействия. В WordPress, например, у нас есть функция $wpdb->prepare(), тогда как, если вы используете инфраструктуру MVC, она сделает всю грязную работу за вас, и вам даже не придется думать о предотвращении SQL-инъекций. Немного раздражает то, что в WordPress вам приходится явно подготавливать операторы, но мы же говорим о WordPress. 🙂
В любом случае, я хочу сказать, что современному поколению веб-разработчиков не нужно думать о SQL-инъекциях, и в результате они даже не подозревают о такой возможности. Таким образом, даже если они оставят открытым один бэкдор в своем приложении (возможно, это параметр запроса $_GET и старая привычка запускать грязный запрос), результаты могут быть катастрофическими. Поэтому всегда лучше потратить время на то, чтобы глубже погрузиться в основы.
Вывод
SQL-инъекция — очень неприятная атака на веб-приложение, но ее легко избежать. Как мы видели в этой статье, нужно быть осторожным при обработке пользовательского ввода (кстати, SQL Injection — не единственная угроза, связанная с обработкой пользовательского ввода) и выполнять запросы к базе данных — это все, что нужно. Тем не менее, мы не всегда работаем с безопасностью веб-фреймворка, поэтому лучше знать об этом типе атак и не поддаваться на них.