• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Статья Перехват базовых запросов с помощью Selenium Wire и Python

В данной статье я хотел бы вернуться к способам перехвата запросов и ответов API в браузере. С помощью Selenium в связке с Python можно парсить достаточно сложные сайты, которые требуют выполнения различного рода скриптов. Но иногда этого бывает недостаточно и требуется получить данные, которые возвращает API на внешний интерфейс. В этом случае можно воспользоваться локальным прокси-сервером, как в этой статье (Статья - Перехват запросов с веб-страницы с помощью selenium и browsermobproxy в Python). Или, для перехвата запросов и ответов, можно воспользоваться Wireshark. Но это уже будет внешнее приложение. Давайте разберемся, как с помощью дополнительной библиотеки и Python перехватывать запросы и ответы без использования внешнего прокси.

000.png


Давайте поставим некоторую задачу, выполнение которой продемонстрирует работу по перехвату запросов и ответов. К примеру, у нас есть Облако Mail.ru. И в этом облаке храниться некий мультимедийный контент, например видео-курсы по Python, которые нам хотелось бы смотреть не скачивая себе на компьютер и не пользуясь браузером, а используя для этой цели любой плеер, который может воспроизводить плейлисты. Для примера, VLC. Таким образом, мы «расшариваем» доступ к видео. Но, доступ будет только лишь к странице с плеером. А вот сами плейлисты уже получаются плеером на странице с помощью запросов. Вот эти то запросы нам и нужно будет перехватить. Ну и, чтобы пример был законченным, выполним разбор перехваченного плейлиста и найдем ссылку на видео с самым большим разрешением.

Конечно же, все эти запросы можно увидеть и забрать из инструментов разработчика. Но, в данном случае нам требуется автоматизация.

screenshot1.png


Что потребуется?

Для того, чтобы перехватывать запросы и ответы, мы будем использовать библиотеку на python, которая расширяет привязки Selenium Webdriver. У данной библиотеки минимальные внешние зависимости. Называется она Selenium Wire. Для ее установки требуется выполнить команду:

pip install selenium-wire

Отдельно устанавливать Selenium нет необходимости.
Данной библиотеке требуется OpenSSL для расшифровки HTTPS-запросов. Если вы используете операционную систему Linux, обязательно проверьте наличие данной библиотеки с помощью команды:

openssl version

01.png

Если данной библиотеки не установлено, для ее установки необходимо выполнить команду:

sudo apt install openssl

Для операционной системы Windows установка OpenSSL не требуется.
Так как в данном примере мы будем скачивать нужные файлы после перехвата, то нам потребуется установить библиотеку requests. Для ее установки необходимо выполнить команду:

pip install requests

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

Дополнительно сделаем наш браузер неопределяемым как автоматизированное ПО. Так как здесь не получиться использовать библиотеку undetected-chromedriver, о которой написано вот в этой статье (Статья - Методы обхода защиты от автоматизированного ПО в браузере Chrome под управлением Selenium в Python), воспользуемся ручным способом, который описан там же.

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

Python:
import os
import time
from pathlib import Path
from platform import system
from urllib.parse import urlparse, urljoin

from requests import get
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from seleniumwire import webdriver

Следующим шагом будет добавление опций для Chrome-драйвера, которые отключат детектирование автоматизированного ПО.

Python:
options = Options()
options.add_argument("--headless")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument("—disable-blink-features=AutomationControlled")

Как видите, здесь же была добавлена опция, с помощью которой включается «безголовость», то есть, загрузка и работа браузера без отображения для пользователя. Здесь опции включены по полной, не так как в статье, так как я обнаружил, что при использовании только лишь одной опции, а именно последней, браузер не детектируется в Linux, однако для браузера под Windows этого недостаточно.

Теперь установим путь к веб-драйверу, если вы его не добавили в переменные окружения:

Python:
exec_path = str(Path.cwd() / 'chromedriver' / 'chromedriver.exe') if system() == "Windows" else \
    str(Path.cwd() / 'chromedriver' / 'chromedriver')

Как видите, веб-драйвер расположен в папке 'chromedriver', в одной директории со скриптом. Скачать версию Chrome-драйвера можно со страницы по ссылке: .
После того, как выполнены все необходимые приготовления, давайте приступим к написанию кода.


Перехват ссылки на плейлист

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

Немного теории. Selenium Wire перехватывает весь трафик, который выполняется в браузере. Получить все запросы можно с помощью атрибута driver.requests. Здесь запросы представлены в виде списка, поэтому его можно легко итерировать и индексировать.

Каждый запрос имеет следующие атрибуты:

- method: собственно метод запроса GET или POST;
- url: адрес запроса;
- path: маршрут запроса;
- querystring: строка запроса;
- params: словарь с параметрами запроса;
- headers: словарь с заголовками;
- body: тело запроса в виде байт;
- response: ответ связанный с запросом.

Ответ может быть получен из запроса с помощь атрибута response, который, в свою очередь, имеет следующие атрибуты:

- status_code: статус-код;
- reason: описание ответа: OK или Not Found
- headers: словарь с заголовками;
- body: тело ответа в виде байт.

Создадим функцию get_link(url: str) -> (str, bool), которая на входе принимает ссылку на страницу сайта, где требуется перехватывать трафик. И будет возвращать перехваченную ссылку на плейлист. Вы же можете возвращать из функции ссылку на что-то другое.

Создадим объект драйвера. Передадим в него указанные ранее опции и путь к веб-драйверу. Затем выполним скрипт, который удаляет методы помогающие определить автоматизированное ПО. После чего переходим на страницу из ссылки и ждем одну секунду после ее загрузки. В нашем случае этого вполне достаточно для перехвата ссылки на плейлист. Заметил, что в некоторых случаях требуется отключить «безголовость», так как не всегда в этом режиме выполняются запросы.

Python:
    driver = webdriver.Chrome(options=options, service=Service(log_path=os.devnull, executable_path=exec_path))
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        'source': '''
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
      '''
    })
    driver.get(url)
    time.sleep(1)

Теперь в цикле итерируемся по перехваченным запросам. Проверяем, есть ли перехваченные запросы и если есть, делаем проверку, содержится ли в перехваченном запросе элемент, который указывает на перехваченную ссылку на плейлист со ссылками: "/0p/". Если данный элемент присутствует, возвращаем его из функции. В противном случае возвращаем False.

Python:
    for req in driver.requests:
        if req.response:
            if "/0p/" in urlparse(req.url).path:
                return req.url
            continue
        return False

Python:
def get_link(url: str) -> (str, bool):
    """
    Получение ссылки на плейлист со ссылками на плейлисты с различным разрешением.
    :param url: Ссылка на страницу с ресурсом (в данном случае видео).
    :return: Возвращаем найденную и отфильтрованную ссылку.
    """
    driver = webdriver.Chrome(options=options, service=Service(log_path=os.devnull, executable_path=exec_path))
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        'source': '''
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
      '''
    })
    driver.get(url)
    time.sleep(1)

    for req in driver.requests:
        if req.response:
            if "/0p/" in urlparse(req.url).path:
                return req.url
            continue
        return False


Получение содержимого плейлиста

Если мы смогли получить необходимую нам ссылку на плейлист, нам нужно получить ее содержимое. А получить его мы можем с помощью библиотеки requests, выполнив get-запрос по перехваченной ссылке. Создадим функцию get_playlist(pl_url: str) -> (str, bool), которая на входе получает ссылку на плейлист и возвращает полученный текст плейлиста.

Определим заголовки для запроса. Укажем user-agent и host. Выполняем запрос, проверяем статус-код. Если он 200, возвращаем из функции текст ответа. В противном случае возвращаем False.


Python:
def get_playlist(pl_url: str) -> (str, bool):
    """
    Скачивание плейлиста со ссылками на плейлисты с разрешениями.
    :param pl_url: Ссылка на плейлист с разрешениями.
    :return: Текст плейлиста для обработки.
    """
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/108.0.0.0 YaBrowser/23.1.2.987 Yowser/2.5 Safari/537.36',
        'Host': 'cloud.mail.ru'
    }
    try:
        res = get(url=pl_url, headers=headers, timeout=5)
        return res.text if res.status_code == 200 else False
    except Exception:
        return False


Получение ссылки на плейлист с максимально доступным разрешением

Теперь, когда ссылка на плейлист получена, а также получено его содержимое, нужно получить ссылку на части видео с максимально доступным разрешением. Так как оно будет разное у разных видео, будем итерироваться по полученному тексту и забирать каждую строку с описанием, откуда будем брать разрешение, сравнивать с уже полученным и в конце возвратим самое большое. Конечно, можно было этого не делать. В Облаке Mail плейлисты достаточно упорядоченны и структурированы. Потому, можно было бы получать только определенную строку. Но, лучше распарсить полученные данные для большей надежности. Создадим функцию get_resolution(text: str, url: str) -> (str, bool). На вход она получает текст плейлиста и оригинальную ссылку, по которой он был загружен. Это необходимо для того, чтобы собрать полную ссылку на плейлист определенного разрешения, так как представленные ссылки относительны. Ну и на выходе возвращаем ссылку на плейлист с самым большим разрешением видео, которое доступно в данном плейлисте. Создаем две целочисленные переменные, которые будут хранить разрешение видео для сравнения. Создадим переменную, в которой будет храниться ссылка на плейлист с текущим разрешением. Итерируемся по полученному тексту разбитому на строки с помощью splitlines(). Получаем разрешение видео, присваиваем полученное значение в переменную. Затем, на следующей итерации, проверяем, является ли строка ссылкой. Если да, проверяем уже полученное разрешение. Если оно больше чем то, что уже определено ранее, присваиваем текущее значение в значение для сравнения, а в переменную link, в которой храниться ссылка, присваиваем текущую ссылку. И так, до самого конца плейлиста. Ну и, по окончании итерации, возвращаем из функции полученную ссылку на плейлист с максимальным разрешением.

Python:
def get_resolution(text: str, url: str) -> (str, bool):
    """
    Парсинг содержимого плейлиста и получение ссылки на плейлист
    с самым большим разрешением.
    :param text: Текст плейлиста.
    :param url: Ссылка на плейлист со ссылками (исходная ссылка).
    :return: Ссылка на плейлист с самым большим разрешением.
    """
    res = num = 0
    link = ""
    for txt in text.splitlines():
        if txt.startswith("#EXT-X"):
            num = int(txt.strip().split("x")[-1])
            continue
        elif txt.startswith("/"):
            if num > res:
                res = num
                link = urljoin(url, txt.strip())
    return link if link else False


Запуск перехвата запросов и ответов, а также остальных функций

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

Python:
def main():
    """
    Запрос ссылки на ресурс. Запуск перехвата запросов.
    Получение текста плейлиста. Парсинг. Вывод в терминал ссылки
    на плейлист с самым высоким разрешением.
    """
    # url = "https://cloud.mail.ru/public/5XF1/hvgyw486Z"
    url = input("Введите ссылку на видео-файл из Облака: ")
    if link := get_link(url):
        print(f"Ссылка на плейлист с разрешениями получена: {link}")
        print(f"Получение плейлиста с разрешениями")
        if text := get_playlist(link):
            print(f"Плейлист с разрешениями получен. Обработка")
            if res_link := get_resolution(text, link):
                print(f"Ссылка на самое большое разрешение в плейлисте: {res_link}")
            else:
                print(f'Не удалось получить ссылку{res_link}')
        else:
            print(f'Не могу получить плейлист: {text}')
    else:
        print(f'Не могу получить ссылку на плейлист с разрешениями: {link}')


if __name__ == "__main__":
    main()

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

Python:
"""
Скрипт для перехвата запросов из браузера, а также, для примера,
перехвата и получения ссылок на плейлисты видео из Облака Mail.ru.
Для работы требует установки следующих библиотек:
pip install selenium-wire requests.
Или, вы можете запустить установку с помощью файла requirements.txt.
В некоторых Linux-системах требуется установка библиотеки OpenSSL.
Сделать это можно с помощью команды:
sudo apt install openssl
В Windows-системах установка не требуется.
Проверить версию OpenSSL можно командой: openssl version
"""

# pip install selenium-wire requests


import os
import time
from pathlib import Path
from platform import system
from urllib.parse import urlparse, urljoin

from requests import get
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from seleniumwire import webdriver

options = Options()
options.add_argument("--headless")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument("--disable-blink-features=AutomationControlled")

exec_path = str(Path.cwd() / 'chromedriver' / 'chromedriver.exe') if system() == "Windows" else \
    str(Path.cwd() / 'chromedriver' / 'chromedriver')


def get_link(url: str) -> (str, bool):
    """
    Получение ссылки на плейлист со ссылками на плейлисты с различным разрешением.
    :param url: Ссылка на страницу с ресурсом (в данном случае видео).
    :return: Возвращаем найденную и отфильтрованную ссылку.
    """
    driver = webdriver.Chrome(options=options, service=Service(log_path=os.devnull, executable_path=exec_path))
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        'source': '''
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
            delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
      '''
    })
    driver.get(url)
    time.sleep(1)

    for req in driver.requests:
        if req.response:
            if "/0p/" in urlparse(req.url).path:
                return req.url
            continue
        return False


def get_playlist(pl_url: str) -> (str, bool):
    """
    Скачивание плейлиста со ссылками на плейлисты с разрешениями.
    :param pl_url: Ссылка на плейлист с разрешениями.
    :return: Текст плейлиста для обработки.
    """
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/108.0.0.0 YaBrowser/23.1.2.987 Yowser/2.5 Safari/537.36',
        'Host': 'cloud.mail.ru'
    }
    try:
        res = get(url=pl_url, headers=headers, timeout=5)
        return res.text if res.status_code == 200 else False
    except Exception:
        return False


def get_resolution(text: str, url: str) -> (str, bool):
    """
    Парсинг содержимого плейлиста и получение ссылки на плейлист
    с самым большим разрешением.
    :param text: Текст плейлиста.
    :param url: Ссылка на плейлист со ссылками (исходная ссылка).
    :return: Ссылка на плейлист с самым большим разрешением.
    """
    res = num = 0
    link = ""
    for txt in text.splitlines():
        if txt.startswith("#EXT-X"):
            num = int(txt.strip().split("x")[-1])
            continue
        elif txt.startswith("/"):
            if num > res:
                res = num
                link = urljoin(url, txt.strip())
    return link if link else False


def main():
    """
    Запрос ссылки на ресурс. Запуск перехвата запросов.
    Получение текста плейлиста. Парсинг. Вывод в терминал ссылки
    на плейлист с самым высоким разрешением.
    """
    # url = "https://cloud.mail.ru/public/5XF1/hvgyw486Z"
    url = input("Введите ссылку на видео-файл из Облака: ")
    if link := get_link(url):
        print(f"Ссылка на плейлист с разрешениями получена: {link}")
        print(f"Получение плейлиста с разрешениями")
        if text := get_playlist(link):
            print(f"Плейлист с разрешениями получен. Обработка")
            if res_link := get_resolution(text, link):
                print(f"Ссылка на самое большое разрешение в плейлисте: {res_link}")
            else:
                print(f'Не удалось получить ссылку{res_link}')
        else:
            print(f'Не могу получить плейлист: {text}')
    else:
        print(f'Не могу получить ссылку на плейлист с разрешениями: {link}')


if __name__ == "__main__":
    main()

Ну и скрин с результатами выполнения. У меня в Облаке как раз есть видео. Потому я открыл к нему доступ. Кстати, ссылка на видео указана в функции main в закомментированной строке.

03.png

Ну, а теперь протестируем полученную ссылку. Скормим ее плееру и увидим, что все воспроизводиться успешно.

02.png

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

А на этом, пожалуй, все.

Спасибо за внимание. Надеюсь, данная информация будет вам полезна
 

Вложения

  • requests_interception.zip
    2,9 КБ · Просмотры: 106
Последнее редактирование модератором:

satfan

Green Team
26.06.2022
75
1
BIT
42
Редактор выделяет красным эту строку кода:
raise exception_class(message, screen, stacktrace)
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Вот полная ошибка:
Python 3.10.1 (tags/v3.10.1:2cd268a, Dec 6 2021, 19:10:37) [MSC v.1929 64 bit (AMD64)]
Type "help", "copyright", "credits" or "license" for more information.
[анализируем requests_interception.py]
Введите ссылку на видео-файл из Облака:
Traceback (most recent call last):
Файл "C:\Users\Пк\Desktop\Перехват запросов с Selenium Wire и Python\requests_interception.py", строка 126, из <module> main()
Файл "C:\Users\Пк\Desktop\Перехват запросов с Selenium Wire и Python\requests_interception.py", строка 110, из <module> if link := get_link(url):
Файл "C:\Users\Пк\Desktop\Перехват запросов с Selenium Wire и Python\requests_interception.py", строка 44, из <module> driver = webdriver.Chrome(options=options, service=Service(log_path=os.devnull, executable_path=exec_path))
Файл "c:\python\lib\site-packages\seleniumwire\webdriver.py", строка 218, из __init__ super().__init__(*args, **kwargs)
Файл "c:\python\lib\site-packages\selenium\webdriver\chrome\webdriver.py", строка 70, из __init__ super(WebDriver, self).__init__(DesiredCapabilities.CHROME['browserName'], "goog",
Файл "c:\python\lib\site-packages\selenium\webdriver\chromium\webdriver.py", строка 93, из __init__ RemoteWebDriver.__init__(
Файл "c:\python\lib\site-packages\selenium\webdriver\remote\webdriver.py", строка 268, из __init__ self.start_session(capabilities, browser_profile)
Файл "c:\python\lib\site-packages\selenium\webdriver\remote\webdriver.py", строка 359, из start_session response = self.execute(Command.NEW_SESSION, parameters)
Файл "c:\python\lib\site-packages\selenium\webdriver\remote\webdriver.py", строка 424, из execute self.error_handler.check_response(response)
Файл "c:\python\lib\site-packages\selenium\webdriver\remote\errorhandler.py", строка 247, из check_response raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: unknown error: cannot find Chrome binary
Stacktrace:
Backtrace:
Ordinal0 [0x00CBA113+1548563]
Ordinal0 [0x00C3DDA1+1039777]
Ordinal0 [0x00BBE485+517253]
Ordinal0 [0x00B4C0F1+49393]
Ordinal0 [0x00B6B797+178071]
Ordinal0 [0x00B6B59D+177565]
Ordinal0 [0x00B695FB+169467]
Ordinal0 [0x00B5160A+71178]
Ordinal0 [0x00B52690+75408]
Ordinal0 [0x00B52629+75305]
Ordinal0 [0x00C571B7+1143223]
GetHandleVerifier [0x00D52B46+507814]
GetHandleVerifier [0x00D52864+507076]
GetHandleVerifier [0x00D59F47+537511]
GetHandleVerifier [0x00D53402+510050]
Ordinal0 [0x00C4F29C+1110684]
Ordinal0 [0x00C5938B+1151883]
Ordinal0 [0x00C594F3+1152243]
Ordinal0 [0x00C583F5+1147893]
BaseThreadInitThunk [0x776400F9+25]
RtlGetAppContainerNamedObjectPath [0x77D97BBE+286]
RtlGetAppContainerNamedObjectPath [0x77D97B8E+238]

Еще раз говорю, проверьте версию браузера и попробуйте заменить веб-драйвер.

Вот обработанная ваша ссылка. Как видите, все работает.

1677294421688.png
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Редактор выделяет красным эту строку кода:
raise exception_class(message, screen, stacktrace)

Ну да. Вот погуглил немного. Эта ошибка: WebDriverException: Message: unknown error: cannot find Chrome binary означает, что ChromeDriver не смог найти двоичный файл Chrome в директории по умолчанию. Возможно, что у вас Google Chrome установлен куда-то в другое место. Вот страница на Stack Overflow: . Изучайте
 

satfan

Green Team
26.06.2022
75
1
BIT
42
Спасибо.
Всё супер.
Дело было в драйвере.
Я Вам отправлю на почту моё общение с ChatGPT
Там пытаемся созлать новый плейлист. Который будет работать на ПК.
 
18.05.2023
2
0
BIT
0
Здравствуйте! У меня вот такой результат выполнения скрипта:

Введите ссылку на видео-файл из Облака:
Не могу получить ссылку на плейлист с разрешениями: None

Process finished with exit code 0
Подскажите пожалуйста, что может быть не так?
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Здравствуйте! У меня вот такой результат выполнения скрипта:


Подскажите пожалуйста, что может быть не так?

Здравствуйте. Я только что попробовал работу скрипта. Все отработало хорошо. Но, на моей ссылке. То есть, я закинул видео в облако, поделился и взял ссылку.

screenshot1.png


А вот плейлист, который грузиться по этой ссылке:

screenshot2.png


По вашей же ссылке, когда ее открываешь в браузере получается вот такая вот картинка:

screenshot3.png


Соответственно, по вашей ссылке у меня такой же результат, как у вас:

screenshot4.png


Может быть дело в этом? ))
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!