Защита от sql-инъекций в php
Содержание:
- Что такое SQL-инъекция
- Принцип атаки инъекции SQL
- Simple SQL Injection Example
- Демонстрация атаки с использованием SQL-инъекции
- Дополнительная защита сайта от взлома
- Что такое SQL инъекция
- Шифрование и хеширование
- Как можно использовать SQL-инъекции?
- Использовать параметры SQL для защиты
- How to Prevent against SQL Injection Attacks
Что такое SQL-инъекция
SQL-инъекция — это попытка изменить запрос к базе данных. Ввести ее можно через форму или ссылку, которая передает параметры методом GET. Представьте, что у вас на сайте есть форма регистрации, в которую пользователь вводит логин и пароль.
<form action=’reg.php’ method=’post’>
<input type=’text’ name=’login’ placeholder=’Логин’ required><br>
<input type=’password’ name=’password’ placeholder=’Пароль’ required><br>
<input type=’submit’ value=’Зарегистрироваться’>
</form>
Они отправляются на сервер и вставляются в запрос такого вида:
$query = «INSERT INTO userlist („.$keys.“) VALUES („.$values.“)»;
В переменной $keys содержатся ключи из супермассива $_POST, а в $values — значения:
$keys = «»;
$values = «»;
$first = 1;
foreach($_POST as $key => $value) {
if($first == 0) { //Добавляем запятую перед каждым пунктом, кроме первого
$keys .= «,»;
$values .= «,»;
}
$keys .= $key;
$values .= «‘».$value.»‘»;
$first = 0;
}
Это удобно, если в форме очень много полей, но из-за этого появляется уязвимость: если пользователь захочет запустить SQL-инъекцию, то он может просто создать в форме новое поле с нужным ему именем и ввести любое значение. Например, можно создать поле admin и ввести значение 1.
Если сайт плохо защищен, то хакер может совершить много попыток, но в итоге все же получить доступ к правам администратора.
Принцип атаки инъекции SQL
Допустим, серверное ПО, получив входной параметр id, использует его для создания SQL-запроса. Рассмотрим следующий PHP-скрипт:
… $id = $_REQUEST'id';$res = mysql_query ( «SELECT * FROM news WHERE id_news = $id») ;…
Если на сервер передан параметр id, равный 5 (например так: http://example.org/script.php?id=5
) , то выполнится следующий SQL-запрос:
SELECT * FROM news WHERE id_news = 5
Но если злоумышленник передаст в качестве параметра id строку -1 OR 1=1 (например, так: http://example.org/script.php?id=-1 + OR + 1=1
) , то выполнится запрос:
SELECT * FROM news WHERE id_news = -1 OR 1=1
Таким образом, изменение входных параметров путём добавления в них конструкций языка SQL вызывает изменение в логике выполнения SQL-запроса (в данном примере вместо новости с заданным идентификатором будут выбраны все имеющиеся в базе новости, поскольку выражение 1=1 всегда истинно).
Внедрение в строковые параметры
Предположим, серверное ПО, получив запрос на поиск данных в новостях параметром search_text, использует его в следующем SQL-запросе (здесь параметры экранируются кавычками):
… $search_text = $_REQUEST'search_text';$res = mysql_query ( «SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news WHERE news_caption = LIKE (' %$search_text %') ») ;…
Сделав запрос вида http://example.org/script.php?search_text=Test
мы получим выполнение следующего SQL-запроса:
SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news WHERE news_caption = LIKE (' %Test %')
Но, внедрив в параметр search_text символ кавычки (который используется в запросе) , мы можем кардинально изменить поведение SQL-запроса. Например, передав в качестве параметра search_text значение ‘) + and + (news_id_author=’1, мы вызовем к выполнению запрос:
SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news WHERE news_caption = LIKE (' %') AND (news_id_author='1%')
Использование UNION
Язык SQL позволяет объединять результаты нескольких запросов при помощи оператора UNION
. Это предоставляет злоумышленнику возможность получить несанкционированный доступ к данным.
Рассмотрим скрипт отображения новости (идентификатор новости, которую необходимо отобразить, передается в параметре id):
$res = mysql_query ( «SELECT id_news, header, body, author FROM news WHERE id_news = . $_REQUEST'id') ;
Если злоумышленник передаст в качестве параметра id конструкцию -1 UNION SELECT 1,username,password,1 FROM admin, это вызовет выполнение SQL-запроса
SELECT id_news, header, body, author FROM news WHERE id_news = -1 UNION SELECT 1,username,password,1 FROM admin
Так как новости с идентификатором -1 заведомо не существует, из таблицы news не будет выбрано ни одной записи, однако в результат попадут записи, несанкционированно отобранные из таблицы admin в результате инъекции SQL.
Экранирование хвоста запроса
Зачастую, SQL-запрос, подверженный инъекции, имеет структуру, усложняющую или препятствующую использованию union. Например скрипт
$res = mysql_query ( „SELECT author FROM news WHERE id= . $_REQUEST'id'. «AND author LIKE ('a %') “) ;
отображает имя автора новости по передаваемому идентификатору id только при условии, что имя начинается с буквы а, и инъекция с использованием UNION затруднительна.
В таких случаях, злоумышленниками используется метод экранирования части запроса при помощи символов комментария (/* или — в зависимости от типа СУБД).
В данном примере, злоумышленник может передать в скрипт параметр id со значением -1 UNION SELECT password FROM admin/*, выполнив таким образом запрос
SELECT author FROM news WHERE id=-1 UNION SELECT password FROM admin/* AND author LIKE ('a %')
в котором часть запроса (AND author LIKE (‘a %’) ) помечена как комментарий и не влияет на выполнение.
Расщепление SQL-запроса
Для разделения команд в языке SQL используется символ ; (точка с запятой) , внедряя этот символ в запрос, злоумышленник получает возможность выполнить несколько команд в одном запросе, однако не все диалекты SQL поддерживают такую возможность.
Например, если в параметры скрипта
$id = $_REQUEST'id';$res = mysql_query ( „SELECT * FROM news WHERE id_news = $id“) ;
злоумышленником передается конструкция, содержащая точку с запятой, например 12;INSERT INTO admin (username, password) VALUES (‘HaCkEr’, ‘foo’) ; то в одном запросе будут выполнены 2 команды
SELECT * FROM news WHERE id_news = 12;INSERT INTO admin (username, password) VALUES ('HaCkEr', 'foo') ;
и в таблицу admin будет несанкционированно добавлена запись HaCkEr.
Simple SQL Injection Example
The first example is very simple. It shows, how an attacker can use an SQL Injection vulnerability to go around application security and authenticate as the administrator.
The following script is pseudocode executed on a web server. It is a simple example of authenticating with a username and a password. The example database has a table named with the following columns: and .
These input fields are vulnerable to SQL Injection. An attacker could use SQL commands in the input in a way that would alter the SQL statement executed by the database server. For example, they could use a trick involving a single quote and set the field to:
As a result, the database server runs the following SQL query:
Because of the statement, the clause returns the first from the table no matter what the and are. The first user in a database is very often the administrator. In this way, the attacker not only bypasses authentication but also gains administrator privileges. They can also comment out the rest of the SQL statement to control the execution of the SQL query further:
Демонстрация атаки с использованием SQL-инъекции
Как показано на , SQL-инъекция использует введенные пользователем некорректные данные для получения разрешения на прямое взаимодействие с внутренней базой данных. Теперь проведем эксперимент на веб-сервере. В качестве площадки для проведения демонстрационного тестирования мы будем использовать веб-сайт вымышленного банка Altoro Mutual
(). Банк применяет производственную версию веб-сервера, которая имеет естественные уязвимости.
Для начала откройте в своем веб-браузере этот целевой сайт. Вы увидите страницу приветствия, показанную на . В правой части верхней строки находится ссылка Sign In, которая является нашей целью в этом примере. При нажатии на эту ссылку вы попадаете на страницу входа в систему.
Рисунок 2. Веб-сайта банка Altoro Mutual: начальная страница
На странице входа в систему () с помощью плагина Firebug браузера Mozilla Firefox можно увидеть, что веб-сайт выполняет валидацию полей с помощью JavaScript-функции .
Рисунок 3. Изучение исходного кода страницы с помощью плагина Firebug
При попытке ввести пробелы в любом из этих полей появляется сообщение о недопустимости использования пробелов для полей. Проработаем эту идею более детально. Попробуйте ввести в поле пароля произвольное имя пользователя и специальный символ, чтобы увидеть реакцию приложения. В данном случае (см. ), я ввел тестовое имя пользователя () и пароль, состоящий из символа (‘).
Рисунок 4. Сообщение об ошибке при вводе символа (‘) в поле Password
Вместо краткого сообщения о некорректном сочетании имени пользователя и пароля веб-сервер великодушно предоставляет подробную информацию о SQL-запросе. В частности, использование символа (‘) нарушило синтаксис запроса для параметра
к внутренней базе данных. Вы можете увидеть ошибку в конструкции , которая показана в следующем SQL-запросе:
query = SELECT User FROM Users WHERE Username = 'donald' AND Password = '''
Вы можете воспользоваться этим знанием, чтобы оценить уязвимость этого процесса. В языке SQL символы () служат признаком комментария. Признак комментария дает указание SQL-базе данных проигнорировать остальную часть текущей строки.
С целью успешного выполнения запроса без знания пароля можно попытаться использовать SQL-комментарий для отбрасывания фрагмента запроса — а именно, фрагмента с паролем. Из предыдущего примера мы знаем, что ввод символа (‘) нарушил запрос. С учетом этого измените запрос, вставив определенный SQL-код в поле Username. В данном случае я ввел в поле Username, а затем произвольную информацию в поле Password. В результате веб-сервер выдал сообщение об ошибке, показанное на .
Рисунок 5. Сообщение об ошибке после вставки SQL-кода в поле Username
Вместо того чтобы вызывать исключение (как я сделал на ),
теперь я изменяю запрос таким образом, чтобы он воспрепятствовал намерениям разработчика. Теперь внутри системы этот запрос будет выглядеть следующим образом:
query = SELECT User FROM Users WHERE Username = 'don'--' AND Password = '*'
Как показано в этом примере, простое добавление символов (‘—) в поле
Username сделало фрагмент запроса с паролем несущественным, тем не менее, этот запрос передается в базу данных как успешный. Опираясь на эту информацию, предположим, что в системе имеется административная учетная запись с именем
admin. Если вы выполните эту же операцию, но с именем пользователя admin’—, то сможете успешно войти в систему по этой учетной записи и увидеть имя пользователя admin, найти другие сведения об учетной записи admin и использовать учетную запись admin в качестве собственной учетной записи.
Итак, с помощью скромных познаний в области SQL вы успешно обошли внутреннюю проверку на ошибки незащищенного веб-сайта и аутентифицировали себя в качестве его администратора без знания соответствующего пароля. Если учетная запись admin не существует (или недоступна извне), мы можем получить доступ к первой учетной записи в таблице с помощью немного усложненного имени пользователя. В этом случае мы изменим запрос таким образом, чтобы предоставляемое условие всегда имело значение true. Этот результат достигается вводом имени пользователя вида
. Хотя эта уязвимость и не предоставит вам привилегий администратора, она обеспечит более глубокий доступ к веб-сайту и возможность поиска дальнейших уязвимостей.
Дополнительная защита сайта от взлома
Кроме того, чтобы обезопасить сам запрос и переменные, нужно сделать всё, чтобы хакер не смог найти уязвимости. Для этого устраните все возможные дыры, из-за которых может появиться утечка информации и вероятность несанкционированного доступа к правам администратора.
1. Запретите прямой доступ к служебным файлам
Все файлы на сайте можно открыть или скачать, если знать их адрес. Например, можно попытаться скачать файл header.php, чтобы найти там уязвимости (если скачивание будет удачным).
Поэтому лучше все подключаемые файлы переместить в отдельную директорию и запретить к ней прямой доступ. Например, можно создать папку includes и поместить туда:
- отдельные блоки сайта;
- библиотеки пользовательских функций;
- файл подключения к базе данных;
- обработчики форм и так далее.
Для этого создайте в этой папке файл .htaccess и добавьте туда такую строчку:
deny from all
Прямой доступ заблокируется, но подключать эти файлы через PHP все еще будет можно.
Порой нам всем бывает нужна помощь коллег, поэтому мы выкладываем фрагменты своего кода на форумы или Stackoverflow. Делать это рекомендуется только в очень крайних случаях и лишь тогда, когда вы убедитесь, что никто не сможет вычислить ваш сайт.
Ничто не должно намекать на то, какой тематики сайт, какой у него адрес, на каком он хостинге и так далее. Чем больше информации вы добровольно расскажете, тем выше риск быть взломанным.
3. Проверяйте копипасту
Всегда проверяйте код, который вы копируете, — в нем может быть какой-то незаметный эксплойт, который навредит вашему сайту или позволит автору кода получить к нему доступ. Старайтесь хотя бы читать то, что вы вставляете. Даже если там всего одна строчка, ее может быть достаточно, чтобы создать брешь в вашей защите.
В идеале лучше вручную переписывать код — так вы точно заметите все подозрительные команды.
4. Отключите вывод ошибок
Вывод ошибок — очень полезная функция на этапе разработки. Но если сайт уже выложен в сеть, то оповещение о багах лучше отключить, потому что злоумышленник может увидеть, какие у сайта проблемы, и попытаться использовать эти уязвимости в своих целях.
Отключить вывод можно в файле .htaccess, добавив туда следующие строки:
php_flag display_errors off
php_value error_reporting 0
Кроме того, уберите вывод ошибок, который вы прописали в самом коде.
5. Ограничьте права пользователя базы данных
Обычно для подключения к базе данных создают учетную запись со всеми правами, чтобы можно было использовать все преимущества SQL, но лучше их ограничить. Для этого зайдите в phpmyadmin, добавьте нового пользователя и выдайте ему только часть прав:
Пока ни одна галочка не отмечена — поставьте ее на пункт «Данные». Так можно будет оперировать существующими данными, но нельзя будет создать новую таблицу или удалить старую. Так вы защититесь в том числе и от атаки с помощью команды DROP, которая может удалить все статьи или комментарии с сайта.
Если же менеджеру вашего сайта все же регулярно нужны привилегии администрирования и изменения структуры, создайте отдельного пользователя, которого будете использовать в админке. Но риск того, что доступ к ней кто-то получит, все равно существует. Поэтому стоит ограничить максимальное количество подключений и запросов.
Также лучше поставить пароль на root, чтобы взломщик не смог подключиться через него.
6. Установите последнюю версию языка
Старые версии языков (любых) часто не только менее функциональны, но и обладают букетом уязвимостей. Критические дыры стали известны хакерам много лет назад, поэтому они используют их, чтобы взломать сайт.
Да, в обновлении тоже могут быть баги, но не такие опасные. Тем более о них никто не будет знать, если после релиза прошло немного времени.
7. Используйте сложный пароль
Банально, но простой пароль можно подобрать за несколько секунд. Особенно если он содержит персональные данные:
- дату рождения;
- кличку питомца;
- годовщину свадьбы;
- девичью фамилию матери и так далее.
Усугубляет положение то, что эту информацию мы часто выкладываем в свободный доступ.
Что такое SQL инъекция
Возьмем некий псевдо код
if( intval($_GET) ) mysql_query('select * from news where id="'.$_GET.'"';
Здесь есть проверка на то, что intval должен вернуть целое число, и если в id лежит не число, то условие не сработает и запрос не будет исполнен.
И это верно работает, если сделать такой запрос
http://sitename.ru/index.php?id=new
то условие не выполнится: intval(‘new’) вернет 0.
Кажется, что этого достаточно. Но вопрос, что произойдет при следующем запросе
http://sitename.ru/index.php?id=39new
Удивительно, но условие сработает. intval в этом плане, очень крутая функция. В отличии к примеру от JavaScript’овского parseInt, который вернет в таком случае NaN. Она спокойно переварит такие входные данные, и возьмет из них только число (заметим, что intval(‘new39’) вернет 0.)
Отсюда вытекает дыра в безопасности. Через нее, можно вставить любой SQL запрос. Вплоть до delete from news
Это очень распространенная ошибка в web’деве. Подвержены ей не только проекты начинающих программистов. Если Вы используете CMS с открытым исходным кодом, то такая вероятность тоже есть. Ведь злоумышленник легко может изучить исходники этой системы или воспользоваться опубликованной другими хакерами информацией и найти ее слабое место.
Говорят, таким атакам подвержены даже смартфоны и подобные девайсы. Большинство из них, в своей операционной системе данные хранят в SQLLite, а следовательно и сопутствующие неприятности связанные с SQL инъекциями возникнуть могут. Вероятно какой-нибудь samsung galaxy s iv этому не подвержен, но не стоит забывать, что старые устройства никуда не уходят, и подвержены риску.
Шифрование и хеширование
Шифрование — это обратимое преобразование текста в случайный набор символов.
Хеширование — это необратимое преобразование текста в случайный набор символов.
Разница между этими двумя действиями в том, можем ли мы из случайного набора символов получить исходную строку по какому-то известному алгоритму.
Приведу пример шифрования. У нас есть сообщение:
Зашифруем сообщение по следующему алгоритму: сдвинем каждую букву на 1 в алфавитном порядке, т.е. а превращается в б, г превращается в д, я превращается в а. Так будет выглядеть зашифрованный текст:
Зашифровали. Теперь для расшифровки нужно выполнить обратную операцию, сдвинуть все буквы на 1 символ назад. К слову, этот алгоритм шифрования называется шифр Цезаря (Википедия).
В отличие от шифрования, хеширование не имеет (вернее, не должно иметь) способа «расхешировать» строку обратно:
Как можно использовать SQL-инъекции?
Манипуляции с базой данных. В базе данных может быть что угодно. Если у нас есть возможность читать данные из бд, мы можем узнать много интересного:
- Логины/пароли
- Почтовые ящики
- Кредитки и личные данные
- Токены (например для восстановления пароля)
- Переписки
- Скрытый контент
- Информация о заказах
- Цифровые товары
- Коды запуска ядерных боеголовок
- (вставьте свой вариант)
А инъекция в INSERT, UPDATE или возможность конкатенации запросов позволит нам произвольно менять данные в БД. Сменить пароль, назначить нового администратора, отредактировать или удалить новость, изменить статус заказа или его сумму, изменить баланс на счете…
Запись в файл/чтение файлов. Если звезды сложились удачно и у нас есть достаточно прав, известен абсолютный путь к файлам и не фильтруются кавычки, мы можем с легкостью залить шелл. Читать файлы можно даже при ограниченных правах. Например, можно прочесть из конфига логин/пароль от соединения с базой данных, и если повезет, подключиться напрямую. Если очень повезет — узнать пароль от фтп (в конфигах jooml’ы иногда хранится).
SiXSS. Union based и Error based инъекции позволяют провести XSS атаку. Для этого, вместо вывода данных из таблицы, мы пишем необходимый нам js-код:
?id=-1′ union select 1,2,3,4,5,<script>alert(‘xss’);</script>,6,7,8 —+
1 | ?id=-1’union select1,2,3,4,5,<script>alert(‘xss’);</script>,6,7,8—+ |
По сути — это тоже самое, что и отраженная XSS, т.к. вся нагрузка уже в самом запросе. Но в отличиии от обычной отраженной, она способна обойти защиту большинства браузеров (которые хавают обычные xss на раз-два):
?id=-1′ union select 1,2,3,4,5,unhex(‘3c7363726970743e616c657274285c277873735c27293b3c2f7363726970743e’),6,7,8 —+
1 | ?id=-1′ union select 1,2,3,4,5,unhex(‘3c7363726970743e616c657274285c277873735c27293b3c2f7363726970743e’),6,7,8—+ |
Обход авторизации. Если уязвимость спряталась в форме авторизации, то вам не нужно будет знать пароль (а иногда и логин) для входа под учеткой другого пользователя/администратора.
SQLi + Denial of Service. С помощью одного нехитрого запроса можно повалить сайт к ебени-матери (например используя вложенный Benchmark).
Использовать параметры SQL для защиты
Для защиты веб-узла от внедрения SQL-кода можно использовать параметры SQL.
Параметры SQL — это значения, которые добавляются в SQL-запрос во время выполнения, контролируемым образом.
Пример ASP.NET бритвы
txtUserId = getRequestString(«UserId»);txtSQL = «SELECT *
FROM Users WHERE UserId = @0»;db.Execute(txtSQL,txtUserId);
Обратите внимание, что параметры представлены в инструкции SQL с помощью маркера @. Ядро SQL проверяет каждый параметр, чтобы убедиться, что он является правильным для своего столбца
и рассматриваются буквально, а не как часть SQL, который будет выполняться
Ядро SQL проверяет каждый параметр, чтобы убедиться, что он является правильным для своего столбца
и рассматриваются буквально, а не как часть SQL, который будет выполняться.
Другой пример
txtNam = getRequestString(«CustomerName»);txtAdd = getRequestString(«Address»);txtCit = getRequestString(«City»);
txtSQL = «INSERT INTO Customers (CustomerName,Address,City) Values(@0,@1,@2)»;db.Execute(txtSQL,txtNam,txtAdd,txtCit);
How to Prevent against SQL Injection Attacks
An organization can adopt the following policy to protect itself against SQL Injection attacks.
- User input should never be trusted — It must always be sanitized before it is used in dynamic SQL statements.
- Stored procedures – these can encapsulate the SQL statements and treat all input as parameters.
- Prepared statements –prepared statements to work by creating the SQL statement first then treating all submitted user data as parameters. This has no effect on the syntax of the SQL statement.
- Regular expressions –these can be used to detect potential harmful code and remove it before executing the SQL statements.
- Database connection user access rights –only necessary access rights should be given to accounts used to connect to the database. This can help reduce what the SQL statements can perform on the server.
- Error messages –these should not reveal sensitive information and where exactly an error occurred. Simple custom error messages such as “Sorry, we are experiencing technical errors. The technical team has been contacted. Please try again later” can be used instead of display the SQL statements that caused the error.