Категория OWASP: MASVS-CODE: Качество кода
Обзор
SQL-инъекция использует уязвимые приложения, вставляя код в операторы SQL для доступа к базовым базам данных за пределами их намеренно открытых интерфейсов. Атака может раскрыть частные данные, повредить содержимое базы данных и даже поставить под угрозу внутреннюю инфраструктуру.
SQL может быть уязвим для внедрения через запросы, которые создаются динамически путем объединения пользовательского ввода перед выполнением. Ориентированная на веб-сайты, мобильные устройства и любые приложения баз данных SQL, SQL-инъекция обычно входит в десятку веб-уязвимостей OWASP . Злоумышленники использовали эту технику в нескольких громких нарушениях.
В этом базовом примере неэкранированный ввод пользователя в поле номера заказа может быть вставлен в строку SQL и интерпретирован как следующий запрос:
SELECT * FROM users WHERE email = 'example@example.com' AND order_number = '251542'' LIMIT 1
 Такой код вызовет синтаксическую ошибку базы данных в веб-консоли, что указывает на то, что приложение может быть уязвимо для SQL-инъекции. Замена номера заказа на 'OR 1=1– означает, что аутентификация может быть достигнута, поскольку база данных оценивает оператор как True , поскольку единица всегда равна единице.
Аналогично, этот запрос возвращает все строки из таблицы:
SELECT * FROM purchases WHERE email='admin@app.com' OR 1=1;
Поставщики контента
 Поставщики контента предлагают структурированный механизм хранения, который можно ограничить приложением или экспортировать для совместного использования с другими приложениями. Разрешения должны устанавливаться по принципу наименьших привилегий; экспортированный ContentProvider может иметь одно указанное разрешение на чтение и запись.
Стоит отметить, что не все SQL-инъекции приводят к эксплуатации. Некоторые поставщики контента уже предоставляют читателям полный доступ к базе данных SQLite; возможность выполнять произвольные запросы дает мало преимуществ. К шаблонам, которые могут представлять проблему безопасности, относятся:
-  Несколько поставщиков контента совместно используют один файл базы данных SQLite.- В этом случае каждая таблица может быть предназначена для уникального поставщика контента. Успешная SQL-инъекция в одном поставщике контента предоставит доступ к любым другим таблицам.
 
-  Поставщик контента имеет несколько разрешений на контент в одной базе данных.- Внедрение SQL в одного поставщика контента, который предоставляет доступ с разными уровнями разрешений, может привести к локальному обходу настроек безопасности или конфиденциальности.
 
Влияние
SQL-инъекция может раскрыть конфиденциальные данные пользователя или приложения, обойти ограничения аутентификации и авторизации и сделать базы данных уязвимыми для повреждения или удаления. Последствия могут включать в себя опасные и долгосрочные последствия для пользователей, чьи личные данные были раскрыты. Поставщики приложений и услуг рискуют потерять интеллектуальную собственность или доверие пользователей.
Смягчения
Сменные параметры
 С использованием ? в качестве заменяемого параметра в предложениях выбора и отдельного массива аргументов выбора привязывается пользовательский ввод непосредственно к запросу, а не интерпретируется как часть оператора SQL. 
Котлин
// Constructs a selection clause with a replaceable parameter.
val selectionClause = "var = ?"
// Sets up an array of arguments.
val selectionArgs: Array<String> = arrayOf("")
// Adds values to the selection arguments array.
selectionArgs[0] = userInput
Ява
// Constructs a selection clause with a replaceable parameter.
String selectionClause =  "var = ?";
// Sets up an array of arguments.
String[] selectionArgs = {""};
// Adds values to the selection arguments array.
selectionArgs[0] = userInput;
Пользовательский ввод привязывается непосредственно к запросу, а не обрабатывается как SQL, что предотвращает внедрение кода.
Вот более подробный пример, показывающий запрос приложения для покупок на получение сведений о покупке с заменяемыми параметрами:
Котлин
fun validateOrderDetails(email: String, orderNumber: String): Boolean {
    val cursor = db.rawQuery(
        "select * from purchases where EMAIL = ? and ORDER_NUMBER = ?",
        arrayOf(email, orderNumber)
    )
    val bool = cursor?.moveToFirst() ?: false
    cursor?.close()
    return bool
}
Ява
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;
}
Используйте объекты ReadedStatement.
 Интерфейс PreparedStatement предварительно компилирует операторы SQL как объект, который затем можно эффективно выполнять несколько раз. ReadedStatement использует ? в качестве заполнителя для параметров, что сделает следующую скомпилированную попытку внедрения неэффективной:
WHERE id=295094 OR 1=1;
 В этом случае оператор 295094 OR 1=1 считывается как значение идентификатора и, скорее всего, не дает результатов, тогда как необработанный запрос интерпретирует оператор OR 1=1 как еще одну часть предложения WHERE . В примере ниже показан параметризованный запрос: 
Котлин
val pstmt: PreparedStatement = con.prepareStatement(
        "UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?").apply {
    setString(1, "Barista")
    setInt(2, 295094)
}
Ява
PreparedStatement pstmt = con.prepareStatement(
                                "UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?");
pstmt.setString(1, "Barista")   
pstmt.setInt(2, 295094)
Используйте методы запроса
 В этом более длинном примере selection и selectionArgs метода query() объединяются, образуя предложение WHERE . Поскольку аргументы предоставляются отдельно, они экранируются перед их комбинацией, что предотвращает внедрение SQL. 
Котлин
val db: SQLiteDatabase = dbHelper.getReadableDatabase()
// Defines a projection that specifies which columns from the database
// should be selected.
val projection = arrayOf(
    BaseColumns._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_SUBTITLE
)
// Filters results WHERE "title" = 'My Title'.
val selection: String = FeedEntry.COLUMN_NAME_TITLE.toString() + " = ?"
val selectionArgs = arrayOf("My Title")
// Specifies how to sort the results in the returned Cursor object.
val sortOrder: String = FeedEntry.COLUMN_NAME_SUBTITLE.toString() + " DESC"
val cursor = db.query(
    FeedEntry.TABLE_NAME,  // The table to query
    projection,            // The array of columns to return
                           //   (pass null to get all)
    selection,             // The columns for the WHERE clause
    selectionArgs,         // The values for the WHERE clause
    null,                  // Don't group the rows
    null,                  // Don't filter by row groups
    sortOrder              // The sort order
).use {
    // Perform operations on the query result here.
    it.moveToFirst()
}
Ява
SQLiteDatabase db = dbHelper.getReadableDatabase();
// Defines a projection that specifies which columns from the database
// should be selected.
String[] projection = {
    BaseColumns._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filters results WHERE "title" = 'My Title'.
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// Specifies how to sort the results in the returned Cursor object.
String sortOrder =
    FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
    FeedEntry.TABLE_NAME,   // The table to query
    projection,             // The array of columns to return (pass null to get all)
    selection,              // The columns for the WHERE clause
    selectionArgs,          // The values for the WHERE clause
    null,                   // don't group the rows
    null,                   // don't filter by row groups
    sortOrder               // The sort order
    );
Используйте правильно настроенный SQLiteQueryBuilder.
 Разработчики могут дополнительно защитить приложения с помощью SQLiteQueryBuilder — класса, который помогает создавать запросы для отправки в объекты SQLiteDatabase . Рекомендуемые конфигурации включают в себя:
-  Режим setStrict()для проверки запроса.
-  setStrictColumns()для проверки того, что столбцы включены в список разрешенных в setProjectionMap.
-  setStrictGrammar()для ограничения подзапросов.
Использовать библиотеку комнат
 Пакет android.database.sqlite предоставляет API, необходимые для использования баз данных на Android. Однако этот подход требует написания низкоуровневого кода и не требует проверки необработанных SQL-запросов во время компиляции. По мере изменения графиков данных затронутые SQL-запросы необходимо обновлять вручную — это трудоемкий и подверженный ошибкам процесс.
Высокоуровневое решение — использовать библиотеку Room Persistence в качестве уровня абстракции для баз данных SQLite. К особенностям номера относятся:
- Класс базы данных, который служит основной точкой доступа для подключения к сохраненным данным приложения.
- Сущности данных, представляющие таблицы базы данных.
- Объекты доступа к данным (DAO), которые предоставляют методы, которые приложение может использовать для запроса, обновления, вставки и удаления данных.
К преимуществам номера относятся:
- Проверка SQL-запросов во время компиляции.
- Сокращение количества шаблонного кода, подверженного ошибкам.
- Оптимизированная миграция базы данных.
Лучшие практики
SQL-инъекция — это мощная атака, против которой может быть сложно обеспечить полную устойчивость, особенно в случае больших и сложных приложений. Должны быть приняты дополнительные меры безопасности, чтобы ограничить серьезность потенциальных недостатков в интерфейсах данных, в том числе:
-  Надежные, односторонние и соленые хеши для шифрования паролей:- 256-битный AES для коммерческих приложений.
- Размеры открытого ключа 224 или 256 бит для криптографии на основе эллиптических кривых.
 
- Ограничение разрешений.
- Точное структурирование форматов данных и проверка соответствия данных ожидаемому формату.
- Избегание хранения личных или конфиденциальных пользовательских данных, где это возможно (например, реализация логики приложения путем хеширования, а не передачи или хранения данных).
- Минимизация API и сторонних приложений, которые получают доступ к конфиденциальным данным.
