浅度剖析B站的新

您所在的位置:网站首页 bilibili显示网络错误 浅度剖析B站的新

浅度剖析B站的新

2024-07-06 03:07| 来源: 网络整理| 查看: 265

某日,PipePipe开始加载不出up主投稿列表,并且报错-352 风控校验失败。开发者根据社区讨论进行了几次修复,但效果都没有持续太久。由于前段时间比较忙,遂暂停使用PipePipe,改用电脑浏览器进行B站冲浪。

电脑浏览器虽然能够加载出up主页面,但投稿列表加载前会跳出登录提示框。拒绝登录后投稿列表就不会加载了。根据bilibili-API-collect项目的讨论,特殊UAMozilla/5.0可以绕过B站的新风控策略,猜测是B站开发者特意留作debug用途。我没有仔细研究,直接用插件将访问B站使用的UA改为Mozilla/5.0,投稿列表就可以加载出来了。

然而好景不长,这周这个“后门”UA失效了。为了持续收看心爱的up主的最新投稿,我决定把这个新的风控机制彻底解决一下。

逆向工程是一门很复杂的学问,初学者通常很难找到方法上手,因此本篇会讲得详细一些,希望能够帮助到对web逆向工程有兴趣的新手。

开始!

在继续使用后门UAMozilla/5.0的情况下,访问up主主页(形如https://space.bilibili.com/8047632)时会在加载内容之前跳出登录提示框(二维码处的条纹图案是因为我的日用浏览器LibreWolf禁用WebGL)

2024-01-21T23:52:56.png

拒绝登录之后就会出现如下错误提示,并且整个页面空白。

2024-01-21T23:54:39.png

那么老规矩,掏出我们的开发者工具切到网络tab上,筛选出所有XHR请求(本意指通过XMLHttpRequest发出的请求,实际上fetch请求也算在内,可以认为包含所有对后端API的AJAX请求)逐条看过来。B站的API没有使用标准HTTP错误码,而是在返回的json中定义自己的错误码体系,所以我们得点进Response里才能看到哪里出了问题。首先是两条对/x/kv-frontend/namespace/data返回-304的请求。

2024-01-21T23:55:16.png

这两个请求的参数看起来没什么意思,返回的message似乎也无关紧要。我们继续往下看:

2024-01-21T23:55:24.png

这条请求有一些不对劲。错误代码和PipePipe抓取投稿列表的时候的报错一致,那么我们来仔细看看这个请求。众所周知,HTTP请求由类型、端点、参数和headers构成,其中最重要的headers包括cookies和user-agent。首先看参数:

2024-01-21T23:55:33.png

w_rid和wts是B站特有的WBI签名,mid是up主的用户ID(这里拿官方举个例子),此外似乎就没有什么有趣的信息了。那么问题大概率出在headers上。此时可以选择控制变量法来观察到底是哪个header触发了风控。右键一条请求可以把它以各种格式导出,比如可以导出为cURL命令:

2024-01-21T23:55:50.png

导出的内容可以直接放在命令行执行:

2024-01-21T23:55:58.png

可以看到返回了一样的结果-352。

如果我们打开一个什么配置都没有的阳春Firefox,访问同样的页面,投稿列表则是能够成功加载的。那么我们可以用同样的方法找到同样的API请求,并且对比headers的不同之处,通过逐渐修改header的值向能够成功的请求靠近,并找出具体触发风控的header项。

最后我发现问题在于User Agent(……)如果使用一个常见浏览器的User Agent值,比如Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0或者Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36,就可以过风控:

投稿列表接口的新风控机制

所以研究了半天,居然是之前设置的后门UA起到了反作用。解决方案也很简单:用同样的方法把UA设置成某个常见值就完事了。解决完这个小问题之后我们来研究投稿列表为什么加载不出。改完UA之后打开up主页面是这样的:

2024-01-21T23:56:54.png

老规矩,不登录:

2024-01-21T23:57:06.png

还是一样的方法挖API请求,可以看见这条:

2024-01-21T23:57:22.png2024-01-21T23:57:30.png

相比之下,一个阳春Firefox发出的这条请求会返回的正是投稿列表:2024-01-21T23:58:09.png

我们一样对这两条请求进行对比。需要注意的是每次修改请求参数都必须重新生成WBI签名,否则100%会触发-403 访问权限不足(修改header则不需要)。直接在命令行操作太麻烦了,bilibili-API-collect介绍WBI签名的文档有提供计算签名的Python脚本,可以拿来用。

最后得出结论如下:

Cookies无关紧要,包括里面的bvuid3、bvuid4以及bili_ticket等,忽略这些cookie也可以。User Agent必须是常见值。多一个字符都不行。dm_img_list参数必须有,值用空列表即可,但是不能省略。dm_img_str和dm_cover_img_str也必须有。这两个参数我们接下来介绍。

用来发出成功请求的最小Python示例是这样的:

import wbi # wbi.py内容修改自bilibili-API-collect的例子 import requests import json headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"} def get_videos(mid): endpoint = "https://api.bilibili.com/x/space/wbi/arc/search" params = { "mid": mid, "dm_img_list": "[]", "dm_img_str": "V2ViR0wgMS", "dm_cover_img_str": "SW50ZWwoUikgSEQgR3JhcGhpY3NJbnRlbA", } params = wbi.sign(params) return json.loads(requests.get(endpoint, params, headers=headers).text) WebGL指纹

现在我们来看dm_img_str和dm_cover_img_str这两个参数。以上脚本使用的值是阳春Firefox发出的请求里截取出来的。应该不难想到这两串东西可能是base64编码,因此我们可以尝试base64解码:2024-01-21T23:58:25.png

虽然报错invalid input,但是这个报错无关痛痒,我们还是得到了一些信息。这是因为base64编码的方式是将8bit的字符三个一组连成24个bit,对于每6个bit进行查表翻译(不足6bit的部分用0补全),最后用=补到4的整数倍长度。这个字符串长度为10,可见B站前端逻辑裁掉了最后两个字符。

另外一串dm_cover_img_str解码出来则是Intel(R) HD GraphicsIntel。看到这个格式我们应该就知道这是什么了。为了确认,我们可以再试试Chromium,解码得到的分别是WebGL 1.0 (OpenGL ES 2.0 Chromium)和ANGLE (Intel, Mesa Intel(R) Graphics (ADL GT2), OpenGL 4.6)Google Inc. (Inte。很显然,这两个分别是WebGL的版本和渲染引擎的相关信息,可以用以下JS代码获取:

gl = document.createElement('canvas').getContext('webgl'); version = gl.getParameter(gl.VERSION) ext = gl.getExtension('WEBGL_debug_renderer_info'); vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); console.log(`${version} | ${renderer} | ${vendor}`);

Firefox输出:WebGL 1.0 | Intel(R) HD Graphics | Intel

Chromium输出:WebGL 1.0 (OpenGL ES 2.0 Chromium) | ANGLE (Intel, Mesa Intel(R) Graphics (ADL GT2), OpenGL 4.6) | Google Inc. (Intel)

而在禁用了WebGL的LibreWolf上,第二行就报错了,因为不存在WebGL context,gl的值为null。这时B站前端使用的dm_img_str和dm_cover_img_str都为bm8gd2ViZ2,经base64解码为no webg,推测应该是no webgl。

到这里,思路已经非常清晰了。我们可以推断B站做了这几件事:

尝试获取WebGL版本、vendor以及renderer。如果失败,使用缺省值no webgl。把renderer和vendor连起来,使用base64编码作为dm_cover_img_str。把版本号用base64编码作为dm_img_str。在请求search端点的时候带上这两个参数,以及空的dm_img_list。后端进行解码后,no webgl或者非常见值将触发风控返回-352。

还记得B站对User Agent的风控策略吗?这里看来B站的工程师采用了相同的思路,即对一些常见浏览器的WebGL指纹予以白名单放行。

看源码

有了这个猜想,大致的应对方法就有了。在我们开始实现之前,最好能把相关JS源码挖出来确认一下。这时我们可以利用Dev Tools的Stack Trace功能。找到对应的请求,切到Stack Trace页面,就可以看到发起请求的函数调用栈:

2024-01-21T23:59:18.png

最上面的两条是浏览器内置函数的调用,因此我们可以直接点进第三条(t/ { let url = new URL((args[0].startsWith("//") ? "https:" : "") + args[0]); if (url.host == "api.bilibili.com" && url.pathname == "/x/space/wbi/arc/search") { url.searchParams.set("dm_img_list", "[]"); url.searchParams.set("dm_img_str", "V2ViR0wgMS"); url.searchParams.set("dm_cover_img_str", "SW50ZWwoUikgSEQgR3JhcGhpY3NJbnRlbA"); let paramsObj = Object.fromEntries(Array.from(url.searchParams)) delete paramsObj.wts; delete paramsObj.w_rid; const { wts, w_rid } = wbiSign(paramsObj); url.searchParams.set("wts", wts); url.searchParams.set("w_rid", w_rid); args[0] = url.href; } const response = await origFetch(...args); return response; };

MD5这里,我在Greasy Fork上找到一个实现,直接在脚本头上@require 即可使用。顺带着我把dm_img_str和dm_cover_img_str也处理了一下,这样就不需要前面伪装WebGL指纹的代码了。

杂谈

逆向工程是一件有趣的事。通过一些技术手段,能够从一个特殊的视角去看到普通用户感知不到的现象。在分析代码的过程中,我似乎与写出这些代码的人建立了某种对话,透过代码去接近开发者的思维、洞察开发者的意图。

我不知道写出这些代码的人怎么看待自己写出的“作品”,甚至我也不知道自己应该怎么看待这篇博文和它会产生的实际影响。我必须承认风控策略存在的合理性,但我非常、非常不喜欢现行的风控思维,即通过过度侵略性的方式事无巨细地收集一切能收集到的信息,然后通过这些信息建立起的用户画像判断用户的风险等级。在本文的例子中,收集WebGL指纹尚且不算作具有侵略性,但是如你所见,对一个具有少量web知识储备的人(比如我)来说,这种风控形同虚设。从我决定解决这个风控带来的困扰到我写完这篇文章的初稿,一共只花了两天时间。因此,在以数据为根基的风控哲学指导下,风控对隐私权的僭越只会越来越变本加厉,比如这里的dm_img_list风控法。可以看出B站这次新推出的风控暂且没有做得很绝——使用空列表就能过,但这里一定有开发人员预留的操作空间,方便日后进一步收紧。

我对自己隐私的介意程度可能超过了99%的互联网用户,以至于我禁用了WebGL,常年通过一系列代理上网,还使用了很多反指纹手段让我的浏览器难以被精准识别。然而,这么做的后果就是我时不时会碰到各式的captcha,并且一些网页的浏览体验会大打折扣。很遗憾,在现行的大数据时代的“风控”机制眼中,重视隐私权的用户会被算法和黑产人士划上等号,因而享受到黑产级待遇。至于这些所谓的“风控”有多少程度是出于打击黑产,又有多少程度是披着风控的皮干着监视、跟踪、分析、控制用户的勾当,我也说不清——或许没有人说得清。

长期以来,B站(和许多其他互联网产品)开发人员和高级用户之间维持着一种微妙的平衡。我一样不知道B站采用这么简陋的风控手段有多少是出于开发者只想讨个生活混口饭吃,又有多少是出于做人留一线日后好想见(或许完全没有)。作为一家规模不小的科技企业,如果B站想,完全可以把整个站点都放到登录墙后,但无论如何感谢B站没有这么做,并且留了不少“漏洞”给我这样的用户。前段时间我阅读了B站官方发布的关于风控机制的一篇文章,在其中B站开发拼命给WBI签名这套风控系统贴金,将其宣传得无比高级。很好笑的是说不定在几年后,我也会成为这种文章的作者,吹捧着自己从心底里讨厌的内容。因为这就是生活,而生活一直都是一个充满妥协、充满不确定、充满将就和对付、一地鸡毛的东西。

到头来我也只是一个迷茫的普通人。诚实地讲,我也不知道有什么更好的方式来达到风控的效果。我处在B站用户的困境中,但能够理解B站运营者和开发人员的困境。正是因此,我不会将我逆向B站后写出的脚本公开出来。我讲解了思路,贴出了主要代码,并相信愿意仔细阅读我的博文的读者都是有他们的苦衷但本质善良的技术爱好者。如果你花了一些时间来研究,复现我的解决方案应该非常容易,并且如果你愿意与我交流技术,也可以在Telegram上找到我。

仅有一条评论 l34xbiftamg l34xbiftamg 2024-03-13 20:34

不用这么麻烦。&dm_img_list=[{"x":748,"y":-1686,"z":0,"timestamp":716,"k":123,"type":0}]&dm_img_str=V2ViR0wgMS&dm_cover_img_str=QU5HTEUgKE5WSURJQSwgTlZJRElBIEdlRm9yY2UgR1RYIDk4MCBEaXJlY3QzRDExIHZzXzVfMCBwc181XzApLCBvciBzaW1pbGFyR29vZ2xlIEluYy4gKE5WSURJQS&dm_img_inter={"ds":[{"t":0,"c":"","p":[9,3,3],"s":[80,6232,2116]}],"wh":[4904,4883,48],"of":[212,424,212]}上面这一些参数是原来的请求没有的,随便打开一个b站请求,截取到上面的参数,追加到原来的url中,done!

为什么要留个人信息啊?

回复 取消回复 逆向!去除B站不登录1分钟自动暂停限制 Blog Site Upgrade


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3