@anonympins/fingerprint 0.7.2 → 0.7.4
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 +36 -9
- package/README.md +1 -1
- package/package.json +109 -107
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/fingerprint.client.js +252 -45
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +265 -185
- package/src/js/fingerprint.utils.js +32 -5
- package/src/js/fingerprint.worker.js +81 -0
- package/src/js/library.js +6 -2
- package/src/js/pow.solver.js +2 -3
- package/src/js/problem-manager.js +113 -113
- package/src/js/tests/fingerprint.client.init.test.js +30 -0
- package/src/js/tests/fingerprint.test.js +136 -4
- package/src/js/tests/threatIntel.test.js +76 -0
- package/src/php/AutoTuner.php +36 -18
- package/src/php/Challenge/ChallengeUtils.php +121 -240
- package/src/php/DirectFingerprint.php +16 -4
- package/src/php/FingerprintClient.php +17 -19
- package/src/php/FingerprintEngine.php +77 -39
- 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/ChallengeUtilsTest.php +469 -453
- package/src/php/Tests/FingerprintEngineTest.php +6 -1
- package/src/php/Tests/RequestUtilsTest.php +1 -1
- package/src/php/Tests/ThreatIntelTest.php +64 -0
- package/src/php/Utils/Env.php +51 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/RequestUtils.php +231 -132
- package/src/php/WordPress/WpDbStore.php +4 -0
- package/src/php/WordPress/{fingerprint-wordpress.php → fingerprint-anti-bot.php} +131 -126
- 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/package.php +6 -3
- package/src/php/WordPress/readme.txt +36 -0
- package/public/fingerprint-wordpress.zip +0 -0
|
@@ -392,125 +392,145 @@ class RequestUtils
|
|
|
392
392
|
|
|
393
393
|
/**
|
|
394
394
|
* Calcule un score basé sur les métriques comportementales envoyées par le client.
|
|
395
|
-
* @
|
|
395
|
+
* @param RequestContext $context Le contexte de la requête.
|
|
396
|
+
* @return array{behaviorScore: float}
|
|
396
397
|
*/
|
|
397
398
|
public static function getBehaviorScore(RequestContext $context): array
|
|
398
399
|
{
|
|
399
|
-
$
|
|
400
|
-
if (
|
|
400
|
+
$header = $context->getHeader('x-behavior-metrics');
|
|
401
|
+
if (empty($header)) {
|
|
401
402
|
return ['behaviorScore' => 0.0];
|
|
402
403
|
}
|
|
403
404
|
|
|
404
|
-
$
|
|
405
|
-
|
|
406
|
-
return ['behaviorScore' => 10.0]; // En-tête malformé
|
|
405
|
+
if ($header === '1' || $header === 1) {
|
|
406
|
+
return ['behaviorScore' => 0.0];
|
|
407
407
|
}
|
|
408
|
-
|
|
409
|
-
if (!empty($metrics['honeypotInteraction'])) {
|
|
408
|
+
if ($header === '0' || $header === 0) {
|
|
410
409
|
return ['behaviorScore' => 100.0];
|
|
411
410
|
}
|
|
412
411
|
|
|
413
|
-
$
|
|
414
|
-
|
|
415
|
-
$score
|
|
412
|
+
if (is_numeric($header)) {
|
|
413
|
+
$score = max(0.0, min(100.0, (float)$header));
|
|
414
|
+
return ['behaviorScore' => $score];
|
|
416
415
|
}
|
|
417
416
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
$score
|
|
417
|
+
if (is_string($header)) {
|
|
418
|
+
$trimmed = trim($header);
|
|
419
|
+
if (is_numeric($trimmed)) {
|
|
420
|
+
if ($trimmed === '1') {
|
|
421
|
+
return ['behaviorScore' => 0.0];
|
|
422
|
+
}
|
|
423
|
+
if ($trimmed === '0') {
|
|
424
|
+
return ['behaviorScore' => 100.0];
|
|
425
|
+
}
|
|
426
|
+
$score = max(0.0, min(100.0, (float)$trimmed));
|
|
427
|
+
return ['behaviorScore' => $score];
|
|
429
428
|
}
|
|
430
429
|
}
|
|
431
430
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
431
|
+
$metrics = json_decode($header, true);
|
|
432
|
+
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
433
|
+
return ['behaviorScore' => 10.0];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if ($metrics === 1) {
|
|
437
|
+
return ['behaviorScore' => 0.0];
|
|
438
|
+
}
|
|
439
|
+
if ($metrics === 0) {
|
|
440
|
+
return ['behaviorScore' => 100.0];
|
|
437
441
|
}
|
|
438
442
|
|
|
439
|
-
if (($metrics
|
|
440
|
-
|
|
443
|
+
if (is_numeric($metrics)) {
|
|
444
|
+
$score = max(0.0, min(100.0, (float)$metrics));
|
|
445
|
+
return ['behaviorScore' => $score];
|
|
446
|
+
}
|
|
441
447
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
448
|
+
if (!is_array($metrics)) {
|
|
449
|
+
return ['behaviorScore' => 0.0];
|
|
450
|
+
}
|
|
445
451
|
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
$stdDevDwell = sqrt($varDwell);
|
|
452
|
+
if ($metrics['honeypotInteraction'] ?? false) {
|
|
453
|
+
return ['behaviorScore' => 100.0];
|
|
454
|
+
}
|
|
450
455
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if ($meanDwell < 15.0) {
|
|
455
|
-
$score += 25.0;
|
|
456
|
-
}
|
|
456
|
+
$score = 0.0;
|
|
457
|
+
if ($metrics['prototypeTampered'] ?? false) {
|
|
458
|
+
$score += 80.0;
|
|
457
459
|
}
|
|
458
460
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
$meanFlight = array_sum($times) / count($times);
|
|
462
|
-
$varFlight = array_reduce($times, fn($carry, $item) => $carry + pow($item - $meanFlight, 2), 0) / count($times);
|
|
463
|
-
$stdDevFlight = sqrt($varFlight);
|
|
461
|
+
$mouseHistory = $metrics['mouseMovementsHistory'] ?? [];
|
|
462
|
+
$touchHistory = $metrics['touchMovementsHistory'] ?? [];
|
|
464
463
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
464
|
+
$mouseAnalysis = RequestUtils::analyzeMouseMovements($mouseHistory);
|
|
465
|
+
$touchAnalysis = RequestUtils::analyzeTouchMovements($touchHistory);
|
|
466
|
+
|
|
467
|
+
$mouseAvgSpeed = (float)($mouseAnalysis['avgSpeed'] ?? 0.0);
|
|
468
|
+
$touchAvgSpeed = (float)($touchAnalysis['avgSpeed'] ?? 0.0);
|
|
469
|
+
$keystrokeLatency = (float)($metrics['keystrokeLatency'] ?? 0.0);
|
|
470
|
+
|
|
471
|
+
if (isset($metrics['historyLength'])) {
|
|
472
|
+
$historyLength = (int)$metrics['historyLength'];
|
|
473
|
+
if ($historyLength === 1) $score += 15;
|
|
474
|
+
elseif ($historyLength >= 5) $score -= 20;
|
|
475
|
+
elseif ($historyLength >= 2) $score -= 10;
|
|
476
|
+
} else {
|
|
477
|
+
if ($mouseAvgSpeed === 0.0 && $touchAvgSpeed === 0.0 && $keystrokeLatency === 0.0) {
|
|
478
|
+
$noInteractionPenalty = 5.0;
|
|
479
|
+
if ($metrics['rendering']['offscreenAnom'] ?? false) {
|
|
480
|
+
$noInteractionPenalty += 40.0;
|
|
481
|
+
}
|
|
482
|
+
$score += $noInteractionPenalty;
|
|
474
483
|
}
|
|
475
484
|
}
|
|
476
485
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if ($
|
|
481
|
-
|
|
482
|
-
}
|
|
486
|
+
if ($mouseAvgSpeed > 0) {
|
|
487
|
+
if ($mouseAvgSpeed > 3.0) $score += 25;
|
|
488
|
+
if (($mouseAnalysis['avgAcceleration'] ?? 0.0) > 0.5) $score += 20;
|
|
489
|
+
if (($mouseAnalysis['straightness'] ?? 1.0) > 0.95) $score += 30;
|
|
490
|
+
if (($mouseAnalysis['pauses'] ?? 0) === 0 && count($mouseAnalysis['segments'] ?? []) > 20) $score += 15;
|
|
483
491
|
}
|
|
484
|
-
// Analyse comportementale des événements tactiles (Touch Move)
|
|
485
|
-
$touchHistory = $metrics['touchMovementsHistory'] ?? null;
|
|
486
|
-
if (!empty($touchHistory)) {
|
|
487
|
-
if ($touch['avgSpeed'] > 0) {
|
|
488
|
-
if ($touch['avgSpeed'] > 5) $score += 30;
|
|
489
|
-
if ($touch['avgAcceleration'] > 0.8) $score += 20;
|
|
490
|
-
if ($touch['straightness'] > 0.98) $score += 35;
|
|
491
|
-
if ($touch['pauses'] === 0 && count($touch['segments']) > 25) $score += 15;
|
|
492
492
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
493
|
+
if ($touchAvgSpeed > 0 || !empty($touchHistory)) {
|
|
494
|
+
if (count($touchHistory) >= 3) {
|
|
495
|
+
if (($touchAnalysis['pressureVariance'] ?? 1.0) < 0.0001) {
|
|
496
|
+
$score += 35.0;
|
|
497
|
+
}
|
|
498
|
+
if (($touchAnalysis['radiusVariance'] ?? 1.0) < 0.0001) {
|
|
499
|
+
$score += 25.0;
|
|
496
500
|
}
|
|
497
|
-
if ($
|
|
498
|
-
$score +=
|
|
501
|
+
if (($touchAnalysis['straightness'] ?? 0.0) > 0.98) {
|
|
502
|
+
$score += 20.0;
|
|
499
503
|
}
|
|
500
504
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
$dwellTimes = $metrics['keystrokeDwellTimes'] ?? [];
|
|
508
|
+
if (is_array($dwellTimes) && count($dwellTimes) >= 3) {
|
|
509
|
+
$dwellNumeric = array_filter($dwellTimes, 'is_numeric');
|
|
510
|
+
if (count($dwellNumeric) >= 3) {
|
|
511
|
+
$meanDwell = array_sum($dwellNumeric) / count($dwellNumeric);
|
|
512
|
+
$dwellVar = array_reduce($dwellNumeric, fn($carry, $val) => $carry + pow((float)$val - $meanDwell, 2), 0.0) / count($dwellNumeric);
|
|
513
|
+
if ($dwellVar < 2.0) {
|
|
514
|
+
$score += 40.0;
|
|
505
515
|
}
|
|
506
516
|
}
|
|
517
|
+
}
|
|
507
518
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
$
|
|
511
|
-
|
|
512
|
-
if ((
|
|
513
|
-
$
|
|
519
|
+
$flightTimes = $metrics['keystrokeFlightTimes'] ?? [];
|
|
520
|
+
if (is_array($flightTimes) && count($flightTimes) >= 3) {
|
|
521
|
+
$extractedFlightTimes = [];
|
|
522
|
+
foreach ($flightTimes as $ft) {
|
|
523
|
+
if (is_array($ft) && isset($ft['time']) && is_numeric($ft['time'])) {
|
|
524
|
+
$extractedFlightTimes[] = (float)$ft['time'];
|
|
525
|
+
} elseif (is_numeric($ft)) {
|
|
526
|
+
$extractedFlightTimes[] = (float)$ft;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (count($extractedFlightTimes) >= 3) {
|
|
530
|
+
$meanFlight = array_sum($extractedFlightTimes) / count($extractedFlightTimes);
|
|
531
|
+
$flightVar = array_reduce($extractedFlightTimes, fn($carry, $val) => $carry + pow($val - $meanFlight, 2), 0.0) / count($extractedFlightTimes);
|
|
532
|
+
if ($flightVar < 2.0) {
|
|
533
|
+
$score += 40.0;
|
|
514
534
|
}
|
|
515
535
|
}
|
|
516
536
|
}
|
|
@@ -518,6 +538,7 @@ class RequestUtils
|
|
|
518
538
|
return ['behaviorScore' => min(100.0, $score)];
|
|
519
539
|
}
|
|
520
540
|
|
|
541
|
+
|
|
521
542
|
/**
|
|
522
543
|
* Calcule un score basé sur la régularité d'affichage (V-Sync) et l'utilisation suspecte d'OffscreenCanvas.
|
|
523
544
|
* @return array{'renderingAnomalyScore': float}
|
|
@@ -1038,19 +1059,34 @@ class RequestUtils
|
|
|
1038
1059
|
public static function getThreatIntelScore(RequestContext $context, array $threatIntelConfig): array
|
|
1039
1060
|
{
|
|
1040
1061
|
$score = 0.0;
|
|
1062
|
+
$signals = [];
|
|
1063
|
+
|
|
1041
1064
|
$zkpY = $context->zkpY;
|
|
1042
1065
|
if (!empty($zkpY)) {
|
|
1043
1066
|
$store = StoreManager::getStore();
|
|
1044
1067
|
$isBanned = $store->has("banned-zkp-y:{$zkpY}");
|
|
1045
1068
|
if ($isBanned) {
|
|
1046
1069
|
$score = 100.0;
|
|
1070
|
+
$signals[] = [
|
|
1071
|
+
'ruleId' => 'FEDERATED_ZKP_BANNED',
|
|
1072
|
+
'confidence' => 1.0,
|
|
1073
|
+
'score' => 100.0,
|
|
1074
|
+
'rationale' => 'Cryptographic identity matched federated banned list consensus'
|
|
1075
|
+
];
|
|
1047
1076
|
}
|
|
1048
1077
|
}
|
|
1049
1078
|
|
|
1050
|
-
$rttProxyScore = \Anonympins\Fingerprint\Ja3AnomalyDetector::getRttProxyScore($context);
|
|
1051
|
-
|
|
1079
|
+
$rttProxyScore = (float)\Anonympins\Fingerprint\Ja3AnomalyDetector::getRttProxyScore($context);
|
|
1080
|
+
if ($rttProxyScore > 0.0) {
|
|
1081
|
+
$score = max($score, $rttProxyScore);
|
|
1082
|
+
$signals[] = [
|
|
1083
|
+
'ruleId' => 'RESIDENTIAL_PROXY_RTT_DISCREPANCY',
|
|
1084
|
+
'score' => $rttProxyScore,
|
|
1085
|
+
'rationale' => 'Physical discrepancy between TCP edge RTT and application transit latency'
|
|
1086
|
+
];
|
|
1087
|
+
}
|
|
1052
1088
|
|
|
1053
|
-
return ['threatIntelScore' => $score];
|
|
1089
|
+
return ['threatIntelScore' => $score, 'threatIntelSignals' => $signals];
|
|
1054
1090
|
}
|
|
1055
1091
|
|
|
1056
1092
|
/**
|
|
@@ -1145,7 +1181,8 @@ class RequestUtils
|
|
|
1145
1181
|
*/
|
|
1146
1182
|
public static function cleanUrlFromPowParams(string $originalPath, array $incomingQuery): string
|
|
1147
1183
|
{
|
|
1148
|
-
|
|
1184
|
+
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Uses wp_parse_url when running inside WordPress
|
|
1185
|
+
$urlParts = function_exists('wp_parse_url') ? wp_parse_url($originalPath) : parse_url($originalPath);
|
|
1149
1186
|
$path = $urlParts['path'] ?? '/';
|
|
1150
1187
|
$finalQuery = $incomingQuery;
|
|
1151
1188
|
|
|
@@ -1326,7 +1363,8 @@ class RequestUtils
|
|
|
1326
1363
|
|
|
1327
1364
|
// Utilisation de la partie stable du fingerprint matériel plutôt que l'ID de cookie volatil
|
|
1328
1365
|
$currentDeviceHash = self::getCompositeDeviceHash($context);
|
|
1329
|
-
$
|
|
1366
|
+
$stablePart = self::extractStablePart($currentDeviceHash);
|
|
1367
|
+
$stableFpId = !empty($stablePart) ? (string)FingerprintBuilder::cyrb53($stablePart) : (!empty($deviceId) ? $deviceId : (string)FingerprintBuilder::cyrb53($currentDeviceHash));
|
|
1330
1368
|
|
|
1331
1369
|
$currentDeviceContributions = $subnetData['highScoreDevices'][$stableFpId] ?? 0;
|
|
1332
1370
|
if ($currentDeviceContributions < 1 && $finalScore < 95) {
|
|
@@ -1991,6 +2029,21 @@ class RequestUtils
|
|
|
1991
2029
|
return ['tcpAnomalyScore' => $tcpAnomalyScore];
|
|
1992
2030
|
}
|
|
1993
2031
|
|
|
2032
|
+
/**
|
|
2033
|
+
* Helper to find the first key of a value from a list of needles in an array.
|
|
2034
|
+
* @param array<int, string> $haystack
|
|
2035
|
+
* @param array<int, string> $needles
|
|
2036
|
+
* @return int|null
|
|
2037
|
+
*/
|
|
2038
|
+
private static function array_find_key(array $haystack, array $needles): ?int
|
|
2039
|
+
{
|
|
2040
|
+
foreach ($needles as $needle) {
|
|
2041
|
+
$key = array_search($needle, $haystack, true);
|
|
2042
|
+
if ($key !== false) return (int)$key;
|
|
2043
|
+
}
|
|
2044
|
+
return null;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1994
2047
|
/**
|
|
1995
2048
|
* Détecte les anomalies de protocole (HTTP/2 et QUIC/HTTP3) par rapport au User-Agent.
|
|
1996
2049
|
* @param RequestContext $context
|
|
@@ -2005,52 +2058,96 @@ class RequestUtils
|
|
|
2005
2058
|
$uaParts = self::parseUserAgent($ua);
|
|
2006
2059
|
$browser = $uaParts['browser'] ?? null;
|
|
2007
2060
|
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
$
|
|
2012
|
-
|
|
2013
|
-
$
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
if ($
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2061
|
+
if ($browser) {
|
|
2062
|
+
// HTTP/2 Anomaly logic
|
|
2063
|
+
$h2Fp = $context->getHeader('x-http2-fingerprint') ?? $context->http2Fingerprint ?? null;
|
|
2064
|
+
if ($h2Fp && is_string($h2Fp)) {
|
|
2065
|
+
$parts = explode('|', $h2Fp);
|
|
2066
|
+
if (count($parts) >= 3) {
|
|
2067
|
+
$connWindow = (int)$parts[1];
|
|
2068
|
+
$streamPriority = $parts[2] ?? '';
|
|
2069
|
+
$headerOrder = $parts[3] ?? '';
|
|
2070
|
+
$isChromium = str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge');
|
|
2071
|
+
$isFirefox = str_starts_with($browser, 'Firefox');
|
|
2072
|
+
$isSafari = str_starts_with($browser, 'Safari');
|
|
2073
|
+
|
|
2074
|
+
if ($isChromium) {
|
|
2075
|
+
if ($headerOrder && $headerOrder !== 'm,a,s,p') $http2Anomaly += 60.0;
|
|
2076
|
+
if ($connWindow === 65535 || $connWindow === 65536) $http2Anomaly += 40.0;
|
|
2077
|
+
if ($streamPriority === '0' || $streamPriority === '') $http2Anomaly += 50.0;
|
|
2078
|
+
} elseif ($isFirefox) {
|
|
2079
|
+
if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
|
|
2080
|
+
} elseif ($isSafari) {
|
|
2081
|
+
if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
|
|
2082
|
+
}
|
|
2026
2083
|
}
|
|
2027
2084
|
}
|
|
2028
|
-
}
|
|
2029
2085
|
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2086
|
+
// QUIC Anomaly logic
|
|
2087
|
+
$quicFp = $context->getHeader('x-quic-fp') ?? $context->quicFingerprint ?? null;
|
|
2088
|
+
if ($quicFp && is_string($quicFp)) {
|
|
2089
|
+
$parts = explode(';', $quicFp);
|
|
2090
|
+
if (count($parts) >= 2) {
|
|
2091
|
+
$params = [];
|
|
2092
|
+
foreach (explode(',', $parts[1]) as $p) {
|
|
2093
|
+
$kv = explode('=', $p, 2);
|
|
2094
|
+
if (count($kv) === 2) {
|
|
2095
|
+
$params[$kv[0]] = $kv[1];
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
$priorityOrder = $parts[2] ?? '';
|
|
2099
|
+
$frameOrderRaw = $parts[3] ?? $context->getHeader('x-quic-frame-order') ?? '';
|
|
2100
|
+
$frameOrder = array_values(array_filter(array_map('trim', explode(',', strtolower($frameOrderRaw)))));
|
|
2101
|
+
|
|
2102
|
+
$isChromium = str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge');
|
|
2103
|
+
$isFirefox = str_starts_with($browser, 'Firefox');
|
|
2104
|
+
$isSafari = str_starts_with($browser, 'Safari');
|
|
2105
|
+
|
|
2106
|
+
$maxData = (int)($params['1'] ?? $params['0x01'] ?? 0);
|
|
2107
|
+
$maxStreams = (int)($params['4'] ?? $params['8'] ?? $params['0x08'] ?? 0);
|
|
2108
|
+
$bidiLocal = (int)($params['5'] ?? $params['0x05'] ?? 0);
|
|
2109
|
+
$bidiRemote = (int)($params['6'] ?? $params['0x06'] ?? 0);
|
|
2110
|
+
|
|
2111
|
+
if ($isChromium) {
|
|
2112
|
+
if ($maxData > 0 && $maxData < 1048576) $quicAnomaly += 40.0;
|
|
2113
|
+
if ($maxStreams > 0 && $maxStreams !== 100) $quicAnomaly += 30.0;
|
|
2114
|
+
if (!empty($priorityOrder) && !str_contains($priorityOrder, 'u=')) $quicAnomaly += 30.0;
|
|
2115
|
+
if ($bidiLocal > 0 && ($bidiLocal < 524288 || $bidiLocal === 262144)) $quicAnomaly += 40.0;
|
|
2116
|
+
if ($bidiRemote > 0 && ($bidiRemote < 524288 || $bidiRemote === 262144)) $quicAnomaly += 30.0;
|
|
2117
|
+
|
|
2118
|
+
if (count($frameOrder) >= 2) {
|
|
2119
|
+
$sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
|
|
2120
|
+
$mIdx = self::array_find_key($frameOrder, ['m', 'max_streams', '18']);
|
|
2121
|
+
$pIdx = self::array_find_key($frameOrder, ['p', 'priority', 'priority_update', '15']);
|
|
2122
|
+
|
|
2123
|
+
if ($sIdx !== null && $sIdx !== 0) $quicAnomaly += 50.0;
|
|
2124
|
+
if ($mIdx !== null && $sIdx !== null && $mIdx < $sIdx) $quicAnomaly += 60.0;
|
|
2125
|
+
if ($pIdx !== null && $sIdx !== null && $pIdx < $sIdx) $quicAnomaly += 60.0;
|
|
2126
|
+
}
|
|
2127
|
+
} elseif ($isFirefox) {
|
|
2128
|
+
if ($maxData > 0 && $maxData > 5000000) $quicAnomaly += 40.0;
|
|
2129
|
+
if ($maxStreams === 100) $quicAnomaly += 50.0;
|
|
2130
|
+
if ($bidiLocal === 6291456) $quicAnomaly += 50.0;
|
|
2131
|
+
|
|
2132
|
+
if (count($frameOrder) >= 2) {
|
|
2133
|
+
$sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
|
|
2134
|
+
if ($sIdx !== null && $sIdx !== 0) $quicAnomaly += 50.0;
|
|
2135
|
+
}
|
|
2136
|
+
} elseif ($isSafari) {
|
|
2137
|
+
if ($maxStreams === 100 && $maxData === 1572864 && str_contains($priorityOrder, 'u=2,i')) {
|
|
2138
|
+
$quicAnomaly += 60.0;
|
|
2139
|
+
}
|
|
2140
|
+
if ($bidiLocal === 6291456) $quicAnomaly += 50.0;
|
|
2141
|
+
|
|
2142
|
+
if (count($frameOrder) >= 2) {
|
|
2143
|
+
$sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
|
|
2144
|
+
$mIdx = self::array_find_key($frameOrder, ['m', 'max_streams']);
|
|
2145
|
+
if ($mIdx !== null && ($mIdx === 0 || ($sIdx !== null && $mIdx < $sIdx))) {
|
|
2146
|
+
$quicAnomaly += 50.0;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2040
2149
|
}
|
|
2041
2150
|
}
|
|
2042
|
-
$priorityOrder = $parts[2] ?? '';
|
|
2043
|
-
|
|
2044
|
-
if (str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge')) {
|
|
2045
|
-
$maxData = isset($params['1']) ? (int)$params['1'] : 0;
|
|
2046
|
-
$maxStreams = isset($params['4']) ? (int)$params['4'] : 0;
|
|
2047
|
-
if ($maxData > 0 && $maxData < 1048576) $quicAnomaly += 40.0;
|
|
2048
|
-
if ($maxStreams > 0 && $maxStreams !== 100) $quicAnomaly += 30.0;
|
|
2049
|
-
if (!empty($priorityOrder) && !str_contains($priorityOrder, 'u=')) $quicAnomaly += 30.0;
|
|
2050
|
-
} elseif (str_starts_with($browser, 'Firefox')) {
|
|
2051
|
-
$maxData = isset($params['1']) ? (int)$params['1'] : 0;
|
|
2052
|
-
if ($maxData > 0 && $maxData > 5000000) $quicAnomaly += 40.0;
|
|
2053
|
-
}
|
|
2054
2151
|
}
|
|
2055
2152
|
}
|
|
2056
2153
|
|
|
@@ -2059,7 +2156,9 @@ class RequestUtils
|
|
|
2059
2156
|
0.0,
|
|
2060
2157
|
min(100.0, $http2Anomaly),
|
|
2061
2158
|
min(100.0, $quicAnomaly)
|
|
2062
|
-
)
|
|
2159
|
+
),
|
|
2160
|
+
'http2AnomalyScore' => min(100.0, $http2Anomaly),
|
|
2161
|
+
'quicAnomalyScore' => min(100.0, $quicAnomaly)
|
|
2063
2162
|
];
|
|
2064
2163
|
}
|
|
2065
2164
|
|