- PVSM.RU - https://www.pvsm.ru -
Так сложилось, что мне необходимо было изучить исходные коды механизма шифрования, передачи и дешифрования сообщений в Telegram для мобильных платформ iOS и Android. То есть речь идет о клиентских приложениях, именно их исходники (iOS [1], Android [2]) находятся в свободном доступе.
Так как я больше специализируюсь в iOS, то в первую очередь приступил к изучению версии для этой платформы. Потратив около дня на чтение исходников и на работу с отладчиком, я сообразил что к чему и приступил к Android версии. Несложно догадаться, что механизмы и принципы работы должны быть идентичны в силу совместимости всех платформ между собой. Но к своему удивлению я обнаружил несколько отличий в алгоритме дешифрования сообщений в Android версии, что и породило уязвимость, если можно так выразиться. Общая суть уязвимости заключается в том, что в клиентском приложении отсутствует сравнение хеша дешифрованного сообщения с оригинальным хешем, передаваемым вместе с зашифрованным сообщением. По сути отсутствует проверка подписи сообщения. Отсутствие такой проверки может позволить третьим лицам, имеющим доступ к серверу, создавать рандомную активность от лиц участвующих в секретном чате. При этом доступ к общему секретному ключу не требуется, и он остается неуязвим для третьих лиц.
Чтобы разобраться в сути, давайте для начала рассмотрим принцип обмена сообщениями. Он состоит из трех основных этапов:
Замечание: Я здесь намеренно опустил этапы клиент-серверного взаимодействия (установка соединения, передача/прием сообщений), так как они представляют собой точно такие же 3 этапа. То есть для шифрования/дешифрования отдельного сообщения и для передачи данных между клиентом и сервером используется один и тот же принцип защиты.
Принцип генерации общего секретного ключа построен на протоколе Диффи-Хеллмана [3].
Шифрование:
Дешифрование:
С теорией разобрались. Пришло время перейти к практике.
Рассмотрим код дешифрования сообщения для обеих платформ (в коде генерации общего секретного ключа и шифрования сообщения отличий либо ошибок найдено не было, поэтому мы его опустим). Код соответствует последней ревизии ветки master. Принципиально важные проверки пронумерованы в комментариях (1, 2 ,3).
Telegram iOS: TGUpdateStateRequestBuilder.mm [4]
//———————————————————————Cut———————————————————————
int64_t keyId = 0;
[encryptedMessage.bytes getBytes:&keyId range:NSMakeRange(0, 8)];
NSData *messageKey = [encryptedMessage.bytes subdataWithRange:NSMakeRange(8, 16)];
int64_t localKeyId = 0;
NSData *key = nil;
bool keyFound = false;
if (cachedKeys != NULL)
{
auto it = cachedKeys->find(conversationId);
if (it != cachedKeys->end())
{
keyFound = true;
localKeyId = it->second.first;
key = it->second.second;
}
}
if (!keyFound)
{
key = [TGDatabaseInstance() encryptionKeyForConversationId:conversationId keyFingerprint:&localKeyId];
if (cachedKeys != NULL)
(*cachedKeys)[conversationId] = std::pair<int64_t, NSData *>(localKeyId, key);
}
if (key != nil && keyId == localKeyId) // 1)
{
MessageKeyData keyData = [TGConversationSendMessageActor generateMessageKeyData:messageKey incoming:false key:key];
NSMutableData *messageData = [[encryptedMessage.bytes subdataWithRange:NSMakeRange(8 + 16, encryptedMessage.bytes.length - (8 + 16))] mutableCopy];
encryptWithAESInplace(messageData, keyData.aesKey, keyData.aesIv, false);
int32_t messageLength = 0;
[messageData getBytes:&messageLength range:NSMakeRange(0, 4)];
if (messageLength < 0 || messageLength > (int32_t)messageData.length - 4) // 2)
TGLog(@"***** Ignoring message from conversation %lld with invalid message length", encryptedMessage.chat_id);
else
{
NSData *localMessageKeyFull = computeSHA1ForSubdata(messageData, 0, messageLength + 4);
NSData *localMessageKey = [[NSData alloc] initWithBytes:(((int8_t *)localMessageKeyFull.bytes) + localMessageKeyFull.length - 16) length:16];
if (![localMessageKey isEqualToData:messageKey]) // 3)
TGLog(@"***** Ignoring message from conversation with message key mismatch %lld", encryptedMessage.chat_id);
else
{
NSInputStream *is = [[NSInputStream alloc] initWithData:messageData];
[is open];
[is readInt32];
int32_t signature = [is readInt32];
id decryptedObject = TLMetaClassStore::constructObject(is, signature, nil, nil, nil);
//———————————————————————Cut———————————————————————
Telegram Android: SecretChatHelper.java [5]
//———————————————————————Cut———————————————————————
ByteBufferDesc is = BuffersStorage.getInstance().getFreeBuffer(message.bytes.length);
is.writeRaw(message.bytes);
is.position(0);
long fingerprint = is.readInt64();
byte[] keyToDecrypt = null;
boolean new_key_used = false;
if (chat.key_fingerprint == fingerprint) { // 1)
keyToDecrypt = chat.auth_key;
} else if (chat.future_key_fingerprint != 0 && chat.future_key_fingerprint == fingerprint) {
keyToDecrypt = chat.future_auth_key;
new_key_used = true;
}
if (keyToDecrypt != null) {
byte[] messageKey = is.readData(16);
MessageKeyData keyData = Utilities.generateMessageKeyData(keyToDecrypt, messageKey, false);
Utilities.aesIgeEncryption(is.buffer, keyData.aesKey, keyData.aesIv, false, false, 24, is.limit() - 24);
int len = is.readInt32();
TLObject object = TLClassStore.Instance().TLdeserialize(is, is.readInt32());
//———————————————————————Cut———————————————————————
Как видно из кода, в iOS версии выполняются следующие проверки:
В Android версии проверки 2 и 3 отсутствуют.
Рассмотрим ситуацию, в которой отсутствие этих проверок может повлиять на секретный чат:
Для конструктивного диалога позовем Алису и Боба.
И так, действующие лица:
Сценарий:
Вероятность успешного создания объекта примерно равна 382 / 2^32 ≃ 8.9 * 10^-8, где
382 — количество классов содержащихся в словаре;
32 — длина идентификатора класса в битах.
Вероятность, конечно, невысокая, но так как неуспешные случаи проходят незаметно для пользователя, то злоумышленник может непрерывно отправлять сообщения, ограничиваясь только шириной канала подключения клиента к серверу. В таком случае атака может быть вполне осуществимой. Если предположить, что минимальный трафик на одно сообщение может составлять около 100 байт, то потребуется около 1 ГБ трафика для гарантированного создания объекта.
Попробуем прикинуть вероятность успешной атаки в случае наличия хотя одной из пропущенных проверок:
При наличии проверки длины сообщения: (2^10 / 2^32) * (382 / 2^32) ≃ 2.1 * 10^-18, где
2^10 = 1024 — максимальная валидная длина сообщения, примерно столько памяти занимает обычное сообщение;
32 = 4 байта, столько памяти занимает длина сообщения.
При наличии проверки ключа (хеша) сообщения: (1 / 2^128) * (382 / 2^32) ≃ 2.6 * 10^-46, где
128 — длина ключа (хеша) сообщения.
Стоит отметить, что на других уровнях защиты проверка подписи сообщения присутствует. Например, при установке клиент-серверного соединения (используется тот же принцип, что и при обмене сообщениями): ConnectionsManager.java [7]
//———————————————————————Cut———————————————————————
byte[] realMessageKeyFull = Utilities.computeSHA1(data.buffer, 24, Math.min(messageLength + 32 + 24, data.limit()));
if (realMessageKeyFull == null) {
return;
}
if (!Utilities.arraysEquals(messageKey, 0, realMessageKeyFull, realMessageKeyFull.length - 16)) { // 3)
FileLog.e("tmessages", "***** Error: invalid message key");
connection.suspendConnection(true);
connection.connect();
return;
}
//———————————————————————Cut———————————————————————
Хоть это и выглядит немного странно, но я все-таки не думаю, что в отсутствии проверки подписи спрятан какой-то злой умысел, так как уязвимость не является критической. С другой стороны, возможно, есть и другие уязвимости, которые в паре с этой дают больший профит.
Тем не менее, на данный момент разработчики внесли необходимые правки в Dev [8] ветку и обновили сборку в Google Play. Также хочется отметить тот факт, что за найденные мной недочеты разработчики выплатили вознаграждение в размере 5000$. Как говорится «не мелочь и приятно».
Автор: visput
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/uyazvimost/83461
Ссылки в тексте:
[1] iOS: https://github.com/aaronraimist/Telegram-iOS
[2] Android: https://github.com/DrKLO/Telegram
[3] протоколе Диффи-Хеллмана: https://ru.wikipedia.org/wiki/Протокол_Диффи_—_Хеллмана
[4] TGUpdateStateRequestBuilder.mm: https://github.com/aaronraimist/Telegram-iOS/blob/master/Telegraph/Telegraph/TGUpdateStateRequestBuilder.mm
[5] SecretChatHelper.java: https://github.com/DrKLO/Telegram/blob/master/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java
[6] TLClassStore.java: https://github.com/DrKLO/Telegram/blob/master/TMessagesProj/src/main/java/org/telegram/messenger/TLClassStore.java
[7] ConnectionsManager.java: https://github.com/DrKLO/Telegram/blob/master/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java
[8] Dev: https://github.com/DrKLO/Telegram/blob/dev/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java
[9] Источник: http://habrahabr.ru/post/247409/
Нажмите здесь для печати.