From 9033eff8ea10466b32712a89f14d2fcd99fb7482 Mon Sep 17 00:00:00 2001 From: Qi HU Date: Sat, 11 Oct 2025 23:38:27 +0800 Subject: [PATCH] feat: Refine captcha configuration and drivers Introduce a configurable captcha manager with drivers for image, Cloudflare Turnstile, and Google reCAPTCHA, including fallback behaviour. Refactor login, signup, complain, and related flows to use the new abstraction while simplifying the legacy image endpoint. Document captcha environment options and restore classic defaults in .env.example. Signed-off-by: Qi HU --- .env.example | 16 ++ .../Captcha/CaptchaDriverInterface.php | 19 +++ app/Services/Captcha/CaptchaManager.php | 120 ++++++++++++++ .../Captcha/Drivers/ImageCaptchaDriver.php | 156 ++++++++++++++++++ .../Drivers/RecaptchaV2CaptchaDriver.php | 134 +++++++++++++++ .../Drivers/TurnstileCaptchaDriver.php | 143 ++++++++++++++++ .../Exceptions/CaptchaValidationException.php | 10 ++ config/captcha.php | 27 +++ include/functions.php | 130 ++++++++++----- include/globalfunctions.php | 1 + public/complains.php | 2 +- public/confirm_resend.php | 2 +- public/image.php | 66 ++------ public/recover.php | 2 +- public/takelogin.php | 2 +- public/takesignup.php | 4 +- 16 files changed, 734 insertions(+), 100 deletions(-) create mode 100644 app/Services/Captcha/CaptchaDriverInterface.php create mode 100644 app/Services/Captcha/CaptchaManager.php create mode 100644 app/Services/Captcha/Drivers/ImageCaptchaDriver.php create mode 100644 app/Services/Captcha/Drivers/RecaptchaV2CaptchaDriver.php create mode 100644 app/Services/Captcha/Drivers/TurnstileCaptchaDriver.php create mode 100644 app/Services/Captcha/Exceptions/CaptchaValidationException.php create mode 100644 config/captcha.php diff --git a/.env.example b/.env.example index 670e5402..cbd1aa08 100644 --- a/.env.example +++ b/.env.example @@ -96,6 +96,22 @@ CHANNEL_NAME_SETTING=channel_setting CHANNEL_NAME_MODEL_EVENT=channel_model_event FORCE_SCHEME= +# Captcha settings +# Available drivers: image, cloudflare_turnstile, google_recaptcha_v2 +CAPTCHA_DRIVER=image + +# Cloudflare Turnstile keys (used when CAPTCHA_DRIVER=cloudflare_turnstile) +TURNSTILE_SITE_KEY= +TURNSTILE_SECRET_KEY= +TURNSTILE_THEME=light +TURNSTILE_SIZE=flexible + +# Google reCAPTCHA v2 keys (used when CAPTCHA_DRIVER=google_recaptcha_v2) +RECAPTCHA_SITE_KEY= +RECAPTCHA_SECRET_KEY= +RECAPTCHA_THEME=light +RECAPTCHA_SIZE=normal + CROWDIN_ACCESS_TOKEN= CROWDIN_PROJECT_ID= diff --git a/app/Services/Captcha/CaptchaDriverInterface.php b/app/Services/Captcha/CaptchaDriverInterface.php new file mode 100644 index 00000000..743777c3 --- /dev/null +++ b/app/Services/Captcha/CaptchaDriverInterface.php @@ -0,0 +1,19 @@ + */ + protected array $drivers = []; + + protected ?array $config = null; + + public function driver(?string $name = null): CaptchaDriverInterface + { + $name = $name ?? $this->getDefaultDriver(); + + $driver = $this->getDriverInstance($name); + + if ($name !== 'image' && !$driver->isEnabled()) { + return $this->driver('image'); + } + + return $driver; + } + + public function render(array $context = []): string + { + return $this->driver()->render($context); + } + + public function verify(array $payload, array $context = []): bool + { + try { + return $this->driver()->verify($payload, $context); + } catch (CaptchaValidationException $exception) { + throw $exception; + } + } + + public function isEnabled(): bool + { + return $this->driver()->isEnabled(); + } + + protected function getDriverInstance(string $name): CaptchaDriverInterface + { + if (!isset($this->drivers[$name])) { + try { + $this->drivers[$name] = $this->resolveDriver($name); + } catch (\InvalidArgumentException $exception) { + if ($name !== 'image') { + return $this->getDriverInstance('image'); + } + throw $exception; + } + } + + return $this->drivers[$name]; + } + + protected function resolveDriver(string $name): CaptchaDriverInterface + { + $config = $this->getConfigValue("drivers.$name", []); + + if (!is_array($config) || empty($config)) { + throw new \InvalidArgumentException("Captcha driver [$name] is not defined."); + } + + $driverClass = Arr::get($config, 'class'); + + if (!$driverClass || !class_exists($driverClass)) { + throw new \InvalidArgumentException("Captcha driver class for [$name] is invalid."); + } + + $driver = new $driverClass($config); + + if (!$driver instanceof CaptchaDriverInterface) { + throw new \InvalidArgumentException("Captcha driver [$name] must implement " . CaptchaDriverInterface::class); + } + + return $driver; + } + + protected function getDefaultDriver(): string + { + return (string) $this->getConfigValue('default', 'image'); + } + + protected function getConfigValue(string $key, $default = null) + { + if ($this->config === null) { + $config = null; + if (function_exists('app')) { + try { + $repository = app('config'); + if ($repository) { + $config = $repository->get('captcha'); + } + } catch (\Throwable $exception) { + $config = null; + } + } + + if (!is_array($config) && function_exists('nexus_config')) { + $config = nexus_config('captcha', []); + } + + if (!is_array($config)) { + $path = (defined('ROOT_PATH') ? ROOT_PATH : dirname(__DIR__, 3) . DIRECTORY_SEPARATOR) . 'config/captcha.php'; + $config = is_file($path) ? require $path : []; + } + + $this->config = is_array($config) ? $config : []; + } + + return Arr::get($this->config, $key, $default); + } +} diff --git a/app/Services/Captcha/Drivers/ImageCaptchaDriver.php b/app/Services/Captcha/Drivers/ImageCaptchaDriver.php new file mode 100644 index 00000000..69952c99 --- /dev/null +++ b/app/Services/Captcha/Drivers/ImageCaptchaDriver.php @@ -0,0 +1,156 @@ +config = $config; + } + + public function isEnabled(): bool + { + return true; + } + + public function render(array $context = []): string + { + $labels = $context['labels'] ?? []; + $imageLabel = $labels['image'] ?? 'Security Image'; + $codeLabel = $labels['code'] ?? 'Security Code'; + $secret = $context['secret'] ?? ''; + + $imagehash = $this->issue(); + $imageUrl = htmlspecialchars(sprintf('image.php?action=regimage&imagehash=%s&secret=%s', $imagehash, $secret ?? ''), ENT_QUOTES, 'UTF-8'); + + return implode("\n", [ + sprintf('%sCAPTCHA', htmlspecialchars($imageLabel, ENT_QUOTES, 'UTF-8'), $imageUrl), + sprintf('%s', htmlspecialchars($codeLabel, ENT_QUOTES, 'UTF-8'), htmlspecialchars($imagehash, ENT_QUOTES, 'UTF-8')), + ]); + } + + public function verify(array $payload, array $context = []): bool + { + $imagehash = trim((string) ($payload['imagehash'] ?? '')); + $imagestring = trim((string) ($payload['imagestring'] ?? '')); + + if ($imagehash === '' || $imagestring === '') { + throw new CaptchaValidationException('Missing captcha parameters.'); + } + + $query = sprintf( + "SELECT dateline FROM regimages WHERE imagehash='%s' AND imagestring='%s'", + mysql_real_escape_string($imagehash), + mysql_real_escape_string($imagestring) + ); + + $sql = sql_query($query); + $imgcheck = mysql_fetch_array($sql); + + $this->deleteByHash($imagehash); + + if (empty($imgcheck['dateline'])) { + throw new CaptchaValidationException('Invalid captcha response.'); + } + + return true; + } + + public function issue(): string + { + $random = random_str(); + $imagehash = md5($random); + $dateline = time(); + + $sql = sprintf( + "INSERT INTO `regimages` (`imagehash`, `imagestring`, `dateline`) VALUES ('%s', '%s', '%s')", + mysql_real_escape_string($imagehash), + mysql_real_escape_string($random), + mysql_real_escape_string((string) $dateline) + ); + + sql_query($sql); + + return $imagehash; + } + + public function outputImage(string $imagehash): void + { + $query = sprintf( + "SELECT imagestring FROM regimages WHERE imagehash=%s", + sqlesc($imagehash) + ); + + $sql = sql_query($query); + $regimage = mysql_fetch_array($sql); + $imagestring = $regimage['imagestring'] ?? ''; + + if ($imagestring === '') { + $this->renderFallback(); + return; + } + + $characters = implode(' ', str_split($imagestring)); + + if (!function_exists('imagecreatefrompng')) { + $this->renderFallback(); + return; + } + + $fontwidth = imageFontWidth(5); + $fontheight = imageFontHeight(5); + $textwidth = $fontwidth * strlen($characters); + $textheight = $fontheight; + + $randimg = rand(1, 5); + $imagePath = ROOT_PATH . "public/pic/regimages/reg{$randimg}.png"; + + if (!is_file($imagePath)) { + $this->renderFallback(); + return; + } + + $im = imagecreatefrompng($imagePath); + $imgheight = imagesy($im); + $imgwidth = imagesx($im); + $textposh = (int) floor(($imgwidth - $textwidth) / 2); + $textposv = (int) floor(($imgheight - $textheight) / 2); + + $dots = (int) floor($imgheight * $imgwidth / 35); + for ($i = 1; $i <= $dots; $i++) { + imagesetpixel($im, rand(0, $imgwidth - 1), rand(0, $imgheight - 1), imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255))); + } + + $textcolor = imagecolorallocate($im, 0, 0, 0); + imagestring($im, 5, $textposh, $textposv, $characters, $textcolor); + + header('Content-type: image/png'); + imagepng($im); + imagedestroy($im); + } + + protected function deleteByHash(string $imagehash): void + { + if ($imagehash === '') { + return; + } + + $delete = sprintf( + "DELETE FROM regimages WHERE imagehash='%s'", + mysql_real_escape_string($imagehash) + ); + + sql_query($delete); + } + + protected function renderFallback(): void + { + http_response_code(404); + } +} diff --git a/app/Services/Captcha/Drivers/RecaptchaV2CaptchaDriver.php b/app/Services/Captcha/Drivers/RecaptchaV2CaptchaDriver.php new file mode 100644 index 00000000..04d0a157 --- /dev/null +++ b/app/Services/Captcha/Drivers/RecaptchaV2CaptchaDriver.php @@ -0,0 +1,134 @@ +config = $config; + } + + public function isEnabled(): bool + { + return !empty($this->config['site_key']) && !empty($this->config['secret_key']); + } + + public function render(array $context = []): string + { + if (!$this->isEnabled()) { + return ''; + } + + $labels = $context['labels'] ?? []; + $label = $labels['image'] ?? $labels['code'] ?? 'Security Check'; + $theme = $this->config['theme'] ?? 'light'; + $size = $this->config['size'] ?? 'normal'; + $validSizes = ['compact', 'normal']; + if (!in_array($size, $validSizes, true)) { + $size = 'normal'; + } + + $attributes = sprintf( + 'class="g-recaptcha" data-sitekey="%s" data-theme="%s" data-size="%s"', + htmlspecialchars($this->config['site_key'], ENT_QUOTES, 'UTF-8'), + htmlspecialchars($theme, ENT_QUOTES, 'UTF-8'), + htmlspecialchars($size, ENT_QUOTES, 'UTF-8') + ); + + return sprintf( + '%s
%s', + htmlspecialchars($label, ENT_QUOTES, 'UTF-8'), + $attributes, + '' + ); + } + + public function verify(array $payload, array $context = []): bool + { + $token = trim((string) ($payload['request']['g-recaptcha-response'] ?? '')); + + if ($token === '') { + throw new CaptchaValidationException('Captcha verification token is missing.'); + } + + $secret = $this->config['secret_key'] ?? ''; + + if ($secret === '') { + throw new CaptchaValidationException('Captcha secret key is not configured.'); + } + + $data = [ + 'secret' => $secret, + 'response' => $token, + ]; + + $remoteIp = $context['ip'] ?? null; + + if (!empty($remoteIp)) { + $data['remoteip'] = $remoteIp; + } + + $result = $this->sendVerificationRequest('https://www.google.com/recaptcha/api/siteverify', $data); + + if (!($result['success'] ?? false)) { + throw new CaptchaValidationException('Captcha verification failed.'); + } + + return true; + } + + protected function sendVerificationRequest(string $url, array $data): array + { + $payload = http_build_query($data); + + if (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_TIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => true, + ]); + + $response = curl_exec($ch); + + if ($response === false) { + $error = curl_error($ch); + curl_close($ch); + throw new CaptchaValidationException('Captcha verification request failed: ' . $error); + } + + curl_close($ch); + } else { + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'content' => $payload, + 'timeout' => 10, + ], + ]); + + $response = file_get_contents($url, false, $context); + + if ($response === false) { + throw new CaptchaValidationException('Captcha verification request failed.'); + } + } + + $decoded = json_decode($response, true); + + if (!is_array($decoded)) { + throw new CaptchaValidationException('Unexpected captcha verification response.'); + } + + return $decoded; + } +} diff --git a/app/Services/Captcha/Drivers/TurnstileCaptchaDriver.php b/app/Services/Captcha/Drivers/TurnstileCaptchaDriver.php new file mode 100644 index 00000000..21a2e214 --- /dev/null +++ b/app/Services/Captcha/Drivers/TurnstileCaptchaDriver.php @@ -0,0 +1,143 @@ +config = $config; + } + + public function isEnabled(): bool + { + return !empty($this->config['site_key']) && !empty($this->config['secret_key']); + } + + public function render(array $context = []): string + { + if (!$this->isEnabled()) { + return ''; + } + + $labels = $context['labels'] ?? []; + $label = $labels['image'] ?? $labels['code'] ?? 'Security Check'; + $theme = $this->config['theme'] ?? 'light'; + $size = $this->config['size'] ?? 'auto'; + if (is_string($size)) { + $size = strtolower($size); + } + $validSizes = ['compact', 'normal', 'flexible']; + if (!in_array($size, $validSizes, true)) { + $size = 'auto'; + } + + $attributes = sprintf( + 'class="cf-turnstile" data-sitekey="%s" data-theme="%s"%s', + htmlspecialchars($this->config['site_key'], ENT_QUOTES, 'UTF-8'), + htmlspecialchars($theme, ENT_QUOTES, 'UTF-8'), + $size === 'auto' ? '' : sprintf(' data-size="%s"', htmlspecialchars($size, ENT_QUOTES, 'UTF-8')) + ); + + $markup = sprintf( + '%s
%s', + htmlspecialchars($label, ENT_QUOTES, 'UTF-8'), + $attributes, + self::$scriptInjected ? '' : '' + ); + + self::$scriptInjected = true; + + return $markup; + } + + public function verify(array $payload, array $context = []): bool + { + $token = trim((string) ($payload['request']['cf-turnstile-response'] ?? '')); + + if ($token === '') { + throw new CaptchaValidationException('Captcha verification token is missing.'); + } + + $secret = $this->config['secret_key'] ?? ''; + + if ($secret === '') { + throw new CaptchaValidationException('Captcha secret key is not configured.'); + } + + $data = [ + 'secret' => $secret, + 'response' => $token, + ]; + + $remoteIp = $context['ip'] ?? null; + + if (!empty($remoteIp)) { + $data['remoteip'] = $remoteIp; + } + + $result = $this->sendVerificationRequest('https://challenges.cloudflare.com/turnstile/v0/siteverify', $data); + + if (!($result['success'] ?? false)) { + throw new CaptchaValidationException('Captcha verification failed.'); + } + + return true; + } + + protected function sendVerificationRequest(string $url, array $data): array + { + $payload = http_build_query($data); + + if (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_TIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => true, + ]); + + $response = curl_exec($ch); + + if ($response === false) { + $error = curl_error($ch); + curl_close($ch); + throw new CaptchaValidationException('Captcha verification request failed: ' . $error); + } + + curl_close($ch); + } else { + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'content' => $payload, + 'timeout' => 10, + ], + ]); + + $response = file_get_contents($url, false, $context); + + if ($response === false) { + throw new CaptchaValidationException('Captcha verification request failed.'); + } + } + + $decoded = json_decode($response, true); + + if (!is_array($decoded)) { + throw new CaptchaValidationException('Unexpected captcha verification response.'); + } + + return $decoded; + } +} diff --git a/app/Services/Captcha/Exceptions/CaptchaValidationException.php b/app/Services/Captcha/Exceptions/CaptchaValidationException.php new file mode 100644 index 00000000..2171f52f --- /dev/null +++ b/app/Services/Captcha/Exceptions/CaptchaValidationException.php @@ -0,0 +1,10 @@ + nexus_env('CAPTCHA_DRIVER', 'image'), + + 'drivers' => [ + 'image' => [ + 'class' => \App\Services\Captcha\Drivers\ImageCaptchaDriver::class, + ], + + 'cloudflare_turnstile' => [ + 'class' => \App\Services\Captcha\Drivers\TurnstileCaptchaDriver::class, + 'site_key' => nexus_env('TURNSTILE_SITE_KEY'), + 'secret_key' => nexus_env('TURNSTILE_SECRET_KEY'), + 'theme' => nexus_env('TURNSTILE_THEME', 'auto'), + 'size' => nexus_env('TURNSTILE_SIZE', 'auto'), + ], + + 'google_recaptcha_v2' => [ + 'class' => \App\Services\Captcha\Drivers\RecaptchaV2CaptchaDriver::class, + 'site_key' => nexus_env('RECAPTCHA_SITE_KEY'), + 'secret_key' => nexus_env('RECAPTCHA_SECRET_KEY'), + 'theme' => nexus_env('RECAPTCHA_THEME', 'light'), + 'size' => nexus_env('RECAPTCHA_SIZE', 'normal'), + ], + ], +]; diff --git a/include/functions.php b/include/functions.php index 89aba8fe..27641fae 100644 --- a/include/functions.php +++ b/include/functions.php @@ -1767,56 +1767,102 @@ function random_str($length="6") } return $str; } -function image_code () { - $randomstr = random_str(); - $imagehash = md5($randomstr); - $dateline = time(); - $sql = 'INSERT INTO `regimages` (`imagehash`, `imagestring`, `dateline`) VALUES (\''.$imagehash.'\', \''.$randomstr.'\', \''.$dateline.'\');'; - sql_query($sql); - return $imagehash; +function captcha_manager(): \App\Services\Captcha\CaptchaManager +{ + static $manager; + + if (!$manager) { + $manager = new \App\Services\Captcha\CaptchaManager(); + } + + return $manager; } -function check_code ($imagehash, $imagestring, $where = 'signup.php',$maxattemptlog=false,$head=true) { - global $lang_functions; +function image_code () { + $driver = captcha_manager()->driver('image'); + + if (!method_exists($driver, 'issue')) { + throw new \RuntimeException('Image captcha driver is unavailable.'); + } + + return $driver->issue(); +} + +function check_code ($imagehash, $imagestring, $where = 'signup.php', $maxattemptlog = false, $head = true) { + global $lang_functions; global $iv; + if ($iv !== 'yes') { return true; } - $query = sprintf("SELECT * FROM regimages WHERE imagehash='%s' AND imagestring='%s'", - mysql_real_escape_string((string)$imagehash), - mysql_real_escape_string((string)$imagestring) - ); - $sql = sql_query($query); - $imgcheck = mysql_fetch_array($sql); - if(!$imgcheck['dateline']) { - $delete = sprintf("DELETE FROM regimages WHERE imagehash='%s'", - mysql_real_escape_string((string)$imagehash) - ); - sql_query($delete); - if (!$maxattemptlog) - stderr('Error',$lang_functions['std_invalid_image_code']."".$lang_functions['std_here_to_request_new'], false); - else - failedlogins($lang_functions['std_invalid_image_code']."".$lang_functions['std_here_to_request_new'],true,$head); - }else{ - $delete = sprintf("DELETE FROM regimages WHERE imagehash='%s'", - mysql_real_escape_string((string)$imagehash) - ); - sql_query($delete); - return true; - } + + $manager = captcha_manager(); + + if (!$manager->isEnabled()) { + return true; + } + + $payload = [ + 'imagehash' => $imagehash, + 'imagestring' => $imagestring, + 'request' => array_merge($_POST ?? [], $_GET ?? []), + ]; + + $context = [ + 'where' => $where, + 'maxattemptlog' => $maxattemptlog, + 'head' => $head, + 'ip' => getip(), + ]; + + try { + if ($manager->verify($payload, $context)) { + return true; + } + } catch (\App\Services\Captcha\Exceptions\CaptchaValidationException $exception) { + $message = $exception->getMessage(); + + $defaultMessage = $lang_functions['std_invalid_image_code'] . "" . $lang_functions['std_here_to_request_new']; + + if ($message === '' || $message === 'Invalid captcha response.' || $message === 'Missing captcha parameters.') { + $message = $defaultMessage; + } + + if (!$maxattemptlog) { + stderr('Error', $message, false); + } else { + failedlogins($message, true, $head); + } + } + + return false; } + function show_image_code () { - global $lang_functions; - global $iv; - if ($iv == "yes") { - unset($imagehash); - $imagehash = image_code () ; - print ("".$lang_functions['row_security_image'].""); - print ("\"CAPTCHA\""); - print ("".$lang_functions['row_security_code'].""); - print(""); - print(""); - } + global $lang_functions; + global $iv; + + if ($iv !== 'yes') { + return; + } + + $manager = captcha_manager(); + + if (!$manager->isEnabled()) { + return; + } + + $markup = $manager->render([ + 'labels' => [ + 'image' => $lang_functions['row_security_image'], + 'code' => $lang_functions['row_security_code'], + ], + 'secret' => $_GET['secret'] ?? '', + ]); + + if ($markup !== '') { + echo $markup; + } } function get_ip_location($ip) diff --git a/include/globalfunctions.php b/include/globalfunctions.php index c0a0e89d..dd8003f4 100644 --- a/include/globalfunctions.php +++ b/include/globalfunctions.php @@ -307,6 +307,7 @@ function nexus_config($key, $default = null) $files = [ ROOT_PATH . 'config/nexus.php', ROOT_PATH . 'config/emoji.php', + ROOT_PATH . 'config/captcha.php', ]; foreach ($files as $file) { $basename = basename($file); diff --git a/public/complains.php b/public/complains.php index 8f8c8b1f..91b80f5b 100644 --- a/public/complains.php +++ b/public/complains.php @@ -18,7 +18,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST'){ switch($action = filter_input(INPUT_POST, 'action', FILTER_SANITIZE_FULL_SPECIAL_CHARS)){ case 'new': cur_user_check(); - check_code ($_POST['imagehash'], $_POST['imagestring'],'complains.php'); + check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,'complains.php'); \Nexus\Database\NexusLock::lockOrFail("complains:lock:" . getip(), 10); $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); \Nexus\Database\NexusLock::lockOrFail("complains:lock:" . $email, 600); diff --git a/public/confirm_resend.php b/public/confirm_resend.php index ce9b6115..0022b1d9 100644 --- a/public/confirm_resend.php +++ b/public/confirm_resend.php @@ -29,7 +29,7 @@ bark($lang_confirm_resend['std_need_admin_verification']); if ($_SERVER["REQUEST_METHOD"] == "POST") { if ($iv == "yes") - check_code ($_POST['imagehash'], $_POST['imagestring'],"confirm_resend.php",true); + check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,"confirm_resend.php",true); $email = unesc(htmlspecialchars(trim($_POST["email"] ?? ''))); $wantpassword = unesc(htmlspecialchars(trim($_POST["wantpassword"]))); $passagain = unesc(htmlspecialchars(trim($_POST["passagain"]))); diff --git a/public/image.php b/public/image.php index f013e5d4..fa2df541 100644 --- a/public/image.php +++ b/public/image.php @@ -1,60 +1,22 @@ driver('image'); + +if (!method_exists($driver, 'outputImage')) { + http_response_code(404); + exit('Captcha driver does not support image rendering'); } + +$driver->outputImage($imagehash); + ?> diff --git a/public/recover.php b/public/recover.php index 128c23f9..5e5979cc 100644 --- a/public/recover.php +++ b/public/recover.php @@ -28,7 +28,7 @@ $mailTwoFour = sprintf($lang_recover['mail_two_four'], $siteName); if ($_SERVER["REQUEST_METHOD"] == "POST") { if ($iv == "yes") - check_code ($_POST['imagehash'], $_POST['imagestring'],"recover.php",true); + check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,"recover.php",true); $email = unesc(htmlspecialchars(trim($_POST["email"] ?? ''))); $email = safe_email($email); if (!$email) diff --git a/public/takelogin.php b/public/takelogin.php index 398f4b07..23a44761 100644 --- a/public/takelogin.php +++ b/public/takelogin.php @@ -15,7 +15,7 @@ function bark($text = "") stderr($lang_takelogin['std_login_fail'], $text,false); } if ($iv == "yes") { - check_code ($_POST['imagehash'], $_POST['imagestring'],'login.php',true); + check_code($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null, 'login.php', true); } //同时支持新旧两种登录方式 $useChallengeResponse = \App\Models\Setting::getIsUseChallengeResponseAuthentication(); diff --git a/public/takesignup.php b/public/takesignup.php index 3358a344..94d4a075 100644 --- a/public/takesignup.php +++ b/public/takesignup.php @@ -21,13 +21,13 @@ if ($type == 'invite'){ registration_check(); failedloginscheck ("Invite Signup"); if ($iv == "yes") - check_code ($_POST['imagehash'], $_POST['imagestring'],'signup.php?type=invite&invitenumber='.htmlspecialchars($_POST['hash'])); + check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null,'signup.php?type=invite&invitenumber='.htmlspecialchars($_POST['hash'])); } else{ registration_check("normal"); failedloginscheck ("Signup"); if ($iv == "yes") - check_code ($_POST['imagehash'], $_POST['imagestring']); + check_code ($_POST['imagehash'] ?? null, $_POST['imagestring'] ?? null); } function isportopen($port) {