Pull to refresh

Защита баз данных в iOS

Reading time4 min
Views18K


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

При установке приложения внутри директории /var/mobile/Applications/ создается директория, служащая его «песочницей». Ее рамками ограничены практически все возможные действия приложения. Именно здесь хранятся настойки NSUserDefaults, прочие plist файлы, Cookies, а также базы данных и прочие ресурсы, которые могут быть изменены пользователем. Для доступа к этой директории на устройстве не должен быть установлен Jailbreak — это возможно с помощью любого файлового менеджера (например, iFunBox).

В качестве иллюстрации распространенности этой уязвимости возьмем приложение, постоянно мелькающее в платном топе App Store — Smart Safe.

С помощью iFunBox ознакомимся с содержимым папки /Documents. Помимо всего прочего, она содержит в себе следующие файлы: notes.sqlite, notes.sqlite-shm, notes.sqlite-wal. База данных notes.sqlite содержит в себе три таблицы:
  • ZNOTE,
  • Z_METADATA,
  • Z_PRIMARYKEY.

Особенный интерес представляет первая из них. В поле ZDATE содержится дата публикации, в ZBODY – текст заметки, в ZTITLE – ее заголовок. Весь текст не зашифрован и хранится в открытом виде:



Disclaimer
Разработчик был уведомлен об обнаруженной уязвимости, но никаких комментариев и/или исправлений не последовало. Упомянутая уязвимость — не единственная, приложение хранит абсолютно все данные в открытом виде.

Один из способов борьбы с этой уязвимостью — использование фреймворка SQLCipher, поддерживающего прозрачное шифрование на лету. Для всех криптографических функций (AES256, генератор псевдослучайных чисел, PBKDF2 ключи) SQLCipher использует библиотеку OpenSSL. Подробное описание, документация и сборки находятся по этой ссылке.

В рамках статьи рассмотрим использование SQLCipher совместно с FMDatabase — популярной оберткой для работы с SQLite базами данных в Cocoa.

Если вы используете Cocoapods, то для добавления FMDatabase и SQLCipher в проект достаточно добавить строчку pod 'FMDB/SQLCipher' в podfile. Теперь обратимся к AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDir = [documentPaths objectAtIndex:0];
    self.databasePath = [documentDir stringByAppendingPathComponent:@"sqmple.sqlite"];
    
    [self createAndCheckDatabase];
    
    return YES;
}

-(void) createAndCheckDatabase
{
    BOOL success;
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    success = [fileManager fileExistsAtPath:self.databasePath];
    
    if (success) return; // If file exists, don't do anything
    
    // if file does not exist, make a copy of the one in the Resources folder
    NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"sample.sqlite"]; // File path
    
    [fileManager copyItemAtPath:databasePathFromApp toPath:self.databasePath error:nil]; // Make a copy of the file in the Documents folder
    
    // Set the new encrypted database path to be in the Documents Folder
    NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDir = [documentPaths objectAtIndex:0];
    NSString *encryptedDatabasePath = [documentDir stringByAppendingPathComponent:@"encrypted.sqlite"];
    NSString *key = @"PassKey";
    
    // SQL Query. NOTE THAT DATABASE IS THE FULL PATH NOT ONLY THE NAME
    const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY '%@';", encryptedDatabasePath, key] UTF8String];
    
    sqlite3 *unencrypted_DB;
    if (sqlite3_open([self.databasePath UTF8String], &unencrypted_DB) == SQLITE_OK) {
        
        // Attach empty encrypted database to unencrypted database
        sqlite3_exec(unencrypted_DB, sqlQ, NULL, NULL, NULL);
        
        // export database
        sqlite3_exec(unencrypted_DB, "SELECT sqlcipher_export('encrypted');", NULL, NULL, NULL);
        
        // Detach encrypted database
        sqlite3_exec(unencrypted_DB, "DETACH DATABASE encrypted;", NULL, NULL, NULL);
        
        sqlite3_close(unencrypted_DB);
    }
    else {
        sqlite3_close(unencrypted_DB);
        NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
    }
    
    self.databasePath = [documentDir stringByAppendingPathComponent:@"encrypted.sqlite"];
}

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

- (FMDatabase *)openWriteableDatabase
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"encrypted.sqlite"];
    FMDatabase* database = [FMDatabase databaseWithPath:writableDBPath];
    [database open];
    
    NSString *key = @"PassKey";
    [database setKey:key];
    
    return database;
}

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

NSString *newKey = @"NewPassKey";
[database rekey:newKey];

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

Конечно, использование SQLCipher не сможет полностью обезопасить данные пользователей — но оно отпугнет начинающих исследователей и усложнит жизнь опытным взломщикам. Напоследок стоит упомянуть тот факт, что для появления в App Store приложения, использующего этот фреймворк, необходимо предоставить форму ERN, процесс получения которой прекрасно описан JacobL в этом посте.
Tags:
Hubs:
+26
Comments14

Articles