Привет! Пару дней я писал о крошечных личных программах , и упомянул, что может быть забавно использовать «секретные» недокументированные API, где вам нужно скопировать ваши куки из браузера, чтобы получить к ним доступ.
Несколько человек спросили, как это сделать, поэтому я хотел объяснить, как это сделать, потому что это довольно просто. Мы также немного поговорим о том, что может пойти не так, об этических проблемах и о том, как это относится к вашим недокументированным API.
В качестве примера возьмем Google Hangouts. Я выбираю это не потому, что это самый полезный пример (я думаю, что есть официальный API, который будет гораздо более практичным в использовании), а потому, что многие сайты, где это действительно полезно, являются небольшими сайтами, которые более уязвимы для злоупотреблений. Так что мы просто собираемся использовать Google Hangouts, потому что я на 100% уверен, что серверная часть Google Hangouts спроектирована так, чтобы быть устойчивой к такого рода возням.
Давайте начнем!
шаг 1: посмотрите в инструментах разработчика многообещающий ответ JSON
Я начинаю с того, что захожу на https://hangouts.google.com , открываю вкладку сети в инструментах разработчика Firefox и ищу ответы JSON. Вы также можете использовать инструменты разработчика Chrome.
Вот как это выглядит
Запрос является хорошим кандидатом, если в столбце «Тип» указано «json».
Мне пришлось некоторое время осмотреться, пока я не нашел что-то интересное, но в конце концов я нашел конечную точку «люди», которая, кажется, возвращает информацию о моих контактах. Звучит весело, давайте посмотрим на это.
шаг 2: скопировать как cURL
Затем я щелкаю правой кнопкой мыши по интересующему меня запросу и нажимаю «Копировать» -> «Копировать как cURL».
Затем я вставляю команду curl
в свой терминал и запускаю ее. Вот что происходит.
$ curl 'https://people-pa.clients6.google.com/v2/people/?key=REDACTED' -X POST ........ (a bunch of headers removed) Warning: Binary output can mess up your terminal. Use "--output -" to tell Warning: curl to output it to your terminal anyway, or consider "--output Warning: <FILE>" to save to a file.
Вы можете подумать — это странно, что это за ошибка «двоичный вывод может испортить ваш терминал»? Это связано с тем, что по умолчанию браузеры отправляют на сервер заголовок Accept-Encoding: gzip, deflate
для получения сжатого вывода.
Мы могли бы распаковать его, направив вывод в gunzip
, но я считаю, что проще просто не отправлять этот заголовок. Итак, давайте удалим некоторые ненужные заголовки.
шаг 2: удалите ненужные заголовки
Вот полная командная строка curl
, которую я получил из браузера. Здесь много! Я начинаю с разделения запроса с помощью обратной косой черты ( \
), чтобы каждый заголовок находился в отдельной строке, чтобы с ним было проще работать:
curl 'https://people-pa.clients6.google.com/v2/people/?key=REDACTED' \ -X POST \ -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0' \ -H 'Accept: */*' \ -H 'Accept-Language: en' \ -H 'Accept-Encoding: gzip, deflate' \ -H 'X-HTTP-Method-Override: GET' \ -H 'Authorization: SAPISIDHASH REDACTED' \ -H 'Cookie: REDACTED' -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'X-Goog-AuthUser: 0' \ -H 'Origin: https://hangouts.google.com' \ -H 'Connection: keep-alive' \ -H 'Referer: https://hangouts.google.com/' \ -H 'Sec-Fetch-Dest: empty' \ -H 'Sec-Fetch-Mode: cors' \ -H 'Sec-Fetch-Site: same-site' \ -H 'Sec-GPC: 1' \ -H 'DNT: 1' \ -H 'Pragma: no-cache' \ -H 'Cache-Control: no-cache' \ -H 'TE: trailers' \ --data-raw 'personId=101777723309&personId=1175339043204&personId=1115266537043&personId=116731406166&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_GET&extensionSet.extensionNames=HANGOUTS_PHONE_DATA&includedProfileStates=ADMIN_BLOCKED&includedProfileStates=DELETED&includedProfileStates=PRIVATE_PROFILE&mergedPersonSourceOptions.includeAffinity=CHAT_AUTOCOMPLETE&coreIdParams.useRealtimeNotificationExpandedAcls=true&requestMask.includeField.paths=person.email&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo&requestMask.includeField.paths=person.read_only_profile_info&requestMask.includeField.paths=person.organization&requestMask.includeField.paths=person.location&requestMask.includeField.paths=person.cover_photo&requestMask.includeContainer=PROFILE&requestMask.includeContainer=DOMAIN_PROFILE&requestMask.includeContainer=CONTACT&key=REDACTED'
Сначала это может показаться огромным количеством вещей, но вам не нужно думать о том, что все это означает на данном этапе. Вам просто нужно удалить ненужные строки.
Обычно я просто выясняю, какие заголовки я могу удалить методом проб и ошибок — я продолжаю удалять заголовки, пока запрос не начнет давать сбой. В общем случае вам, вероятно, не нужны Accept*
, Referer
, Sec-*
, DNT
, User-Agent
и кэширующие заголовки.
В этом примере я смог сократить запрос до этого:
curl 'https://people-pa.clients6.google.com/v2/people/?key=REDACTED' \ -X POST \ -H 'Authorization: SAPISIDHASH REDACTED' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'Origin: https://hangouts.google.com' \ -H 'Cookie: REDACTED'\ --data-raw 'personId=101777723309&personId=1175339043204&personId=1115266537043&personId=116731406166&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_GET&extensionSet.extensionNames=HANGOUTS_PHONE_DATA&includedProfileStates=ADMIN_BLOCKED&includedProfileStates=DELETED&includedProfileStates=PRIVATE_PROFILE&mergedPersonSourceOptions.includeAffinity=CHAT_AUTOCOMPLETE&coreIdParams.useRealtimeNotificationExpandedAcls=true&requestMask.includeField.paths=person.email&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo&requestMask.includeField.paths=person.read_only_profile_info&requestMask.includeField.paths=person.organization&requestMask.includeField.paths=person.location&requestMask.includeField.paths=person.cover_photo&requestMask.includeContainer=PROFILE&requestMask.includeContainer=DOMAIN_PROFILE&requestMask.includeContainer=CONTACT&key=REDACTED'
Так что мне просто нужно 4 заголовка: Authorization
, Content-Type
, Origin
и Cookie
. Это гораздо более управляемо.
шаг 3: переведите его на Python
Теперь, когда мы знаем, какие заголовки нам нужны, мы можем перевести нашу команду curl
в программу на Python! Эта часть также представляет собой довольно механический процесс, цель которого состоит в том, чтобы отправить точно такие же данные с помощью Python, как мы это делали с помощью curl.
Вот как это выглядит. Это точно так же, как и предыдущая команда curl
, но с использованием requests
Python. Я также разбил очень длинную строку данных на массив кортежей, чтобы упростить программную работу с ней.
import requests import urllib data = [ ('personId','101777723'), # I redacted these IDs a bit too ('personId','117533904'), ('personId','111526653'), ('personId','116731406'), ('extensionSet.extensionNames','HANGOUTS_ADDITIONAL_DATA'), ('extensionSet.extensionNames','HANGOUTS_OFF_NETWORK_GAIA_GET'), ('extensionSet.extensionNames','HANGOUTS_PHONE_DATA'), ('includedProfileStates','ADMIN_BLOCKED'), ('includedProfileStates','DELETED'), ('includedProfileStates','PRIVATE_PROFILE'), ('mergedPersonSourceOptions.includeAffinity','CHAT_AUTOCOMPLETE'), ('coreIdParams.useRealtimeNotificationExpandedAcls','true'), ('requestMask.includeField.paths','person.email'), ('requestMask.includeField.paths','person.gender'), ('requestMask.includeField.paths','person.in_app_reachability'), ('requestMask.includeField.paths','person.metadata'), ('requestMask.includeField.paths','person.name'), ('requestMask.includeField.paths','person.phone'), ('requestMask.includeField.paths','person.photo'), ('requestMask.includeField.paths','person.read_only_profile_info'), ('requestMask.includeField.paths','person.organization'), ('requestMask.includeField.paths','person.location'), ('requestMask.includeField.paths','person.cover_photo'), ('requestMask.includeContainer','PROFILE'), ('requestMask.includeContainer','DOMAIN_PROFILE'), ('requestMask.includeContainer','CONTACT'), ('key','REDACTED') ] response = requests.post('https://people-pa.clients6.google.com/v2/people/?key=REDACTED', headers={ 'X-HTTP-Method-Override': 'GET', 'Authorization': 'SAPISIDHASH REDACTED', 'Content-Type': 'application/x-www-form-urlencoded', 'Origin': 'https://hangouts.google.com', 'Cookie': 'REDACTED', }, data=urllib.parse.urlencode(data), ) print(response.text)
Я запустил эту программу, и она работает — она выводит кучу JSON! Ура!
Вы заметите, что я заменил кучу вещей на REDACTED
, потому что, если бы я включил эти значения, вы могли бы получить доступ к API Google Hangouts для моей учетной записи, что было бы бесполезно.
и мы закончили!
Теперь я могу модифицировать программу Python, чтобы она делала все, что захочу, например, передавать разные параметры или анализировать вывод.
Я не собираюсь делать с ним ничего интересного, потому что я вообще не заинтересован в использовании этого API, я просто хотел показать, как выглядит процесс.
Но мы получаем кучу JSON, с которыми вы определенно можете что-то сделать.
это в основном всегда работает
Некоторым из вас может быть интересно, всегда ли вы можете это делать?
Ответ в основном да — браузеры не волшебство! Вся информация, которую браузеры отправляют на ваш сервер, — это просто HTTP-запросы. Поэтому, если я скопирую все HTTP-заголовки, которые отправляет мой браузер, серверная часть буквально не сможет определить, что запрос отправлен не моим браузером, а на самом деле отправлен случайной программой Python.
Конечно, мы удалили кучу заголовков, отправленных браузером, так что теоретически серверная часть могла это определить, но обычно они не проверяют.
Теперь, когда мы увидели, как использовать подобные недокументированные API, давайте поговорим о некоторых вещах, которые могут пойти не так.
проблема 1: срок действия файлов cookie сеанса
Одна большая проблема здесь заключается в том, что мы используем мой файл cookie сеанса Google для аутентификации, поэтому этот скрипт перестанет работать всякий раз, когда истечет срок действия моего сеанса браузера.
Это означает, что этот подход не будет работать для долго работающей программы (я бы хотел использовать настоящий API), но если мне просто нужно быстро получить немного данных за один раз, он может отлично работать. !
проблема 2: злоупотребление
Если я использую небольшой веб-сайт, есть шанс, что мой маленький скрипт Python может отключить их службу, потому что он выполняет гораздо больше запросов, чем они могут обработать. Поэтому, когда я делаю это, я стараюсь быть уважительным и не делать слишком много запросов.
В этом примере, очевидно, это не проблема — я думаю, что сделал всего 20 запросов к серверной части Google Hangouts, когда писал этот пост в блоге, и они определенно справятся.
Кроме того, если вы используете учетные данные своей учетной записи для доступа к API чрезмерным образом и создаете проблемы, вы можете (весьма разумно) приостановить действие своей учетной записи.
Я также придерживаюсь загрузки данных, которые либо принадлежат мне, либо предназначены для публичного доступа — я не ищу уязвимости.
помните, что любой может использовать ваши недокументированные API
Я думаю, что самое важное, что нужно знать об этом, — это не то, как использовать недокументированные API других людей . Это забавно, но у этого есть много ограничений, и я на самом деле делаю это не так часто.
Гораздо важнее понимать, что любой может сделать это с вашим внутренним API! У всех есть инструменты разработчика и вкладка сети, и довольно легко увидеть, какие параметры вы передаете серверной части, и изменить их.
Поэтому, если кто-то может просто изменить некоторые параметры, чтобы получить информацию о другом пользователе, это нехорошо. Я думаю, что большинство разработчиков, создающих общедоступные API-интерфейсы, знают об этом, но я упоминаю об этом, потому что каждый должен изучить это впервые в какой-то момент 🙂