尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

054-滑块验证码:知网期刊接口

054-滑块验证码:知网期刊接口 案例地址中国药理学与毒理学杂志·期刊导航郑重声明本文只做学习研究分享若有人用文章技术做出不法行为与本人无关如有侵权联系作者立即删除先埋个坑一个很像 md5的请求头先按 F12 打开开发者工具点Network网络面板勾Fetch/XHR只看接口刷新期刊页。你会看到数据是这条接口给的POST https://navi.cnki.net/knavi/journals/YLBS/papers/outline有意思的是早些年这个接口当时还叫/papers的请求头里会多出一个signature值是 64 个十六进制字符旁边还跟一个timestamp。64 位十六进制 SHA256/md5 那类摘要的长度于是很多人包括当年照着写的我二话不说就去逆这个signature把它当成加密参数。但这次我想先讲一个更重要的习惯看到可疑参数先别急着逆先验证它到底作不作数。因为知网这个signature就是个障眼法。验证一signature 其实是骗你的我抓了当前线上的/papers/outline先看它现在的请求头——里面根本没有signature/timestamp了只有常规的 Cookie、UA、Referer。也就是说这个字段早就下线了。那它当年到底校不校验我做了个对照实验同一个接口、同一套参数只动signature看服务器反不反应。带 cookie、不带 signature → 20059280 字节正常返回论文数据 不带 cookie → 200但只有 75 字节返回检索暂无数据 带一个我瞎编的假 signature → 20059280 字节照样是完整论文数据看出问题了吧塞一个假的signature进去服务器理都不理数据照给。它压根不校验这个头。真正决定你能不能拿到数据的是那条 cookie——没有效的会话 cookie接口就只回你一句检索暂无数据。所以这个signature从头到尾就是个放出来骗人的让你以为要逆 md5实际上服务器根本不读它。这类假签名在很多站点都有专门用来劝退爬虫、或者纯粹是历史遗留没清干净。教训就一句逆之前先拿改一个字节看它还认不认这招验一下。真正的门blockPuzzle 滑块那 cookie 从哪来你第一次访问、没有效会话时知网会把你重定向到一个验证页https://kns.cnki.net/verify/home?captchaTypeblockPuzzleident...captchaId...一个拼图滑块——过滑块才发给你那套 session cookie。这才是真正要过的关。抓它的接口一眼就能认出这是anji-plus 的开源滑块识别特征captchaTypeblockPuzzle 一把secretKey 两张 base64 图 一个tokenPOST https://kns.cnki.net/verify-api/get请求体{captchaType:blockPuzzle,clientUid:32位随机,ident:eea05a,captchaId:页面给的,ts:1792923801272}返回节选{code:0,data:{ secretKey:0sdMA6LYTYqV9rQm, originalImageBase64:iVBORw0KGgo...带缺口的背景图, jigsawImageBase64:iVBORw0KGgo...要拼进去的那块, token:42933c4eca6541a0b61725f4... }}逆滑块三步就能过1.get拿背景图、缺口块、secretKey、token2.算缺口 x把缺口块当模板在背景图上做模板匹配匹配到的横坐标就是要滑过去的距离3.check把{x, y}用secretKey做 AES 加密提交验证。上图就是真实的那张滑块背景图红框是我用cv2.matchTemplate算出来的缺口位置这张匹配分 0.96。缺口块长这样check 的构造是关键我去翻了它的 JSverify/js/xxx.js确认了字段e 310 * 移动像素 / 显示图宽; // 把显示距离换算回原图(310宽)坐标 const n { captchaType: blockPuzzle, pointJson: AES(JSON.stringify({x:e, y:5}), secretKey), // 加密的是 {x,y} token: 那个token, ident: ..., returnUrl: ... }; POST /verify-api/check body n三个坑我全踩过给你标出来check 的字段叫pointJson不是reqData我一开始按 anji 老文档写reqData直接返回未注册token是单独一个字段不要塞进加密的{x,y}里x用原图310 宽坐标——我是直接在原图上做模板匹配的所以拿到的 x 已经是原图坐标不用再乘那个310/显示宽的缩放。完整可跑的 Python真机验证过import time, hashlib, base64, json import numpy as np, cv2, requests from Crypto.Cipher import AES from Crypto.Util.Padding import pad IDENT, CID eea05a, 页面给的captchaId RETURN 页面给的returnUrl UA (Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36) s requests.Session() s.headers.update({User-Agent: UA, uniplatform: NZKPT, Origin: https://kns.cnki.net, Content-Type: application/json;charsetUTF-8, Referer: https://kns.cnki.net/verify/home?captchaTypeblockPuzzle}) cu hashlib.md5(str(time.time()).encode()).hexdigest() # 1) get拿图 secretKey token data s.post(https://kns.cnki.net/verify-api/get, json{ captchaType: blockPuzzle, clientUid: cu, ident: IDENT, captchaId: CID, ts: int(time.time()*1000)}).json()[data] sk, token data[secretKey], data[token] # 2) 缺口识别模板匹配用拼图块的 alpha 当掩码 orig cv2.imdecode(np.frombuffer(base64.b64decode(data[originalImageBase64]), np.uint8), cv2.IMREAD_COLOR) jig cv2.imdecode(np.frombuffer(base64.b64decode(data[jigsawImageBase64]), np.uint8), cv2.IMREAD_UNCHANGED) res cv2.matchTemplate(orig, jig[:, :, :3], cv2.TM_CCOEFF_NORMED, maskjig[:, :, 3]) x cv2.minMaxLoc(res)[3][0] # 3) checkpointJson base64(AES-ECB/PKCS7(JSON{x,y:5}, secretKey)) point json.dumps({x: int(x), y: 5}, separators(,, :)) reqData base64.b64encode(AES.new(sk.encode(), AES.MODE_ECB).encrypt(pad(point.encode(), 16))).decode() r s.post(https://kns.cnki.net/verify-api/check, json{ captchaType: blockPuzzle, pointJson: reqData, token: token, ident: IDENT, returnUrl: RETURN, clientUid: cu, ts: int(time.time()*1000)}).json() print(check:, r.get(code), r.get(data, {}).get(result))过完滑块怎么就换成数据了check返回code:0只是第一步——它本身并不直接给你数据权限。我实测过同一个会话里 check 成功了再直接打/papers/outline照样只回暂无数据。真正放行的是 check 成功后、前端紧接着做的那一步。去翻它的 JSverify/js/342.xxx.jscheck 成功后它这么干// 1) 用当初那把 secretKey把 token---{x,y} 再加密一次得到通行凭证 var cv AES( backToken --- JSON.stringify({x:e, y:5}), secretKey ); // base64 // 2) 连同 returnUrl 一起交给父组件父组件带它回跳原页面 this.$parent.$emit(success, { captchaVerification: cv, url: returnUrl ?captchaId... });所以check 返回里真正能用上的是这个captchaVerification顺带一提它响应里那个pointJson用同一把 secretKey 能解出{x:168,y:5}——那是服务器把正确答案回显给你方便你校验识别准不准不是凭证。整条放行链是这样1. 前端算出captchaVerification AES(token --- {x,y}, secretKey)2. 带着它 returnUrl服务端加密过的目标地址回跳3. 后端用当初给这个token存的那把secretKey解开captchaVerification核对x对不对、token是不是刚验过的那次4. 对上了才Set-Cookie 下发 navi 的会话 cookieJSESSIONID、Ecp_ClientId、cnkiUserKey…并放回原页面5. 之后你带着这套 cookie 再请求/papers/outline就正常出数据了。一句话服务器是靠clientUidtokencaptchaVerification这套关联在服务端记你过了再发 cookie 放行——不是把权限塞进某个你能自己造的字段里。所以纯算复现时check 之后还得走这步captchaVerification兑换光有code:0不够。结果这段我连跑了 4 次check每次都返回code:0、result:true——anji-plus 这套滑块是可稳定程序化通过的gap x258 score0.918 check code0 resultTrue gap x225 score0.967 check code0 resultTrue gap x240 score0.957 check code0 resultTrue gap x130 score0.925 check code0 resultTruecheck 成功后站点用返回的returnUrl回跳、发放会话 cookie有了这套 cookie前面那条/papers/outline就正常出数据了对照实验里带 cookie→59280 字节有数据、不带→暂无数据就是这条线。踩坑别一上来就逆signature它十有八九是障眼法先改一个字节看服务器认不认验证一下省得白干anji-plus 的 check 字段是pointJson不是reqDatatoken单独传x用原图坐标缺口匹配用拼图块的alpha 通道当 maskTM_CCOEFF_NORMED更稳一般 0.9 就是准的顺带一个 Python 坑cv2.imwrite存含中文的路径会静默失败返回 False 不报错改用cv2.imencode open().write(buf)。小结这篇的反转就是知网那个看着像 md5 的signature是故意放出来骗人的服务器根本不校验真正挡路的是 anji-plus 的 blockPuzzle 滑块而它是可逆的get 拿图secretKeytoken → cv2 模板匹配算缺口 → AES 加密{x,y}成pointJson→ check 返回 code:0。所以以后见着可疑 header/参数第一件事永远是验证它到底作不作数别被障眼法带沟里。如有问题请及时提出加油加油。
返回列表