SQL-инъекции#

SQL-инъекция (sqli) — это уязвимость безопасности, которая позволяет злоумышленнику вмешиваться в запросы к базе данных приложения, внедряя злонамеренный SQL-код через входные данные, меняя исходный SQL-запрос.
Проблема возникает, когда приложение «слепо» объединяет пользовательские данные с кодом SQL-запроса, не проверяя и не обрабатывая их.

Пример инъекции#

Исходный запрос:

SELECT * FROM users WHERE username = '[user_input_1]' AND password = '[user_input_2]'

Злоумышленник вводит в поле username специальную строку: OR 1=1 --. Поле password он может оставить пустым или заполнить чем угодно.

Злонамеренный ввод:
user_input_1: admin“ or 1=1 –
user_input_2: [любой]
Результат:

SELECT * FROM users WHERE username = 'admin' or 1=1 --' AND password = 'text'

Чем опасны SQL-инъекции?#

Злоумышленник может:

  1. Обойти аутентификацию.

  2. Получить несанкционированный доступ к данным: читать, копировать, скачивать всю базу данных.

  3. Изменять и удалять данные: удалять, изменять, добавлять записи.

  4. Выполнять команды на сервере БД: в некоторых случаях атака может привести к полному захвату сервера.

Методы защиты#

  1. Подготовленные выражения (Prepared Statement) - наиболее эффективный метод защиты. Это механизм выполнения SQL-запросов, при котором шаблон SQL-запроса и данные передаются отдельно. Это полностью исключает возможность SQL-инъекций. Предпочтительнее использовать именно этот метод защиты.

val svName = "admin"
val svPass = "qwerty1234"
SQL(s"SELECT * FROM users WHERE username = {sUserName} AND password = {sPassHash}")
    .on("sUserName" -> svName, "sPassHash" -> svPass)
  1. Использование функций экранирования. Если использовать подготовленные выражения не представляется возможным, необходимо воспользоваться функциями экранирования, они «обезвреживают» специальные символы:

  • escapeString(input: String, quoteValue: Boolean): String - для экранирования строки. Экранирует символы одинарной кавычки (“) в строке. Если quoteValue = true, заключит значение в одинарные кавычки.

    val sUserInput = SqlEscape.escapeString("' union select id from Btk_User --", false)
    SQL(s"SELECT id FROM Bs_Goods WHERE sName = '$sUserInput' ")
  • escapeNumber(input: String): String - для экранирования числа. Удаляет спецсимволы („;“, „–“, „/“, „/“, пробелы).

    val sIdGood = SqlEscape.escapeNumber("1 OR 1=1 --")
    SQL(s"""SELECT id FROM Bs_Goods WHERE id = $sIdGood""")
  • escapeName(input: String): String - для экранирования имен сущностей, например имена таблиц и колонок. Экранирует одинарные кавычки („), удаляет спецсимволы ';', '--', '/*', '*/', '(', ')'.

  • escapeDate(input: String): String - для экранирования даты. Экранирует символы одинарной кавычки („).

  • escapeJson(input: String): String - для экранирования json. Экранирует символы одинарной кавычки („).

  • escapeBoolean(input: String): String - для экранирования булевого значения. Если строка не равна «true», вернет «false».