052-滑块验证码:丰巢登录

案例地址:丰巢登录

郑重声明:本文只做学习研究分享,若有人用文章技术做出不法行为与本人无关,如有侵权联系作者立即删除

找加密参数

丰巢登录会先弹一个滑块。先把它抓下来:按 F12 打开开发者工具,点 Network (网络)面板,勾上 Fetch/XHR (只看接口请求,过滤掉图片那些);然后在页面上把滑块滑一下,面板里就会唰唰列出这期间发的请求。挨个点开看,滑块相关的就两个接口,都在 acs.fcbox.com:

  • 取图:POST /captcha/querySlideImage/{uuid} ------ 把滑块图和背景图给你
  • 校验:POST /captcha/checkCode/{uuid} ------ 把你滑的结果提交上去

抓包整体长这样(和滑块有关的我都标出来了:①取图、②校验、③校验时才加载的加密代码):

取图接口是明文,POST 一个 {} 就返回一堆东西:

复制代码
data = { key, clientIp, checkId, pointX, pointY, slideImageUrl, shadeImageUrl }
  • key:这张图配的一把密码(AES 密钥),一张图一把,不能写死;
  • slideImageUrl:那个能拖的小滑块;shadeImageUrl:带缺口的背景大图;
  • uuid:前端随机生成的一个编号,取图和校验要用同一个。

在 Network 里点开这个取图请求、切到 Response ,就能看到这些参数------红框里的 key 就是这张图的 AES 密钥,后面加密用的就是它:

要还原的是校验接口那个看着像乱码的 body ------它是一整串 Base64(Base64 只是把一堆字节换成能打印的字符,方便传输,本身不是加密)。点开校验请求的 Payload,整屏就是它:

所以事情就两件:里面的内容怎么拼,以及怎么加密。而里面拼的东西,就是 sign(签名)和 track(滑动轨迹)。

定位加密位置

先说"去哪儿看代码",这一步很多新手会卡,我一步一步来。

第一步:认识 Sources。 按 F12 打开开发者工具,点最上面一排的 Sources。这个面板就是"这个网页加载过的所有 JS 文件"的仓库:左边是文件目录树,点哪个文件,右边就显示它的源码。

第二步:滑块代码在哪个文件。 一个网页的 HTML 里,会用 <script src="xxx.js"> 这种标签告诉浏览器"把 xxx.js 下载下来执行"。丰巢登录页的 HTML 里就写了 <script src=".../captcha/slider/fc_slider_v2.0.js">,所以页面一打开,fc_slider_v2.0.js 就已经下载好了 ,在 Sources 左边能直接找到(common.fcbox.com → ... → captcha/slider/fc_slider_v2.0.js)。滑块的主逻辑------画滑块、拼请求------都在这。

第三步:打开并美化。 点开这个文件(懒得翻目录就按 Ctrl+P,输入 fc_slider_v2.0.js 回车打开)。打开后是一整行压缩代码,没法读,点编辑器左下角那个 {} 图标"美化"一下,就变成多行可读了。

第四步:搜关键词定位到关键行。 在代码里按 Ctrl+F 搜 aesKey,就跳到校验前这段:

别被一堆 _0x 名字吓到,逆着读就四步(对应图里红框 ①~④):

  • _0x58a573 = _0x100f83['md5']:从加密模块里把 md5 函数取出来;
  • _0x5a6b8f = _0x271ca0 + _0x9d8fc6 + _0x31ab21 + _0x44bc33(_0x4c1279):把四段直接拼成一个长串,其中 _0x44bc33(_0x4c1279) 是把滑动轨迹数组拼成一长条;这四段我用真机对过,依次是 clientIp + checkId + uuid + 轨迹串;
  • _0x427682 = _0x58a573(_0x5a6b8f):就是 sign = md5(那串);
  • _0xa5e81f = _0x1ad9f1({'data':{'sign':_0x427682,'track':_0x4c1279},'aesKey':_0x39ab99}):把 sign、track 连同这张图的 aesKey 交给 encrypt,得到密文。

再往下一行就是发请求:data 填这段密文,url 拼 /captcha/checkCode/------你在抓包里看到的那串"乱码 body",就是它。

想看它发出去之前到底带了什么,也可以在 checkCode 上打个 XHR 断点 ,拖一下滑块就停住:右上角 ① 是暂停提示,右边 Scope 里 ② 的 data 就是这次要发的密文,下面 ③ 是 Call Stack 调用栈,一眼看清是谁调的:

更进一步,直接在 {sign, track, aesKey} 这一行下断点,拖滑块停住后,Scope 里每个变量当场就是真值------这是我真机停到的一次:

复制代码
_0x271ca0 (clientIp) = 111.25.238.195
_0x9d8fc6 (checkId)  = DAAA0E65FE4142E9AC216ABC0623EC55
_0x31ab21 (uuid)     = 06754300-6f13-4c25-a166-57ebed2f1ed9
_0x4c1279 (track)    = Array(30),首 {x:5,y:83,time:...} 尾 {x:150,y:83,time:...}
_0x39ab99 (aesKey)   = hwFhkeyTCnkwhtBt          ← 16 位,正是取图返回的那把 key
_0x5a6b8f (拼接串)    = 111.25.238.195DAAA0E65...(clientIp+checkId+uuid+轨迹串)
_0x427682 (sign)     = 559565ff77d07b6fa71c633346034e4d
_0xa5e81f (密文data) = ZCcZ9fALokGEgbMGtyo+DRy0...

# 在暂停的帧里,用页面自己的 md5 函数 _0x58a573 现算一遍:
_0x58a573(_0x5a6b8f) === _0x427682   →   true

到这儿,sign = md5(clientIp + checkId + uuid + 轨迹串) 就不是我嘴上说,是断点现场算给你看的。

那 aes.chunk.js 是怎么回事? 真正做加密的 encrypt 不在上面这个文件里,而在另一个小文件 aes.chunk.js。现在网站习惯把 JS 拆成很多小包(叫 chunk),用到哪块才下载哪块 。刚打开登录页时滑块还没弹、还没用到加密,所以 aes.chunk.js 还没下载,Sources 里暂时看不到它------不是没有,是还没来;等滑块真正要加密时它才被拉下来。知道有这回事就行,不用慌。

进到 aes.chunk.js(就是上面总览图里 ③ 那个"用到才加载"的文件),美化后看 encrypt 这块:

别被 _0x 吓到,图里我圈了三处,照着看就懂:

  • ① 这个模块导出两个函数:encrypt 和 md5(return _0x5b9645 就是 encrypt 本体);
  • ② _0x210a36 = _0xe93988'n' 这行,是从 crypto-js 里把 AES 模块取出来;
  • ③ 最后这个 return:把"明文"和"key"都先按 Utf8 转一下,再 AES.encrypt(明文, key, {mode, padding}),末尾 .toString()。

翻成人话就是这段(变量名换成你能读的):

复制代码
function encrypt(payload) {
  var data = payload.data;                 // {sign, track}
  var key  = CryptoJS.enc.Utf8.parse(payload.aesKey);
  var text = typeof data === 'string' ? data : JSON.stringify(data);
  return CryptoJS.AES.encrypt(
      CryptoJS.enc.Utf8.parse(text), key,
      { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }
  ).toString();
}

怎么看出来它是标准 AES? 先看写法:AES.encrypt(明文, key, {mode, padding}).toString() 就是 crypto-js 里最标准的调用,参数没有被魔改;toString() 默认就是把密文转成 Base64。但光"看着像"不算数------混淆之后 mode/padding 具体是哪个常量,肉眼看不出,得验。

怎么验(相互证明):

  1. 在浏览器里真滑一次,抓下校验请求那串密文,再拿同一张图的 key;

  2. 用 Python 的 AES-ECB + 这个 key 去解那串密文------能解回正常的 {sign, track} JSON ,就说明确实是 AES-ECB(mode 错一点,解出来就是一坨乱码);

  3. 再把解出来的 track 按 sign = md5(clientIp + checkId + uuid + 轨迹串) 重算一遍,跟包里带的 sign 对一下,看是不是一字不差。

真实跑出来的结果:

复制代码
# 用 key 解 checkCode 密文,得到明文(节选)
{"sign":"b5e692e202ae88a008b23077a72b34d1","track":[{"x":5,"y":58,"time":1792747684676},{"x":10,"y":58,"time":1792747684676}, ...]}

# 把 sign 重算一遍比对
sign in package : b5e692e202ae88a008b23077a72b34d1
md5 recomputed  : b5e692e202ae88a008b23077a72b34d1
MATCH           : True

两边一对上,"AES-ECB + PKCS7 + Base64、key 用取图返回的那把、sign 是四段拼接的 md5"------整条链就都实锤了。

补一句人话:AES 是「对称加密」,加密和解密用同一把钥匙;ECB 是它最省事的一种分组模式------不用额外的初始向量,同样的明文块永远得到同样的密文块。丰巢这里就是 ECB。

这里 sign 是「签名」:把几个字段拼一起丢进 md5 压成一小串,服务端用同样的规则再算一遍来核对,你改其中一个字段就对不上。轨迹串就是把每个滑动点的 x、y、时间 直接连成一长条(中间不加逗号、不加空格)。这四段拼接的顺序是固定的,错一个就过不了。

图像处理(找缺口)

能不能过,命门在这一步:滑块要滑到缺口的水平位置,得先从背景图上把缺口找出来。

丰巢这个缺口,不是 把滑块图原样贴回背景,而是背景上有一块比周围暗的、滑块形状的影子------你要找的就是这块"暗影"在哪。

我一开始按"把图贴回去"的思路做模板匹配(模板匹配就是拿一小块图在大图上滑,找最像的位置),结果它经常跑偏到离谱的地方(有次直接给到 x=14,实际缺口在 250 开外,差得没边)。后来换了个更对症的办法:拿滑块的形状在背景上从左往右移,看移到哪个位置时"框里比框外暗得最明显",那里就是缺口;再用一个只看边缘轮廓的方法独立算一遍,两个结果差不多就取平均。

下面这张是实拍的图:左边是滑块小图,右边是背景大图,红框就是程序找出来的缺口。这张里"暗影法"给 256、"轮廓法"给 259,差在容差内,取平均就是 258:

复现加密逻辑

把缺口位置换算成要滑的距离:背景图显示宽度和原始宽度差不多,所以缺口在第几个像素,基本就等于要滑过去多少像素 ,再加一个初始偏移 pointX;拖动有上限(最多 274 像素),每个点的 y 都固定等于 pointY,时间 用毫秒。

然后按 sign = md5(...) 和 AES-ECB 把 payload 拼出来、加密。轨迹也别画得太"完美":匀速一条直线风控一眼看出是机器,所以手动加了点"人味"------起步慢、中间快、快到头再慢下来,中间带一点点上下抖,冲过头一两像素再倒回去对准,每段花的时间也随机一点。

Python 侧加密就两行(pad 是把明文补齐到 16 字节的整数倍,AES 分组要求,补法叫 PKCS7):

复制代码
cipher = AES.new(key.encode(), AES.MODE_ECB)
base64.b64encode(cipher.encrypt(pad(明文.encode(), 16))).decode()

至于为什么是 ECB 不是 CBC:我把网站真实的 JS 拿到 Node 里跑,喂一段长明文,看密文分块之间有没有前后牵连------没有,就是 ECB。Python 写出来跟它逐字节比,一模一样。

结果

一个 fcbox_slider.py 从头跑到尾,每步都打日志,卡在哪一眼看见。这是我本机刚跑的一次:

复制代码
==============================================================
蜂巢滑块验证码 · 单文件全链路
==============================================================
[取图]     uuid=1b251e4f-48d6-4701-b835-2b17c98b1819  pointX=0  pointY=96
[识别缺口] hole=227  grad_ncc=230  sqdiff_rgb=201  ->  gap_x=228
[生成轨迹] 67 点  x: 0 -> 228  过冲 4px  ~1.30s
[提交校验] 第 1/3 次  code=400100000  [PASS]  通过
--------------------------------------------------------------
结果       : 通过  [PASS]
响应码     : 400100000
凭证 token : 267E216923AA4107871338AFF54E376B
--------------------------------------------------------------

code=400100000 就是通过,token 是滑块给的凭证,后面登录会用到它。失败重试时注意:响应里 checkId 会换新的,要用新的重新签,别拿旧的。

踩坑

  • 定位加密别一上来就搜 CryptoJS:丰巢页面上它只会命中一个不相干的 jsencrypt.min.js(那是另一套 RSA 库)。真正能一下定位到加密的,是搜接口名 querySlideImage 或 aesKey;
  • key 一张图一把,别写死;uuid 取图和校验要用同一个;
  • sign 里 clientIp + checkId + uuid + 轨迹串 的顺序、以及轨迹点数字"直接相连不加分隔",错一处就过不了;
  • 缺口识别别用"把滑块贴回背景"的思路,它是背景上一块暗影,用贴图的模板匹配会跑偏。

小结

丰巢这点事,加密不难(就是标准 AES-ECB + md5 签名),难在找缺口 ------别被"把滑块贴回去"的惯性想法带偏,它其实是背景上一块暗影子。找对位置基本一把过,我连测 4 次全中。定位的时候也别被文件名骗着去搜 CryptoJS,顺着 aesKey 这条线反而最快。如有问题请及时提出,加油加油。

相关推荐
东鹏特饮3 小时前
A 股创历史新低筛选器phthon爬虫脚本
爬虫·股票·phthon
深蓝电商API3 天前
用GPT-4o解析无结构网页:提示词工程实战
爬虫·gpt-4o
数据狐(Datafox)3 天前
一文读懂京东商品详情 API:返回字段解读与 JSON 样例
开发语言·数据库·爬虫·json
傻啦嘿哟3 天前
百度图片爬虫:爬取海量图片数据,搭建图像识别训练集
爬虫·百度
SEO_juper3 天前
AI 爬虫 robots.txt 检查工具:你的站是不是把 AI 挡在门外了
开发语言·人工智能·爬虫·python·外贸独立站
OnlineProxy3 天前
Python 高负载异步爬虫优化指南:深挖 httpx 与 aiohttp 的性能极限
爬虫·python·httpx
SEO_juper4 天前
hreflang 审计工具:外贸多语言站谷歌SEO的隐性杀手
开发语言·人工智能·爬虫·python·seo
深蓝电商API4 天前
Firecrawl、ScrapeGraphAI:AI爬虫框架横评
人工智能·爬虫·firecrawl·scrapegraphai
小花皮猪4 天前
从 Prompt 到结构化 JSON:如何利用 Bright Data 快速搭建 YouTube 监测工作流?
人工智能·爬虫·agent·数据采集