Recaptcha

PHP admin_5abc 10 Views Size: 4.79 KB Posted on: Aug 14, 25 @ 11:22 PM
  1. <?php
  2. /*
  3.  * Paste $v3.1 2025/08/16 https://github.com/boxlabss/PASTE
  4.  * demo: https://paste.boxlabs.uk/
  5.  *
  6.  * https://phpaste.sourceforge.io/
  7.  *
  8.  * This program is free software; you can redistribute it and/or
  9.  * modify it under the terms of the GNU General Public License
  10.  * as published by the Free Software Foundation; either version 3
  11.  * of the License, or (at your option) any later version.
  12.  *
  13.  * This program is distributed in the hope that it will be useful,
  14.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.  * GNU General Public License in LICENCE for more details.
  17.  */
  18.  
  19. // includes/recaptcha.php
  20. declare(strict_types=1);
  21.  
  22. // pull secrets and knobs
  23. $RECAPTCHA_V3_SECRET = trim($_SESSION['recaptcha_secretkey'] ?? '');
  24. $RECAPTCHA_V2_SECRET = trim($_SESSION['recaptcha_secretkey'] ?? '');
  25. $RECAPTCHA_MIN_SCORE = 0.8;
  26. $RECAPTCHA_MAX_AGE   = 120;
  27. $RECAPTCHA_HOST = parse_url((string)($_SESSION['baseurl'] ?? ''), PHP_URL_HOST) ?: ($_SERVER['SERVER_NAME'] ?? '');
  28.  
  29.  
  30. // low-level HTTP call
  31. function recaptcha_siteverify(array $payload): ?array {
  32.     $ctx = stream_context_create([
  33.         'http' => [
  34.             'method'  => 'POST',
  35.             'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
  36.             'content' => http_build_query($payload),
  37.             'timeout' => 8,
  38.         ]
  39.     ]);
  40.     $resp = @file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, $ctx);
  41.     if ($resp === false) return null;
  42.     $data = json_decode($resp, true);
  43.     return is_array($data) ? $data : null;
  44. }
  45.  
  46. // accept exact host or subdomain suffix
  47. function host_matches(string $reported, string $expected): bool {
  48.     $reported = strtolower($reported);
  49.     $expected = strtolower($expected);
  50.     if ($reported === $expected) return true;
  51.     return (bool)preg_match('/(^|\.)' . preg_quote($expected, '/') . '$/i', $reported);
  52. }
  53.  
  54. // v3 verify
  55. function verify_recaptcha_v3(string $token, string $expectedAction): array {
  56.     global $RECAPTCHA_V3_SECRET, $RECAPTCHA_MIN_SCORE, $RECAPTCHA_MAX_AGE, $RECAPTCHA_HOST;
  57.     if ($token === '' || $RECAPTCHA_V3_SECRET === '') return ['ok' => false, 'why' => 'missing'];
  58.  
  59.     $data = recaptcha_siteverify([
  60.         'secret'   => $RECAPTCHA_V3_SECRET,
  61.         'response' => $token,
  62.         'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
  63.     ]);
  64.     if (!$data || empty($data['success'])) {
  65.         return ['ok' => false, 'why' => ($data['error-codes'][0] ?? 'not_success')];
  66.     }
  67.  
  68.     if (!empty($data['action']) && $expectedAction !== '' && $data['action'] !== $expectedAction) {
  69.         return ['ok' => false, 'why' => 'bad_action'];
  70.     }
  71.     if (!empty($data['hostname']) && $RECAPTCHA_HOST && !host_matches($data['hostname'], $RECAPTCHA_HOST)) {
  72.         return ['ok' => false, 'why' => 'bad_host'];
  73.     }
  74.  
  75.     if (!empty($data['challenge_ts'])) {
  76.         $age = time() - strtotime($data['challenge_ts']);
  77.         if ($age > $RECAPTCHA_MAX_AGE) return ['ok' => false, 'why' => 'stale'];
  78.     }
  79.  
  80.     $score = (float)($data['score'] ?? 0.0);
  81.     if ($score < $RECAPTCHA_MIN_SCORE) return ['ok' => false, 'why' => 'low_score', 'score' => $score];
  82.  
  83.     return ['ok' => true, 'score' => $score];
  84. }
  85.  
  86. // v2 verify (checkbox)
  87. function verify_recaptcha_v2(string $token): array {
  88.     global $RECAPTCHA_V2_SECRET;
  89.     if ($token === '' || $RECAPTCHA_V2_SECRET === '') return ['ok' => false, 'why' => 'missing'];
  90.     $data = recaptcha_siteverify([
  91.         'secret'   => $RECAPTCHA_V2_SECRET,
  92.         'response' => $token,
  93.         'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
  94.     ]);
  95.     if (!$data || empty($data['success'])) return ['ok' => false, 'why' => ($data['error-codes'][0] ?? 'not_success')];
  96.     return ['ok' => true];
  97. }
  98.  
  99. // single gate used by controllers
  100. function require_human(string $expectedAction): void {
  101.     $cap_e   = $_SESSION['cap_e']              ?? 'off';
  102.     $mode    = $_SESSION['mode']               ?? 'Normal';
  103.     $ver     = $_SESSION['recaptcha_version']  ?? 'v2';
  104.     $token   = trim((string)($_POST['g-recaptcha-response'] ?? ''));
  105.  
  106.     // only enforce when admin enabled reCAPTCHA
  107.     if ($cap_e !== 'on' || $mode !== 'reCAPTCHA') return;
  108.  
  109.     if ($ver === 'v3') {
  110.         $res = verify_recaptcha_v3($token, $expectedAction);
  111.         if (!$res['ok']) {
  112.             error_log('reCAPTCHA v3 failed: ' . ($res['why'] ?? 'unknown') . (isset($res['score']) ? (' score=' . $res['score']) : ''));
  113.             http_response_code(403);
  114.             die('reCAPTCHA failed.');
  115.         }
  116.         return;
  117.     }
  118.  
  119.     $res = verify_recaptcha_v2($token);
  120.     if (!$res['ok']) {
  121.         error_log('reCAPTCHA v2 failed: ' . ($res['why'] ?? 'unknown'));
  122.         http_response_code(403);
  123.         die('reCAPTCHA failed.');
  124.     }
  125. }

Raw Paste