SQL-инъекция в ContentProvider
Описание
Что такое Content Provider?
Как следует из названия, Content Provider отвечает за предоставление данных и управление доступом к ним. Эти данные могут храниться в файловой системе, в базе данных SQLite или в любом другом постоянном хранилище, доступном из приложения. С помощью Content Provider любое приложение с соответствующими разрешениями может читать или даже изменять данные, используя стандартный набор API, который позволяет выполнять необходимые транзакции.
Что такое SQL-инъекция?
SQL-инъекция — это уязвимость, основанная на внедрении в SQL-запрос произвольного SQL-кода. Как правило, это позволяет злоумышленнику просматривать данные, которые он обычно не может получить. Атака может раскрыть личные данные, повредить содержимое базы данных и даже скомпрометировать внутреннюю инфраструктуру. Обычно SQL-инъекция реализуется через запросы, которые создаются динамически — путем объединения пользовательского ввода перед выполнением SQL-запроса.
В этом базовом примере неэкранированный ввод пользователя в поле номера заказа может быть вставлен в строку SQL и интерпретирован как следующий запрос.
Замену номера заказа наOR 1=1 база данных оценит как условие True, поскольку единица всегда равна единице.
Точно так же следующий запрос возвращает все строки из таблицы.
Стоит отметить, что не все SQL-инъекции приводят к потенциальным проблемам. Некоторые Content Provider уже предоставляют читателям полный доступ к базе данных SQLite; возможность выполнять произвольные запросы дает небольшое преимущество. А вот реализации, которые могут представлять проблему безопасности:
-
Несколько Content Provider совместно используют один файл базы данных SQLite. Успешная SQL-инъекция в одном контент-провайдере предоставит доступ к любым другим таблицам.
-
Content Provider имеет несколько разрешений для содержимого в одной и той же базе данных. Внедрение SQL в одного Content Provider, который предоставляет доступ с разными уровнями разрешений, может привести к локальному обходу настроек безопасности или конфиденциальности.
Влияние
SQL-инъекция может раскрыть конфиденциальные данные пользователя или приложения, обойти ограничения аутентификации и авторизации и сделать базы данных уязвимыми для повреждения или удаления. Данные действия могут включать опасные и долговременные последствия для пользователей, чьи личные данные были раскрыты. Поставщики приложений и услуг рискуют потерять интеллектуальную собственность или доверие пользователей.
Дополнительная информация
Projection: позволяет выбирать столбцы таблицы, к которой мы обращаемся.
Selection: позволяет использовать условие для выбора строк таблицы, к которой мы обращаемся.

С помощью этого запроса мы хотим получить поля: имя, размер и формат (столбцы — projection) всех записей (строки — selection), размер которых больше 20.
Рекомендации
-
Используйте заменяемые параметры. Используя
?как заменяемый параметр в selection clauses и отдельный массив selection, мы связываем пользовательский ввод непосредственно с запросом и он не интерпретируется как часть инструкции SQL.// Формирование selection с заменяемыми параметрами. String selectionClause = "var = ?"; // Создание массива аргументов. String[] selectionArgs = {""}; // Добавляем значения в массив с selection arguments. selectionArgs[0] = userInput;Пользовательский ввод привязывается непосредственно к запросу, а не обрабатывается как SQL, что предотвращает внедрение кода.
Вот более сложный пример, показывающий запрос приложения для покупок с целью получения сведений о покупке с заменяемыми параметрами.
public boolean validateOrderDetails(String email, String orderNumber) { boolean bool = false; Cursor cursor = db.rawQuery( "select * from purchases where EMAIL = ? and ORDER_NUMBER = ?", new String[]{email, orderNumber}); if (cursor != null) { if (cursor.moveToFirst()) { bool = true; } cursor.close(); } return bool; } -
Используйте объекты PreparedStatement. Интерфейс PreparedStatement предварительно компилирует операторы SQL как объект, который затем может эффективно выполняться несколько раз. PreparedStatement использует
?в качестве заполнителя для параметров, которые сделают следующую скомпилированную попытку внедрения неэффективной.В этом случае выражение
295094 OR 1=1считывается как значение дляid, тогда как необработанный запрос будет интерпретировать операторOR 1=1как другую часть условияWHERE. В приведенном ниже примере показан параметризованный запрос. -
Используйте query методы. В этом более длинном примере аргументы
selectionиselectionArgsметодаquery()объединены в предложениеWHERE. Поскольку аргументы предоставляются отдельно, они экранируются перед их комбинацией, что предотвращает внедрение SQL.SQLiteDatabase db = dbHelper.getReadableDatabase(); // Формируем projection, который определяет какие столбцы мы выбираем из базы данных. String[] projection = { BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE }; // Фильтруем результат WHERE "title" = 'My Title'. String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?"; String[] selectionArgs = { "My Title" }; // Определяем, как сортировать результат в Cursor object. String sortOrder = FeedEntry.COLUMN_NAME_SUBTITLE + " DESC"; Cursor cursor = db.query( FeedEntry.TABLE_NAME, // Таблица projection, // Массив столбцов, которые не нужны (передаём null чтобы получить все столбцы) selection, // Столбцы для WHERE условия selectionArgs, // Значения для WHERE условия null, // Строки не группировать null, // Не фильтровать по группам строк sortOrder // Порядок сортировки ); -
Используйте правильно сконфигурированный SQLiteQueryBuilder. Разработчики могут дополнительно защитить приложения с помощью SQLiteQueryBuilder — класса, который помогает создавать запросы для отправки в SQLiteDatabase. Рекомендуемые конфигурации включают:
- Режим
setStrict()для проверки запроса. setStrictColumns()для проверки того, что столбцы включены в список разрешенных в setProjectionMap.setStrictGrammar()для ограничения подзапросов.
- Режим
-
Используйте Room библиотеку. Пакет android.database.sqlite предоставляет API, необходимые для использования баз данных на Android. Однако этот подход требует написания низкоуровневого кода и не требует проверки необработанных SQL-запросов во время компиляции. По мере изменения данных затронутые SQL-запросы необходимо обновлять вручную — процесс, требующий много времени и подверженный ошибкам.
Решением является использование библиотеки Room Persistence Library в качестве уровня абстракции для баз данных SQLite. Room library включают:
- Класс базы данных, который служит основной точкой доступа для подключения к данным приложения.
- Data entities, представляющие таблицы базы данных.
- Data access objects (DAOs), предоставляющие методы, которые приложение может использовать для запроса, обновления, вставки и удаления данных.
К преимуществам Room library относятся:
- Проверка SQL-запросов во время компиляции.
- Сокращение подверженного ошибкам шаблонного кода.
- Оптимизированная миграция базы данных.
-
Защищайте Content Provider, если необходимо, чтобы провайдер был экспортируемым. Ограничивайте доступ к Content Provider с помощью атрибутов
android:permission,android:readPermissionилиandroid:writePermission, или предоставляйте доступ только к конкретным uri с помощью<grant-uri-permission …/>, либо<path-permission.….А также, если у Content Provider есть атрибуты
android:readPermissionиandroid:writePermissionодновременно, то не рекомендуется использовать одно и то же разрешение, чтобы, например, приложения, которым необходимо только чтение, не получали дополнительную возможность на запись, и наоборот.