你好!几天前,我写了一篇关于小型个人程序的文章,我提到使用“秘密”未记录的 API 会很有趣,您需要将 cookie 从浏览器中复制出来才能访问它们。
有几个人问如何做到这一点,所以我想解释一下,因为它非常简单。我们还将讨论可能出现的问题、道德问题,以及这如何适用于您未记录的 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
header 以获取压缩输出。
我们可以通过将输出传递到gunzip
来解压缩它,但我发现不发送该标头更简单。所以让我们删除一些不相关的标题。
第 3 步:删除不相关的标题
这是我从浏览器获得的完整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
。这更易于管理。
第 4 步:将其翻译成 Python
现在我们知道我们需要什么标头,我们可以将curl
命令转换为 Python 程序!这部分也是一个非常机械的过程,目标只是使用 Python 发送与使用 curl 完全相同的数据。
这就是它的样子。这与前面的curl
命令完全相同,但使用 Python 的requests
。我还将很长的请求正文字符串分解为一个元组数组,以便更容易以编程方式使用。
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
替换了一堆东西,那是因为如果我包含这些值,你可以访问我的帐户的 Google Hangouts API,这将是不好的。
我们完成了!
现在我可以修改 Python 程序来做任何我想做的事情,比如传递不同的参数或解析输出。
我不会用它做任何有趣的事情,因为我实际上根本对使用这个 API 不感兴趣,我只是想展示一下这个过程是什么样子的。
但是我们得到了一堆你绝对可以用它来做点什么的 JSON。
这基本上总是有效的
你们中的一些人可能想知道——你能一直这样做吗?
答案基本上是肯定的——浏览器并不神奇!浏览器发送到后端的所有信息都只是 HTTP 请求。因此,如果我复制浏览器发送的所有 HTTP 标头,后端实际上无法判断请求不是由我的浏览器发送的,而是由随机 Python 程序发送的。
当然,我们删除了浏览器发送的一堆标头,因此理论上后端可以知道,但通常他们不会检查。
既然我们已经了解了如何使用此类未记录的 API,让我们来谈谈可能出错的一些事情。
问题 1:过期的会话 cookie
这里的一个大问题是我正在使用我的 Google 会话 cookie 进行身份验证,所以只要我的浏览器会话过期,这个脚本就会停止工作。
这意味着这种方法不适用于长时间运行的程序(我想使用真正的 API),但如果我只需要快速抓取一点数据作为 1-time 的东西,它可以很好地工作!
问题2:滥用
如果我使用的是小型网站,我的小 Python 脚本有可能会取消他们的服务,因为它处理的请求比他们能够处理的要多得多。因此,当我这样做时,我会尽量保持尊重,不要过快提出太多要求。
这一点尤其重要,因为许多没有官方 API 的站点都是资源较少的较小站点。
在这个例子中,这显然不是问题——我想我在写这篇博文时总共向 Google Hangouts 后端发出了 20 个请求,他们绝对可以处理。
此外,如果您过度使用您的帐户凭据访问 API 并导致问题,您可能(非常合理地)暂停您的帐户。
我还坚持下载属于我的或打算公开访问的数据——我不是在寻找漏洞。
请记住,任何人都可以使用您未记录的 API
我认为最重要的是要知道这实际上并不是如何使用其他人未记录的 API。这很有趣,但它有很多限制,我实际上并不经常这样做。
了解任何人都可以对您的后端 API 执行此操作更为重要!每个人都有开发者工具和网络标签,很容易看到你传递给后端的参数并改变它们。
因此,如果任何人都可以通过更改某些参数来获取其他用户的信息,那就不好了。我认为大多数构建公开可用 API 的开发人员都知道这一点,但我之所以提到它,是因为每个人都需要在某个时候第一次学习它 🙂
来源: https://jvns.ca/blog/2022/03/10/how-to-use-undocumented-web-apis/