Pull to refresh

История любви или как мы «взломали» телеграм бота анонимных вопросов

Level of difficultyMedium
Reading time4 min
Views22K

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

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

Одним прекрасным днём на улице светило солнце. Мы зашли в телеграм и видим сообщение от бота анонимных вопросов с таким тексом: ты тут? я тя люблю 3 года, и я очень надеюсь что ты не узнаешь моё имя, я пишу в анонимку полную фигню)).
Мы решили что это наш шанс на великое возможно и мы решили выяснить, кто нам написал это сообщение!

Начнём с самого начала. При старте бота, нам предоставляют подобную ссылку: https://t.me/anonimnye_voprosy_bot?start=1537461322. Мы имеем опыт в разработке телеграм ботов, так что сделали вывод, что это наш id. У каждого пользователя телеграма он уникален. И его невозможно подделать, конечно если вы не Паша Дуров. Единственный вывод из этого - его можно подменить. Но при условии, что пользователь, чьё id вы ввели, пользуется ботом. Ничего особенного нам это не даёт. Давайте изучим бота получше.

Изучение бота

Что-ж, приступим к самому интересному - изучению бота изнутри. При каждом "анонимном" вопросе бот присылает нам подобное сообщение:
?Пришел анонимный вопрос!
<текст вопроса>
Далее две кнопки, которые прикреплены к сообщению: "Ответить✍️" и "Узнать имя?". При нажатии на первую кнопку, бот предлагает нам написать сообщение в ответ на анонимный вопрос. Это сообщение так же будет анонимным. При нажатии на вторую кнопку, бот предлагает нам заскамиться подписаться за 9 рублей в день. Полное сообщение:
Выбирая любой из тарифов, вы соглашаетесь с автоматической пролонгацией 299 ₽ каждые 3 дня по истечению оплаченного периода. Возможно частичное списание 99 ₽ за 1 день доступа.
Продолжая оплату, вы соглашаетесь с условиями пользования. (
https://sms.evocloud.su/terms)

Бот прямым словом говорит нам: а хочешь ли ты, чтобы мы с тебя списали 5 миллионов?) Зато узнаешь кто тебе написал.
Это нас совсем не устроило, мы были в ярости! На кону любовь всей нашей жизни, а разработчики бота предлагают нам платить бешеные деньги за нажатие на одну лишь кнопку! По сути, вы покупаете свою любовь? Так быть уж точно не должно и мы решили это исправить.

К каждому сообщению, которое содержит кнопки, на самом деле прикреплена клавиатура. Даже если кнопка всего лишь одна. На уровне API телеграма она называется InlineKeyboardMarkup. И уже эта клавиатура содержит наши кнопки, которые на уровне API называются InlineKeyboardButton. В документации Pyrogram'а мы можем просмотреть все свойства кнопок. Нас интересует свойство callback_data. Оно содержит данные, которые будут отправлены боту, при нажатии на кнопку. Любая кнопка имеет это свойство. И кнопка "Ответить✍️" не исключение. Понимаете к чему мы ведём? Правильно, к тому, что эта кнопка содержит данные пользователя, который написал нам анонимное сообщение. Ради любви, восстановления справедливости и проверки этого утверждения мы написали маленький скрипт.

import pyrogram

# учётные данные. Можно получить тут: https://my.telegram.org/
API_ID    = "api id"
API_HASH  = "api hash"

# создание клиента Pyrogram
app = pyrogram.Client("restorejustice", api_id=API_ID, api_hash=API_HASH)

async def main():
  async with app:
    # перебираем историю сообщений в чате с ботом
    async for m in app.get_chat_history("anonimnye_voprosy_bot"):
      # выводим все данные сообщения. Благо, разработчики
      # Pyrogram позаботились о том, чтобы 
      print(m)

app.run(main())

Наш скрипт перебирает историю сообщений с нашим ботом и выводит сырые данные сообщений в консоль. Pyrogram выведет нам все свойства и данные сообщений.

После запуска скрипта в терминале, мы видим месиво из сырых данных сообщений. Но даже подобная нечитабельность данных не позволит нам пропустить сообщение от человека, который написал то анонимное сообщение! И среди всего этого месива мы увидели некоторые сообщения, которые содержат свойство inline_keyboard. А внутри двух списков и кнопку "Ответить✍️". Эта картина выглядела примерно так:

...
    "text": "?Пришел анонимный вопрос!\n\nсообщение с другого нашего аккаунта",
...
    "reply_markup": {
        "_": "InlineKeyboardMarkup",
        "inline_keyboard": [
            [
                {
                    "_": "InlineKeyboardButton",
                    "text": "? Ответить",
                    "callback_data": "message:answer:400492523"
                }
            ],
            [
                {
                    "_": "InlineKeyboardButton",
                    "text": "? Кто это?",
                    "callback_data": "message:whois:400492523"
                }
            ]
        ]
    }
...

И... Бинго! Данные кнопки содержат id пользователя, который написал это сообщение! Мы получили то, что хотели. А чтобы узнать, данные пользователя по id мы можем воспользоваться функцией pyrogram.Client.get_users(). Но есть маленькое условие: тот, чьи данные вы пытаетесь узнать, должен находиться в ваших контактах. Но мы также предположили такой вариант развития событий, как если бы вы опубликовали свою ссылку в большой канал, то вы могли бы пробежаться по списку подписчиков на ваш канал и сравнить id. Нам достаточно и первого варианта, ведь ссылку мы разместили в поле "О себе".

Уязвимости InlineKeyboardMarkup

Мы нашли любовь всей нашей жизни! Мы восстановили справедливость и бесплатно узнали того, кто нам написал. Хотя погодите-ка...

...
    "text": "?Пришел анонимный вопрос!\n\nты тут? я тя люблю 3 года, и я очень надеюсь что ты не узнаешь моё имя, я пишу в анонимку полную фигню))",
...
    "reply_markup": {
        "_": "InlineKeyboardMarkup",
        "inline_keyboard": [
            [
                {
                    "_": "InlineKeyboardButton",
                    "text": "Ответить ✍️",
                    "callback_data": "message:answer:bot"
                }
            ],
            [
                {
                    "_": "InlineKeyboardButton",
                    "text": "Узнать имя ?",
                    "callback_data": "message:whois:bot-123"
                }
            ]
        ]
    }
...

Это... Это лишь скам со стороны бота... Мы были очень расстроены и злы. В ярости мы написали инструмент, который помогает выявлять от кого всё-таки написан анонимный вопрос. DeanonVoprosy. Он показывает уязвимое место любого бота, который использует InlineKeyboardMarkup.

Если вы разработчик ботов, всегда проверяйте данные CallbackQuery, которые вы получили от пользователя. Не давайте пользователю много власти. В нашем случае, нам нужно скрыть id пользователя любыми доступными способами. К примеру, base64 с собственным словарём.

Наше разбитое сердце

Бот нас очень сильно обнадёжил тем, что мы всё таки найдём наше великое возможное.. Но он всё обломал, за что и поплатился и мы опубликовали полный скрипт на GitHub.

Вдогонку мы дополнили скрипт ещё двумя ботами, помимо нашего основного, которого мы разобрали в статье. Первый это полная копия anonimnye_voprosy_bot - anonka_ru_bot. Кто у кого скопировал интерфейс - лишь одному Богу известно. Второй уже отличается своим интерфейсом и работой - questianonbot. Единственное отличие от тех близнецов это то, что callback_data кнопки, отвечающей за ответ на сообщение, содержит данные JSON.

Tags:
Hubs:
Total votes 23: ↑21 and ↓2+26
Comments9

Articles