出 token -> 回调本页 action=verify 做服务端二次校验 -> 展示官方接口原文。 * VAPTCHA 服务端两种方式:ep=1 官方 v41.vaptcha.com/api/verify;ep=2 本地 HMAC-SHA256 验签。 * * 密钥来源(软依赖,按优先级): * captcha_keys.php(不入库)> config.php 常量 > URL参数(临时)> robots.json(兜底) * robots.json / captcha_keys.php 均严禁入库(前者已在 .gitignore)。 */ // ==================== 服务端二次校验代理 ==================== 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'] : ''; $ep = isset($_GET['ep']) ? (int) $_GET['ep'] : 1; if ($token === '' || strlen($token) > 2048) { echo json_encode(['ok' => false, 'err' => 'token 为空'], JSON_UNESCAPED_UNICODE); exit; } $ip = $_SERVER['REMOTE_ADDR'] ?? ''; if ($provider === 'cf') { if ($k['cf_secret'] === '') { echo json_encode(['ok' => false, 'err' => 'CF 密钥(secret)未配置'], JSON_UNESCAPED_UNICODE); exit; } $endpoint = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; $body = ['secret' => $k['cf_secret'], 'response' => $token]; if ($ip !== '') $body['remoteip'] = $ip; $r = captchaHttpPostForm($endpoint, $body); echo json_encode([ 'ok' => $r['errno'] === 0, 'provider' => 'CF Turnstile', 'endpoint' => $endpoint, 'http' => $r['status'], 'body' => $r['body'], 'err' => $r['err'], ], JSON_UNESCAPED_UNICODE); exit; } if ($provider === 'vaptcha') { if ($k['v_key'] === '' || $k['v_vid'] === '') { echo json_encode(['ok' => false, 'err' => 'vaptcha VID/Key 未配置'], JSON_UNESCAPED_UNICODE); exit; } // 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 = [ '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 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; } echo json_encode(['ok' => false, 'err' => '未知 provider'], JSON_UNESCAPED_UNICODE); exit; } // ==================== 密钥解析(软依赖四级回退) ==================== function captchaTestKeys() { static $cache = null; if ($cache !== null) return $cache; $k = ['source' => '未配置', 'cf_sitekey' => '', 'cf_secret' => '', 'v_vid' => '', 'v_key' => '', 'v_scene' => 0]; // 1) captcha_keys.php(独立密钥文件,不入库) $f = __DIR__ . '/captcha_keys.php'; if (is_file($f)) { $CF_SITEKEY = ''; $CF_SECRET = ''; $VAPTCHA_VID = ''; $VAPTCHA_VKEY = ''; $VAPTCHA_SCENE = 0; @include $f; if ($CF_SITEKEY !== '' || $VAPTCHA_VID !== '') { $k = ['source' => 'captcha_keys.php', 'cf_sitekey' => (string) $CF_SITEKEY, 'cf_secret' => (string) $CF_SECRET, 'v_vid' => (string) $VAPTCHA_VID, 'v_key' => (string) $VAPTCHA_VKEY, 'v_scene' => (int) $VAPTCHA_SCENE]; return $cache = $k; } } // 2) config.php 常量(config.php 本身不入库) if (defined('CF_TURNSTILE_SITEKEY') || defined('VAPTCHA_VID')) { $k = ['source' => 'config.php 常量', 'cf_sitekey' => defined('CF_TURNSTILE_SITEKEY') ? CF_TURNSTILE_SITEKEY : '', 'cf_secret' => defined('CF_TURNSTILE_SECRET') ? CF_TURNSTILE_SECRET : '', 'v_vid' => defined('VAPTCHA_VID') ? VAPTCHA_VID : '', 'v_key' => defined('VAPTCHA_VKEY') ? VAPTCHA_VKEY : '', 'v_scene' => defined('VAPTCHA_SCENE') ? VAPTCHA_SCENE : 0]; if ($k['cf_sitekey'] !== '' || $k['v_vid'] !== '') return $cache = $k; } // 3) URL 参数(临时联调) $gcf = isset($_GET['cf_sitekey']) ? trim((string) $_GET['cf_sitekey']) : ''; $gvid = isset($_GET['v_vid']) ? trim((string) $_GET['v_vid']) : ''; if ($gcf !== '' || $gvid !== '') { $k = ['source' => 'URL参数(临时)', 'cf_sitekey' => $gcf, 'cf_secret' => isset($_GET['cf_secret']) ? (string) $_GET['cf_secret'] : '', 'v_vid' => $gvid, 'v_key' => isset($_GET['v_key']) ? (string) $_GET['v_key'] : '', 'v_scene' => isset($_GET['v_scene']) ? (int) $_GET['v_scene'] : 0]; return $cache = $k; } // 4) robots.json 兜底(严禁入库的密钥清单) $rj = __DIR__ . '/robots.json'; if (is_file($rj)) { $arr = json_decode((string) @file_get_contents($rj), true); if (is_array($arr)) { foreach ($arr as $item) { if (!is_array($item) || !isset($item['名字'])) continue; if ($item['名字'] === 'CF') { $k['cf_sitekey'] = (string) ($item['站点密钥'] ?? ''); $k['cf_secret'] = (string) ($item['密钥'] ?? ''); } elseif ($item['名字'] === 'vaptcha') { $k['v_vid'] = (string) ($item['VID'] ?? ''); $k['v_key'] = (string) ($item['VKey'] ?? ''); $k['v_scene'] = 0; } } if ($k['cf_sitekey'] !== '' || $k['v_vid'] !== '') { $k['source'] = 'robots.json'; } } } return $cache = $k; } function captchaHttpPostForm($url, array $fields) { $r = ['errno' => -1, 'status' => 0, 'body' => '', 'err' => '']; if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($fields), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 5, ]); $body = curl_exec($ch); $r['status'] = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $r['err'] = curl_error($ch); // PHP 8.0+ curl_close 为 no-op,8.5 起抛 Deprecated —— 不再调用,句柄随作用域释放 if ($body !== false) { $r['body'] = (string) $body; $r['errno'] = 0; } return $r; } $ctx = stream_context_create(['http' => [ 'method' => 'POST', 'timeout' => 10, 'header' => "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => http_build_query($fields)]]); $body = @file_get_contents($url, false, $ctx); if ($body !== false) { $r['body'] = (string) $body; $r['errno'] = 0; $r['err'] = 'file_get_contents'; } else $r['err'] = '无 curl 且请求失败'; return $r; } function captchaHttpPostJson($url, array $payload) { $r = ['errno' => -1, 'status' => 0, 'body' => '', 'err' => '']; $json = json_encode($payload, JSON_UNESCAPED_UNICODE); if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $json, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 5, ]); $body = curl_exec($ch); $r['status'] = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $r['err'] = curl_error($ch); // PHP 8.0+ curl_close 为 no-op,8.5 起抛 Deprecated —— 不再调用,句柄随作用域释放 if ($body !== false) { $r['body'] = (string) $body; $r['errno'] = 0; } return $r; } $ctx = stream_context_create(['http' => [ 'method' => 'POST', 'timeout' => 10, 'header' => "Content-Type: application/json\r\n", 'content' => $json]]); $body = @file_get_contents($url, false, $ctx); if ($body !== false) { $r['body'] = (string) $body; $r['errno'] = 0; $r['err'] = 'file_get_contents'; } else $r['err'] = '无 curl 且请求失败'; return $r; } // ==================== 页面 ==================== $K = captchaTestKeys(); function maskKey($s) { if ($s === '') return '(未配置)'; $len = strlen($s); if ($len <= 10) return $s; return substr($s, 0, 8) . str_repeat('*', max(4, $len - 12)) . substr($s, -4); } ?>
action=verify&provider=cf 走服务端 siteverify,
展示官方接口原文。action=verify&provider=vaptcha(默认官方接口,
失败可 改用本地 HMAC 验签重试)。