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