@anonympins/fingerprint 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -3
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/fingerprint.js +106 -29
- package/src/js/fingerprint.utils.js +32 -5
- package/src/js/tests/fingerprint.test.js +91 -4
- package/src/js/tests/threatIntel.test.js +76 -0
- package/src/php/AutoTuner.php +36 -18
- package/src/php/Challenge/ChallengeUtils.php +107 -225
- package/src/php/DirectFingerprint.php +16 -4
- package/src/php/FingerprintClient.php +17 -19
- package/src/php/FingerprintEngine.php +114 -51
- package/src/php/Ja3AnomalyDetector.php +20 -12
- package/src/php/Optimization/Optimization.php +5 -1
- package/src/php/Optimization/OptimizationOperators.php +5 -1
- package/src/php/Optimization/ProblemInitializers.php +16 -4
- package/src/php/ProblemManager.php +20 -14
- package/src/php/Tests/FingerprintEngineTest.php +705 -662
- package/src/php/Tests/ThreatIntelTest.php +64 -0
- package/src/php/Utils/BlockList.php +99 -99
- package/src/php/Utils/Env.php +51 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/RequestUtils.php +22 -5
- package/src/php/WordPress/WpDbStore.php +4 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +559 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
- package/src/php/WordPress/package.php +102 -4
- package/src/php/WordPress/readme.txt +36 -0
- package/public/fingerprint-wordpress.zip +0 -0
- package/src/js/fingerprint.client.obfuscated.js +0 -1
- package/src/php/WordPress/fingerprint-wordpress.php +0 -217
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Name: Fingerprint Anti-Bot & Proof-of-Work
|
|
4
|
+
* Plugin URI: https://github.com/anonympins/fingerprint
|
|
5
|
+
* Description: High-performance client-side anti-bot protection and Proof-of-Work challenge verification for WordPress.
|
|
6
|
+
* Version: 0.7.3
|
|
7
|
+
* Author: anonympins
|
|
8
|
+
* Requires at least: 5.9
|
|
9
|
+
* Requires PHP: 8.0
|
|
10
|
+
* License: MIT
|
|
11
|
+
* Text Domain: fingerprint-anti-bot
|
|
12
|
+
* Domain Path: /languages
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare(strict_types=1);
|
|
16
|
+
|
|
17
|
+
use Anonympins\Fingerprint\DirectFingerprint;
|
|
18
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
19
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
20
|
+
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
21
|
+
use Anonympins\Fingerprint\WordPress\WpDbStore;
|
|
22
|
+
|
|
23
|
+
if (!defined('ABSPATH')) {
|
|
24
|
+
exit;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// PIÈGES HONEYPOT POUR ENVIRONNEMENTS TIERS (NON-WORDPRESS)
|
|
29
|
+
// Détecte et bloque instantanément les scans de vulnérabilités ciblant
|
|
30
|
+
// Laravel, Symfony, Spring Boot, Django, ASP.NET, phpMyAdmin, Git, Cloud, etc.
|
|
31
|
+
// =============================================================================
|
|
32
|
+
$fingerprint_foreign_env_trap_urls = [
|
|
33
|
+
// Outils d'administration BDD & scripts de debug
|
|
34
|
+
'/phpmyadmin', '/pma', '/adminer', '/mysql', '/dbadmin', '/phpinfo',
|
|
35
|
+
// Frameworks PHP (Laravel, Symfony) & fichiers d'environnement
|
|
36
|
+
'/.env', '/artisan', '/telescope', '/_profiler', '/config/database.php', '/vendor/',
|
|
37
|
+
// Java / Spring Boot / Actuator
|
|
38
|
+
'/actuator', '/actuator/health', '/actuator/gateway', '/api-docs', '/swagger-ui',
|
|
39
|
+
// Python / Django / Flask
|
|
40
|
+
'/.flask', '/django_admin', '/__pycache__',
|
|
41
|
+
// Node.js & configuration paquets
|
|
42
|
+
'/.npmrc', '/package.json', '/package-lock.json',
|
|
43
|
+
// ASP.NET / IIS / CGI
|
|
44
|
+
'/elmah.axd', '/trace.axd', '/cgi-bin/',
|
|
45
|
+
// DevOps, VCS & Déploiements cloud
|
|
46
|
+
'/.git', '/.svn', '/.aws', '/.kube', '/docker-compose', '/serverless'
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
$fingerprint_foreign_env_honeypot_fields = [
|
|
50
|
+
// Tokens CSRF et formulaires spécifiques à d'autres frameworks
|
|
51
|
+
'_token', // Laravel
|
|
52
|
+
'csrfmiddlewaretoken', // Django
|
|
53
|
+
'authenticity_token', // Ruby on Rails
|
|
54
|
+
'form_build_id', // Drupal
|
|
55
|
+
'form_id', // Drupal
|
|
56
|
+
'j_username', // Java / Spring Security
|
|
57
|
+
'j_password', // Java / Spring Security
|
|
58
|
+
'__VIEWSTATE', // ASP.NET WebForms
|
|
59
|
+
'__EVENTVALIDATION', // ASP.NET WebForms
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// CONFIGURATION PAR DÉFAUT DES PROFILS DE SÉCURITÉ PAR CONTEXTE
|
|
64
|
+
// =============================================================================
|
|
65
|
+
$fingerprint_security_profiles = [
|
|
66
|
+
'api' => [
|
|
67
|
+
'profile' => 'api',
|
|
68
|
+
'overrides' => [
|
|
69
|
+
'verbose' => false,
|
|
70
|
+
'challengeNewDevices' => false,
|
|
71
|
+
'honeypot' => [
|
|
72
|
+
'fields' => $fingerprint_foreign_env_honeypot_fields,
|
|
73
|
+
'trapUrls' => $fingerprint_foreign_env_trap_urls,
|
|
74
|
+
'detectInjections' => true,
|
|
75
|
+
],
|
|
76
|
+
],
|
|
77
|
+
],
|
|
78
|
+
'admin' => [
|
|
79
|
+
'profile' => 'strict',
|
|
80
|
+
'overrides' => [
|
|
81
|
+
'verbose' => true,
|
|
82
|
+
'challengeNewDevices' => true,
|
|
83
|
+
'honeypot' => [
|
|
84
|
+
'fields' => $fingerprint_foreign_env_honeypot_fields,
|
|
85
|
+
'trapUrls' => $fingerprint_foreign_env_trap_urls,
|
|
86
|
+
'detectInjections' => true,
|
|
87
|
+
],
|
|
88
|
+
],
|
|
89
|
+
],
|
|
90
|
+
'frontend' => [
|
|
91
|
+
'profile' => 'blog',
|
|
92
|
+
'overrides' => [
|
|
93
|
+
'verbose' => false,
|
|
94
|
+
'challengeNewDevices' => false,
|
|
95
|
+
'honeypot' => [
|
|
96
|
+
'fields' => $fingerprint_foreign_env_honeypot_fields,
|
|
97
|
+
'trapUrls' => $fingerprint_foreign_env_trap_urls,
|
|
98
|
+
'detectInjections' => true,
|
|
99
|
+
],
|
|
100
|
+
],
|
|
101
|
+
],
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Récupère les configurations fusionnées avec les réglages persistés dans WordPress.
|
|
106
|
+
*/
|
|
107
|
+
function fingerprint_get_effective_profiles(array $defaultProfiles): array {
|
|
108
|
+
$saved = get_option('fingerprint_security_options', []);
|
|
109
|
+
if (!is_array($saved) || empty($saved)) {
|
|
110
|
+
return $defaultProfiles;
|
|
111
|
+
}
|
|
112
|
+
return [
|
|
113
|
+
'frontend' => SecurityProfiles::deepMerge($defaultProfiles['frontend'], $saved['frontend'] ?? []),
|
|
114
|
+
'admin' => SecurityProfiles::deepMerge($defaultProfiles['admin'], $saved['admin'] ?? []),
|
|
115
|
+
'api' => SecurityProfiles::deepMerge($defaultProfiles['api'], $saved['api'] ?? []),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 1. Autoloader PSR-4 pour le moteur Fingerprint
|
|
120
|
+
if (!class_exists(DirectFingerprint::class)) {
|
|
121
|
+
$fingerprint_composer_paths = [
|
|
122
|
+
__DIR__ . '/vendor/autoload.php',
|
|
123
|
+
dirname(__DIR__, 2) . '/vendor/autoload.php',
|
|
124
|
+
dirname(__DIR__, 3) . '/vendor/autoload.php',
|
|
125
|
+
(defined('ABSPATH') ? ABSPATH . 'vendor/autoload.php' : ''),
|
|
126
|
+
];
|
|
127
|
+
foreach ($fingerprint_composer_paths as $fingerprint_composer_path) {
|
|
128
|
+
if (!empty($fingerprint_composer_path) && file_exists($fingerprint_composer_path)) {
|
|
129
|
+
require_once $fingerprint_composer_path;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
spl_autoload_register(function (string $class): void {
|
|
135
|
+
$prefix = 'Anonympins\\Fingerprint\\';
|
|
136
|
+
$len = strlen($prefix);
|
|
137
|
+
if (strncmp($prefix, $class, $len) !== 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
$relativeClass = substr($class, $len);
|
|
142
|
+
|
|
143
|
+
// Classes spécifiques WordPress (ex: WpDbStore)
|
|
144
|
+
if (str_starts_with($relativeClass, 'WordPress\\')) {
|
|
145
|
+
$localClass = substr($relativeClass, strlen('WordPress\\'));
|
|
146
|
+
$localFile = __DIR__ . '/' . str_replace('\\', '/', $localClass) . '.php';
|
|
147
|
+
if (file_exists($localFile)) {
|
|
148
|
+
require_once $localFile;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Chemins candidats pour le moteur PHP (packagé ou environnement dev)
|
|
154
|
+
$candidateDirs = [
|
|
155
|
+
__DIR__ . '/src/',
|
|
156
|
+
__DIR__ . '/includes/',
|
|
157
|
+
__DIR__ . '/',
|
|
158
|
+
dirname(__DIR__) . '/',
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
$fileRelative = str_replace('\\', '/', $relativeClass) . '.php';
|
|
162
|
+
foreach ($candidateDirs as $dir) {
|
|
163
|
+
$fullPath = $dir . $fileRelative;
|
|
164
|
+
if (file_exists($fullPath)) {
|
|
165
|
+
require_once $fullPath;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2. Activation du plugin : Création de la table de cache SQL
|
|
173
|
+
register_activation_hook(__FILE__, function () {
|
|
174
|
+
$store = new WpDbStore();
|
|
175
|
+
$store->ensureTable();
|
|
176
|
+
|
|
177
|
+
if (!wp_next_scheduled('fingerprint_prune_expired_entries')) {
|
|
178
|
+
wp_schedule_event(time(), 'hourly', 'fingerprint_prune_expired_entries');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// 3. Désactivation : Nettoyage du WP-Cron
|
|
183
|
+
register_deactivation_hook(__FILE__, function () {
|
|
184
|
+
wp_clear_scheduled_hook('fingerprint_prune_expired_entries');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// 4. Tâche de fond WP-Cron : Purge des clés expirées dans la BDD
|
|
188
|
+
add_action('fingerprint_prune_expired_entries', function () {
|
|
189
|
+
$store = new WpDbStore();
|
|
190
|
+
$store->pruneExpired();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// 5. Avertissement Admin si le site n'utilise pas HTTPS (Environnement Non Sécurisé)
|
|
194
|
+
add_action('admin_notices', function () {
|
|
195
|
+
if (!is_ssl()) {
|
|
196
|
+
echo '<div class="notice notice-warning is-dismissible">';
|
|
197
|
+
echo '<p><strong>' . esc_html__('[Fingerprint Anti-Bot Security Warning]', 'fingerprint-anti-bot') . '</strong> : ' .
|
|
198
|
+
esc_html__('Your website is currently running on unencrypted HTTP. In this mode, modern web browsers disable the native Web Cryptography API (crypto.subtle) for security reasons, forcing a JavaScript fallback simulation that is slower and vulnerable to Man-in-the-Middle (MitM) attacks. We strongly recommend deploying a TLS/SSL certificate and enforcing HTTPS to ensure the cryptographic integrity of Proof-of-Work computations and identity protection.', 'fingerprint-anti-bot') .
|
|
199
|
+
'</p>';
|
|
200
|
+
echo '</div>';
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// 6. Interception de sécurité au plus tôt du cycle de vie WordPress
|
|
205
|
+
add_action('plugins_loaded', function () use ($fingerprint_security_profiles) {
|
|
206
|
+
// Ignore WP-CLI et les exécutions internes de cron
|
|
207
|
+
if ((defined('WP_CLI') && WP_CLI) || (defined('DOING_CRON') && DOING_CRON)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Initialiser le Store persistant basé sur $wpdb
|
|
212
|
+
$store = new WpDbStore();
|
|
213
|
+
StoreManager::configureStore($store);
|
|
214
|
+
|
|
215
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized with sanitize_text_field
|
|
216
|
+
$rawUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '/';
|
|
217
|
+
$requestUri = !empty($rawUri) ? $rawUri : '/';
|
|
218
|
+
$isRestApi = defined('REST_REQUEST') && REST_REQUEST;
|
|
219
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
220
|
+
$isAdmin = is_admin() || (isset($_SERVER['PHP_SELF']) && str_contains(sanitize_text_field(wp_unslash($_SERVER['PHP_SELF'])), 'wp-login.php')) || str_contains($requestUri, 'wp-login.php');
|
|
221
|
+
|
|
222
|
+
// Fusion avec les options enregistrées via l'admin WP
|
|
223
|
+
$effectiveProfiles = fingerprint_get_effective_profiles($fingerprint_security_profiles);
|
|
224
|
+
|
|
225
|
+
// Possibilité de filtrer la configuration via functions.php ou un thème
|
|
226
|
+
$configs = apply_filters('fingerprint_security_profiles', $effectiveProfiles);
|
|
227
|
+
|
|
228
|
+
// Sélection adaptative du profil selon la cible de la requête
|
|
229
|
+
if ($isRestApi || str_starts_with($requestUri, '/wp-json/')) {
|
|
230
|
+
$contextConfig = $configs['api'] ?? $fingerprint_security_profiles['api'];
|
|
231
|
+
} elseif ($isAdmin) {
|
|
232
|
+
$contextConfig = $configs['admin'] ?? $fingerprint_security_profiles['admin'];
|
|
233
|
+
} else {
|
|
234
|
+
$contextConfig = $configs['frontend'] ?? $fingerprint_security_profiles['frontend'];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
$securityConfig = SecurityProfiles::createSecurityProfile($contextConfig['profile'], $contextConfig['overrides'] ?? []);
|
|
238
|
+
$guard = new DirectFingerprint($securityConfig);
|
|
239
|
+
|
|
240
|
+
// Inspecte la requête : bloque ou envoie le challenge si nécessaire et stoppe le script
|
|
241
|
+
$guard->protect();
|
|
242
|
+
}, 0);
|
|
243
|
+
|
|
244
|
+
// =============================================================================
|
|
245
|
+
// INTERFACE D'ADMINISTRATION, RÉGLAGES & INSPECTION DES MÉTRIQUES
|
|
246
|
+
// =============================================================================
|
|
247
|
+
|
|
248
|
+
add_action('admin_menu', function () {
|
|
249
|
+
add_options_page(
|
|
250
|
+
__('Fingerprint Anti-Bot', 'fingerprint-anti-bot'),
|
|
251
|
+
__('Fingerprint Anti-Bot', 'fingerprint-anti-bot'),
|
|
252
|
+
'manage_options',
|
|
253
|
+
'fingerprint-settings',
|
|
254
|
+
'fingerprint_render_admin_page'
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Rendu de la page de réglages et du visualiseur de métriques.
|
|
260
|
+
*/
|
|
261
|
+
function fingerprint_render_admin_page(): void {
|
|
262
|
+
if (!current_user_can('manage_options')) {
|
|
263
|
+
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'fingerprint-anti-bot'));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
global $fingerprint_security_profiles, $wpdb;
|
|
267
|
+
|
|
268
|
+
// Sauvegarde des réglages
|
|
269
|
+
if (isset($_POST['fingerprint_save_settings']) && check_admin_referer('fingerprint_settings_nonce', 'fingerprint_nonce')) {
|
|
270
|
+
$saved = get_option('fingerprint_security_options', []);
|
|
271
|
+
if (!is_array($saved)) {
|
|
272
|
+
$saved = [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Profils sélectionnés
|
|
276
|
+
$saved['frontend']['profile'] = isset($_POST['frontend_profile']) ? sanitize_text_field(wp_unslash($_POST['frontend_profile'])) : 'blog';
|
|
277
|
+
$saved['admin']['profile'] = isset($_POST['admin_profile']) ? sanitize_text_field(wp_unslash($_POST['admin_profile'])) : 'strict';
|
|
278
|
+
$saved['api']['profile'] = isset($_POST['api_profile']) ? sanitize_text_field(wp_unslash($_POST['api_profile'])) : 'api';
|
|
279
|
+
|
|
280
|
+
// Seuils Frontend
|
|
281
|
+
$saved['frontend']['overrides']['thresholds'] = [
|
|
282
|
+
'low' => max(1, isset($_POST['frontend_threshold_low']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_low'])) : 20),
|
|
283
|
+
'medium' => max(5, isset($_POST['frontend_threshold_medium']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_medium'])) : 45),
|
|
284
|
+
'high' => max(10, isset($_POST['frontend_threshold_high']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_high'])) : 75),
|
|
285
|
+
'block' => max(20, isset($_POST['frontend_threshold_block']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_block'])) : 95),
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
// Options booléennes
|
|
289
|
+
$saved['frontend']['overrides']['challengeNewDevices'] = !empty($_POST['frontend_challenge_new']);
|
|
290
|
+
$saved['frontend']['overrides']['verbose'] = !empty($_POST['frontend_verbose']);
|
|
291
|
+
$saved['frontend']['overrides']['honeypot']['detectInjections'] = !empty($_POST['detect_injections']);
|
|
292
|
+
|
|
293
|
+
// Surcharges de l'ensemble des poids de métriques
|
|
294
|
+
foreach ($_POST as $postKey => $postVal) {
|
|
295
|
+
if (str_starts_with((string)$postKey, 'weight_')) {
|
|
296
|
+
$wKey = sanitize_key(substr((string)$postKey, 7));
|
|
297
|
+
$val = (float)sanitize_text_field(wp_unslash($postVal));
|
|
298
|
+
$saved['frontend']['overrides']['weights'][$wKey] = max(0.0, min(2.0, $val));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
update_option('fingerprint_security_options', $saved);
|
|
303
|
+
echo '<div class="notice notice-success is-dismissible"><p><strong>' . esc_html__('Fingerprint settings updated successfully.', 'fingerprint-anti-bot') . '</strong></p></div>';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Action pour vider le cache SQL manuellement
|
|
307
|
+
if (isset($_POST['fingerprint_clear_store']) && check_admin_referer('fingerprint_clear_nonce', 'fingerprint_nonce_clear')) {
|
|
308
|
+
$store = new WpDbStore();
|
|
309
|
+
$store->clear();
|
|
310
|
+
echo '<div class="notice notice-info is-dismissible"><p>' . esc_html__('SQL cache table ($wpdb) cleared successfully.', 'fingerprint-anti-bot') . '</p></div>';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Récupération des profils effectifs
|
|
314
|
+
$effective = fingerprint_get_effective_profiles($fingerprint_security_profiles);
|
|
315
|
+
$frontendConfig = SecurityProfiles::createSecurityProfile($effective['frontend']['profile'], $effective['frontend']['overrides'] ?? []);
|
|
316
|
+
|
|
317
|
+
// Statistiques de la table de cache SQL
|
|
318
|
+
$tableName = esc_sql($wpdb->prefix . 'fingerprint_store');
|
|
319
|
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Administrative stats on custom high-velocity table.
|
|
320
|
+
$totalRows = (int)$wpdb->get_var("SELECT COUNT(*) FROM `{$tableName}`");
|
|
321
|
+
$now = time();
|
|
322
|
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Administrative stats on custom high-velocity table.
|
|
323
|
+
$expiredRows = (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM `{$tableName}` WHERE expires_at IS NOT NULL AND expires_at < %d", $now));
|
|
324
|
+
|
|
325
|
+
// Métriques Prometheus au format texte
|
|
326
|
+
$prometheusRaw = MetricsManager::getPrometheusMetrics($frontendConfig);
|
|
327
|
+
?>
|
|
328
|
+
<div class="wrap">
|
|
329
|
+
<h1><span class="dashicons dashicons-shield-alt" style="font-size:32px;vertical-align:middle;margin-right:8px;"></span> Fingerprint Anti-Bot & Proof-of-Work</h1>
|
|
330
|
+
<p class="description"><?php esc_html_e('Client-side behavioral and cryptographic anti-bot protection without third-party CAPTCHA for WordPress.', 'fingerprint-anti-bot'); ?></p>
|
|
331
|
+
|
|
332
|
+
<div style="background:#fff;border-left:4px solid #2271b1;padding:12px 18px;margin:18px 0;box-shadow:0 1px 1px rgba(0,0,0,.04);">
|
|
333
|
+
<p style="margin:4px 0;">
|
|
334
|
+
<strong><?php esc_html_e('Official documentation reference:', 'fingerprint-anti-bot'); ?></strong>
|
|
335
|
+
<a href="https://github.com/anonympins/fingerprint/blob/main/doc/full_options.md" target="_blank" rel="noopener noreferrer" class="button button-secondary" style="margin-left:8px;">
|
|
336
|
+
<span class="dashicons dashicons-external" style="vertical-align:middle;"></span> <?php esc_html_e('View Full Configuration Options on GitHub', 'fingerprint-anti-bot'); ?>
|
|
337
|
+
</a>
|
|
338
|
+
<a href="https://github.com/anonympins/fingerprint" target="_blank" rel="noopener noreferrer" class="button button-secondary" style="margin-left:4px;">
|
|
339
|
+
<span class="dashicons dashicons-admin-plugins" style="vertical-align:middle;"></span> <?php esc_html_e('GitHub Repository', 'fingerprint-anti-bot'); ?>
|
|
340
|
+
</a>
|
|
341
|
+
</p>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<!-- TABLEAU DE BORD STATISTIQUES & ÉTAT -->
|
|
345
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(240px, 1fr));gap:16px;margin-bottom:24px;">
|
|
346
|
+
<div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
|
|
347
|
+
<h3 style="margin-top:0;"><?php esc_html_e('HTTPS Status', 'fingerprint-anti-bot'); ?></h3>
|
|
348
|
+
<?php if (is_ssl()): ?>
|
|
349
|
+
<p style="color:#007017;font-weight:bold;font-size:16px;">
|
|
350
|
+
<span class="dashicons dashicons-yes-alt"></span> <?php esc_html_e('Active (WebCrypto Subtle available)', 'fingerprint-anti-bot'); ?>
|
|
351
|
+
</p>
|
|
352
|
+
<?php else: ?>
|
|
353
|
+
<p style="color:#d63638;font-weight:bold;font-size:16px;">
|
|
354
|
+
<span class="dashicons dashicons-warning"></span> <?php esc_html_e('Insecure (Unencrypted HTTP)', 'fingerprint-anti-bot'); ?>
|
|
355
|
+
</p>
|
|
356
|
+
<?php endif; ?>
|
|
357
|
+
</div>
|
|
358
|
+
<div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
|
|
359
|
+
<h3 style="margin-top:0;"><?php esc_html_e('SQL Cache ($wpdb)', 'fingerprint-anti-bot'); ?></h3>
|
|
360
|
+
<p style="font-size:22px;margin:0;font-weight:600;"><?php echo esc_html((string)$totalRows); ?> <span style="font-size:14px;color:#646970;font-weight:normal;"><?php esc_html_e('keys in database', 'fingerprint-anti-bot'); ?></span></p>
|
|
361
|
+
<small style="color:#8c8f94;"><?php
|
|
362
|
+
/* translators: %d: number of expired keys awaiting cron purge */
|
|
363
|
+
echo esc_html(sprintf(__(' %d expired keys awaiting cron purge', 'fingerprint-anti-bot'), $expiredRows));
|
|
364
|
+
?></small>
|
|
365
|
+
</div>
|
|
366
|
+
<div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
|
|
367
|
+
<h3 style="margin-top:0;"><?php esc_html_e('Active Profiles', 'fingerprint-anti-bot'); ?></h3>
|
|
368
|
+
<p style="margin:0;">
|
|
369
|
+
<?php esc_html_e('Frontend:', 'fingerprint-anti-bot'); ?> <strong><?php echo esc_html($effective['frontend']['profile']); ?></strong><br>
|
|
370
|
+
<?php esc_html_e('Admin:', 'fingerprint-anti-bot'); ?> <strong><?php echo esc_html($effective['admin']['profile']); ?></strong><br>
|
|
371
|
+
<?php esc_html_e('REST API:', 'fingerprint-anti-bot'); ?> <strong><?php echo esc_html($effective['api']['profile']); ?></strong>
|
|
372
|
+
</p>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<!-- ONGLETS DE NAVIGATION -->
|
|
377
|
+
<h2 class="nav-tab-wrapper">
|
|
378
|
+
<a href="#tab-settings" class="nav-tab nav-tab-active" onclick="fingerprintSwitchTab(event, 'tab-settings')"><?php esc_html_e('Settings & Profiles', 'fingerprint-anti-bot'); ?></a>
|
|
379
|
+
<a href="#tab-metrics" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-metrics')"><?php esc_html_e('Metrics & Weights View', 'fingerprint-anti-bot'); ?></a>
|
|
380
|
+
<a href="#tab-prometheus" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-prometheus')"><?php esc_html_e('Prometheus Stream', 'fingerprint-anti-bot'); ?></a>
|
|
381
|
+
</h2>
|
|
382
|
+
|
|
383
|
+
<!-- TAB 1 : RÉGLAGES -->
|
|
384
|
+
<div id="tab-settings" class="fingerprint-tab-content" style="background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
|
|
385
|
+
<form method="post" action="">
|
|
386
|
+
<?php wp_nonce_field('fingerprint_settings_nonce', 'fingerprint_nonce'); ?>
|
|
387
|
+
|
|
388
|
+
<h3><?php esc_html_e('1. Target Security Profiles', 'fingerprint-anti-bot'); ?></h3>
|
|
389
|
+
<table class="form-table">
|
|
390
|
+
<tr>
|
|
391
|
+
<th scope="row"><label for="frontend_profile"><?php esc_html_e('Visitors & Frontend', 'fingerprint-anti-bot'); ?></label></th>
|
|
392
|
+
<td>
|
|
393
|
+
<select name="frontend_profile" id="frontend_profile">
|
|
394
|
+
<option value="blog" <?php selected($effective['frontend']['profile'], 'blog'); ?>><?php esc_html_e('Blog (Optimal for content, smooth UX)', 'fingerprint-anti-bot'); ?></option>
|
|
395
|
+
<option value="balanced" <?php selected($effective['frontend']['profile'], 'balanced'); ?>><?php esc_html_e('Balanced (General purpose)', 'fingerprint-anti-bot'); ?></option>
|
|
396
|
+
<option value="strict" <?php selected($effective['frontend']['profile'], 'strict'); ?>><?php esc_html_e('Strict (Maximum protection)', 'fingerprint-anti-bot'); ?></option>
|
|
397
|
+
<option value="ecommerce" <?php selected($effective['frontend']['profile'], 'ecommerce'); ?>><?php esc_html_e('E-commerce (Anti-scraping / scalping)', 'fingerprint-anti-bot'); ?></option>
|
|
398
|
+
</select>
|
|
399
|
+
<p class="description"><?php esc_html_e('Profile applied to all public pages of the WordPress site.', 'fingerprint-anti-bot'); ?></p>
|
|
400
|
+
</td>
|
|
401
|
+
</tr>
|
|
402
|
+
<tr>
|
|
403
|
+
<th scope="row"><label for="admin_profile"><?php esc_html_e('Administration Area (wp-login / wp-admin)', 'fingerprint-anti-bot'); ?></label></th>
|
|
404
|
+
<td>
|
|
405
|
+
<select name="admin_profile" id="admin_profile">
|
|
406
|
+
<option value="strict" <?php selected($effective['admin']['profile'], 'strict'); ?>><?php esc_html_e('Strict (Recommended to secure logins)', 'fingerprint-anti-bot'); ?></option>
|
|
407
|
+
<option value="balanced" <?php selected($effective['admin']['profile'], 'balanced'); ?>><?php esc_html_e('Balanced', 'fingerprint-anti-bot'); ?></option>
|
|
408
|
+
</select>
|
|
409
|
+
</td>
|
|
410
|
+
</tr>
|
|
411
|
+
<tr>
|
|
412
|
+
<th scope="row"><label for="api_profile"><?php esc_html_e('REST API (/wp-json/)', 'fingerprint-anti-bot'); ?></label></th>
|
|
413
|
+
<td>
|
|
414
|
+
<select name="api_profile" id="api_profile">
|
|
415
|
+
<option value="api" <?php selected($effective['api']['profile'], 'api'); ?>><?php esc_html_e('API (JSON challenges & machine PoW)', 'fingerprint-anti-bot'); ?></option>
|
|
416
|
+
<option value="strict" <?php selected($effective['api']['profile'], 'strict'); ?>><?php esc_html_e('Strict', 'fingerprint-anti-bot'); ?></option>
|
|
417
|
+
</select>
|
|
418
|
+
</td>
|
|
419
|
+
</tr>
|
|
420
|
+
</table>
|
|
421
|
+
|
|
422
|
+
<hr>
|
|
423
|
+
|
|
424
|
+
<h3><?php esc_html_e('2. Action Thresholds (Frontend)', 'fingerprint-anti-bot'); ?></h3>
|
|
425
|
+
<table class="form-table">
|
|
426
|
+
<tr>
|
|
427
|
+
<th scope="row"><?php esc_html_e('Suspicion Score Thresholds (0 - 100)', 'fingerprint-anti-bot'); ?></th>
|
|
428
|
+
<td>
|
|
429
|
+
<label><?php esc_html_e('Low (Initial challenge):', 'fingerprint-anti-bot'); ?>
|
|
430
|
+
<input type="number" name="frontend_threshold_low" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['low'] ?? 20)); ?>" min="1" max="50" style="width:80px;">
|
|
431
|
+
</label><br><br>
|
|
432
|
+
<label><?php esc_html_e('Medium (Hardened challenge):', 'fingerprint-anti-bot'); ?>
|
|
433
|
+
<input type="number" name="frontend_threshold_medium" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['medium'] ?? 45)); ?>" min="10" max="75" style="width:80px;">
|
|
434
|
+
</label><br><br>
|
|
435
|
+
<label><?php esc_html_e('High (Heavy Proof-of-Work):', 'fingerprint-anti-bot'); ?>
|
|
436
|
+
<input type="number" name="frontend_threshold_high" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['high'] ?? 75)); ?>" min="30" max="95" style="width:80px;">
|
|
437
|
+
</label><br><br>
|
|
438
|
+
<label><?php esc_html_e('Block (Immediate 403 Forbidden):', 'fingerprint-anti-bot'); ?>
|
|
439
|
+
<input type="number" name="frontend_threshold_block" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['block'] ?? 95)); ?>" min="50" max="100" style="width:80px;">
|
|
440
|
+
</label>
|
|
441
|
+
</td>
|
|
442
|
+
</tr>
|
|
443
|
+
<tr>
|
|
444
|
+
<th scope="row"><?php esc_html_e('Advanced Behaviors', 'fingerprint-anti-bot'); ?></th>
|
|
445
|
+
<td>
|
|
446
|
+
<label>
|
|
447
|
+
<input type="checkbox" name="frontend_challenge_new" value="1" <?php checked(!empty($frontendConfig['challengeNewDevices'])); ?>>
|
|
448
|
+
<?php esc_html_e('Automatically challenge unknown new devices (without device_id cookie)', 'fingerprint-anti-bot'); ?>
|
|
449
|
+
</label><br>
|
|
450
|
+
<label>
|
|
451
|
+
<input type="checkbox" name="detect_injections" value="1" <?php checked(!empty($frontendConfig['honeypot']['detectInjections'])); ?>>
|
|
452
|
+
<?php esc_html_e('Enable recursive WAF inspection (SQL, NoSQL, Log4j, Traversal in GET/POST)', 'fingerprint-anti-bot'); ?>
|
|
453
|
+
</label><br>
|
|
454
|
+
<label>
|
|
455
|
+
<input type="checkbox" name="frontend_verbose" value="1" <?php checked(!empty($frontendConfig['verbose'])); ?>>
|
|
456
|
+
<?php esc_html_e('Verbose mode in PHP logs (Debugging)', 'fingerprint-anti-bot'); ?>
|
|
457
|
+
</label>
|
|
458
|
+
</td>
|
|
459
|
+
</tr>
|
|
460
|
+
</table>
|
|
461
|
+
|
|
462
|
+
<?php submit_button(esc_html__('Save Changes', 'fingerprint-anti-bot'), 'primary', 'fingerprint_save_settings'); ?>
|
|
463
|
+
</form>
|
|
464
|
+
|
|
465
|
+
<hr>
|
|
466
|
+
<form method="post" action="" style="margin-top:16px;">
|
|
467
|
+
<?php wp_nonce_field('fingerprint_clear_nonce', 'fingerprint_nonce_clear'); ?>
|
|
468
|
+
<p>
|
|
469
|
+
<strong><?php esc_html_e('Store Maintenance:', 'fingerprint-anti-bot'); ?></strong>
|
|
470
|
+
<button type="submit" name="fingerprint_clear_store" class="button button-secondary" onclick="return confirm('<?php echo esc_js(__('Purge all stored sessions and nonces?', 'fingerprint-anti-bot')); ?>');">
|
|
471
|
+
<?php esc_html_e('Clear SQL cache table ($wpdb)', 'fingerprint-anti-bot'); ?>
|
|
472
|
+
</button>
|
|
473
|
+
</p>
|
|
474
|
+
</form>
|
|
475
|
+
</div>
|
|
476
|
+
|
|
477
|
+
<!-- TAB 2 : VUE DÉTAILLÉE DES MÉTRIQUES -->
|
|
478
|
+
<div id="tab-metrics" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
|
|
479
|
+
<h3><?php esc_html_e('Behavioral & Transport Indicator Weights (Active Frontend Profile)', 'fingerprint-anti-bot'); ?></h3>
|
|
480
|
+
<p class="description"><?php echo wp_kses_post(__('The final score of each request is computed using the weighted sum: <code>Score = Σ (Metric × Weight)</code>.', 'fingerprint-anti-bot')); ?></p>
|
|
481
|
+
|
|
482
|
+
<form method="post" action="">
|
|
483
|
+
<?php wp_nonce_field('fingerprint_settings_nonce', 'fingerprint_nonce'); ?>
|
|
484
|
+
<table class="wp-list-table widefat fixed striped">
|
|
485
|
+
<thead>
|
|
486
|
+
<tr>
|
|
487
|
+
<th style="width:280px;"><?php esc_html_e('Indicator / Metric', 'fingerprint-anti-bot'); ?></th>
|
|
488
|
+
<th style="width:120px;"><?php esc_html_e('Current Weight', 'fingerprint-anti-bot'); ?></th>
|
|
489
|
+
<th><?php esc_html_e('Description & Detection Role', 'fingerprint-anti-bot'); ?></th>
|
|
490
|
+
</tr>
|
|
491
|
+
</thead>
|
|
492
|
+
<tbody>
|
|
493
|
+
<?php
|
|
494
|
+
$metricDescriptions = [
|
|
495
|
+
'honeypotScore' => __('Triggering of invisible trap links, multi-framework honeypots, and injection probes.', 'fingerprint-anti-bot'),
|
|
496
|
+
'headerAnomalyScore' => __('Missing, malformed, or out-of-order HTTP headers inconsistent with legitimate browsers.', 'fingerprint-anti-bot'),
|
|
497
|
+
'tlsSpoofingScore' => __('Discrepancy between User-Agent and TLS cryptographic fingerprints (JA3/JA4, extensions, GREASE).', 'fingerprint-anti-bot'),
|
|
498
|
+
'behaviorScore' => __('Dynamic biometrics: mouse movements, keystroke dynamics, and mobile touch anomalies.', 'fingerprint-anti-bot'),
|
|
499
|
+
'requestPatternScore' => __('Velocity, robotic bursts, request standard deviation, and Benford\'s law statistical analysis.', 'fingerprint-anti-bot'),
|
|
500
|
+
'inconsistencyScore' => __('Alteration or drift in the hardware fingerprint between visits with the same cookie.', 'fingerprint-anti-bot'),
|
|
501
|
+
'subnetScore' => __('Aggregated IP reputation of the network subnet (/24 or /48) and nearby attacker density.', 'fingerprint-anti-bot'),
|
|
502
|
+
'ipReputationScore' => __('Local historical reputation score of the client IP address with temporal decay over time.', 'fingerprint-anti-bot'),
|
|
503
|
+
'renderingAnomalyScore' => __('Display rendering anomalies, V-Sync/FPS cadence, and OffscreenCanvas hooking.', 'fingerprint-anti-bot'),
|
|
504
|
+
'protocolAnomalyScore' => __('Divergence in HTTP/2 and QUIC / HTTP/3 flow control frame settings.', 'fingerprint-anti-bot'),
|
|
505
|
+
'quicAnomalyScore' => __('Anomalies in QUIC transport parameters (flow control windows, max streams, and control frame ordering).', 'fingerprint-anti-bot'),
|
|
506
|
+
'tcpAnomalyScore' => __('Passive TCP/IP stack fingerprinting (SYN packet TTL, window size, MSS, options) vs declared operating system.', 'fingerprint-anti-bot'),
|
|
507
|
+
'historyScore' => __('Suspicious rotation of multiple IP addresses associated with a single hardware identity.', 'fingerprint-anti-bot'),
|
|
508
|
+
'rotationScore' => __('Rapid and abnormal modifications to the application stack.', 'fingerprint-anti-bot'),
|
|
509
|
+
'botScore' => __('Detection of native automation attributes (navigator.webdriver, Chrome CDP).', 'fingerprint-anti-bot'),
|
|
510
|
+
'cookieDroppingScore' => __('Detection of cookie deletion, dropping, or refusal between consecutive requests to bypass device identification.', 'fingerprint-anti-bot'),
|
|
511
|
+
'threatIntelScore' => __('Correlation with known proxy IPs, Tor exit nodes, and federated threat intelligence blacklist.', 'fingerprint-anti-bot'),
|
|
512
|
+
'clientHintsInconsistencyScore' => __('Discrepancies between standard User-Agent and Sec-CH-UA Client Hints headers (browser family, version drift).', 'fingerprint-anti-bot'),
|
|
513
|
+
'clickVarianceScore' => __('Statistical analysis of click coordinate variance; flags unnaturally low pixel variance typical of click bots.', 'fingerprint-anti-bot'),
|
|
514
|
+
'botnetClusterScore' => __('Clustering of volatile client requests sharing identical hardware signatures across distinct IP addresses.', 'fingerprint-anti-bot'),
|
|
515
|
+
'timeInconsistencyScore' => __('Time delta between client timestamp and server reception to detect replay and automated delay attacks.', 'fingerprint-anti-bot'),
|
|
516
|
+
'virtualizationScore' => __('Detection of headless browsers, virtual GPU renderers (SwiftShader, llvmpipe) and emulated display resolutions.', 'fingerprint-anti-bot'),
|
|
517
|
+
'crossLayerInconsistencyScore' => __('Strict contradiction between layers (e.g. declared OS in User-Agent vs network TCP/IP stack OS).', 'fingerprint-anti-bot'),
|
|
518
|
+
];
|
|
519
|
+
|
|
520
|
+
foreach ($frontendConfig['weights'] as $indicator => $weight):
|
|
521
|
+
$desc = $metricDescriptions[$indicator] ?? __('Behavioral suspicion indicator.', 'fingerprint-anti-bot');
|
|
522
|
+
?>
|
|
523
|
+
<tr>
|
|
524
|
+
<td><code><?php echo esc_html($indicator); ?></code></td>
|
|
525
|
+
<td>
|
|
526
|
+
<input type="number" step="0.05" min="0" max="2.0" name="weight_<?php echo esc_attr($indicator); ?>" value="<?php echo esc_attr((string)$weight); ?>" style="width:75px;">
|
|
527
|
+
</td>
|
|
528
|
+
<td><?php echo esc_html($desc); ?></td>
|
|
529
|
+
</tr>
|
|
530
|
+
<?php endforeach; ?>
|
|
531
|
+
</tbody>
|
|
532
|
+
</table>
|
|
533
|
+
<p style="margin-top:16px;">
|
|
534
|
+
<?php submit_button(esc_html__('Update Metric Weights', 'fingerprint-anti-bot'), 'primary', 'fingerprint_save_settings', false); ?>
|
|
535
|
+
</p>
|
|
536
|
+
</form>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<!-- TAB 3 : FLUX PROMETHEUS -->
|
|
540
|
+
<div id="tab-prometheus" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
|
|
541
|
+
<h3><?php esc_html_e('Real-Time Prometheus Metrics', 'fingerprint-anti-bot'); ?></h3>
|
|
542
|
+
<p class="description"><?php esc_html_e('Raw text format exported for scraping by Prometheus, Grafana Agent, or Datadog.', 'fingerprint-anti-bot'); ?></p>
|
|
543
|
+
<textarea readonly style="width:100%;height:380px;font-family:monospace;background:#f6f7f7;padding:12px;"><?php echo esc_textarea($prometheusRaw); ?></textarea>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
|
|
547
|
+
<script>
|
|
548
|
+
function fingerprintSwitchTab(evt, tabId) {
|
|
549
|
+
evt.preventDefault();
|
|
550
|
+
const tabs = document.querySelectorAll('.fingerprint-tab-content');
|
|
551
|
+
tabs.forEach(t => t.style.display = 'none');
|
|
552
|
+
const navs = document.querySelectorAll('.nav-tab-wrapper a');
|
|
553
|
+
navs.forEach(n => n.classList.remove('nav-tab-active'));
|
|
554
|
+
document.getElementById(tabId).style.display = 'block';
|
|
555
|
+
evt.currentTarget.classList.add('nav-tab-active');
|
|
556
|
+
}
|
|
557
|
+
</script>
|
|
558
|
+
<?php
|
|
559
|
+
}
|
|
Binary file
|