From 5ccb8faaf94de44a807e56b736c781f0c3ed44f1 Mon Sep 17 00:00:00 2001 From: Paul Derscheid Date: Fri, 27 Jun 2025 10:05:47 +0200 Subject: [PATCH] Bug 39906: [FOR DISCUSSION] Add a worker to waste some CPU cycles if likely bot - The only change to the antibot config is the addition of opac-search.pl (just noticed that the root path should be included as well). - Added a config object to adjust and experiment with the settings. - Added a worker script to calculate the number of primes in a given number of iterations set by index.js. --- .../templates/apache-shared-opac-antibot.conf | 2 +- .../lib/koha_fast_challenge/index.js | 134 ++++++++++++++---- .../lib/koha_fast_challenge/worker.js | 19 +++ 3 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 koha-tmpl/opac-tmpl/lib/koha_fast_challenge/worker.js diff --git a/debian/templates/apache-shared-opac-antibot.conf b/debian/templates/apache-shared-opac-antibot.conf index d69832b2264..0b6ca9d0bd0 100644 --- a/debian/templates/apache-shared-opac-antibot.conf +++ b/debian/templates/apache-shared-opac-antibot.conf @@ -3,7 +3,7 @@ RewriteEngine on #NOTE: To override this setting of ANTIBOT_DO, in the parent VirtualHost add the following: SetEnvIf Request_URI ^ ANTIBOT_OVERRIDE=true #You can then provide your own conditions for setting the ANTIBOT_DO environmental variable RewriteCond %{ENV:ANTIBOT_OVERRIDE} ^$ -RewriteCond expr "%{REQUEST_URI} =~ m#^/cgi-bin/koha/(opac-detail.pl|opac-export.pl|opac-suggestions.pl|opac-search.pl|opac-authoritiesdetail.pl|opac-ISBDdetail.pl|opac-MARCdetail.pl|opac-shelves.pl)$#" +RewriteCond expr "%{REQUEST_URI} =~ m#^/cgi-bin/koha/(opac-main.pl|opac-detail.pl|opac-export.pl|opac-suggestions.pl|opac-search.pl|opac-authoritiesdetail.pl|opac-ISBDdetail.pl|opac-MARCdetail.pl|opac-shelves.pl)$#" RewriteCond %{HTTP:Cookie} !(^|;\s*)CGISESSID= [NC] #NOTE: The KOHA_INIT is set by Javascript by the fast challenge webpage RewriteCond %{HTTP:Cookie} !(^|;\s*)KOHA_INIT= [NC] diff --git a/koha-tmpl/opac-tmpl/lib/koha_fast_challenge/index.js b/koha-tmpl/opac-tmpl/lib/koha_fast_challenge/index.js index 974e57024bb..a9474cedeb6 100644 --- a/koha-tmpl/opac-tmpl/lib/koha_fast_challenge/index.js +++ b/koha-tmpl/opac-tmpl/lib/koha_fast_challenge/index.js @@ -1,34 +1,120 @@ -document.addEventListener("DOMContentLoaded", event => { - const timeout_base = 0; - let timeout_incr = 250; +document.addEventListener("DOMContentLoaded", (event) => { + // --- Configuration --- + const config = { + baseTimeout: 250, + penalties: { + webdriver: 20, + spoofedWebdriver: 50, + noPlugins: 10, + noMimeTypes: 10, + noLanguages: 10, + noUserAgent: 50, + fastRFA: 5, + noInteraction: 25, + }, + workerBaseIterations: 750000, + interactionCheckDelay: 3000, + botScoreThreshold: 15, + }; - if (navigator.webdriver){ - timeout_incr += 10000 - } - if (navigator.plugins.length === 0){ - timeout_incr += 5000; - } - if (navigator.mimeTypes.length === 0){ - timeout_incr += 5000; - } - if (!navigator.languages || navigator.languages.length === 0){ - timeout_incr += 5000; + let checksHaveRun = false; + + function runAntiBotChecks() { + if (checksHaveRun) return; + checksHaveRun = true; + + let botScore = 0; + let userInteracted = false; + + // --- Detection Phase --- + if (navigator.webdriver) botScore += config.penalties.webdriver; + if (navigator.plugins.length === 0) botScore += config.penalties.noPlugins; + if (navigator.mimeTypes.length === 0) + botScore += config.penalties.noMimeTypes; + if (!navigator.languages || navigator.languages.length === 0) + botScore += config.penalties.noLanguages; + if (!navigator.userAgent) botScore += config.penalties.noUserAgent; + + try { + const descriptor = Object.getOwnPropertyDescriptor( + Navigator.prototype, + "webdriver" + ); + if (descriptor && !descriptor.get.toString().includes("native code")) { + botScore += config.penalties.spoofedWebdriver; + } + } catch (e) { + botScore += config.penalties.spoofedWebdriver; } + const timestamp1 = performance.now(); requestAnimationFrame(() => { - const delay = performance.now() - timestamp1; - if (delay < 20){ - timeout_incr += 1000; - } + if (performance.now() - timestamp1 < 20) { + botScore += config.penalties.fastRFA; + } }); - let final_timeout = timeout_base + timeout_incr; + const interactionEvents = ["mousemove", "keydown", "scroll"]; + const recordInteraction = () => { + userInteracted = true; + interactionEvents.forEach((name) => + document.removeEventListener(name, recordInteraction) + ); + }; + interactionEvents.forEach((name) => + document.addEventListener(name, recordInteraction, { passive: true }) + ); + + // --- Action Phase --- setTimeout(() => { - let koha_init_cookie = "KOHA_INIT=1; path=/; SameSite=Lax"; - if (location.protocol === 'https:'){ - koha_init_cookie += "; Secure"; + if (!userInteracted) { + botScore += config.penalties.noInteraction; + } + + console.log(`Final bot score: ${botScore}`); + + if (botScore >= config.botScoreThreshold) { + console.log("High bot score. Engaging parallel CPU trap."); + try { + const worker = new Worker("worker.js"); + const taskIterations = + config.workerBaseIterations * (1 + botScore / 20); + worker.postMessage({ iterations: taskIterations }); + } catch (e) { + console.error("Could not create Web Worker.", e); } - document.cookie = koha_init_cookie; - location.reload(); + } + + const finalTimeout = config.baseTimeout + botScore * 150; + setCookieAndReload(finalTimeout); + }, config.interactionCheckDelay); + } + + function setCookieAndReload(final_timeout) { + console.log(`Final timeout: ${final_timeout}ms`); + setTimeout(() => { + let koha_init_cookie = "KOHA_INIT=1; path=/; SameSite=Lax"; + if (location.protocol === "https:") { + koha_init_cookie += "; Secure"; + } + document.cookie = koha_init_cookie; + location.reload(); }, final_timeout); + } + + // --- Visibility API Implementation --- + if (document.visibilityState === "visible") { + runAntiBotChecks(); + } else { + document.addEventListener( + "visibilitychange", + function onVisible() { + if (document.visibilityState === "visible") { + document.removeEventListener("visibilitychange", onVisible); + runAntiBotChecks(); + } + }, + { once: true } + ); + } }); diff --git a/koha-tmpl/opac-tmpl/lib/koha_fast_challenge/worker.js b/koha-tmpl/opac-tmpl/lib/koha_fast_challenge/worker.js new file mode 100644 index 00000000000..936cb4159a6 --- /dev/null +++ b/koha-tmpl/opac-tmpl/lib/koha_fast_challenge/worker.js @@ -0,0 +1,19 @@ +self.onmessage = function (e) { + const iterations = e.data.iterations || 100000; + let primes = 0; + + function isPrime(num) { + for (let i = 2, s = Math.sqrt(num); i <= s; i++) { + if (num % i === 0) return false; + } + return num > 1; + } + + for (let i = 0; i < iterations; i++) { + if (isPrime(i)) { + primes++; + } + } + + self.postMessage({ done: true, primes: primes }); +}; -- 2.50.0