@anonympins/fingerprint 0.7.4 → 0.7.5
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 +27 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/dynamic-wasm.js +218 -218
- package/src/js/fingerprint.client.js +77 -43
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +291 -71
- package/src/js/tests/dynamic-wasm.test.js +77 -77
- package/src/js/tests/fingerprint.client.init.test.js +78 -0
- package/src/js/tests/fingerprint.test.js +18 -0
- package/src/js/tests/threatIntel.test.js +40 -1
- package/src/php/Challenge/ChallengeUtils.php +10 -0
- package/src/php/Config/SecurityProfiles.php +5 -0
- package/src/php/FingerprintEngine.php +186 -37
- package/src/php/Tests/ThreatIntelTest.php +34 -0
- package/src/php/Utils/RequestUtils.php +48 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
- package/src/php/WordPress/readme.txt +1 -1
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
private static ?array $googlebotEntries = null;
|
|
32
32
|
private static ?array $yandexEntries = null;
|
|
33
33
|
private static ?array $bingbotEntries = null;
|
|
34
|
+
private static ?array $facebookEntries = null;
|
|
34
35
|
|
|
35
36
|
private static array $dnsCircuitBreaker = [
|
|
36
37
|
'state' => 'CLOSED',
|
|
@@ -265,7 +266,8 @@
|
|
|
265
266
|
'similarityThreshold', 'summary', 'description',
|
|
266
267
|
'ed25519_private_key', 'ed25519_public_key',
|
|
267
268
|
'wasm', 'enableProofOfSpace', 'pospace', 'federatedPeers', 'federationSecret', 'reset',
|
|
268
|
-
'filterWhitelist'
|
|
269
|
+
'filterWhitelist',
|
|
270
|
+
'differentialPrivacy', 'dpEpsilon'
|
|
269
271
|
];
|
|
270
272
|
|
|
271
273
|
if (empty($config['weights'])) {
|
|
@@ -347,12 +349,26 @@
|
|
|
347
349
|
];
|
|
348
350
|
}
|
|
349
351
|
|
|
352
|
+
public static function facebook_whitelist(): array
|
|
353
|
+
{
|
|
354
|
+
if (self::$facebookEntries === null) {
|
|
355
|
+
self::$facebookEntries = self::loadBotWhitelist('facebook.json', [
|
|
356
|
+
"31.13.64.0/18", "66.220.144.0/20", "69.63.176.0/20", "157.240.0.0/16"
|
|
357
|
+
]);
|
|
358
|
+
}
|
|
359
|
+
return [
|
|
360
|
+
'type' => 'allowlist',
|
|
361
|
+
'entries' => self::$facebookEntries
|
|
362
|
+
];
|
|
363
|
+
}
|
|
364
|
+
|
|
350
365
|
public static function default_whitelist(): array
|
|
351
366
|
{
|
|
352
367
|
return [
|
|
353
368
|
self::googlebot_whitelist(),
|
|
354
369
|
self::bingbot_whitelist(),
|
|
355
370
|
self::yandex_whitelist(),
|
|
371
|
+
self::facebook_whitelist(),
|
|
356
372
|
['userAgent' => 'Googlebot', 'hostnameSuffix' => '.googlebot.com'],
|
|
357
373
|
['userAgent' => 'Google-Extended', 'hostnameSuffix' => '.google.com'],
|
|
358
374
|
['userAgent' => 'AdsBot-Google', 'hostnameSuffix' => '.googlebot.com'],
|
|
@@ -394,14 +410,30 @@
|
|
|
394
410
|
|
|
395
411
|
public function calculateFinalScore(array $suspicionVector): float
|
|
396
412
|
{
|
|
397
|
-
$
|
|
398
|
-
if (empty($weights)) {
|
|
399
|
-
return 0.0;
|
|
400
|
-
}
|
|
413
|
+
$baseWeights = $this->securityConfig['weights'] ?? [];
|
|
401
414
|
|
|
402
415
|
$score = 0.0;
|
|
403
|
-
|
|
404
|
-
|
|
416
|
+
// NOUVEAU: Logique d'amplification des poids
|
|
417
|
+
// Si un tunnel est détecté, on augmente le poids des autres signaux suspects.
|
|
418
|
+
$dynamicWeights = $baseWeights;
|
|
419
|
+
if (($suspicionVector['mtuAnomalyScore'] ?? 0.0) > 50.0) {
|
|
420
|
+
$this->log('Tunnel detected, amplifying suspicion weights.', ['mtuScore' => $suspicionVector['mtuAnomalyScore']]);
|
|
421
|
+
|
|
422
|
+
// Augmente le poids des incohérences de bas niveau (difficiles à falsifier)
|
|
423
|
+
$dynamicWeights['tlsSpoofingScore'] = ($baseWeights['tlsSpoofingScore'] ?? 0.8) * 1.25;
|
|
424
|
+
$dynamicWeights['crossLayerInconsistencyScore'] = ($baseWeights['crossLayerInconsistencyScore'] ?? 0.4) * 1.4;
|
|
425
|
+
$dynamicWeights['clientHintsInconsistencyScore'] = ($baseWeights['clientHintsInconsistencyScore'] ?? 0.7) * 1.2;
|
|
426
|
+
|
|
427
|
+
// Augmente le poids des anomalies comportementales (un bot derrière un VPN est plus suspect)
|
|
428
|
+
$dynamicWeights['behaviorScore'] = ($baseWeights['behaviorScore'] ?? 0.7) * 1.15;
|
|
429
|
+
$dynamicWeights['requestPatternScore'] = ($baseWeights['requestPatternScore'] ?? 0.6) * 1.2;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
foreach ($dynamicWeights as $key => $weight) {
|
|
433
|
+
$metricScore = $suspicionVector[$key] ?? 0.0;
|
|
434
|
+
if ($metricScore > 0) {
|
|
435
|
+
$score += $metricScore * $weight;
|
|
436
|
+
}
|
|
405
437
|
}
|
|
406
438
|
|
|
407
439
|
return min(100.0, $score);
|
|
@@ -519,7 +551,7 @@
|
|
|
519
551
|
if ($whitelisted) {
|
|
520
552
|
$filterWhitelist = $this->securityConfig['filterWhitelist'] ?? false;
|
|
521
553
|
$bypassWhitelist = false;
|
|
522
|
-
if ($filterWhitelist === true) {
|
|
554
|
+
if ($filterWhitelist === true || (is_numeric($filterWhitelist) && $type === 'bot')) {
|
|
523
555
|
$bypassWhitelist = $this->hasCertainAttack($context);
|
|
524
556
|
} elseif (is_numeric($filterWhitelist)) {
|
|
525
557
|
if ($context->preCalculatedScore === null) {
|
|
@@ -624,6 +656,21 @@
|
|
|
624
656
|
}
|
|
625
657
|
}
|
|
626
658
|
|
|
659
|
+
// Validation de l'ancre matérielle WebAuthn (Secure Enclave / TPM)
|
|
660
|
+
$behaviorHeader = $context->getHeader('x-behavior-metrics');
|
|
661
|
+
if (!empty($behaviorHeader) && is_string($behaviorHeader) && str_starts_with($behaviorHeader, '{')) {
|
|
662
|
+
try {
|
|
663
|
+
$parsedMetrics = json_decode($behaviorHeader, true);
|
|
664
|
+
if (is_array($parsedMetrics) && !empty($parsedMetrics['webauthnAnchor']) && is_array($deviceData)) {
|
|
665
|
+
if ($this->verifyWebAuthnHardwareAnchor($parsedMetrics['webauthnAnchor'], $deviceData)) {
|
|
666
|
+
$deviceData['webauthnVerified'] = true;
|
|
667
|
+
$store->set("device:{$deviceId}", $deviceData);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
} catch (\Throwable $e) {
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
627
674
|
// Bind the current TLS session ID to the device ID
|
|
628
675
|
if ($deviceId && $tlsSessionId) {
|
|
629
676
|
$store->set("tls-session:{$tlsSessionId}", $deviceId, 3600); // 1h cache duration
|
|
@@ -950,6 +997,7 @@
|
|
|
950
997
|
*/
|
|
951
998
|
public function processRequest(RequestContext $context): array
|
|
952
999
|
{
|
|
1000
|
+
$store = StoreManager::getStore();
|
|
953
1001
|
$this->translatePolymorphicHeaders($context);
|
|
954
1002
|
|
|
955
1003
|
// Initialiser le vecteur de suspicion pour éviter les erreurs de type.
|
|
@@ -974,6 +1022,64 @@
|
|
|
974
1022
|
$context->graphqlOperation = $gqlInfo;
|
|
975
1023
|
}
|
|
976
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
// 0. Résoudre l'identité de l'appareil et vérifier l'attestation matérielle WebAuthn
|
|
1027
|
+
$identity = $this->resolveRequestIdentity($context, $suspicionVector);
|
|
1028
|
+
$deviceId = $identity['deviceId'];
|
|
1029
|
+
$deviceData = $identity['deviceData'];
|
|
1030
|
+
|
|
1031
|
+
// Règle Anti-Ferme : Un appareil condamné ou menant une attaque certaine ne bénéficie d'aucun bypass
|
|
1032
|
+
if (($deviceData['condemned'] ?? false) || $this->hasCertainAttack($context)) {
|
|
1033
|
+
if (is_array($deviceData)) {
|
|
1034
|
+
$deviceData['condemned'] = true;
|
|
1035
|
+
$deviceData['webauthnVerified'] = false;
|
|
1036
|
+
$store->set("device:{$deviceId}", $deviceData);
|
|
1037
|
+
}
|
|
1038
|
+
$this->log('Condemned device or attack detected - revoking hardware trust and blocking', ['deviceId' => $deviceId]);
|
|
1039
|
+
$decision = ['action' => 'block', 'status' => 403, 'body' => 'Forbidden', 'score' => 100.0, 'vector' => ['honeypotScore' => 100.0]];
|
|
1040
|
+
if ($this->dryRun) {
|
|
1041
|
+
$this->log("[Dry Run] Intended action: {$decision['action']}", ['score' => $decision['score']]);
|
|
1042
|
+
$decision['intendedAction'] = $decision['action'];
|
|
1043
|
+
$decision['action'] = 'next';
|
|
1044
|
+
unset($decision['status'], $decision['body']);
|
|
1045
|
+
}
|
|
1046
|
+
return $decision;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Bypass instantané si l'appareil a prouvé cryptographiquement son identité matérielle (Secure Enclave / TPM)
|
|
1050
|
+
if ($deviceData && !empty($deviceData['webauthnVerified'])) {
|
|
1051
|
+
$this->log('Hardware-anchored device verified (WebAuthn) - full bypass granted', ['deviceId' => $deviceId]);
|
|
1052
|
+
$greenlistTtl = 365 * 86400 * 1000; // 1 an (permanent)
|
|
1053
|
+
$greenlistTicket = ChallengeUtils::generateStatelessTicket([
|
|
1054
|
+
'expiry' => (int)floor(microtime(true) * 1000) + $greenlistTtl,
|
|
1055
|
+
'originalIp' => $context->clientIp,
|
|
1056
|
+
'deviceId' => $deviceId,
|
|
1057
|
+
'deviceHash' => 'webauthn:greenlist:' . ($deviceData['webauthnCredentialId'] ?? $deviceId),
|
|
1058
|
+
'greenlist' => true
|
|
1059
|
+
]);
|
|
1060
|
+
|
|
1061
|
+
$isHttps = !empty($context->isHttps);
|
|
1062
|
+
$decision = [
|
|
1063
|
+
'action' => 'next',
|
|
1064
|
+
'score' => 0.0,
|
|
1065
|
+
'vector' => ['webauthn_verified' => 100.0],
|
|
1066
|
+
'cookie' => [
|
|
1067
|
+
'name' => 'pow_clearance',
|
|
1068
|
+
'value' => $greenlistTicket,
|
|
1069
|
+
'options' => [
|
|
1070
|
+
'httponly' => true,
|
|
1071
|
+
'secure' => $isHttps,
|
|
1072
|
+
'samesite' => 'Strict',
|
|
1073
|
+
'expires' => time() + ($greenlistTtl / 1000),
|
|
1074
|
+
'path' => '/',
|
|
1075
|
+
]
|
|
1076
|
+
]
|
|
1077
|
+
];
|
|
1078
|
+
if (isset($context->newCookieForResponse)) {
|
|
1079
|
+
$decision['newCookieForResponse'] = $context->newCookieForResponse;
|
|
1080
|
+
}
|
|
1081
|
+
return $decision;
|
|
1082
|
+
}
|
|
977
1083
|
|
|
978
1084
|
// 1. Vérifier les listes blanches
|
|
979
1085
|
if ($this->checkAllowlists($context)) {
|
|
@@ -1567,42 +1673,85 @@
|
|
|
1567
1673
|
$peers = $this->securityConfig['federatedPeers'] ?? [];
|
|
1568
1674
|
if (empty($peers)) return;
|
|
1569
1675
|
|
|
1570
|
-
$
|
|
1571
|
-
$
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
$
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1676
|
+
$dpConfig = $this->securityConfig['differentialPrivacy'] ?? [];
|
|
1677
|
+
$dpEnabled = ($dpConfig['enabled'] ?? true) !== false;
|
|
1678
|
+
$epsilon = isset($dpConfig['epsilon']) && is_numeric($dpConfig['epsilon'])
|
|
1679
|
+
? (float)$dpConfig['epsilon']
|
|
1680
|
+
: (float)($this->securityConfig['dpEpsilon'] ?? 1.0);
|
|
1681
|
+
|
|
1682
|
+
$sendThreatReport = function (string $targetZkpY, int $ts) use ($peers) {
|
|
1683
|
+
$msg = "{$ts}:{$targetZkpY}";
|
|
1684
|
+
$signature = '';
|
|
1685
|
+
$isAsymmetric = false;
|
|
1686
|
+
|
|
1687
|
+
$privateKey = Env::get('ED25519_PRIVATE_KEY');
|
|
1688
|
+
if ($privateKey) {
|
|
1689
|
+
try {
|
|
1690
|
+
$cleanKey = str_replace('\n', "\n", $privateKey);
|
|
1691
|
+
$pkeyObj = openssl_pkey_get_private($cleanKey);
|
|
1692
|
+
if ($pkeyObj && openssl_sign($msg, $sigBytes, $pkeyObj, null)) {
|
|
1693
|
+
$signature = bin2hex($sigBytes);
|
|
1694
|
+
$isAsymmetric = true;
|
|
1695
|
+
}
|
|
1696
|
+
} catch (\Throwable $e) {
|
|
1697
|
+
self::logError('[Fingerprint] Asymmetric broadcast signing failed: ' . $e->getMessage());
|
|
1584
1698
|
}
|
|
1585
|
-
} catch (\Throwable $e) {
|
|
1586
|
-
self::logError('[Fingerprint] Asymmetric broadcast signing failed: ' . $e->getMessage());
|
|
1587
1699
|
}
|
|
1588
|
-
}
|
|
1589
1700
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1701
|
+
if (!$isAsymmetric) {
|
|
1702
|
+
$secret = $this->securityConfig['federationSecret'] ?? ChallengeUtils::getPowSecret();
|
|
1703
|
+
$signature = hash_hmac('sha256', $msg, $secret);
|
|
1704
|
+
}
|
|
1594
1705
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1706
|
+
foreach ($peers as $peerUrl) {
|
|
1707
|
+
$this->asyncPost($peerUrl . '?coop_op=share_threat_intel', [
|
|
1708
|
+
'zkpY' => $targetZkpY,
|
|
1709
|
+
'signature' => $isAsymmetric ? '' : $signature,
|
|
1710
|
+
'signature_ed25519' => $isAsymmetric ? $signature : '',
|
|
1711
|
+
'timestamp' => $ts
|
|
1712
|
+
]);
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
|
|
1716
|
+
$now = (int)(microtime(true) * 1000);
|
|
1717
|
+
$reportTimestamp = $now;
|
|
1718
|
+
|
|
1719
|
+
if ($dpEnabled) {
|
|
1720
|
+
// 1. Differential Privacy : Bruit laplacien sur l'horodatage
|
|
1721
|
+
$deltaT = 5000;
|
|
1722
|
+
$b = $deltaT / max(0.1, $epsilon);
|
|
1723
|
+
$u = (random_int(1, 999999) / 1000000.0) - 0.5;
|
|
1724
|
+
$safeU = abs($u) < 1e-7 ? ($u >= 0 ? 1e-7 : -1e-7) : $u;
|
|
1725
|
+
$laplaceNoise = -$b * ($safeU <=> 0) * log(1.0 - 2.0 * abs($safeU));
|
|
1726
|
+
$clampedNoise = (int)max(-60000, min(60000, round($laplaceNoise)));
|
|
1727
|
+
$reportTimestamp = $now + $clampedNoise;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
$sendThreatReport($zkpY, $reportTimestamp);
|
|
1731
|
+
|
|
1732
|
+
if ($dpEnabled) {
|
|
1733
|
+
// 2. Differential Privacy : Injection de clés leurres (Decoy ZKP)
|
|
1734
|
+
$decoyProb = isset($dpConfig['dummyRate']) && is_numeric($dpConfig['dummyRate'])
|
|
1735
|
+
? (float)$dpConfig['dummyRate']
|
|
1736
|
+
: (1.0 / (1.0 + exp($epsilon)));
|
|
1737
|
+
|
|
1738
|
+
$randFloat = random_int(0, 999999) / 1000000.0;
|
|
1739
|
+
if ($randFloat < $decoyProb) {
|
|
1740
|
+
$decoyBytes = random_bytes(32);
|
|
1741
|
+
$decoyZkpY = ltrim(bin2hex($decoyBytes), '0') ?: '1';
|
|
1742
|
+
|
|
1743
|
+
$uDecoy = (random_int(1, 999999) / 1000000.0) - 0.5;
|
|
1744
|
+
$safeUDecoy = abs($uDecoy) < 1e-7 ? ($uDecoy >= 0 ? 1e-7 : -1e-7) : $uDecoy;
|
|
1745
|
+
$b = 5000 / max(0.1, $epsilon);
|
|
1746
|
+
$decoyNoise = (int)max(-60000, min(60000, round(-$b * ($safeUDecoy <=> 0) * log(1.0 - 2.0 * abs($safeUDecoy)))));
|
|
1747
|
+
$decoyTimestamp = $now + $decoyNoise;
|
|
1748
|
+
|
|
1749
|
+
$sendThreatReport($decoyZkpY, $decoyTimestamp);
|
|
1750
|
+
}
|
|
1602
1751
|
}
|
|
1603
1752
|
}
|
|
1604
1753
|
|
|
1605
|
-
|
|
1754
|
+
protected function asyncPost(string $url, array $params): void
|
|
1606
1755
|
{
|
|
1607
1756
|
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- wp_parse_url used if available
|
|
1608
1757
|
$parts = function_exists('wp_parse_url') ? wp_parse_url($url) : parse_url($url);
|
|
@@ -61,4 +61,38 @@ class ThreatIntelTest extends TestCase
|
|
|
61
61
|
$this->assertGreaterThan(75.0, $score);
|
|
62
62
|
$this->assertLessThanOrEqual(95.0, $score);
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
public function testDifferentialPrivacyDecoyInjectionAndTimestampNoise(): void
|
|
66
|
+
{
|
|
67
|
+
$config = [
|
|
68
|
+
'federatedPeers' => ['https://peer1.example.com'],
|
|
69
|
+
'federationSecret' => 'test-secret-key-32-chars-long!!',
|
|
70
|
+
'differentialPrivacy' => [
|
|
71
|
+
'enabled' => true,
|
|
72
|
+
'epsilon' => 1.0,
|
|
73
|
+
'dummyRate' => 1.0 // Forcer l'injection d'un leurre pour le test
|
|
74
|
+
]
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
$postedCalls = [];
|
|
78
|
+
$engineSub = new class($config, $postedCalls) extends \Anonympins\Fingerprint\FingerprintEngine {
|
|
79
|
+
public array $calls = [];
|
|
80
|
+
public function __construct(array $cfg, array &$callsRef) {
|
|
81
|
+
$this->calls = &$callsRef;
|
|
82
|
+
parent::__construct($cfg);
|
|
83
|
+
}
|
|
84
|
+
protected function asyncPost(string $url, array $params): void {
|
|
85
|
+
$this->calls[] = ['url' => $url, 'params' => $params];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
$refSub = new \ReflectionClass($engineSub);
|
|
90
|
+
$broadcastMethod = $refSub->getMethod('broadcastBannedZkp');
|
|
91
|
+
$broadcastMethod->setAccessible(true);
|
|
92
|
+
|
|
93
|
+
$realZkpY = 'realzkpykey12345';
|
|
94
|
+
$broadcastMethod->invoke($engineSub, $realZkpY);
|
|
95
|
+
|
|
96
|
+
$this->assertCount(2, $postedCalls, 'Doit diffuser à la fois la clé réelle et la clé leurre');
|
|
97
|
+
}
|
|
64
98
|
}
|
|
@@ -116,6 +116,54 @@ class RequestUtils
|
|
|
116
116
|
return (string)$srv;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Analyzes TCP MTU and fragmentation flags to detect network tunnels (VPN/Proxy).
|
|
121
|
+
* @param RequestContext $context The request context.
|
|
122
|
+
* @return array{'mtuAnomalyScore': float}
|
|
123
|
+
*/
|
|
124
|
+
public static function getMtuAnomalyScore(RequestContext $context): array
|
|
125
|
+
{
|
|
126
|
+
$mtuHeader = $context->getHeader('x-tcp-mtu-info');
|
|
127
|
+
if (empty($mtuHeader) || !is_string($mtuHeader)) {
|
|
128
|
+
return ['mtuAnomalyScore' => 0.0];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
$parts = explode(':', $mtuHeader);
|
|
132
|
+
if (count($parts) < 2) {
|
|
133
|
+
return ['mtuAnomalyScore' => 0.0];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
$mtu = filter_var($parts[0], FILTER_VALIDATE_INT);
|
|
137
|
+
$df = filter_var($parts[1], FILTER_VALIDATE_INT);
|
|
138
|
+
|
|
139
|
+
if ($mtu === false || $df === false) {
|
|
140
|
+
return ['mtuAnomalyScore' => 0.0];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
$score = 0.0;
|
|
144
|
+
|
|
145
|
+
// 1. Pénalité modérée pour les MTU typiques des VPNs/tunnels.
|
|
146
|
+
if ($mtu > 1200 && $mtu <= 1420) {
|
|
147
|
+
$score += 35.0;
|
|
148
|
+
} elseif ($mtu > 1420 && $mtu < 1492) {
|
|
149
|
+
$score += 20.0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2. OS vs. Network Stack Inconsistency
|
|
153
|
+
$ua = $context->getHeader('user-agent') ?? '';
|
|
154
|
+
$uaParts = self::parseUserAgent($ua);
|
|
155
|
+
$os = $uaParts['os'] ?? null;
|
|
156
|
+
|
|
157
|
+
// Pénalités additionnelles en cas d'incohérence OS vs signature réseau.
|
|
158
|
+
if ($os) {
|
|
159
|
+
if (str_starts_with($os, 'Windows') && $mtu < 1492) $score += 20.0;
|
|
160
|
+
if ((str_starts_with($os, 'Android') || str_starts_with($os, 'iOS')) && $mtu < 1480) $score += 15.0;
|
|
161
|
+
if ($df === 0 && (str_starts_with($os, 'Windows') || str_starts_with($os, 'Mac') || str_starts_with($os, 'Linux'))) $score += 40.0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return ['mtuAnomalyScore' => min(100.0, $score)];
|
|
165
|
+
}
|
|
166
|
+
|
|
119
167
|
/**
|
|
120
168
|
* Calcule un score d'incohérence entre la signature TLS (JA3) et le User-Agent.
|
|
121
169
|
* @return array{'tlsSpoofingScore': float}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Plugin Name: Fingerprint Anti-Bot & Proof-of-Work
|
|
4
4
|
* Plugin URI: https://github.com/anonympins/fingerprint
|
|
5
5
|
* Description: High-performance client-side anti-bot protection and Proof-of-Work challenge verification for WordPress.
|
|
6
|
-
* Version: 0.7.
|
|
6
|
+
* Version: 0.7.5
|
|
7
7
|
* Author: anonympins
|
|
8
8
|
* Requires at least: 5.9
|
|
9
9
|
* Requires PHP: 8.0
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
msgid ""
|
|
2
2
|
msgstr ""
|
|
3
|
-
"Project-Id-Version: Fingerprint Anti-Bot 0.7.
|
|
3
|
+
"Project-Id-Version: Fingerprint Anti-Bot 0.7.5\n"
|
|
4
4
|
"Report-Msgid-Bugs-To: https://github.com/anonympins/fingerprint/issues\n"
|
|
5
5
|
"POT-Creation-Date: 2026-09-24 12:00+0000\n"
|
|
6
6
|
"PO-Revision-Date: 2026-09-24 12:00+0000\n"
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
msgid ""
|
|
2
2
|
msgstr ""
|
|
3
|
-
"Project-Id-Version: Fingerprint Anti-Bot 0.7.
|
|
3
|
+
"Project-Id-Version: Fingerprint Anti-Bot 0.7.5\n"
|
|
4
4
|
"Report-Msgid-Bugs-To: https://github.com/anonympins/fingerprint/issues\n"
|
|
5
5
|
"POT-Creation-Date: 2026-09-24 12:00+0000\n"
|
|
6
6
|
"PO-Revision-Date: 2026-09-24 12:00+0000\n"
|