[slide_captcha]极验验证热
144423
- 极验验证ID slide_captcha版本 2.0.2插件制作者 li3295售价 5 积分830 行 / 35.4 KBHook / 路由 / 后台页接入极验行为验证第四代(GT4),用于注册、发帖、回帖校验;可限制换 IP / 设备重复注册。收费插件安装说明购买后点击“授权码”获取一次性授权码;在后台 > 插件广场点击在线安装或更新,并填写该授权码。已购买用户更新免费:每次在线安装或更新前,重新点击“授权码”获取新的一次性授权码即可,无需再次购买;也可直接下载新版后用后台“插件上传”更新。尊重并感谢他人劳动,请勿传播代码。app/plugins/slide_captcha/plugin.php开发日志已有 4 条
此项目接受所有登录用户协助维护和提交更新。
主楼 插件质量报告
插件:
slide_captchaHook 功能范围 循环 使用频率 文件读写 修改系统表 读写系统表 读写自己的表 page.before_render 全站页面输出前 否 5 - - - - page.head 全站页面 head 否 5 - - - - register.form_extra 注册表单 否 3 - - - - reply.before_save 回帖发布/编辑表单提交 回调内 3 读写 修正❗️ - - - topic.before_save 主题发布/编辑表单提交 回调内 3 读写 修正❗️ - - - user.after_save 用户注册/资料保存后 否 3 - - - 写 plugin_slide_captcha_reg_marks user.before_save 用户注册/资料保存前 回调内 3 读写 修正❗️ - - 读 plugin_slide_captcha_reg_marks reply.form_extra 回帖编辑表单 否 2 - - - - topic.form_extra 主题发布/编辑表单 否 2 - - - - 颜色说明:黄色表示有数据读写,红色表示 Hook 在系统循环中执行,浅蓝色表示有文件写入。“列表跳过”表示回调在列表路径会提前返回,“回调内”表示操作位于回调自身或其帮助函数的循环体内。“文件批处理”表示逐个处理用户一次提交的文件,属于必要操作,不标记修正。只有操作实际处于需要优化的循环路径时,才会标记“修正❗️”。
数据字典
plugin_slide_captcha_reg_marks字段 类型 可空 默认值 约束 id id 否 - 主键、自增 kind key 否 - 唯一 mark_key key 否 - 唯一 user_id uint 否 0 - created_at uint 否 - - 版本 1.2.0 更新:
放灌水 后台可以设置开启关闭 注册、发帖和回帖前必须完成滑动验证,无法跳过;并可限制同一 IP 与浏览器只能注册一个账号。注:以下仅适用于 滑块验证 本身而并不包括其余限制措施。
该验证存在以下问题
- 服务端仅验证数据,且过度相信客户端数据:
percent = 100; // 直接 100% duration = 800; // 合法区间 moves = 10; // 合法区间使用此数据发送请求即可直接通过。
存在严重问题:前端丢弃了收集的拖动信息,在松开后重新获取了令牌,并提交了
[0, 16, 32, 48, 64, 80]这组预设数据,导致行为验证完全失效。
同时,这也暴露出了一个问题:预制的机器数据可以通过验证!
您可复制以下脚本,粘贴至浏览器控制台执行。
(async function bypassSlideCaptcha() { const widgets = document.querySelectorAll('[data-slide-captcha]'); if (!widgets.length) { console.error('未找到滑动验证组件'); return; } for (const root of widgets) { const scene = root.getAttribute('data-slide-captcha-scene') || 'register'; const challengeUrl = root.getAttribute('data-challenge-url'); const tickUrl = root.getAttribute('data-tick-url'); const verifyUrl = root.getAttribute('data-verify-url'); if (!challengeUrl || !tickUrl || !verifyUrl) { console.warn('滑块缺少必要URL,跳过'); continue; } const csrf = document.querySelector('input[name="_csrf"]')?.value || ''; const post = async (url, bodyObj) => { const formData = new FormData(); for (const [k, v] of Object.entries(bodyObj)) { formData.append(k, v); } if (csrf) formData.append('_csrf', csrf); const res = await fetch(url, { method: 'POST', body: formData, credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } }); return await res.json(); }; const challengeResp = await fetch( challengeUrl + (challengeUrl.includes('?') ? '&' : '?') + 'scene=' + encodeURIComponent(scene), { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } } ); const challengeData = await challengeResp.json(); if (!challengeData || !challengeData.ok || !challengeData.token) { console.error('获取挑战失败', challengeData); continue; } const token = challengeData.token; console.log(`[${scene}] token:`, token); const points = [0, 16, 32, 48, 64, 80]; let prevPercent = -1; for (let i = 0; i < points.length; i++) { const p = points[i]; if (p <= prevPercent) continue; const delay = i === 0 ? 30 : 75; await new Promise(r => setTimeout(r, delay)); const tickData = await post(tickUrl, { token, percent: String(p) }); if (!tickData || !tickData.ok) { console.error('tick 失败', tickData); throw new Error('tick 失败'); } prevPercent = p; } const verifyData = await post(verifyUrl, { token, scene }); if (!verifyData || !verifyData.ok || !verifyData.ticket) { console.error('验证失败', verifyData); continue; } const ticket = verifyData.ticket; console.log(`[${scene}] ticket:`, ticket); const ticketInput = root.querySelector('[data-slide-captcha-ticket]'); if (ticketInput) { ticketInput.value = ticket; } // 更新 UI,可选,不影响后端判断 root.classList.add('is-ok'); root.classList.remove('is-fail'); const textEl = root.querySelector('[data-slide-captcha-text]'); if (textEl) textEl.textContent = '验证通过'; const progress = root.querySelector('[data-slide-captcha-progress]'); if (progress) progress.style.width = '100%'; const knob = root.querySelector('[data-slide-captcha-knob]'); if (knob) { knob.style.left = 'auto'; knob.style.right = '3px'; knob.disabled = true; } const refresh = root.querySelector('[data-slide-captcha-refresh]'); if (refresh) refresh.hidden = false; console.log(`[${scene}] 验证绕过成功`); } console.log('滑动验证已绕过,请尝试提交表单'); })();正在优化 优化完了更新
版本 1.4.4 更新:
优化bug还不如接入极验的验证码。
版本 2.0.2 更新:
更新了