From a7e349d8cbb34676854c81087791b643db743b75 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 28 Oct 2024 12:34:05 +0100 Subject: [PATCH] post.php: workaround captcha and js/ajax.js bug --- post.php | 120 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 49 deletions(-) diff --git a/post.php b/post.php index 232a1f8e..b16b7847 100644 --- a/post.php +++ b/post.php @@ -167,6 +167,68 @@ function check_turnstile($secret, $response, $remote_ip, $expected_action) return $json_ret['success'] === true && $json_ret['action'] === $expected_action; } +/** + * A "sophisticated" workaround to js/ajax.js calling post.php multiple times on error/ban. + */ +function check_captcha(array $captcha_config, string $form_id, string $board_uri, string $response, string $remote_ip, string $expected_action) { + $dynamic = $captcha_config['dynamic']; + if ($dynamic !== false && $remote_ip !== $dynamic) { + return true; + } + + switch ($captcha_config['mode']) { + case 'recaptcha': + case 'hcaptcha': + case 'turnstile': + $mode = $captcha_config['mode']; + break; + case false: + return true; + default: + \error_log("Unknown captcha mode '{$captcha_config['mode']}'"); + throw new \RuntimeException('Captcha configuration error'); + } + + $passthrough_timeout = $captcha_config['passthrough_timeout']; + + if ($passthrough_timeout != 0) { + $pass = Cache::get("captcha_passthrough_{$remote_ip}_{$form_id}"); + if ($pass !== false) { + $let_through = $pass['expires'] > time() && $pass['board_uri'] === $board_uri && $pass['captcha_response'] === $response; + if ($let_through) { + return true; + } + } + } + + $remote_ip_send = $dynamic !== false ? null : $remote_ip; + $private_key = $captcha_config[$mode]['private']; + $public_key = $captcha_config[$mode]['public']; + switch ($mode) { + case 'recaptcha': + $ret = check_recaptcha($private_key, $response, $remote_ip_send); + break; + case 'hcaptcha': + $ret = check_hcaptcha($private_key, $response, $remote_ip_send, $public_key); + break; + case 'turnstile': + $ret = check_turnstile($private_key, $response, $remote_ip_send, $expected_action); + break; + } + + if ($ret && $passthrough_timeout != 0) { + $pass = [ + 'expires' => time() + $passthrough_timeout, + 'board_uri' => $board_uri, + 'captcha_response' => $response + ]; + + Cache::set("captcha_passthrough_{$remote_ip}_{$form_id}", $pass, $passthrough_timeout + 2); + } + + return $ret; +} + /** * Deletes the (single) captcha associated with the ip and code. * @@ -765,56 +827,16 @@ function handle_post() if (!$dropped_post) { - if ($config['dynamic_captcha'] !== false) { - if ($_SERVER['REMOTE_ADDR'] === $config['dynamic_captcha']) { - if ($config['recaptcha']) { - if (!isset($_POST['captcha-response'])) { - error($config['error']['bot']); - } - if (!check_recaptcha($config['recaptcha_private'], $_POST['captcha-response'], null)) { - error($config['error']['captcha']); - } - } elseif ($config['hcaptcha']) { - if (!isset($_POST['captcha-response'])) { - error($config['error']['bot']); - } - if (!check_hcaptcha($config['hcaptcha_private'], $_POST['captcha-response'], null, $config['hcaptcha_public'])) { - error($config['error']['captcha']); - } - } elseif ($config['turnstile']) { - if (!isset($_POST['captcha-response'])) { - error($config['error']['bot']); - } - $expected_action = $post['op'] ? 'post-thread' : 'post-reply'; - if (!check_turnstile($config['turnstile_private'], $_POST['captcha-response'], null, $expected_action)) { - error($config['error']['captcha']); - } - } + // Check for CAPTCHA right after opening the board so the "return" link is in there. + if ($config['captcha']['mode'] !== false) { + if (!isset($_POST['captcha-response'], $_POST['captcha-form-id'])) { + error($config['error']['bot']); } - } else { - // Check for CAPTCHA right after opening the board so the "return" link is in there. - if ($config['recaptcha']) { - if (!isset($_POST['captcha-response'])) { - error($config['error']['bot']); - } - if (!check_recaptcha($config['recaptcha_private'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'])) { - error($config['error']['captcha']); - } - } elseif ($config['hcaptcha']) { - if (!isset($_POST['captcha-response'])) { - error($config['error']['bot']); - } - if (!check_hcaptcha($config['hcaptcha_private'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'], $config['hcaptcha_public'])) { - error($config['error']['captcha']); - } - } elseif ($config['turnstile']) { - if (!isset($_POST['captcha-response'])) { - error($config['error']['bot']); - } - $expected_action = $post['op'] ? 'post-thread' : 'post-reply'; - if (!check_turnstile($config['turnstile_private'], $_POST['captcha-response'], null, $expected_action)) { - error($config['error']['captcha']); - } + + $expected_action = $post['op'] ? 'post-thread' : 'post-reply'; + $ret = check_captcha($config['captcha'], $_POST['captcha-form-id'], $post['board'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'], $expected_action); + if (!$ret) { + error($config['error']['captcha']); } }