Анализ защищенности приложений для операционной системы Android

Возможность запуска вредоносного приложения в контексте доверенного приложении. Использование программного варианта KeyStore. Применение локальной базы данных для хранения конфиденциальной информации. Использования Deep Links для передачи данных.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 14.07.2020
Размер файла 370,7 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

2.2.3 Использование метода openFile()

Уровень критичности: информационный

Использование метода openFile() с пользовательским вводом as-is позволяет злоумышленнику получить доступ к внутренним файлам приложения через проведение атаки PathTraversal. [65]

Однако, следует сразу отметить, что компания Google позаботилась об этом и начиная с 2018 года, загружаемые в Play Market приложения которые имеют в своих поставщиках данных метод openFile() без валидации пользовательского не пройдут проверку на безопасность и будут отклонены. [42]

2.2.4 SQL-инъекции

Уровень критичности: высокий

Реализация данной атаки возможно при наличии определенных сценариев, например:

1. Поставщик данных должен быть доступен для сторонних приложений (в файле “AndroidManifest” приложение используется атрибут android: exported= “true”, вредоносное приложение имеет необходимые права). Так же, для построения SQL - запроса должен использоваться пользовательский ввод без санитизации.

2. Пользовательский ввод, получаемый через интерфейс приложения, используется для построения SQL - запроса без санитизации.

Рассмотрим следующий пример построения запроса к БД:

val getData: Cursor

get(){

val db = this.writableDatabase

val sqlQuery = "SELECT * FROM $TABLE_NAME WHERE $COL_ID = '$userInput'"

return db.rawQuery(sqlQuery, null)

}

Пример 11: SQL-инъекция.

Запрос к БД динамически генерируется на основании пользовательского ввода, который передается в запрос с помощью интерполяции строки из переменной userInput.

В конкретном случае передается в качестве значения для переменной userInput следующая полезная нагрузка: 1' or '1' = '1.

После выполнения запроса произойдет выдача всех записей из БД т. к. условие '1' = '1' всегда возвращает true. [52]

Процесс обнаружения недостатка

Для поиска этого недостатка следует проанализировать имеющихся в приложении поставщиков данных в поисках запросов к БД, которые динамически генерируются на основании пользовательского ввода.

Анализ критичности

Данный недостаток является весьма критичным, т. к. в случае, если в локальной БД приложения хранится конфиденциальная информация, например, логин или пароль пользователя, информация о банковских картах то эксплуатируя данный недостаток вредоносное приложение или злоумышленник могут получить доступ к этой информации.

Рекомендации по устранению

Для избежание подобных угроз рекомендуется использовать параметризированные запросы к БД или использовать готовые библиотеки, которые по умолчанию используют такие запросы, например библиотека Room.

2.3 Хранение данных

В ОС Android встречается два варианта хранилища данных.

Внутреннее хранилище (англ. internal storage) и внешнее хранилище (англ. external storage).

Такое наименование волочит корни из незапамятных времен, когда большинство мобильных устройств имели внутреннюю, «не извлекаемую» память и/или внешнюю - например SD накопитель.

В нынешнее время многие производители разбивают встроенную память устройства на разделы - внутренний и внешний. Важно понимать, что в современных реалиях “внешнее хранилище” уже не указывает на то, что его можно свободно изъять.

Основные понятия, на которые следует обратить внимание при обсуждении видов хранилища мобильных устройств с ОС Android:

1. Доступность. Внутреннее хранилище всегда доступно, внешнее - нет. Различные устройства позволяют подключить внешнее хранилище, например, к компьютеру через USB.

Что автоматически отключит внешнее хранилище от мобильного устройства.

2. Контроль доступа. По умолчанию, данные, которые хранятся во внутреннем хранилище доступны только для приложения, которое их там создало. Данные, хранимые во внешнем хранилище по умолчанию доступны любому приложению.

3. Хранение файлов после удаления приложения. Данные, хранимые во внутреннем хранилище, удаляются вместе с приложением, в то время как файлы, сохраненные во внешнем хранилище там и остаются, даже после удаления приложения.

Исключение составляют файлы, сохраненные в папку, полученную через использование метода getExternalFilesDir. [17]

Исходя из вышесказанного, хранение данных во внутреннем хранилище устройства является более предпочтительным вариантом, с точки зрения безопасности мобильного приложения.

Рассмотрим способы, которые используются мобильным приложением для хранения данных:

1. SharedPreferences - способ хранения данных, используемый приложениями для хранения какой-либо информации в формате ключ:значение. Shared Preferences является XML-файлом и уникальный для каждого приложения. [36]

2. Локальные базы данных. Каждое приложение может создать свою базу данных для хранения какой-либо информации. Обычно, для этого используется SQLite DataBase. Встречаются и другие реализации баз данных, например Realm Database. [37]

3. Система Android Keystore. Данная система была предоставлена с выходом Android 4.3 (API 18) и позволяет разработчикам создавать и хранить криптографические ключи в специальном контейнере, тем самым усложнив злоумышленнику или вредоносному приложению доступ к ним.

При использовании Android Keystore ключевой материал хранятся, в так называемой безопасной среде выполнения (англ. Trusted Execution Environment).

Android Keystore предоставляет API для проведения криптографических операций внутри безопасной среды выполнения.

На данный момент ОС Android поддерживает 7 разных вариантов Keysotore. Например “Android Keystore” использует специализированный чип для безопасного хранения ключей. В то время, как “Bouncy Castle Keystore (BKS)” - программный вариант, который хранит зашифрованный файл внутри системы.

В Android 9 (API 28), появился так называемый “StrongBox Keymaster”, который в отличии от безопасной среды выполнения использует отдельный процессор, ГПСЧ, безопасное хранилище (безопасная среда выполнения является частью главного процессора со своей ОС Trusty). [19]

Далее в главе мы рассмотрим различные недостатки присущие каждому из описанных вариантов хранения данных, а также попытаемся предложить оптимальные способы исправления недостатков.

2.3.1 Android Keystore

Рассмотрим некоторые варианты реализации Android Keystore, вектора атак, а также варианты ошибок, которые могут быть причиной успеха той или иной атаки.

Следует еще раз упомянуть основные задачи KeyStore.

1. Хранение ключевого материала. Способ хранения производится в зависимости от выбранного варианта механизм KeyStore. Как уже отмечалось, это может быть реализовано с использованием специально выделенного, аппаратного хранилища, так и с использованием встроенного хранилища. Следует добавить, что различные типы механизма добавлялись постепенно в различные версии API, соответственно, при реализации того или иного механизма, необходимо позаботиться об обратной совместимости. [33]

Тип KeyStore

Версия Android API

AndroidCAStore

14+

AndroidKeyStore

18+

BCPKCS12

1-8

BKS

1+

BouncyCastle

1+

PKCS12

1+

PKCS12-DEF

1-8

StongBox Keymaster

28+

Пример 12: Таблица с вариантами KeyStore.

Таблица с вариантами KeyStore и версиями их поддерживающими. [19]

Отметим, что существуют и другие KeyStore механизмы, специфические для отдельных производителей.

Например, компания Samsung поддерживает TIMA KeyStore. [56]

Предотвращение извлечения ключевого материала.

Вторым из свойств, которым обладают KeyStore - предотвращение извлечения ключевого материала, для чего используются два механизма:

1. Ключевой материал не фигурирует в контексте приложения.

Когда приложению необходимо произвести криптографические операции используя ключ из Android KeyStore, весь необходимый материал обрабатывается в контексте системного процесса. Соответственно, если приложение было скомпрометировано, злоумышленник способен воспользоваться ключом, но не сможет извлечь ключевой материал, для дальнейшего использования.

2. Ключевой материал может быть привязан к специализированному, аппаратному хранилищу. Таким образом, если данная функциональность используется, ключевой материал никогда не покидает рамки выделенного аппаратного хранилища. Соответственно, даже если произойдет компрометация ОС Android, то злоумышленник не сможет извлечь ключевой материал для дальнейшего переиспользования. [33]

2.3.2 Использование программного варианта KeyStore

Уровень критичности: информационный

Как уже упоминалось ранее, при использовании BKS ключи хранятся во внутреннем хранилище, и если устройство запущено с правами суперпользователя, то пользователь может получить доступ к файлу, после чего попытаться провести атаку перебора с целью подобрать пароль.

Тем не менее, для реализации данной атаки злоумышленнику нужно получить доступ ко внутреннему хранилищу атакуемого приложения, что возможно только в случаях если:

1. Атакуемое мобильное устройство запущено с правами суперпользователя.

2. На атакуемом мобильном устройстве оказалось вредоносное приложение с наивысшими привилегиями.

3. Появился недостаток нулевого дня, который позволит приложению/злоумышленнику получить права суперпользователя на устройстве.

4. Злоумышленник имеет полный физический доступ к атакуемому мобильному устройству.

Таким образом, если будет выполнен один из указанных пунктов, злоумышленник сможет извлечь ключи с мобильного устройства.

Соответственно, даже если приложение использует программный вариант Keystore, вероятность успешного извлечения файла ключей очень низка.

Тем не менее, более предпочтительным вариантом является использование вариантов KeyStore с использованием аппаратного хранилища.

2.3.3 Ключ не инвалидируется при добавлении нового отпечатка пальца

Уровень критичности: информационный

Злоумышленник с физическим доступом к устройству может добавить свой отпечаток пальца и в дальнейшем воспользоваться ключом, который был закрыт биометрией. [33]

Данный сценарий потенциально возможен в случае, если при реализации приложения был написан похожий код:

private fun generateKey() {

val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")

val builder = KeyGenParameterSpec.Builder(MASTER_KEY_ALIAS,

KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT

)

.setBlockModes(KeyProperties.BLOCK_MODE_GCM)

.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)

.setKeySize(KEY_SIZE)

.setInvalidatedByBiometricEnrollment(false)

}

Пример 13: Отсутсвие инвалидации ключа при добавлении нового отпечатка пальца.

Мы видим, что создается новый инстанс KeyGenerator со значением “AndroiKeyStore”.

Далее инициализируются различные требования, среди которых указана следующая директива setInvalidatedByBiometricEnrollment „ѓ аргументом false.

Что явно указывает на то, что при смене отпечатка пальца, ключ не инвалидируется. Также мы видим, что не указана директива setUserAuthenticationRequired со значением true (значение по умолчанию false), которая необходима для того, чтобы явно указать, что использование ключа возможно только при разблокировке экрана.

Дополнительно стоит отметить отсутствие директивы setUserAuthenticationValidityDurationSeconds(-1), смысл которой в том, что каждый раз, для получения доступа к ключу пользователю необходимо аутентифицироваться. [20]

Процесс обнаружения недостатка

При анализе исходного кода мобильного приложения можно воспользоваться поиском используя в качестве ключевых слов директивы, описанные выше. Следует убедиться, что директивы присутствуют и используют безопасные значения в качестве аргументов.

Анализ критичности

Данный недостаток не является уязвимостью сам по себе, однако несет за собой потенциально ослабление защищенности приложения.

Рекомендации по устранению

При реализации KeyStore рекомендуется использовать повышающие безопасность директивы, а точнее [33]:

1. setUserAuthenticationValidityDurationSeconds(-1)

2. setUserAuthenticationRequired(true)

3. setInvalidatedByBiometricEnrollment (true)

Также рекомендуется установить лимит на время доступности ключа воспользовавшись следующей директивой

1. setUserAuthenticationValidityDurationSeconds(120) - где в качестве аргумента количество секунд когда доступен для использования ключ.

В случае, если используется API 28, то стоит добавить поддержку аппаратного хранилища KeyStore воспользовавшись следующим методом -setIsStrongBoxBacked(true).

2.3.4 Использование атрибутов MODE_WORLD_READABLE и MODE_WORLD_WRITABLE для создания файлов

Уровень критичности: информационный

В случае, если рассматриваются внутреннее хранилище приложений, которые старше версии API 24, следует обратить внимание на использование при создании файла устаревших директив, если точнее, то MODE_WORLD_READABLE и MODE_WORLD_WRITABLE как в примере ниже:

val outputStream: FileOutputStream =

openFileOutput(FILENAME, Context.MODE_WORLD_READABLE)

outputStream.write(string.toByteArray())

outputStream.close()}

Пример 14: MODE_WORLD_READABLE.

В случае использования данного атрибута при создании файла, любое стороннее приложение сможет его прочитать. [21]

Отметим, что начиная с Android версии API 17 данный атрибут считался устаревшим, однако только после версии API 23 приложения с данным атрибутом перестали компилироваться.

Процесс обнаружения недостатка

Для обнаружения недостатка достаточно проанализировать исходный код приложения на наличие упомянутого атрибута.

Анализ критичности

Данный недостаток позволит любому приложению читать файлы, которые были созданы с использованием атрибута MODE_WORLD_READABLE и MODE_WORLD_WRITABLE, таким образом, в случае если на устройстве пользователя находится вредоносное приложение то оно сможет получить доступ к информации хранящийся в файлах с таким атрибутом.

Рекомендации по устранению

Рекомендуется всегда использоваться атрибут MODE_PRIVATE при создании какого-либо файла в приложении.

2.3.5 Использование SharedPreferences для хранения конфиденциальной информации

Уровень критичности: информационный

По поводу использования SharedPreferences в качестве хранилища конфиденциальной информации, например ФИО пользователя, логина или пароля можно выделить два аспекта:

С одной стороны, в случае реализации мобильного приложения и использовании внутреннего хранилища для хранения данных, будь то SharedPreferences или локальная БД, вероятность их извлечения достаточно низка. Такое возможно, например, в случае если целевое устройство запущено с правами суперпользователя, или если какой-либо из компонентов приложения, например, поставщик данных или сервис настроен некорректным образом и может предоставить доступ к конфиденциальным файлам. вредоносный приложение база данный

Таким образом, хранение конфиденциальной информации в SharedPreferences может рассматриваться как допустимая мера.

С другой стороны, если злоумышленник каким-то образом смог получить доступ к SharedPreferences приложения (уязвимости нулевого дня, физический доступ с возможностью повышения привилегий и т. д.), то данные будут находиться в открытом состоянии, т. к. нет никакой дополнительной защиты их конфиденциальности.

В рамках данной работы хранения конфиденциальных данных в SharedPreferences не будет рассматриваться как критичный недостаток.

Процесс обнаружения недостатка

Для того, чтобы обнаружить используется ли sharedPreferences в коде мобильного приложения, достаточно воспользоваться поиском, по ключевым словам, и в качестве искомого значения указать следующую строку - “getSharedPreferences”.

Таким образом, можно обнаружить нечто похожее на следующий пример:

val name = userName.text.toString()

val phoneNum = userPhone.text.toString()

val emailAddr = userEmail.text.toString()

val sharedPreferences = getSharedPreferences("DATA", Context.MODE_PRIVATE)

val editor = sharedPreferences.edit()

editor.putString("Name",name)

editor.putString("Phone number", phoneNum)

editor.putString("email", emailAddr)

editor.apply()

Пример 15: SharedPreferences.

Далее, следует обратить внимания на следующее:

1. В случае с SharedPreferences, как и с хранением файлов нужно убедиться, что используется MODE_PRIVATE.

2. Отметить для себя, какие именно данные хранятся, их критичность для пользователя и т. д. (в случае с примером это ФИО, номер телефона и электронный адрес).

3. Отметить, используется ли какие-либо механизмы защиты этих данных, например, шифрование или обфускация.

В примере выше, мы видим, что переменной SharedPreferences присваивается значение штатного экземпляра SharedPreferences. Далее переменные, в которых хранятся строковые значения, содержащие конфиденциальную информацию, используются для создания файла SharedPreferences. Важным здесь является то, что данная информация хранится без каких-либо дополнительных средств защиты.

Соответственно, если злоумышленник каким-то образом смог получить доступ к этому файлу, вся информация, находящаяся в нем, будет ему доступна.

Рекомендации по устранению

В данном контексте, справедливее использовать терминологию «рекомендации по усилению защищенности».

Если при разработке мобильного приложения появилась необходимость хранить данные пользователя во внутреннем хранилище устройства, в частности в файле SharedPreferences, то рекомендуется рассмотреть следующие варианты:

1. Если приложение создается для использования на Android 6.0 и выше, то рекомендуется использовать библиотеку “Jetpack Security Library” для шифрования SharedPreferences.

2. Если речь идет о написании приложения для более старых версий ОС Android, то можно рассмотреть использование других библиотек, например, Armadillo.

Отметим, что рекомендация той или иной библиотеки строится на доступной информации в сети интернет, а также рекомендуемыми практиками при реализации приложения для ОС Android. Содержание и код упомянутых библиотек автором данной работы протестированы небыли.

Использование локальной БД для хранения конфиденциальной информации

Уровень критичности: информационный

Что касается использования локальной БД для хранения конфиденциальной информации пользователя, то по большому счету происходит таже дискуссия, которая была описана в части посвященной SharedPreferences.

Добавим, что наиболее критичный недостаток связанный с локальным БД (SQL -инъекции) мы рассмотрели в главе про безопасность Content Provider.

Когда речь идет об локальной БД в ОС Android по умолчанию рассматривается SQLite т. к. это встроенный в ОС инструментарий для управления базой данных.

SQLite - является проектом с открытым исходным кодом, который поддерживает стандартные возможности обычной БД SQL.

С помощью SQLite можно создавать для своего приложения независимые реляционные базы данных. ОС Android обычно хранит базы данных в каталоге /data/data/<имя_вашего_пакета>/databases, однако на различных устройствах путь может отличаться. [37]

Все информация по умолчанию закрыта и доступ к ней есть только у приложения, которая ее создала.

Процесс обнаружения недостатка

В случае анализа исходного кода мобильного приложения, на наличие использования шифрования данных могут указывать следующие несколько факторов (в качестве примера возьмем SQLite):

1) Классы SQLiteDatabase и SQLiteOpenHelper наследуются не от android.database.sqlite а от какого-либо другого класса.

Это может означать, что разработчиками был использован «нестандартный» вариант имплементации SQLite. Например, была использована библиотека SQLCipher.

2) Следует рассмотреть переменные, которые используются в качестве данных для передачи в БД. Возможно, реализована какая-то система шифрования данных до их сохранения в локальной БД.

Соответственно, если условия выше не выполнены, то есть вероятность того, что данные хранятся в открытом виде и в случае физического доступа к устройству злоумышленником, потенциально, он может получить к ним доступ.

Анализ критичности

Критичность данного недостатка оценивается как информационная в связи с тем, что для эксплуатации недостатка злоумышленнику или вредоносному приложению необходимо как минимум повысить свои привилегии в системе или устройство уже должно быть запущено с правами суперпользователя.

Рекомендации по устранению

Рекомендуется использовать средства шифрования данные при хранении на устройстве.

2.4 WebView

WebView - это компонент платформы Android который позволяет отобразить веб-страницу внутри приложения.

Если точнее, это расширение класса View ОС Android, которое позволяет отображать веб-страницу как часть разметки Activity. Согласно документации, WebView не включает в себя всю функциональность самостоятельного браузера и отвечает только за отображение веб-страницы. WebView может поддерживать JavaScript. [22]

Следует так же упомянуть два класса, которые взаимодействуют с WebView

1. WebViewClient применяется для того, чтобы контролировать процесс загрузки веб-страницы. [23]

2. WebChromeClient применяется, чтобы взаимодействовать с загруженной страницей после того, как загрузка страницы прошла успешно. [24]

Соответственно, до завершения загрузки страницы работает WebViewClient, а уже после - WebChromeClient.

Для наглядности рассмотрим следующий код:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

val myWebView = WebView(this)

setContentView(myWebView)

myWebView.webChromeClient = WebChromeClient()

myWebView.webViewClient = WebViewClient()

myWebView.loadUrl("https://www.hse.ru")

}

}

Пример 16: WebView.

В примере выше происходит добавление WebView в метод onCreate() основного Activity приложения, далее происходит создание инстансов класса WebChromeClient(), WebViewClient() и загрузка веб-страницы, которая находится по адресу https://www.hse.ru используя метод loadUrl().
Разработчики могут переопределять различные методы упомянутых инстансов для более полного контроля над действиями WebView.

2.4.1 Поддержка JavaScript

По умолчанию, поддержка JavaScript отключена. При необходимости ее можно подключить, используя метод setJavaScriptEnabled объекта WebSettings.

myWebView.settings.javaScriptEnabled = true

Пример 17: javaScriptEnabled.

Отметим, что после добавления данной строки в участок кода, в котором происходит взаимодействие с WebView, среда разработки Android Studio оповестит разработчика, что добавление метода setJavaScriptEnabled со значением true потенциально может открыть возможность для проведения атак XSS и следует внимательно отнестись к дальнейшей работе с WebView.

Еще одним способом взаимодействия между JavaScript и кодом приложения Android является метод addJavascriptInterface().

Данный метод позволяет вызывать классы нативного кода приложения (написанного, например, на Kotlin или Java) из JavaScript на загружаемой в WebView HTML-страницы.

Важно подчеркнуть, что начиная с версии Android 4.2 (API 17) взаимодействие возможно только с функциями, имеющими директиву @JavascriptInterface. [22]

Рассмотрим следующий пример:

class WebAppInterface(val context: Context) {

@JavascriptInterface

fun alertData(toast: String) {

Toast.makeText(context, toast, Toast.LENGTH_SHORT).show()

}

}

Пример 17: JavascripInterface.

В данном классе установлена директива которая позволяет JavaScript'у загруженной веб страницы вызвать функцию alertData() которая в качестве параметра принимает строку и отображает ее на экране приложения используя объект Toast.

Соответственно, если загружаемая веб-страница в теле HTML-разметки имеет JavaScript-код подобного содержания, который обращается к функции alertData():

<html>

<head>

<body>

<input type="Button" value="Hello" onclick="javascript:Android.alertData('Hello');"/>

</body>

</head>

</html>

Пример 18: javascript:Android.alertData('Hello').

То на экране приложение появится всплывающее окно с текстом “Hello”.

Данный интерфейс работает и в обратную сторону.

Например, если добавить в класс WebAppInterface следующий код, который возвращает версию SDK:

@JavascriptInterface

fun showVersion(): Int{

return android.os.Build.VERSION.SDK_INT

}

Пример 19: возвращение версии SDK.

То страница, имеющая в теле своей HTML-разметки следующий JavaScript-код, будет внедрять в свою разметку данные, полученные от приложения через JavaScript интерфейс.

<html>

<head>

</head>

<body>

<p id="P1">PlaceHolder</p>

<script type="text/javascript">

var myVar = Android.showVersion();

document.getElementById("P1").innerHTML = "Your SDK version" + myVar;

</script>

</body>

</html>

Пример 20: Обращение к клиентскому коду через JavaScript.

2.4.2 Доступ к локальным ресурсам устройства

У WebView есть доступ к различным локальным ресурсам по умолчанию, однако, в зависимости от версий ОС Android, накладываются некоторые ограничения на доступ к ним.

Рассмотрим следующие разрешения:

1. setAllowContentAccess - по умолчанию используется со значением true и разрешает WebView получать данные из установленных в системе Content Providers.

2. setAllowFileAccess - если используется со значением true, позволяет открывать системные файлы через схему file://. При использовании API 29 и ниже, значение по умолчанию - true.

3. setAllowFileAccessFromFileURLs - Начиная с API версии 16 и выше, в качестве значения по умолчанию используется false. Данное разрешение позволяет JavaScript коду локальной HTML страницы, которая загружается приложением через WebView (и загружена через использование схемы file://) получить доступ к ресурсам файловой системы.[25]

Рассмотрим следующий пример:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

val myWebView = WebView(this)

setContentView(myWebView)

myWebView.webChromeClient = WebChromeClient()

myWebView.webViewClient = WebViewClient()

myWebView.settings.javaScriptEnabled = true

myWebView.settings.allowFileAccessFromFileURLs = true

myWebView.loadUrl("file:///data/local/tmp/attack.html")

}

}

Пример 21: allowFileAccesFromFileURLs.

В коде выше используется метод allowFileAccesFromFileURLs со значением true.

Для примера, в качестве передаваемой HTML страницы для загрузки через loadUrl, передается страница с содержащая JavaScript- код, который содержит в себе функцию, использующую XMLHttpRequest при обращении к файлу /etc/hosts на мобильном устройстве и выводит его содержание в логе разработчика (logcat).

function loadXMLDoc()

{

var arm = "file:///etc/hosts";

var xmlhttp;

if (window.XMLHttpRequest)

{

xmlhttp=new XMLHttpRequest();

}

xmlhttp.onreadystatechange=function()

{ if (xmlhttp.readyState==4)

{

console.log(xmlhttp.responseText);

}

}

xmlhttp.open("GET",arm);

xmlhttp.send(null);

}

Пример 22: полезная нагрузка, которая возвращает содержимое файла /etc/hosts.

4. setAllowUniversalAccessFromFileURLs - по своей сути, аналогичен allowFileAccesFromFileURL, за тем исключением, что данный метод позволяет обращаться не только используя схему file:// а так же схемы http:// и т.д. [25]

Кратко обобщим описанную выше информацию.

WebView - это компонент ОС Android позволяющий загружать веб-страницы напрямую в Activity приложения и при определенных настройках способен выполнять JavaScript-код, обращаться к заранее установленным функциями нативного кода мобильного приложения, а так же читать файлы внутри файловой системы устройства.

Прежде, чем приступить к изучению потенциальных атаки на небезопасно сконфигурированный Activity с WebView, следует рассмотреть еще несколько жизненно важных компонентов ОС Android.

2.5 Broadcast receiver

Это компонент, который позволяет приложению принимать так называемые broadcast (широковещательные сообщения) - специальный вид сообщений, рассылаемых приложением или системой.

Изначально рассылки широковещательных сообщений использовались для передачи данных приложениям которые на них «подписаны».

Приведем пример использования широковещательных сообщений:

Система рассылает широковещательное сообщение (broadcast) сообщение AIRPLANE_MODE_CHANGED при включении или отключении режима «в самолете».

Для получения сообщений используется компонент под названием broadcast receiver.

Broadcast Receiver делятся на два типа:

1. Manifest-declared receivers - определяются в файле AndroidManifest.xml приложения используя директиву <receiver>:

<receiver android:name=".myDummyReceiver"

android:exported="false">

<intent-filter>

<action android:name="android.intent.action.SCREEN_OFF"/>

<action android:name="android.intent.action.SCREEN_ON"/>

</intent-filter>

</receiver>

Пример 23: задекларированный в манифесте Broadcast Receiver.

Затем создается соответствующий подкласс класса BroadcastReceiver внутри кода приложения.

Receiver регистрируется системой при установке приложения и далее, становится отдельной точкой входа в приложение.

ОС Android способна сама запустить приложение в случае, если ей необходимо доставить соответствующий broadcast.

2. Context-registered receivers - задаются с помощью инстанса класса BroadcastReceiver.

В случае использования данного метода, жизненный цикл receiver'а устанавливается с помощью registerReceiver() и unregisterReceiver(). Соответственно, в случае если регистрация receiver'а происходит при создании Activity в методе onCreate, то рекомендуется «дерегестрировать» receiver при вызове метода onDestroy. [26]

На данный момент broadcast receiver'ы используются в основном для обработки компонентов под названием объектов намерения - Intent.

Intent - это тип сообщения, который указывает приложению или системе, что нужно «сделать» (к примеру, открыть URL, открыть приложение Google Maps с координатами, передаваемыми через Intent и т.д.). [27]

Обычно, приложение может создать intent и направить его системе, а система в свою очередь решит, какой компонент будет его выполнять.

Этот компонент запускается системой и ему передаётся Intent для выполнения.

Рассмотрим следующий пример intent'а:

locationButton.setOnClickListener {

val intent = Intent().apply {

action = Intent.ACTION_VIEW

data = Uri.parse("geo:${123.0},${122.0}") }

startActivity(context,intent, null)

}

Пример 24: intent.

Создается обработчик действий onClickListener с лямбда-функцией, в которой создается компонент Intent, задача которого открыть приложение Google Maps на месте с координатами 123.0 и 122.0.

Говоря об обработчиках широковещательных сообщений, следует отметить отправку сообщений.

Платформа Android предлагает три способа отправки широковещательных сообщений:

1. sendOrderedBroadcast - как это можно понять из названия, сообщения рассылаются для каждого «приемника» по очереди.

Последовательность отправки сообщения контролируется параметром android:priority в файле AndroidManifest приложения, которое использует регистрирует broadcast receiver для сообщения.

2. sendBroadcast - сообщения рассылаются всем приемникам, в неопределенной последовательности.

3. LocalBroadcastManager.sendBroadcast - метод, который отправляет сообщения приемникам внутри одного приложения. [21]

Для наглядности рассмотрим следующий пример:

btn_send.setOnClickListener {

Intent().also { intent ->

intent.action = "com.example.broadcast.MY_NOTIFICATION"

intent.putExtra("data", "Everybody can see me!")

sendBroadcast(intent)

}

}

Пример 25: intent.

Создается обработчик действий onClickListener с лямбда-функцией, которая создает intent который будет рассылать всем приложениям, установленным на мобильном устройстве сообщение “Everybody can see me!”.

Имея некоторое представления о компонентах Intent и Broadcast receiver можно рассмотреть потенциальные угрозы безопасности.

2.5.1 Некорректная реализация контроля доступа при отправке широковещательных сообщений

Уровень критичности: средний

Речь идет об использовании методов sendBroadcast и sendOrderedBroadcast для отправки информации, которая должна быть доступна только системе или самому приложению.

Недостаток заключается в том, что если используется один из упомянутых выше методов, то передаваемая информация может быть доступна любому приложению, которое зарегистрирует соответствующий broadcast receiver.

Например, в случае с приложением из примера выше, атакующее приложение может перехватить передаваемую информацию используя следующую последовательность действий:

1. Добавить следующий код в AndroidManifest.xml, который установит значение android:priority=”999” для сообщений от com.example.broadcast.MY_NOTIFICATION:

<receiver android:name=".MyBroadcastReceiver" android:exported="true">

<intent-filter android:priority="999">

<action android:name="com.example.broadcast.MY_NOTIFICATION"/>

</intent-filter>

</receiver>

Пример 26: android:priority= “999”.

2. Реализовать внутри нативного кода функциональность отображения перехватываемого сообщения.

Таким образом, произойдет перехват потенциально конфиденциальной информации. Важно подчеркнуть, что для реализации данной атаки нет необходимости в устройстве с правами суперпользователя.

Атаку может осуществить как не привилегированное вредоносное приложение, так и злоумышленник, получивший физический доступ к устройству.

Процесс обнаружения недостатка

Для обнаружения данного недостатка достаточно проанализировать исходный код атакуемого приложения используя поиск, по следующим ключевым словам:

1. sendBroadcast и sendOrderedBroadcast.

В случае обнаружения использования данных методов следует тщательно проанализировать, какие данные передаются.

Анализ критичности

Данный недостаток является достаточно критичным, т. к. в случае, если широковещательные сообщения используются в качестве средств для передачи конфиденциальной информации, любое приложение способно их перехватить и прочитать.

Рекомендации по устранению

Если данная функциональность используется для передачи сообщений внутри одного приложения, то рекомендуется использовать LocalBroadcastManager.sendBroadcast или воспользоваться компонентом LiveData.

Если данный механизм используется для передачи сторонним приложениям, то рекомендуется не использовать широковещательные сообщения.

2.5.2 Небезопасная реализация контроля доступа при получении широковещательных сообщений

Уровень критичности: низкий

Данный недостаток следует рассматривать поэтапно.

Во-первых, следует отметить, что использование декларированного в манифесте broadcast receiver'а в целом не является рекомендуемой практикой с точки зрения реализации безопасного кода для мобильных приложений.

Однако, если такая необходимость присутствует, следует рассмотреть возможность использования android: exported=false, тем самым, запретив доступ к соответствующему receiver'у всем приложениям кроме собственного.

В случае использование android: exported=true, любое приложение способно отправлять intent для конкретного broadcast receiver.

Эксплуатация данного недостатка возможна в ряде случаев, к примеру:

1. Действия с сообщением полученным через broadcast receiver передаются дальше в систему. Например, для отправки смс, звонков и т. д. В таком случае, потенциально, могут быть произведены попытки массовой отправки сообщений, звонков и т. д.

2. Полученные сообщения обрабатываются для внутреннего использования приложением.

Например, в качестве параметра URL для открытия через WebView, записи данных в локальную БД и т. д.

В таком случае может потенциально появиться угроза инъекции вредоносного кода.

Во-вторых, если используются так называемые динамические broadcast receiver'ы, то может возникнуть ситуация как на примере ниже:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

val br: BroadcastReceiver = MyBroadcastReceiver()

setContentView(R.layout.activity_main)

val filter = IntentFilter().apply {

addAction("com.example.broadcast.MY_NOTIFICATION")

}

registerReceiver(br, filter)

}

}

Пример 27: отсутствие вызова unregisterReceiver.

Если тщательно присмотреться к коду, то можно заметить, что внутри класса MainActivity происходит регистрация broadcast receiver в методе onCreate() который вызывается при создании Activity.

Однако, отсутствует использования метода unregisterReceiver при закрытии или сворачивании приложения.

Анализ критичности

Данный недостаток не является критичным, однако рекомендуется явно прописывать сценарии регистрации и “дерегестрации” выданного broadcast receiver.

Рекомендации по устранению

При реализации механизма broadcast receiver рекомендуется:

1. Использование динамических приемников с явно указанными registerReceiver и unregisterReceiver.

2. Валидировать получаемые параметры на предмет их соответствия ожидаемому множеству.

3. Явно указать список разрешений, которые должны быть у приложения, которые рассылают широковещательные сообщения.

Например, если в соответствующем Broadcast Receiver'у отделе файла AndroidManifest.xml указать следующую строку: android:permission="android.permission.SEND_SMS">, то широковещательные сообщения будут получены только от приложений, которые имеют разрешение на отправку СМС.

Данный метод не нивелирует возможность атаки на приемник сообщений, но усложнит ее.

Рассмотрев такие понятия как Broadcast Receiver и Intent можно перейти к рассмотрению различных векторов атак на WebView приложения.

2.5.3 Отсутствие валидации схемы при открытии в loadUrl() WebView

Уровень критичности: низкий

Данный недостаток потенциально может позволить прочитать содержимое внутреннего хранилища приложения злоумышленником.

Например, рассмотрим следующий участок кода:

Пример 28: отсутствие валидации схемы в loadUrl().

Можно видеть, что переменная url которая передается в качестве аргумента для метода loadUrl() получает значение из компонента Intent.

При этом отсутствует какая-либо обработка схемы URL на предмет соответствия логике приложения. Иными словами, WebView откроет любую страницу, по любой схеме, URL которой оно получит из Intent.

Процесс обнаружения недостатка

val url = intent.data.toString()

myWebView.webChromeClient = WebChromeClient()

myWebView.webViewClient = WebViewClient()

myWebView.settings.javaScriptEnabled = true

myWebView.loadUrl(url)

Для обнаружения недостатка необходимо проанализировать вызовы метода loadUrl() и передаваемых в него аргументов на предмет наличия описанного недостатка.

Анализ критичности

Реализация подобного кода не является рекомендуемой практикой, т. к. злоумышленник может сформировать URL открытие которого может скомпрометировать конфиденциальные данные пользователя.

Рекомендации по устранению

Рекомендуется заранее определить схемы, открытие которых необходимо в контексте WebView.

Рассмотрим следующий пример подобной реализации:

var url = intent.data.toString()

if (!url.startsWith("http")){

url = "about:blank"

}

myWebView.webChromeClient = WebChromeClient()

myWebView.webViewClient = WebViewClient()

myWebView.settings.javaScriptEnabled = true

myWebView.loadUrl(url)

Пример 29: валидация схемы.

Теперь, после присвоения переменной url значения полученного от Intent'a, происходит проверка на то, что URL начинается с http. В случае если схема указана иная чем http, значение переменной url становится “about: blank” и далее передается в качестве аргумента loadUrl.

2.5.4 Использование setAllowFileAccessFromFileURLs и setAllowUniversalAccessFromFileURLs

Уровень критичности: низкий

Использование этих методов не является недостатком как таковым, однако, при их реализации следует обратить внимание на потенциальные вектора атаки и последствия, к которые они могут привести.

В первую очередь необходимо отметить, что использование директивы setAllowUniversalAccesFromFileURLs со значение true нивелирует политику одного источника (англ. Same Origin Policy) т. к. данная директива позволяет указать, доступен ли для JavaScript-кода который выполняется в контексте схемы file:// другие источники (например, находящиеся на схеме https:// и т. д.).

Анализ критичности

Таким образом, если злоумышленник смог каким-то образом заставить приложение загрузить контролируемую им локальную HTML страницу (например, через Intent в открытый Broadcast Receiver), то он может обратиться к интересующему его приложения и отправить его на подконтрольную для него страницу. [34]

Процесс обнаружения недостатка

При анализе исходного кода приложения следует воспользоваться поиском, по ключевым словам, таким как:

1. setAllowUniversalAccessFromFileURLs.

2. setAllowFileAccessFromFileURLs.

В случае обнаружение данных методов следует тщательно проанализировать контекст их использования на предмет потенциальной уязвимости.

Рекомендации по устранению

Дабы избежать потенциальных утечек, рекомендуется не использовать setAllowUniversalAccessFromFileURLs со значением true.

В случае если использование является необходимой мерой то, нужно убедиться, что загружаемые в контексте WebView локальные HTML страницы исходят только из доверенного источника.

2.6 Deep links

Deep links - это URL при переходе по которому происходит перенаправление пользователя к определенному содержанию приложения.

Схема URL для deep links задается в файле AndroidManifest.xml приложения. Если конкретнее, то внутри тега <intent filter> Activity.

Всегда, когда пользователь нажимает на заданный в AndroidManifest URL (действие может производиться в браузере или WebView), пользователя перенаправит в соответствующее Activity.

Пример такого файла можно рассмотреть ниже:

<activity android:name=".exampleActivity">

<intent-filter android:autoVerify="true">

<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />

<category android:name="android.intent.category.BROWSABLE" />

<data

android:host="app.example.ru"

android:pathPrefix="/showExampleActivity"

android:scheme="https" />

</intent-filter>

</activity>

Пример 30: deep links в AndroidManifest.

В атрибуте <data> задается вся необходимая информация для создания URL - схема, префикс пути и имя хоста.

Отметим, что схема может быть уникальная для каждого приложения, например, следующая запись будет корректной:

<data

android:host="app.example.com"

android:pathPrefix="/showActivity"

android:scheme="hacker" />

Пример 31: собственная схема для deep link.

Таким образом, приложение будет открываться в случае, если пользователь нажмет на следующий URL - hacker://app.example.com/showActivity

Необходимо отметить, что любое приложение может зарегистрировать подобную схему, соответственно в случае, если у пользователя на мобильном устройстве установлено больше, чем одно приложение с подобной схемой, то при нажатии на URL пользователю всплывет окошко с выбором, какому приложением передать данный URL. [28]

Можно перечислить следующие случаи применения технологии deep links:

1. Для перенаправления пользователя для регистрации в приложении (реферальная ссылка).

2. Как часть маркетинговой кампании - добавить на веб-страницу ссылку для открытия какой-то информации в мобильном приложении (или перенаправления пользователя в магазин приложений Play Market).

3. Использование Deep Links в push-сообщениях или электронной почте.

Существуют и другие случаи, например, передача данных пользователя (например токен) в другое приложение. [40]

Рассмотрим некоторые угрозы безопасности, которые они могут внести.

2.6.1 Использования Deep Links для передачи конфиденциальных данных

Уровень критичности: средний

Как уже упоминалось ранее, Deep Links могут быть использованы для передачи конфиденциальной информации, например, токена аутентификации.

Приложение “A” хочет передать токен пользователя в приложения “B” для того, чтобы ему пользователю не пришлось заново регистрироваться в новом приложении.

Для этого, в одном из Activity приложения “A” реализуется примерно следующий код:

val token = "secretTokenFromApplicationA"

sendButton.setOnClickListener {

val intent = Intent().apply{

action = Intent.ACTION_VIEW

data= Uri.parse("hse://www.example.com/authentication?token=$token")

}

startActivity(intent)

}

Пример 32: передача токена через deep link.

В коде реализован обработчик действий onClickListener, который при событии нажатия на соответствующую обработчику кнопку, отправит объект Intent содержащий в себе URL с токеном пользователя.

Недостаток заключается в том, что любое приложение на устройстве пользователя, которое использует аналогичную схему может претендовать на получение данной информации. [40]

Процесс обнаружения недостатка

Для обнаружения недостатка при анализе исходного кода мобильного приложения, необходимо просмотреть все Activity которые используют Intent'ы для передачи какой-либо информации.

Анализ критичности

Уровень критичности недостатка оценивается средний т. к. для успешной эксплуатации на устройстве пользователя должно быть установлено вредоносное приложение, подписанное на аналогичную с атакуемым приложением схему.

Рекомендации по устранению

Рекомендуется не использовать механизм Deep Links для передачи конфиденциальной информации.

2.7 Транспортная безопасность

Так как в большинстве своем все мобильные приложения взаимодействуют с сетью Интернет, то справедливо будет рассмотреть такой аспекту как транспортная безопасность мобильных приложений.

Далее в главе будет проведен экскурс в основные понятия, связанные с безопасностью соединений, а также рассмотрены варианты безопасной реализации транспортной безопасности мобильного приложения.

2.7.1 Отсутствие требования использования HTTPS соединений

Уровень критичности: информационный

Данный недостаток позволяет приложению устанавливать соединения по незащищенным протоколам, передающим данные в открытом виде, например HTTP, FTP и т. д.

В случае использования Android 7.0 и выше, примером отсутствия запрета на установления соединения по незащищенным протоколам может послужить следующий код файла network_config.xml:

<?xml version="1.0" encoding="utf-8"?>

<network-security-config>

<domain-config cleartextTrafficPermitted="true">

<domain includeSubdomains="true">secretdomain.com</domain>

</domain-config>

</network-security-config>

Пример 33: network_security_config.

Директива cleartextTrafficPermitted используется со значением true, что значит, что приложение может устанавливать соединения по незащищенному каналу. [29]

Анализ критичности

Данный недостаток не является уязвимостью, однако ослабляет защищенность приложения.

Процесс обнаружения недостатка

Для поиска данного недостатка нужно открыть файл network_config.xml и обратить внимание на директиву cleartextTrafficPermitted.

Рекомендация по устранению

Рекомендуется использовать cleartextTrafficPermitted со значением false.

2.7.2 Certificate Pinning

Когда происходит инициализация HTTPS соединения, сервер предоставляет сертификат, который подтверждает его действительность. Это возможно за счет того, что сертификат подписан некой авторитетной стороной. Соединение рассматривается как безопасное, пока первый сертификат подписан корневым удостоверяющим центром, которому доверяет Android.

Система Android использует проверку сертификатов таким образом, что если сертификат не достоверен с точки зрения системы Android, то соединение прерывается.

Однако, существуют различные способы «обхода» такой проверки.

Например, злоумышленник может каким-либо образом попытаться установить и заставить ОС Android доверять сертификату злоумышленника (это возможно сделать через настройки мобильного устройства).

Или какой-либо из корневых удостоверяющих центров был скомпрометирован, то соответственно, могли быть скомпрометированы подписанные им сертификаты, что в последствии может позволить злоумышленникам проводить атаки человек-посередине.

Для того, чтобы избежать подобного исхода был придуман механизм Certificate Pinning, который блокирует соединения с сервером, если полученный от него сертификат не соответствует ожидаемому.

Иными словами, происходит проверка полученного сертификата с локальной копией, которое хранится в мобильном приложении.

Реализация данного механизма в версиях Android версии 7 и выше достаточно проста.

Для этого необходимы три вещи:

1. Хэш от публичного сертификата сервера, соединение с которым нам необходимо.

2. Файл Network_security_config.xml

3. Добавить в файл AndroidManifest следующую строку: [61]

<application android:networkSecurityConfig="@xml/network_security_config"

Пример 34: network_security_config.

Данная строка инструктирует приложение, где ему искать файл network_security_config.xml.

Процесс получения хэша от публичного ключа зависит от ситуации. Данное действие можно произвести локально используя, например, команду “openssl” с соответствующими аргументами.

Далее, в файле network security-config следует реализовать следующий код (отметим, что для примера хэша используется случайная строка):

<network-security-config>

<domain-config cleartextTrafficPermitted="false">

<domain includeSubdomains="true">example.io</domain>

<pin-set>

<pin digest="SHA-256">tlWQhja7+4LV96qCw71ZE325VzDFNgD7n3xN2soN4JSo=</pin>

</pin-set>

</domain-config>

</network-security-config>

Пример 35: запрет на использование протоколов, которые передают данные в открытом виде.

В данной конфигурации запрещается любые соединения, устанавливаемые по протоколам, которые передают данные в открытом виде.

Для домена example.io в файле network_security_config сохраняется хэш сертификата, с которым будет сравниваться полученный сертификат при установлении приложением соединения с сайтом по адресу https://example.io.

В случае если необходимо поддерживать приложения, написанные для версии Android ниже 7.0, то для реализации такого механизма можно использовать библиотеку TrustKit. После добавления данной библиотеки в проект нужно сделать два действия:

1. Добавить в network_security_config.xml следующую строку

<trustkit-config enforcePinning="true" />

Пример 36: настройка certificate pinning используя библиотеку TrustKit.

2. В метод onload() основного Activity приложения добавить следующую строчку:

TrustKit.initializeWithNetworkSecurityConfiguration(this)

Пример 37: настройка certificate pinning используя библиотеку TrustKit.

3. Реализовать вызов TrustKit при инициализации соединения:

val connection = url.openConnection() as HttpsURLConnection

connection.sslSocketFactory = TrustKit.getInstance().getSSLSocketFactory(url.host)

Пример 38: вызов TrustKit при инициализации соединения.

В примере выше, HttpsURLConnection будет использовать TrustKit Socket Factory, которая возьмет на себя проверку сертификатов. [61]

2.7.3 Отсутствие Certificate Pinning

Уровень критичности: информационный

В предыдущем разделе мы уже упомянули, что в случае, если в приложении не реализован механизм Certificate pinning, то злоумышленник, находящийся на одной Wi-Fi сети с жертвой, может провести атаку “человек посередине” в случае, если:


Подобные документы

  • Общие характеристики операционной системы Android. Разработка приложения на основе создания менеджера файлов. Получение с помощью приложения доступа к файлам, хранящимся в "облачном хранилище" в сети Интернет. Расчет стоимости программного обеспечения.

    дипломная работа [2,7 M], добавлен 03.04.2015

  • Архитектура операционной системы Android, набор библиотек для обеспечения базового функционала приложений и виртуальная машина Dalvik. Объектно-ориентированный язык программирования Java как инструмент разработки мобильных приложений для ОС Android.

    дипломная работа [1,6 M], добавлен 08.07.2015

  • Архитектура и история создания операционной системы Android. Язык программирования Java. Выбор средства для реализации Android приложения. Программная реализация Android приложения. Проведение тестирования разработанного программного обеспечения.

    курсовая работа [167,8 K], добавлен 18.01.2017

  • Разработка программного обеспечения для платформы Android версии 2.3: информационное приложения для поклонников футбольной команды, с возможностью просмотра событий, статистики и иной информации о команде и ее успехах. Листинг JsonDataManager.java.

    дипломная работа [4,1 M], добавлен 24.04.2013

  • Преимущества операционной системы Android. Проектирование интерфейса приложений. Визуальные редакторы и средства кроссплатформенной разработки. Оптимизация игрового процесса, выбор фреймворка и библиотек. Классификация и характеристика игр по жанрам.

    дипломная работа [2,6 M], добавлен 10.07.2017

  • Современное состояние рынка мобильных приложений. Основные подходы к разработке мобильных приложений. Обоснование выбора целевой группы потребителей приложения. Этапы проектирования и разработки мобильного приложения для операционной системы Android.

    курсовая работа [987,1 K], добавлен 27.06.2019

  • Определение базы данных и банков данных. Компоненты банка данных. Основные требования к технологии интегрированного хранения и обработки данных. Система управления и модели организации доступа к базам данных. Разработка приложений и администрирование.

    презентация [17,1 K], добавлен 19.08.2013

  • Характеристика работы операционной системы Android, используемой для мобильных телефонов. Создание Android проекта в среда разработки Eclipse. Общая структура и функции файла манифест. Компоненты Android приложения. Способы осуществления разметки.

    курсовая работа [1,0 M], добавлен 15.11.2012

  • Современные базы данных – многофункциональные программные системы, работающие в открытой распределенной среде изучении администрирования базы данных. Способы организации внешней памяти баз данных. Системы управления базами данных для хранения информации.

    курсовая работа [185,6 K], добавлен 07.12.2010

  • Проведение системного анализа предметной области и разработка проекта по созданию базы данных для хранения информации о перевозках пассажиров и грузов. Обоснование выбора системы управления базой данных и разработка прикладного программного обеспечения.

    курсовая работа [1,1 M], добавлен 18.07.2014

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.