@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -7
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/public/fp.wasm +0 -0
  7. package/src/js/build-client.js +9 -11
  8. package/src/js/dynamic-wasm.js +7 -7
  9. package/src/js/fingerprint.builder.js +37 -38
  10. package/src/js/fingerprint.client.js +236 -152
  11. package/src/js/fingerprint.client.obfuscated.js +1 -1
  12. package/src/js/fingerprint.js +517 -101
  13. package/src/js/fingerprint.utils.js +213 -213
  14. package/src/js/library.js +355 -384
  15. package/src/js/mongodb-store.js +1 -1
  16. package/src/js/optimization.worker.js +27 -27
  17. package/src/js/pow.solver.inline.js +206 -62
  18. package/src/js/pow.solver.js +235 -101
  19. package/src/js/pow.worker.js +48 -26
  20. package/src/js/redis-store.js +1 -1
  21. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  22. package/src/js/tests/fingerprint.test.js +101 -40
  23. package/src/js/tests/ip-reputation.test.js +1 -0
  24. package/src/js/upow-model-task.js +14 -15
  25. package/src/php/AutoTuner.php +34 -34
  26. package/src/php/Challenge/ChallengeUtils.php +204 -32
  27. package/src/php/Config/SecurityProfiles.php +21 -0
  28. package/src/php/DirectFingerprint.php +190 -145
  29. package/src/php/FingerprintBuilder.php +29 -30
  30. package/src/php/FingerprintClient.php +24 -25
  31. package/src/php/FingerprintEngine.php +1836 -1880
  32. package/src/php/Ja3AnomalyDetector.php +258 -227
  33. package/src/php/Optimization/FunctionRegistry.php +7 -7
  34. package/src/php/Optimization/Optimization.php +24 -24
  35. package/src/php/Optimization/OptimizationOperators.php +28 -27
  36. package/src/php/Optimization/ProblemInitializers.php +52 -52
  37. package/src/php/ProblemManager.php +31 -34
  38. package/src/php/RequestContext.php +8 -1
  39. package/src/php/Store/IStore.php +7 -8
  40. package/src/php/Store/InMemoryStore.php +66 -66
  41. package/src/php/Store/MongoDbStore.php +5 -5
  42. package/src/php/Store/RedisStore.php +5 -5
  43. package/src/php/Store/StoreManager.php +35 -35
  44. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  45. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  46. package/src/php/Tests/FingerprintEngineTest.php +662 -693
  47. package/src/php/Tests/IpReputationTest.php +10 -0
  48. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  49. package/src/php/Tests/MetricsTest.php +97 -97
  50. package/src/php/Tests/ProblemManagerTest.php +376 -376
  51. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  52. package/src/php/Tests/RequestUtilsTest.php +471 -471
  53. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  54. package/src/php/Tests/config/ed25519_key.json +3 -3
  55. package/src/php/Utils/Env.php +49 -49
  56. package/src/php/Utils/MaliciousPatterns.php +74 -74
  57. package/src/php/Utils/MetricsManager.php +209 -209
  58. package/src/php/Utils/RequestUtils.php +236 -19
  59. package/src/php/Utils/TLSClientHelloParser.php +369 -212
  60. package/src/php/WordPress/WpDbStore.php +155 -0
  61. package/src/php/WordPress/fingerprint-wordpress.php +217 -0
  62. package/src/php/WordPress/package.php +123 -0
  63. package/src/php/bin/auto-tune.php +118 -118
@@ -411,6 +411,9 @@ class RequestUtils
411
411
  }
412
412
 
413
413
  $score = 0.0;
414
+ if (!empty($metrics['prototypeTampered'])) {
415
+ $score += 80.0;
416
+ }
414
417
 
415
418
  $mouseAnalysis = self::analyzeMouseMovements($metrics['mouseMovementsHistory'] ?? null);
416
419
  $touch = self::analyzeTouchMovements($metrics['touchMovementsHistory'] ?? null);
@@ -501,6 +504,15 @@ class RequestUtils
501
504
  $score += 35;
502
505
  }
503
506
  }
507
+
508
+ // Détection de ferme mobile : Touch actif sur mobile sans aucune vibration physique (châssis/rack ADB)
509
+ $ua = $context->getHeader('user-agent') ?? '';
510
+ $isMobileDevice = str_contains($ua, 'Mobile');
511
+ if ($isMobileDevice && count($touchHistory) >= 5 && isset($metrics['motionVariance']) && is_numeric($metrics['motionVariance'])) {
512
+ if ((float)$metrics['motionVariance'] === 0.0) {
513
+ $score += 50.0;
514
+ }
515
+ }
504
516
  }
505
517
 
506
518
  return ['behaviorScore' => min(100.0, $score)];
@@ -1025,13 +1037,20 @@ class RequestUtils
1025
1037
 
1026
1038
  public static function getThreatIntelScore(RequestContext $context, array $threatIntelConfig): array
1027
1039
  {
1040
+ $score = 0.0;
1028
1041
  $zkpY = $context->zkpY;
1029
- if (empty($zkpY)) {
1030
- return ['threatIntelScore' => 0.0];
1042
+ if (!empty($zkpY)) {
1043
+ $store = StoreManager::getStore();
1044
+ $isBanned = $store->has("banned-zkp-y:{$zkpY}");
1045
+ if ($isBanned) {
1046
+ $score = 100.0;
1047
+ }
1031
1048
  }
1032
- $store = StoreManager::getStore();
1033
- $isBanned = $store->has("banned-zkp-y:{$zkpY}");
1034
- return ['threatIntelScore' => $isBanned ? 100.0 : 0.0];
1049
+
1050
+ $rttProxyScore = \Anonympins\Fingerprint\Ja3AnomalyDetector::getRttProxyScore($context);
1051
+ $score = max($score, (float)$rttProxyScore);
1052
+
1053
+ return ['threatIntelScore' => $score];
1035
1054
  }
1036
1055
 
1037
1056
  /**
@@ -1237,6 +1256,36 @@ class RequestUtils
1237
1256
  return null;
1238
1257
  }
1239
1258
 
1259
+ /**
1260
+ * Calcule la longueur du préfixe commun (en bits) entre deux adresses IP (IPv4 ou IPv6).
1261
+ */
1262
+ public static function getIpCommonPrefixLength(string $ip1, string $ip2): int
1263
+ {
1264
+ $bin1 = @inet_pton($ip1);
1265
+ $bin2 = @inet_pton($ip2);
1266
+ if ($bin1 === false || $bin2 === false || strlen($bin1) !== strlen($bin2)) {
1267
+ return 0;
1268
+ }
1269
+ $len = strlen($bin1);
1270
+ $bits = 0;
1271
+ for ($i = 0; $i < $len; $i++) {
1272
+ $xor = ord($bin1[$i]) ^ ord($bin2[$i]);
1273
+ if ($xor === 0) {
1274
+ $bits += 8;
1275
+ } else {
1276
+ for ($b = 7; $b >= 0; $b--) {
1277
+ if (($xor & (1 << $b)) === 0) {
1278
+ $bits++;
1279
+ } else {
1280
+ break;
1281
+ }
1282
+ }
1283
+ break;
1284
+ }
1285
+ }
1286
+ return $bits;
1287
+ }
1288
+
1240
1289
  /**
1241
1290
  * Met à jour les métriques agrégées pour un sous-réseau IP.
1242
1291
  * @param RequestContext $context
@@ -1256,7 +1305,8 @@ class RequestUtils
1256
1305
  'highScoreDevices' => [],
1257
1306
  'lastActivity' => 0,
1258
1307
  'ips' => [],
1259
- 'uas' => []
1308
+ 'uas' => [],
1309
+ 'attackerIps' => []
1260
1310
  ];
1261
1311
 
1262
1312
  if (!isset($subnetData['highScoreDevices'])) {
@@ -1268,6 +1318,9 @@ class RequestUtils
1268
1318
  if (!isset($subnetData['uas'])) {
1269
1319
  $subnetData['uas'] = [];
1270
1320
  }
1321
+ if (!isset($subnetData['attackerIps']) || !is_array($subnetData['attackerIps'])) {
1322
+ $subnetData['attackerIps'] = [];
1323
+ }
1271
1324
  $now = time();
1272
1325
  self::decaySubnetData($subnetData, $now);
1273
1326
 
@@ -1290,6 +1343,28 @@ class RequestUtils
1290
1343
  }
1291
1344
 
1292
1345
  $userAgent = $context->getHeader('user-agent') ?? '';
1346
+ if ($finalScore >= 70.0) {
1347
+ $foundAttacker = false;
1348
+ foreach ($subnetData['attackerIps'] as &$att) {
1349
+ if (isset($att['ip']) && $att['ip'] === $context->clientIp) {
1350
+ $att['lastSeen'] = $now;
1351
+ $att['score'] = max((float)($att['score'] ?? 0.0), $finalScore);
1352
+ $foundAttacker = true;
1353
+ break;
1354
+ }
1355
+ }
1356
+ unset($att);
1357
+ if (!$foundAttacker) {
1358
+ $subnetData['attackerIps'][] = [
1359
+ 'ip' => $context->clientIp,
1360
+ 'lastSeen' => $now,
1361
+ 'score' => $finalScore
1362
+ ];
1363
+ if (count($subnetData['attackerIps']) > 30) {
1364
+ array_shift($subnetData['attackerIps']);
1365
+ }
1366
+ }
1367
+ }
1293
1368
  if (!empty($userAgent) && !in_array($userAgent, $subnetData['uas'], true)) {
1294
1369
  $subnetData['uas'][] = $userAgent;
1295
1370
  }
@@ -1319,9 +1394,10 @@ class RequestUtils
1319
1394
  * Calcule un score de suspicion basé sur l'activité historique du sous-réseau IP.
1320
1395
  * @param RequestContext $context
1321
1396
  * @param string $currentDeviceId
1397
+ * @param array<string, mixed> $securityConfig
1322
1398
  * @return array{'subnetScore': float}
1323
1399
  */
1324
- public static function getSubnetScore(RequestContext $context, string $currentDeviceId): array
1400
+ public static function getSubnetScore(RequestContext $context, string $currentDeviceId = '', array $securityConfig = []): array
1325
1401
  {
1326
1402
  $subnet = self::getIpSubnet($context->clientIp);
1327
1403
  if ($subnet === null) {
@@ -1355,24 +1431,72 @@ class RequestUtils
1355
1431
  return ['subnetScore' => 0.0];
1356
1432
  }
1357
1433
 
1358
- $suspicionDensity = $highScoreCount / $deviceCount;
1359
- $ipDeviceRatio = $ipCount / $deviceCount;
1434
+ // 1. Régularisation bayésienne continue de la densité
1435
+ $bayesianDensity = ((float)$highScoreCount + 0.5) / ((float)$deviceCount + 2.5);
1436
+
1437
+ // 2. Dispersion IP / Terminal (CGNAT vs Proxy Pool)
1438
+ $ipDispersion = min(2.0, (float)$ipCount / (float)$deviceCount);
1439
+ $ipMultiplier = 0.6 + 0.4 * tanh($ipDispersion);
1360
1440
 
1361
- // Ratio User-Agent / Device : détecte la rotation/spoofing de navigateurs sur une même empreinte matérielle
1362
- $uaDeviceRatio = (double)max(1, $uaCount) / $deviceCount;
1363
- $uaMultiplier = 0.6 + (0.4 * min(2.5, $uaDeviceRatio));
1441
+ // 3. Dispersion User-Agent / Terminal
1442
+ $uaDispersion = min(3.0, (float)max(1, $uaCount) / (float)$deviceCount);
1443
+ $uaMultiplier = 0.7 + 0.3 * tanh($uaDispersion - 1.0);
1364
1444
 
1365
- $baseScore = 100.0 * (1.0 - exp(-0.15 * $highScoreCount));
1445
+ // 4. Intensité brute globale de la menace
1446
+ $rawIntensity = (float)$highScoreCount * $bayesianDensity * $ipMultiplier * $uaMultiplier;
1366
1447
 
1367
- $densityMultiplier = 0.4 + (1.6 * $suspicionDensity);
1368
- $distributionMultiplier = 0.5 + (1.0 * $ipDeviceRatio);
1448
+ // 5. Composante 1 : Score ambiant plafonné en zone Medium (asymptote au seuil medium)
1449
+ $mediumThreshold = (float)($securityConfig['thresholds']['medium'] ?? 45.0);
1450
+ $ambientAsymptote = $mediumThreshold;
1451
+ $maxProximityBoost = max(0.0, 100.0 - $ambientAsymptote);
1452
+ $scale = 10.0;
1453
+ $ambientScore = $ambientAsymptote * tanh($rawIntensity / $scale);
1369
1454
 
1370
- $dampening = 1.0;
1371
- if( $highScoreCount < 3 ){
1372
- $dampening = $highScoreCount / 3.0;
1455
+ // 6. Composante 2 : Boost de proximité micro-réseau avec des attaquants récents (le reste va jusqu'à 100)
1456
+ $proximityBoost = 0.0;
1457
+ $attackerIps = $subnetData['attackerIps'] ?? [];
1458
+ $clientIp = $context->clientIp;
1459
+
1460
+ if (!empty($attackerIps) && !empty($clientIp)) {
1461
+ $maxCommonPrefix = 0;
1462
+ $isV4 = filter_var($clientIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
1463
+ $isV6 = filter_var($clientIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
1464
+
1465
+ foreach ($attackerIps as $att) {
1466
+ $attIp = $att['ip'] ?? '';
1467
+ $lastSeen = $att['lastSeen'] ?? $now;
1468
+ if ($attIp !== '' && ($now - $lastSeen) <= 1800) {
1469
+ $prefixLen = self::getIpCommonPrefixLength($clientIp, $attIp);
1470
+ if ($prefixLen > $maxCommonPrefix) {
1471
+ $maxCommonPrefix = $prefixLen;
1472
+ }
1473
+ }
1474
+ }
1475
+
1476
+ if ($isV4) {
1477
+ if ($maxCommonPrefix >= 32) {
1478
+ $proximityBoost = $maxProximityBoost; // Même adresse IP exacte
1479
+ } elseif ($maxCommonPrefix >= 30) {
1480
+ $proximityBoost = $maxProximityBoost * (45.0 / 55.0); // Même /30 (écart <= 3 adresses)
1481
+ } elseif ($maxCommonPrefix >= 28) {
1482
+ $proximityBoost = $maxProximityBoost * (30.0 / 55.0); // Même /28 (bloc de 16 adresses)
1483
+ } elseif ($maxCommonPrefix >= 26) {
1484
+ $proximityBoost = $maxProximityBoost * (15.0 / 55.0); // Même /26 (bloc de 64 adresses)
1485
+ }
1486
+ } elseif ($isV6) {
1487
+ if ($maxCommonPrefix >= 128) {
1488
+ $proximityBoost = $maxProximityBoost; // Même IPv6 exacte
1489
+ } elseif ($maxCommonPrefix >= 120) {
1490
+ $proximityBoost = $maxProximityBoost * (45.0 / 55.0); // Même /120
1491
+ } elseif ($maxCommonPrefix >= 112) {
1492
+ $proximityBoost = $maxProximityBoost * (30.0 / 55.0); // Même /112
1493
+ } elseif ($maxCommonPrefix >= 96) {
1494
+ $proximityBoost = $maxProximityBoost * (15.0 / 55.0); // Même /96
1495
+ }
1496
+ }
1373
1497
  }
1374
- $finalScore = min(100.0, round($baseScore * $densityMultiplier * $distributionMultiplier * $uaMultiplier * $dampening * 10.0) / 10.0);
1375
1498
 
1499
+ $finalScore = min(100.0, round(($ambientScore + $proximityBoost) * 10.0) / 10.0);
1376
1500
  return ['subnetScore' => $finalScore];
1377
1501
  }
1378
1502
  /**
@@ -1408,6 +1532,12 @@ class RequestUtils
1408
1532
  }
1409
1533
  }
1410
1534
 
1535
+ if (isset($subnetData['attackerIps']) && is_array($subnetData['attackerIps'])) {
1536
+ $subnetData['attackerIps'] = array_values(array_filter($subnetData['attackerIps'], function ($att) use ($now) {
1537
+ return ($now - ($att['lastSeen'] ?? $now)) < 3600;
1538
+ }));
1539
+ }
1540
+
1411
1541
  $subnetData['lastActivity'] = $now - ($inactivitySec % 1800);
1412
1542
  }
1413
1543
  }
@@ -2031,4 +2161,91 @@ class RequestUtils
2031
2161
 
2032
2162
  return !empty($deviceId) && !empty($deviceHash); // Match d'identité matérielle stricte (deviceId + deviceHash validés par HMAC)
2033
2163
  }
2164
+
2165
+ private static function imul(int $a, int $b): int
2166
+ {
2167
+ return ($a * $b) & 0xffffffff;
2168
+ }
2169
+
2170
+ public static function cyrb53(string $str, int $seed = 0): int
2171
+ {
2172
+ $h1 = (0xdeadbeef ^ $seed) & 0xffffffff;
2173
+ $h2 = (0x41c6ce57 ^ $seed) & 0xffffffff;
2174
+ for ($i = 0; $i < strlen($str); $i++) {
2175
+ $ch = ord($str[$i]);
2176
+ $h1 = self::imul($h1 ^ $ch, 2654435761);
2177
+ $h2 = self::imul($h2 ^ $ch, 1597334677);
2178
+ }
2179
+ $h1 = self::imul($h1 ^ ($h1 >> 16), 2246822507) ^ self::imul($h2 ^ ($h2 >> 13), 3266489909);
2180
+ $h2 = self::imul($h2 ^ ($h2 >> 16), 2246822507) ^ self::imul($h1 ^ ($h1 >> 13), 3266489909);
2181
+ $unsigned_h1 = $h1 & 0xffffffff;
2182
+ return 4294967296 * (2097151 & $h2) + $unsigned_h1;
2183
+ }
2184
+
2185
+ public static function getVirtualizationAnomalyScore(RequestContext $context): float
2186
+ {
2187
+ $clientFp = $context->headers['x-device-fingerprint'] ?? null;
2188
+ if (!$clientFp) {
2189
+ return 0.0;
2190
+ }
2191
+
2192
+ $fpMap = [];
2193
+ foreach (explode('|', $clientFp) as $part) {
2194
+ $pair = explode(':', $part, 2);
2195
+ if (count($pair) === 2) {
2196
+ $fpMap[$pair[0]] = $pair[1];
2197
+ }
2198
+ }
2199
+
2200
+ $score = 0.0;
2201
+ $clientGpuHash = $fpMap['gpu'] ?? null;
2202
+ if ($clientGpuHash) {
2203
+ $virtualGpus = [
2204
+ "Google SwiftShader", "SwiftShader",
2205
+ "Mesa llvmpipe", "llvmpipe", "Mesa Gallium",
2206
+ "Microsoft Basic Render Driver", "HeadlessChrome",
2207
+ "Intel(R) HD Graphics"
2208
+ ];
2209
+ $virtualGpuHashes = [];
2210
+ foreach ($virtualGpus as $gpu) {
2211
+ $virtualGpuHashes[] = (string)self::cyrb53($gpu);
2212
+ }
2213
+ if (in_array($clientGpuHash, $virtualGpuHashes, true)) {
2214
+ $score += 75.0;
2215
+ }
2216
+ }
2217
+
2218
+ $clientScreenHash = $fpMap['scr'] ?? null;
2219
+ if ($clientScreenHash) {
2220
+ $headlessResolutions = ["800x600_24", "1024x768_24"];
2221
+ $headlessHashes = [];
2222
+ foreach ($headlessResolutions as $res) {
2223
+ $headlessHashes[] = (string)self::cyrb53($res);
2224
+ }
2225
+ if (in_array($clientScreenHash, $headlessHashes, true)) {
2226
+ $score += 25.0;
2227
+ }
2228
+ }
2229
+
2230
+ return min(100.0, $score);
2231
+ }
2232
+
2233
+ /**
2234
+ * Calcule un score d'incohérence analogique et lisse plafonnant à 99.9 max.
2235
+ */
2236
+ public static function calculateAnalogInconsistencyScore(float $consistencyScore, float $inflectionPoint = 0.72, float $steepness = 12.0): float
2237
+ {
2238
+ $s = max(0.0, min(1.0, $consistencyScore));
2239
+ if ($s >= 0.98) {
2240
+ return 0.0;
2241
+ }
2242
+
2243
+ $asymptote = 99.9;
2244
+ $raw = 1.0 / (1.0 + exp($steepness * ($s - $inflectionPoint)));
2245
+ $minVal = 1.0 / (1.0 + exp($steepness * (1.0 - $inflectionPoint)));
2246
+ $maxVal = 1.0 / (1.0 + exp($steepness * (0.0 - $inflectionPoint)));
2247
+ $normalized = (($raw - $minVal) / ($maxVal - $minVal)) * $asymptote;
2248
+
2249
+ return min($asymptote, round($normalized, 1));
2250
+ }
2034
2251
  }