In November 2016 I've discovered a vulnerability in the Telegram Instant Messenger for Android. This post will describe the discovery, responsible disclosure and Telegram's response.

Replay attack

A replay attack is an attack where an attacker sniffs data sent by the application and then resends them at a different time with a malicious intent. By doing so, the attacker can repeat any message without the user noticing. Curiously, the attacker does not actually know the message. A protection might be realized by implementing a message counter to keep track of the order the messages appear in.

To provide an example let's assume two parties: Alice and Bob (suprisingly) are exchanging messages in an encrypted manner. Alice asks a lot of questions and Bob answers lazily. Finally, Eve, an eavesdropper, listens to the encrypted communication. Eve is unable to read the messages, however, is able to take one of the Bob's answer and resends it later on.

If we further assume that Eve is able to identify Bob's "yes" answer (e.g. using social engineering, length of the encrypted message etc.), she is able to send the message anytime she desires and therefore respond to all Alice questions possitively!

Replay attack on Telegram

During my analysis of the official Android Telegram app (more articles on that here) I've examined some parts of the code in an attempt to discover potential vulnerabilities to exploit.

Amongst others I've analyzed how Telegram deals with the Replay attack issue outlined above. Telegram sends sequence numbers included in each message which should be later validated. The concerned code responsible for checking these numbers (file ConnectionsManager.cpp, line 728) is depicted in the following listing:

if (connection->isMessageIdProcessed(messageId)) {
	doNotProcess = true;
}
if (!doNotProcess) {
	// process
	addProcessedMessageId(messageId);
}

After the message decryption this block of code checks if the message was already processed. The class ConnectionSession holds an internal array of the already processed messages. If the message is accepted and processed, the message ID is then added into the array.

The behavior I believed might be exploitable concerns the addProcessedMessageId() function (ConnectionSession.cpp, line 55). The function checks the size of the array and if it exceeds 300, it erases the first 100 messages:

void addProcessedMessageId(messageId)
{
	if (processedMessageIds.size() > 300) {
		processedMessageIds.erase(processedMessageIds.begin(),
		processedMessageIds.begin() + 100);
	}
	processedMessageIds.push_back(messageId);
}

I came to a conclusion that after processing 300 messages, Telegram could accept older one again. I drafted the attack scenario as follows:

  1. Sniff a message
  2. Wait for 300 other messages
  3. Replace the next message with the first one
  4. Telegram processes the first message again

If Telegram would not provide any additional checks and actually delete the first 100 IDs, the message would be accepted twice.

Responsible Disclosure

The findings were reported to the Telegram security team on December 7th, 2016 with a kind request for comments. The first response from Telegram was received on 12th December 2016 and Telegram actually accepted my remarks.

According to the Telegram team the Android application (as opposed, allegedly, to the other clients, such as Telegram for iOS, Telegram Desktop etc.) does not perform one of the the security checks as is required by the protocol, defined in the Security Guidelines for Client Developers in particular.

Among other checks, the Security Guidelines require the message ID to be checked against the stored ones and Telegram performs that. However, it requires one more additional check – if the incoming message has an ID lower than all or equal to any of the stored IDs, such message is to be discarded. This action does indeed diminish the risk but Telegram for Android did not carry out this check.

The Telegram team further commented that this vulnerability does not allow the attacker to cause any severe damage because of the additional protection on the side of the Telegram API. Message actions (sending, editing, deleting, and changing read status), group membership, secret chats, and
other important areas are not affected [1].

Nevertheless, the Telegram team confirmed the attack would work for nonessential service updates like online or typing statuses. For example, the scenario would allow the attacker to alter the statuses of victim’s friends (as seen in the Android application, not in the Telegram network) or spoof the victim to see typing statuses from contacts not performing such action in reality.

I briefly reviewed these claims and concluded that the Java part of the Telegram application does indeed deal with additional identifiers, such as qts, pts and others. These values do seem to provide additional protection. It is important to mention that I did not perform any deeper research. Telegram promised this will be fixed in the next Android update – most likely in the 3.16 version which was released in early January 2017. Telegram also offered a financial reward for my findings.

Fix

I'm releasing this post quite late after the discovery. The reason is (besides me being lazy) that I wanted to see how Telegram will fix this. I was unfortunately unable to do so, because Telegram didn't update its GitHub repository since October 2016. That finally changed at the end of March 2017.

The developers indeed modified the addProcessedMessageId() and more importantly the isMessageIdProcessed() functions, you can see the code directly on GitHub.

The addProcessedMessageId() function now saves minProcessedMessageId value. This variable is set when the array of message IDs exceeds its 300 items capacity. It is set to the current lowest message ID stored in the array. All processed IDs have lower ID than this value and the check correctly asserts that the examines that.

I've reviewed the code and it seems correct. Great to hear Telegram fixed this properly.

Conclusion

The code quality is, to say it gently, very very doubtful. I think another article might follow on that, but it took me about a month just to familiarize myself with the code base. And it took a lot more effort to find the obfuscation method and this bug.

That being said, the Telegram security team took my report seriously and in a very professional manner (and let's be honest, this ain't no Heartbleed) and the communication was realized in a reasonable time frame. I'm not a Telegram fan, but they dealt with this issue with grace.



  1. According to Telegram, this is because of the checks done in the MessagesController.java class on lines 5731, 5561, 5765, 5817 and others. ↩︎