@anonympins/fingerprint 0.6.7 → 0.7.1
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 +33 -0
- package/README.md +3 -7
- package/composer.json +8 -1
- package/package.json +1 -1
- package/public/fingerprint-wordpress.zip +0 -0
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +9 -11
- package/src/js/dynamic-wasm.js +7 -7
- package/src/js/fingerprint.builder.js +37 -38
- package/src/js/fingerprint.client.js +236 -152
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +517 -101
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +355 -384
- package/src/js/mongodb-store.js +1 -1
- package/src/js/optimization.worker.js +27 -27
- package/src/js/pow.solver.inline.js +206 -62
- package/src/js/pow.solver.js +235 -101
- package/src/js/pow.worker.js +48 -26
- package/src/js/redis-store.js +1 -1
- package/src/js/tests/fingerprint.client.init.test.js +146 -143
- package/src/js/tests/fingerprint.test.js +101 -40
- package/src/js/tests/ip-reputation.test.js +1 -0
- package/src/js/upow-model-task.js +14 -15
- package/src/php/AutoTuner.php +34 -34
- package/src/php/Challenge/ChallengeUtils.php +204 -32
- package/src/php/Config/SecurityProfiles.php +21 -0
- package/src/php/DirectFingerprint.php +190 -145
- package/src/php/FingerprintBuilder.php +29 -30
- package/src/php/FingerprintClient.php +24 -25
- package/src/php/FingerprintEngine.php +1836 -1880
- package/src/php/Ja3AnomalyDetector.php +258 -227
- package/src/php/Optimization/FunctionRegistry.php +7 -7
- package/src/php/Optimization/Optimization.php +24 -24
- package/src/php/Optimization/OptimizationOperators.php +28 -27
- package/src/php/Optimization/ProblemInitializers.php +52 -52
- package/src/php/ProblemManager.php +31 -34
- package/src/php/RequestContext.php +8 -1
- package/src/php/Store/IStore.php +7 -8
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +5 -5
- package/src/php/Store/RedisStore.php +5 -5
- package/src/php/Store/StoreManager.php +35 -35
- package/src/php/Tests/ChallengeUtilsTest.php +453 -453
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintEngineTest.php +662 -693
- package/src/php/Tests/IpReputationTest.php +10 -0
- package/src/php/Tests/MaliciousPatternsTest.php +103 -103
- package/src/php/Tests/MetricsTest.php +97 -97
- package/src/php/Tests/ProblemManagerTest.php +376 -376
- package/src/php/Tests/QuicFingerprintTest.php +53 -53
- package/src/php/Tests/RequestUtilsTest.php +471 -471
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
- package/src/php/Tests/config/ed25519_key.json +3 -3
- package/src/php/Utils/Env.php +49 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +236 -19
- package/src/php/Utils/TLSClientHelloParser.php +369 -212
- package/src/php/WordPress/WpDbStore.php +155 -0
- package/src/php/WordPress/fingerprint-wordpress.php +217 -0
- package/src/php/WordPress/package.php +123 -0
- package/src/php/bin/auto-tune.php +118 -118
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\WordPress;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\Store\IStore;
|
|
8
|
+
|
|
9
|
+
// Polyfill pour ARRAY_A si chargé hors du cycle de vie standard de WordPress
|
|
10
|
+
if (!defined('ARRAY_A')) {
|
|
11
|
+
define('ARRAY_A', 'ARRAY_A');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Adaptateur IStore persistant pour WordPress exploitant l'objet $wpdb.
|
|
16
|
+
* Permet de stocker les sessions d'appareils, nonces et états de PoW
|
|
17
|
+
* sans dépendre d'une extension mémoire comme Redis ou APCu.
|
|
18
|
+
*/
|
|
19
|
+
class WpDbStore implements IStore
|
|
20
|
+
{
|
|
21
|
+
/** @var \wpdb|object|null */
|
|
22
|
+
private $db;
|
|
23
|
+
private string $table;
|
|
24
|
+
|
|
25
|
+
public function __construct($db = null)
|
|
26
|
+
{
|
|
27
|
+
global $wpdb;
|
|
28
|
+
$this->db = $db ?? $wpdb;
|
|
29
|
+
$prefix = (is_object($this->db) && isset($this->db->prefix)) ? $this->db->prefix : 'wp_';
|
|
30
|
+
$this->table = $prefix . 'fingerprint_store';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Crée la table de stockage si elle n'existe pas encore.
|
|
35
|
+
*/
|
|
36
|
+
public function ensureTable(): void
|
|
37
|
+
{
|
|
38
|
+
if (!$this->db || !method_exists($this->db, 'get_charset_collate')) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$charsetCollate = $this->db->get_charset_collate();
|
|
43
|
+
$sql = "CREATE TABLE IF NOT EXISTS {$this->table} (
|
|
44
|
+
`key_id` VARCHAR(191) NOT NULL,
|
|
45
|
+
`value` LONGTEXT NOT NULL,
|
|
46
|
+
`expires_at` BIGINT UNSIGNED NULL,
|
|
47
|
+
PRIMARY KEY (`key_id`),
|
|
48
|
+
KEY `expires_idx` (`expires_at`)
|
|
49
|
+
) {$charsetCollate};";
|
|
50
|
+
|
|
51
|
+
// Chargement sécurisé de dbDelta si ABSPATH est présent
|
|
52
|
+
if (!function_exists('dbDelta') && defined('ABSPATH')) {
|
|
53
|
+
$upgradeFile = ABSPATH . 'wp-admin/includes/upgrade.php';
|
|
54
|
+
if (file_exists($upgradeFile)) {
|
|
55
|
+
require_once $upgradeFile;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (function_exists('dbDelta')) {
|
|
60
|
+
dbDelta($sql);
|
|
61
|
+
} elseif (method_exists($this->db, 'query')) {
|
|
62
|
+
$this->db->query($sql);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public function get(string $key)
|
|
67
|
+
{
|
|
68
|
+
if (!$this->db || !method_exists($this->db, 'prepare')) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$sql = $this->db->prepare(
|
|
73
|
+
"SELECT `value`, `expires_at` FROM {$this->table} WHERE `key_id` = %s",
|
|
74
|
+
$key
|
|
75
|
+
);
|
|
76
|
+
$outputType = defined('ARRAY_A') ? \ARRAY_A : 'ARRAY_A';
|
|
77
|
+
$row = $this->db->get_row($sql, $outputType);
|
|
78
|
+
|
|
79
|
+
if (!$row) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if ($row['expires_at'] !== null && (int)$row['expires_at'] < time()) {
|
|
84
|
+
$this->delete($key);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
$decoded = json_decode((string)$row['value'], true);
|
|
89
|
+
return (json_last_error() === JSON_ERROR_NONE) ? $decoded : $row['value'];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public function set(string $key, $value, ?int $ttl = null): void
|
|
93
|
+
{
|
|
94
|
+
if (!$this->db || !method_exists($this->db, 'replace')) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
$expiresAt = ($ttl !== null && $ttl > 0) ? (time() + $ttl) : null;
|
|
99
|
+
$serialized = is_scalar($value) && !is_bool($value)
|
|
100
|
+
? (string)$value
|
|
101
|
+
: json_encode($value, JSON_UNESCAPED_SLASHES);
|
|
102
|
+
|
|
103
|
+
$this->db->replace(
|
|
104
|
+
$this->table,
|
|
105
|
+
[
|
|
106
|
+
'key_id' => $key,
|
|
107
|
+
'value' => $serialized,
|
|
108
|
+
'expires_at' => $expiresAt
|
|
109
|
+
],
|
|
110
|
+
['%s', '%s', $expiresAt !== null ? '%d' : null]
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public function has(string $key): bool
|
|
115
|
+
{
|
|
116
|
+
return $this->get($key) !== null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public function delete(string $key): void
|
|
120
|
+
{
|
|
121
|
+
if (!$this->db || !method_exists($this->db, 'delete')) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
$this->db->delete(
|
|
126
|
+
$this->table,
|
|
127
|
+
['key_id' => $key],
|
|
128
|
+
['%s']
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public function clear(): void
|
|
133
|
+
{
|
|
134
|
+
if (!$this->db || !method_exists($this->db, 'query')) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
$this->db->query("TRUNCATE TABLE {$this->table}");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Nettoie les lignes expirées (appelé par le WP-Cron périodique).
|
|
142
|
+
*/
|
|
143
|
+
public function pruneExpired(): int
|
|
144
|
+
{
|
|
145
|
+
if (!$this->db || !method_exists($this->db, 'prepare')) {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
$now = time();
|
|
150
|
+
$deleted = $this->db->query(
|
|
151
|
+
$this->db->prepare("DELETE FROM {$this->table} WHERE `expires_at` IS NOT NULL AND `expires_at` < %d", $now)
|
|
152
|
+
);
|
|
153
|
+
return is_numeric($deleted) ? (int)$deleted : 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Name: Fingerprint Anti-Bot & Proof-of-Work
|
|
4
|
+
* Plugin URI: https://github.com/anonympins/fingerprint
|
|
5
|
+
* Description: Protection bot haute performance pour WordPress avec Proof-of-Work client et détection d'anomalies.
|
|
6
|
+
* Version: 0.7.1
|
|
7
|
+
* Author: Anonympins
|
|
8
|
+
* License: MIT
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare(strict_types=1);
|
|
12
|
+
|
|
13
|
+
use Anonympins\Fingerprint\DirectFingerprint;
|
|
14
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
15
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
16
|
+
use Anonympins\Fingerprint\WordPress\WpDbStore;
|
|
17
|
+
|
|
18
|
+
if (!defined('ABSPATH')) {
|
|
19
|
+
exit;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// PIÈGES HONEYPOT POUR ENVIRONNEMENTS TIERS (NON-WORDPRESS)
|
|
24
|
+
// Détecte et bloque instantanément les scans de vulnérabilités ciblant
|
|
25
|
+
// Laravel, Symfony, Spring Boot, Django, ASP.NET, phpMyAdmin, Git, Cloud, etc.
|
|
26
|
+
// =============================================================================
|
|
27
|
+
$foreignEnvTrapUrls = [
|
|
28
|
+
// Outils d'administration BDD & scripts de debug
|
|
29
|
+
'/phpmyadmin', '/pma', '/adminer', '/mysql', '/dbadmin', '/phpinfo',
|
|
30
|
+
// Frameworks PHP (Laravel, Symfony) & fichiers d'environnement
|
|
31
|
+
'/.env', '/artisan', '/telescope', '/_profiler', '/config/database.php', '/vendor/',
|
|
32
|
+
// Java / Spring Boot / Actuator
|
|
33
|
+
'/actuator', '/actuator/health', '/actuator/gateway', '/api-docs', '/swagger-ui',
|
|
34
|
+
// Python / Django / Flask
|
|
35
|
+
'/.flask', '/django_admin', '/__pycache__',
|
|
36
|
+
// Node.js & configuration paquets
|
|
37
|
+
'/.npmrc', '/package.json', '/package-lock.json',
|
|
38
|
+
// ASP.NET / IIS / CGI
|
|
39
|
+
'/elmah.axd', '/trace.axd', '/cgi-bin/',
|
|
40
|
+
// DevOps, VCS & Déploiements cloud
|
|
41
|
+
'/.git', '/.svn', '/.aws', '/.kube', '/docker-compose', '/serverless'
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
$foreignEnvHoneypotFields = [
|
|
45
|
+
// Tokens CSRF et formulaires spécifiques à d'autres frameworks
|
|
46
|
+
'_token', // Laravel
|
|
47
|
+
'csrfmiddlewaretoken', // Django
|
|
48
|
+
'authenticity_token', // Ruby on Rails
|
|
49
|
+
'form_build_id', // Drupal
|
|
50
|
+
'form_id', // Drupal
|
|
51
|
+
'j_username', // Java / Spring Security
|
|
52
|
+
'j_password', // Java / Spring Security
|
|
53
|
+
'__VIEWSTATE', // ASP.NET WebForms
|
|
54
|
+
'__EVENTVALIDATION', // ASP.NET WebForms
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// CONFIGURATION RAPIDE DES PROFILS DE SÉCURITÉ PAR CONTEXTE
|
|
59
|
+
// =============================================================================
|
|
60
|
+
$fingerprintSecurityProfiles = [
|
|
61
|
+
'api' => [
|
|
62
|
+
'profile' => 'api',
|
|
63
|
+
'overrides' => [
|
|
64
|
+
'verbose' => false,
|
|
65
|
+
'challengeNewDevices' => false,
|
|
66
|
+
'honeypot' => [
|
|
67
|
+
'fields' => $foreignEnvHoneypotFields,
|
|
68
|
+
'trapUrls' => $foreignEnvTrapUrls,
|
|
69
|
+
'detectInjections' => true,
|
|
70
|
+
],
|
|
71
|
+
],
|
|
72
|
+
],
|
|
73
|
+
'admin' => [
|
|
74
|
+
'profile' => 'strict',
|
|
75
|
+
'overrides' => [
|
|
76
|
+
'verbose' => true,
|
|
77
|
+
'challengeNewDevices' => true,
|
|
78
|
+
'honeypot' => [
|
|
79
|
+
'fields' => $foreignEnvHoneypotFields,
|
|
80
|
+
'trapUrls' => $foreignEnvTrapUrls,
|
|
81
|
+
'detectInjections' => true,
|
|
82
|
+
],
|
|
83
|
+
],
|
|
84
|
+
],
|
|
85
|
+
'frontend' => [
|
|
86
|
+
'profile' => 'blog',
|
|
87
|
+
'overrides' => [
|
|
88
|
+
'verbose' => false,
|
|
89
|
+
'challengeNewDevices' => false,
|
|
90
|
+
'honeypot' => [
|
|
91
|
+
'fields' => $foreignEnvHoneypotFields,
|
|
92
|
+
'trapUrls' => $foreignEnvTrapUrls,
|
|
93
|
+
'detectInjections' => true,
|
|
94
|
+
],
|
|
95
|
+
],
|
|
96
|
+
],
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// 1. Autoloader PSR-4 pour le moteur Fingerprint
|
|
100
|
+
if (!class_exists(DirectFingerprint::class)) {
|
|
101
|
+
$composerPaths = [
|
|
102
|
+
__DIR__ . '/vendor/autoload.php',
|
|
103
|
+
dirname(__DIR__, 2) . '/vendor/autoload.php',
|
|
104
|
+
dirname(__DIR__, 3) . '/vendor/autoload.php',
|
|
105
|
+
(defined('ABSPATH') ? ABSPATH . 'vendor/autoload.php' : ''),
|
|
106
|
+
];
|
|
107
|
+
foreach ($composerPaths as $cPath) {
|
|
108
|
+
if (!empty($cPath) && file_exists($cPath)) {
|
|
109
|
+
require_once $cPath;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
spl_autoload_register(function (string $class): void {
|
|
115
|
+
$prefix = 'Anonympins\\Fingerprint\\';
|
|
116
|
+
$len = strlen($prefix);
|
|
117
|
+
if (strncmp($prefix, $class, $len) !== 0) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
$relativeClass = substr($class, $len);
|
|
122
|
+
|
|
123
|
+
// Classes spécifiques WordPress (ex: WpDbStore)
|
|
124
|
+
if (str_starts_with($relativeClass, 'WordPress\\')) {
|
|
125
|
+
$localClass = substr($relativeClass, strlen('WordPress\\'));
|
|
126
|
+
$localFile = __DIR__ . '/' . str_replace('\\', '/', $localClass) . '.php';
|
|
127
|
+
if (file_exists($localFile)) {
|
|
128
|
+
require_once $localFile;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Chemins candidats pour le moteur PHP (packagé ou environnement dev)
|
|
134
|
+
$candidateDirs = [
|
|
135
|
+
__DIR__ . '/src/',
|
|
136
|
+
__DIR__ . '/includes/',
|
|
137
|
+
__DIR__ . '/',
|
|
138
|
+
dirname(__DIR__) . '/',
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
$fileRelative = str_replace('\\', '/', $relativeClass) . '.php';
|
|
142
|
+
foreach ($candidateDirs as $dir) {
|
|
143
|
+
$fullPath = $dir . $fileRelative;
|
|
144
|
+
if (file_exists($fullPath)) {
|
|
145
|
+
require_once $fullPath;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2. Activation du plugin : Création de la table de cache SQL
|
|
153
|
+
register_activation_hook(__FILE__, function () {
|
|
154
|
+
$store = new WpDbStore();
|
|
155
|
+
$store->ensureTable();
|
|
156
|
+
|
|
157
|
+
if (!wp_next_scheduled('fingerprint_prune_expired_entries')) {
|
|
158
|
+
wp_schedule_event(time(), 'hourly', 'fingerprint_prune_expired_entries');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// 3. Désactivation : Nettoyage du WP-Cron
|
|
163
|
+
register_deactivation_hook(__FILE__, function () {
|
|
164
|
+
wp_clear_scheduled_hook('fingerprint_prune_expired_entries');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 4. Tâche de fond WP-Cron : Purge des clés expirées dans la BDD
|
|
168
|
+
add_action('fingerprint_prune_expired_entries', function () {
|
|
169
|
+
$store = new WpDbStore();
|
|
170
|
+
$store->pruneExpired();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// 5. Avertissement Admin si le site n'utilise pas HTTPS (Environnement Non Sécurisé)
|
|
174
|
+
add_action('admin_notices', function () {
|
|
175
|
+
if (!is_ssl()) {
|
|
176
|
+
echo '<div class="notice notice-warning is-dismissible">';
|
|
177
|
+
echo '<p><strong>[Fingerprint Anti-Bot Security Warning]</strong> : Votre site fonctionne actuellement sous le protocole non chiffré <code>HTTP</code>. ';
|
|
178
|
+
echo 'Dans ce mode, l\'API native <em>Web Cryptography</em> (<code>crypto.subtle</code>) des navigateurs est désactivée par mesure de sécurité par les navigateurs modernes, ';
|
|
179
|
+
echo 'forçant l\'exécution d\'un simulacre/fallback purement JavaScript plus lent et vulnérable aux attaques par interception (MitM). ';
|
|
180
|
+
echo 'Il est fortement recommandé de déployer un certificat TLS/SSL et de forcer <code>HTTPS</code> pour garantir l\'intégrité des calculs PoW et la protection des cookies d\'identité.</p>';
|
|
181
|
+
echo '</div>';
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// 5. Interception de sécurité au plus tôt du cycle de vie WordPress
|
|
186
|
+
add_action('plugins_loaded', function () use ($fingerprintSecurityProfiles) {
|
|
187
|
+
// Ignore WP-CLI et les exécutions internes de cron
|
|
188
|
+
if ((defined('WP_CLI') && WP_CLI) || (defined('DOING_CRON') && DOING_CRON)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Initialiser le Store persistant basé sur $wpdb
|
|
193
|
+
$store = new WpDbStore();
|
|
194
|
+
StoreManager::configureStore($store);
|
|
195
|
+
|
|
196
|
+
$requestUri = $_SERVER['REQUEST_URI'] ?? '/';
|
|
197
|
+
$isRestApi = defined('REST_REQUEST') && REST_REQUEST;
|
|
198
|
+
$isAdmin = is_admin() || str_contains($requestUri, 'wp-login.php');
|
|
199
|
+
|
|
200
|
+
// Possibilité de filtrer la configuration via functions.php ou un thème
|
|
201
|
+
$configs = apply_filters('fingerprint_security_profiles', $fingerprintSecurityProfiles);
|
|
202
|
+
|
|
203
|
+
// Sélection adaptative du profil selon la cible de la requête
|
|
204
|
+
if ($isRestApi || str_starts_with($requestUri, '/wp-json/')) {
|
|
205
|
+
$contextConfig = $configs['api'] ?? $fingerprintSecurityProfiles['api'];
|
|
206
|
+
} elseif ($isAdmin) {
|
|
207
|
+
$contextConfig = $configs['admin'] ?? $fingerprintSecurityProfiles['admin'];
|
|
208
|
+
} else {
|
|
209
|
+
$contextConfig = $configs['frontend'] ?? $fingerprintSecurityProfiles['frontend'];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
$securityConfig = SecurityProfiles::createSecurityProfile($contextConfig['profile'], $contextConfig['overrides'] ?? []);
|
|
213
|
+
$guard = new DirectFingerprint($securityConfig);
|
|
214
|
+
|
|
215
|
+
// Inspecte la requête : bloque ou envoie le challenge si nécessaire et stoppe le script
|
|
216
|
+
$guard->protect();
|
|
217
|
+
}, 0);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Script d'empaquetage automatique du plugin WordPress.
|
|
7
|
+
* Copie les classes du moteur PHP, les assets JS et génère un ZIP prêt à être installé.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* php src/php/WordPress/package.php
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
if (!extension_loaded('zip')) {
|
|
14
|
+
fwrite(STDERR, "Erreur : L'extension PHP 'zip' est requise pour créer l'archive.\n");
|
|
15
|
+
exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
$rootDir = dirname(__DIR__, 3);
|
|
19
|
+
$phpSrcDir = $rootDir . '/src/php';
|
|
20
|
+
$jsSrcDir = $rootDir . '/src/js';
|
|
21
|
+
$wpDir = $phpSrcDir . '/WordPress';
|
|
22
|
+
$distDir = $rootDir . '/public';
|
|
23
|
+
$pluginSlug = 'fingerprint-wordpress';
|
|
24
|
+
$buildDir = $distDir . '/' . $pluginSlug;
|
|
25
|
+
$zipFile = $distDir . '/' . $pluginSlug . '.zip';
|
|
26
|
+
|
|
27
|
+
echo "==> Début du packaging du plugin WordPress...\n";
|
|
28
|
+
|
|
29
|
+
// 1. Nettoyage du répertoire de build précédent
|
|
30
|
+
if (is_dir($buildDir)) {
|
|
31
|
+
$iterator = new RecursiveIteratorIterator(
|
|
32
|
+
new RecursiveDirectoryIterator($buildDir, FilesystemIterator::SKIP_DOTS),
|
|
33
|
+
RecursiveIteratorIterator::CHILD_FIRST
|
|
34
|
+
);
|
|
35
|
+
foreach ($iterator as $item) {
|
|
36
|
+
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
|
|
37
|
+
}
|
|
38
|
+
rmdir($buildDir);
|
|
39
|
+
}
|
|
40
|
+
if (file_exists($zipFile)) {
|
|
41
|
+
unlink($zipFile);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@mkdir($buildDir . '/src', 0777, true);
|
|
45
|
+
@mkdir($buildDir . '/assets', 0777, true);
|
|
46
|
+
|
|
47
|
+
// 2. Copie des fichiers principaux du plugin WordPress
|
|
48
|
+
copy($wpDir . '/fingerprint-wordpress.php', $buildDir . '/fingerprint-wordpress.php');
|
|
49
|
+
copy($wpDir . '/WpDbStore.php', $buildDir . '/WpDbStore.php');
|
|
50
|
+
|
|
51
|
+
// 3. Copie des assets clients nécessaires (solveur PoW)
|
|
52
|
+
$solverSource = $jsSrcDir . '/pow.solver.inline.js';
|
|
53
|
+
if (file_exists($solverSource)) {
|
|
54
|
+
copy($solverSource, $buildDir . '/assets/pow.solver.inline.js');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 4. Copie récursive de la bibliothèque PHP (src/php -> build/src), en excluant les dossiers de dev
|
|
58
|
+
$excludeDirs = ['WordPress', 'Tests', 'bin'];
|
|
59
|
+
$phpIterator = new RecursiveIteratorIterator(
|
|
60
|
+
new RecursiveDirectoryIterator($phpSrcDir, FilesystemIterator::SKIP_DOTS),
|
|
61
|
+
RecursiveIteratorIterator::SELF_FIRST
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
foreach ($phpIterator as $item) {
|
|
65
|
+
$relativePath = substr($item->getPathname(), strlen($phpSrcDir) + 1);
|
|
66
|
+
$topDir = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $relativePath))[0];
|
|
67
|
+
|
|
68
|
+
if (in_array($topDir, $excludeDirs, true)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$destPath = $buildDir . '/src/' . $relativePath;
|
|
73
|
+
if ($item->isDir()) {
|
|
74
|
+
if (!is_dir($destPath)) {
|
|
75
|
+
mkdir($destPath, 0777, true);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
copy($item->getPathname(), $destPath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
echo "==> Fichiers copiés dans {$buildDir}\n";
|
|
83
|
+
|
|
84
|
+
// 5. Création de l'archive ZIP
|
|
85
|
+
$zip = new ZipArchive();
|
|
86
|
+
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
|
87
|
+
fwrite(STDERR, "Erreur : Impossible de créer le fichier {$zipFile}\n");
|
|
88
|
+
exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$distIterator = new RecursiveIteratorIterator(
|
|
92
|
+
new RecursiveDirectoryIterator($buildDir, FilesystemIterator::SKIP_DOTS),
|
|
93
|
+
RecursiveIteratorIterator::LEAVES_ONLY
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
foreach ($distIterator as $file) {
|
|
97
|
+
if (!$file->isDir()) {
|
|
98
|
+
$filePath = $file->getPathname();
|
|
99
|
+
// Préserve le préfixe du dossier racine dans le ZIP : fingerprint-wordpress/...
|
|
100
|
+
$relativePath = $pluginSlug . '/' . substr($filePath, strlen($buildDir) + 1);
|
|
101
|
+
$zip->addFile($filePath, str_replace('\\', '/', $relativePath));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function removeDirectory(string $dir): void
|
|
105
|
+
{
|
|
106
|
+
if (!is_dir($dir)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
$iterator = new RecursiveIteratorIterator(
|
|
110
|
+
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
|
|
111
|
+
RecursiveIteratorIterator::CHILD_FIRST
|
|
112
|
+
);
|
|
113
|
+
foreach ($iterator as $item) {
|
|
114
|
+
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
|
|
115
|
+
}
|
|
116
|
+
rmdir($dir);
|
|
117
|
+
}
|
|
118
|
+
$zip->close();
|
|
119
|
+
removeDirectory($buildDir);
|
|
120
|
+
$sizeKb = round(filesize($zipFile) / 1024, 2);
|
|
121
|
+
echo "==> Archive ZIP générée avec succès :\n";
|
|
122
|
+
echo " Fichier : {$zipFile} ({$sizeKb} Ko)\n";
|
|
123
|
+
echo " Prêt à être installé via wp-admin ou déployé en production !\n";
|