From a8d2dfcfa13173df1d35f91671db5704b57c180d Mon Sep 17 00:00:00 2001 From: Wizou <11647984+wiz0u@users.noreply.github.com> Date: Thu, 19 May 2022 01:32:22 +0200 Subject: [PATCH] Improve security by preventing replay attacks --- FAQ.md | 4 ++-- src/Client.cs | 23 +++++++++++++++-------- src/Encryption.cs | 2 +- src/Session.cs | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/FAQ.md b/FAQ.md index 4196420..41aa0f7 100644 --- a/FAQ.md +++ b/FAQ.md @@ -94,7 +94,7 @@ If you use the Github source project in an old .NET Framework 4.x or .NET Core x To fix this, you should also switch to using the [WTelegramClient Nuget package](https://www.nuget.org/packages/WTelegramClient) as it will install the required dependencies for it to work. -#### 7. I get errors FLOOD_WAIT_X or PEER_FLOOD, PHONE_NUMBER_BANNED. I can't import phone numbers. +#### 7. I get errors FLOOD_WAIT_X or PEER_FLOOD, PHONE_NUMBER_BANNED, USER_DEACTIVATED_BAN. I can't import phone numbers. You can get these kind of problems if you abuse Telegram [Terms of Service](https://telegram.org/tos), or the [API Terms of Service](https://core.telegram.org/api/terms), or make excessive requests. @@ -108,7 +108,7 @@ If you think your phone number was banned from Telegram for a wrong reason, you In any case, WTelegramClient is not responsible for the bad usage of the library and we are not affiliated to Telegram teams, so there is nothing we can do. -#### 8. How to not get banned from Telegram? +#### 8. How to NOT get banned from Telegram? **Do not share publicly your app's ID and hash!** They cannot be regenerated and are bound to your Telegram account. diff --git a/src/Client.cs b/src/Client.cs index 3475a62..711115a 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -383,12 +383,27 @@ namespace WTelegram byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, (dataLen - 24) & ~0xF), false, _dcSession.AuthKey, data, 8, _sha256Recv); if (decrypted_data.Length < 36) // header below+ctorNb throw new ApplicationException($"Decrypted packet too small: {decrypted_data.Length}"); + _sha256Recv.TransformBlock(_dcSession.AuthKey, 96, 32, null, 0); + _sha256Recv.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); + if (!data.AsSpan(8, 16).SequenceEqual(_sha256Recv.Hash.AsSpan(8, 16))) + throw new ApplicationException($"Mismatch between MsgKey & decrypted SHA256"); + _sha256Recv.Initialize(); using var reader = new TL.BinaryReader(new MemoryStream(decrypted_data), this); var serverSalt = reader.ReadInt64(); // int64 salt var sessionId = reader.ReadInt64(); // int64 session_id var msgId = reader.ReadInt64(); // int64 message_id var seqno = reader.ReadInt32(); // int32 msg_seqno var length = reader.ReadInt32(); // int32 message_data_length + + if (length < 0 || length % 4 != 0) throw new ApplicationException($"Invalid message_data_length: {length}"); + if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new ApplicationException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}"); + if (sessionId != _dcSession.Id) throw new ApplicationException($"Unexpected session ID: {sessionId} != {_dcSession.Id}"); + if ((msgId & 1) == 0) throw new ApplicationException($"msg_id is not odd: {msgId}"); + if (!_dcSession.CheckNewMsgId(msgId)) + { + Helpers.Log(3, $"{_dcSession.DcID}>Ignoring duplicate or old msg_id {msgId}"); + return null; + } if (_lastRecvMsgId == 0) // resync ServerTicksOffset on first message _dcSession.ServerTicksOffset = (msgId >> 32) * 10000000 - DateTime.UtcNow.Ticks + 621355968000000000L; var msgStamp = MsgIdToStamp(_lastRecvMsgId = msgId); @@ -401,15 +416,7 @@ namespace WTelegram if (_saltChangeCounter >= 30) throw new ApplicationException($"Server salt changed too often! Security issue?"); } - if (sessionId != _dcSession.Id) throw new ApplicationException($"Unexpected session ID {sessionId} != {_dcSession.Id}"); - if ((msgId & 1) == 0) throw new ApplicationException($"Invalid server msgId {msgId}"); if ((seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msgId); - if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new ApplicationException($"Unexpected decrypted message_data_length {length} / {decrypted_data.Length - 32}"); - _sha256Recv.TransformBlock(_dcSession.AuthKey, 96, 32, null, 0); - _sha256Recv.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); - if (!data.AsSpan(8, 16).SequenceEqual(_sha256Recv.Hash.AsSpan(8, 16))) - throw new ApplicationException($"Mismatch between MsgKey & decrypted SHA256"); - _sha256Recv.Initialize(); var ctorNb = reader.ReadUInt32(); if (ctorNb != Layer.BadMsgCtor && (msgStamp - DateTime.UtcNow).Ticks / TimeSpan.TicksPerSecond is > 30 or < -300) diff --git a/src/Encryption.cs b/src/Encryption.cs index 970143f..1c65180 100644 --- a/src/Encryption.cs +++ b/src/Encryption.cs @@ -276,7 +276,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB private static byte[] AES_IGE_EncryptDecrypt(Span input, byte[] aes_key, byte[] aes_iv, bool encrypt) { - if (input.Length % 16 != 0) throw new ApplicationException("intput size not divisible by 16"); + if (input.Length % 16 != 0) throw new ApplicationException("AES_IGE input size not divisible by 16"); // code adapted from PHP implementation found at https://mgp25.com/AESIGE/ var output = new byte[input.Length]; diff --git a/src/Session.cs b/src/Session.cs index c4d3e9c..8b7a2c7 100644 --- a/src/Session.cs +++ b/src/Session.cs @@ -33,6 +33,41 @@ namespace WTelegram internal int DcID => DataCenter?.id ?? 0; internal IPEndPoint EndPoint => DataCenter == null ? null : new(IPAddress.Parse(DataCenter.ip_address), DataCenter.port); internal void Renew() { Helpers.Log(3, $"Renewing session on DC {DcID}..."); Id = Helpers.RandomLong(); Seqno = 0; LastSentMsgId = 0; } + + const int msgIdsN = 512; + private long[] msgIds; + private int msgIdsHead; + internal bool CheckNewMsgId(long msg_id) + { + if (msgIds == null) + { + msgIds = new long[msgIdsN]; + for (int i = 0; i < msgIdsN; i++) msgIds[i] = msg_id; + return true; + } + int newHead = (msgIdsHead + 1) % msgIdsN; + if (msg_id > msgIds[msgIdsHead]) + msgIds[msgIdsHead = newHead] = msg_id; + else if (msg_id <= msgIds[newHead]) + return false; + else + { + int min = 0, max = msgIdsN - 1; + while (min <= max) // binary search (rotated at newHead) + { + int mid = (min + max) / 2; + int sign = msg_id.CompareTo(msgIds[(mid + newHead) % msgIdsN]); + if (sign == 0) return false; + else if (sign < 0) max = mid - 1; + else min = mid + 1; + } + msgIdsHead = newHead; + for (min = (min + newHead) % msgIdsN; newHead != min;) + msgIds[newHead] = msgIds[newHead = newHead == 0 ? msgIdsN - 1 : newHead - 1]; + msgIds[min] = msg_id; + } + return true; + } } public DateTime SessionStart => _sessionStart;