diff --git a/.gitignore b/.gitignore index c5b9783..e9d82f3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ db/debug_ip_v2.php api/test_db.php api/diag_db_content.php robots.json +captcha_keys.php # 开发辅助文档(本地保留,不推送到仓库) /代码关联关系.md diff --git a/captcha_test.php b/captcha_test.php index 1f1a8db..5fe54ab 100644 --- a/captcha_test.php +++ b/captcha_test.php @@ -4,8 +4,9 @@ * * 上下布局同时接入: * 1. Cloudflare Turnstile - * 2. VAPTCHA (v3) + * 2. VAPTCHA V4(V3 的 v-cn/cdn/0/api.vaptcha.com 已全部 NXDOMAIN,2026-10-01 切 V4) * 各自渲染 -> 出 token -> 回调本页 action=verify 做服务端二次校验 -> 展示官方接口原文。 + * VAPTCHA 服务端两种方式:ep=1 官方 v41.vaptcha.com/api/verify;ep=2 本地 HMAC-SHA256 验签。 * * 密钥来源(软依赖,按优先级): * captcha_keys.php(不入库)> config.php 常量 > URL参数(临时)> robots.json(兜底) @@ -16,6 +17,7 @@ if (isset($_GET['action']) && $_GET['action'] === 'verify') { header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store'); + ini_set('display_errors', '0'); // PHP 8.5 的 Deprecated 告警不得污染 JSON $k = captchaTestKeys(); $provider = isset($_GET['provider']) ? $_GET['provider'] : ''; $token = isset($_GET['token']) ? (string) $_GET['token'] : ''; @@ -49,23 +51,49 @@ if (isset($_GET['action']) && $_GET['action'] === 'verify') { echo json_encode(['ok' => false, 'err' => 'vaptcha VID/Key 未配置'], JSON_UNESCAPED_UNICODE); exit; } - // 候选端点:1=官方文档新接口,2=经典 verify 接口(实测择优) - $endpoints = [ - 1 => 'https://0.vaptcha.com/verify', - 2 => 'https://api.vaptcha.com/v2/validate', - ]; - $endpoint = $endpoints[$ep] ?? $endpoints[1]; + // VAPTCHA V4 二次验证两种方式(2026-10-01 官方文档:document/install) + // ep=1(默认)官方 verify 接口;ep=2 本地 HMAC-SHA256 验签(token = ts.id.sig) + $knock = isset($_GET['knock']) ? (string) $_GET['knock'] : ''; + $dfu = isset($_GET['dfu']) ? (string) $_GET['dfu'] : ''; + $sip = isset($_GET['ip']) ? (string) $_GET['ip'] : $ip; // 优先 SDK 签名 IP + if (strlen($knock) > 256 || strlen($dfu) > 512 || strlen($sip) > 64) { + echo json_encode(['ok' => false, 'err' => 'knock/dfu/ip 参数过长'], JSON_UNESCAPED_UNICODE); + exit; + } + + if ($ep === 2) { + // 方式二回退:本地验签(文档给的官方签名规则) + $parts = explode('.', $token); + $pass = false; $why = ''; + if (count($parts) !== 3) { $why = 'token 非三段式'; } + elseif (abs(time() - (int) $parts[0]) > 180) { $why = 'token 超过 180s 有效期'; } + else { + $expected = hash_hmac('sha256', $parts[0] . '.' . $sip . '.' . $dfu . '.' . $knock, $k['v_key']); + $pass = hash_equals($expected, $parts[2]); + if (!$pass) $why = '签名校验不通过'; + } + echo json_encode([ + 'ok' => true, 'provider' => 'VAPTCHA V4 本地验签', 'mode' => 'local-hmac', + 'pass' => $pass, 'why' => $why, + ], JSON_UNESCAPED_UNICODE); + exit; + } + + $endpoint = 'https://v41.vaptcha.com/api/verify'; $payload = [ - 'id' => $k['v_vid'], - 'secretkey' => $k['v_key'], - 'scene' => $k['v_scene'], - 'token' => $token, - 'ip' => $ip, + 'vid' => $k['v_vid'], + 'vkey' => $k['v_key'], + 'token' => $token, + 'knock' => $knock, + 'dfu' => $dfu, + 'ip' => $sip, ]; $r = captchaHttpPostJson($endpoint, $payload); + $decoded = json_decode($r['body'], true); echo json_encode([ - 'ok' => $r['errno'] === 0, 'provider' => 'VAPTCHA', + 'ok' => $r['errno'] === 0, 'provider' => 'VAPTCHA V4', 'mode' => 'official-verify', 'endpoint' => $endpoint, 'http' => $r['status'], + 'result' => is_array($decoded) ? ($decoded['data']['result'] ?? null) : null, 'body' => $r['body'], 'err' => $r['err'], ], JSON_UNESCAPED_UNICODE); exit; @@ -156,7 +184,7 @@ function captchaHttpPostForm($url, array $fields) { $body = curl_exec($ch); $r['status'] = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $r['err'] = curl_error($ch); - curl_close($ch); + // PHP 8.0+ curl_close 为 no-op,8.5 起抛 Deprecated —— 不再调用,句柄随作用域释放 if ($body !== false) { $r['body'] = (string) $body; $r['errno'] = 0; } return $r; } @@ -186,7 +214,7 @@ function captchaHttpPostJson($url, array $payload) { $body = curl_exec($ch); $r['status'] = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $r['err'] = curl_error($ch); - curl_close($ch); + // PHP 8.0+ curl_close 为 no-op,8.5 起抛 Deprecated —— 不再调用,句柄随作用域释放 if ($body !== false) { $r['body'] = (string) $body; $r['errno'] = 0; } return $r; } @@ -278,14 +306,20 @@ function maskKey($s) {
action=verify&provider=vaptcha(端点1失败可
- 点此切换端点2重试)。action=verify&provider=vaptcha(默认官方接口,
+ 失败可 改用本地 HMAC 验签重试)。