@anonympins/fingerprint 0.4.4 → 0.4.6
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 +40 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/js/dynamic-wasm.js +180 -0
- package/src/js/fingerprint.client.js +21 -0
- package/src/js/fingerprint.js +763 -56
- package/src/js/library.js +1740 -1729
- package/src/js/pow.solver.inline.js +381 -285
- package/src/js/pow.solver.js +97 -1
- package/src/js/tests/dynamic-wasm.test.js +78 -0
- package/src/js/tests/fingerprint.client.test.js +128 -104
- package/src/js/tests/fingerprint.test.js +79 -1
- package/src/js/tests/ip-reputation.test.js +141 -131
- package/src/js/tests/tcpFingerprint.test.js +78 -0
- package/src/php/AutoTuner.php +1 -1
- package/src/php/Challenge/ChallengeUtils.php +174 -12
- package/src/php/Config/SecurityProfiles.php +10 -0
- package/src/php/FingerprintEngine.php +140 -8
- package/src/php/Optimization/Optimization.php +257 -255
- package/src/php/Optimization/OptimizationOperators.php +41 -35
- package/src/php/RequestContext.php +2 -0
- package/src/php/Tests/FingerprintEngineTest.php +132 -1
- package/src/php/Tests/IpReputationTest.php +175 -156
- package/src/php/Tests/RequestUtilsTest.php +13 -0
- package/src/php/Utils/RequestUtils.php +319 -5
|
@@ -225,6 +225,16 @@
|
|
|
225
225
|
$deviceData = null;
|
|
226
226
|
$newCookie = null;
|
|
227
227
|
|
|
228
|
+
// Cookieless Identity Tracking: Attempt to restore device ID using TLS session resume ID
|
|
229
|
+
$tlsSessionId = $context->tlsSessionId;
|
|
230
|
+
if (!$deviceId && $tlsSessionId) {
|
|
231
|
+
$resumedDeviceId = $store->get("tls-session:{$tlsSessionId}");
|
|
232
|
+
if ($resumedDeviceId) {
|
|
233
|
+
$deviceId = $resumedDeviceId;
|
|
234
|
+
$this->log('Identity resumed via TLS Session ID', ['deviceId' => $deviceId, 'tlsSessionId' => $tlsSessionId]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
228
238
|
if ($deviceId) {
|
|
229
239
|
$deviceData = $store->get("device:{$deviceId}");
|
|
230
240
|
}
|
|
@@ -275,6 +285,11 @@
|
|
|
275
285
|
}
|
|
276
286
|
}
|
|
277
287
|
|
|
288
|
+
// Bind the current TLS session ID to the device ID
|
|
289
|
+
if ($deviceId && $tlsSessionId) {
|
|
290
|
+
$store->set("tls-session:{$tlsSessionId}", $deviceId, 3600); // 1h cache duration
|
|
291
|
+
}
|
|
292
|
+
|
|
278
293
|
return [
|
|
279
294
|
'deviceId' => $deviceId,
|
|
280
295
|
'deviceData' => $deviceData,
|
|
@@ -425,6 +440,9 @@
|
|
|
425
440
|
// NOUVEAU: Score de réputation du sous-réseau IP
|
|
426
441
|
$subnetScore = RequestUtils::getSubnetScore($context, $deviceId);
|
|
427
442
|
|
|
443
|
+
// Score d'anomalie de pile TCP/IP
|
|
444
|
+
$tcpAnomaly = RequestUtils::getTcpAnomalyScore($context);
|
|
445
|
+
|
|
428
446
|
// Assemblage du vecteur de suspicion final
|
|
429
447
|
$suspicionVector = array_merge($suspicionVector, [
|
|
430
448
|
'inconsistencyScore' => $inconsistencyScore,
|
|
@@ -443,6 +461,7 @@
|
|
|
443
461
|
'clientHintsInconsistencyScore' => $clientHintsInconsistency['clientHintsInconsistencyScore'],
|
|
444
462
|
'subnetScore' => $subnetScore['subnetScore'],
|
|
445
463
|
'botnetClusterScore' => $botnetCluster['botnetClusterScore'],
|
|
464
|
+
'tcpAnomalyScore' => $tcpAnomaly['tcpAnomalyScore'],
|
|
446
465
|
]);
|
|
447
466
|
|
|
448
467
|
// Sauvegarder l'état mis à jour de l'appareil dans le store
|
|
@@ -511,6 +530,53 @@
|
|
|
511
530
|
];
|
|
512
531
|
}
|
|
513
532
|
|
|
533
|
+
|
|
534
|
+
private function decodePolymorphicFingerprint(string $fpString, array $mapping): string
|
|
535
|
+
{
|
|
536
|
+
if (empty($mapping['keys'])) {
|
|
537
|
+
return $fpString;
|
|
538
|
+
}
|
|
539
|
+
$reverseKeys = array_flip($mapping['keys']);
|
|
540
|
+
$parts = explode('|', $fpString);
|
|
541
|
+
$mappedParts = [];
|
|
542
|
+
foreach ($parts as $part) {
|
|
543
|
+
$pair = explode(':', $part, 2);
|
|
544
|
+
if (count($pair) === 2) {
|
|
545
|
+
$origKey = $reverseKeys[$pair[0]] ?? $pair[0];
|
|
546
|
+
$mappedParts[] = "{$origKey}:{$pair[1]}";
|
|
547
|
+
} else {
|
|
548
|
+
$mappedParts[] = $part;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return implode('|', $mappedParts);
|
|
552
|
+
}
|
|
553
|
+
private function translatePolymorphicHeaders(RequestContext $context): void
|
|
554
|
+
{
|
|
555
|
+
$store = StoreManager::getStore();
|
|
556
|
+
$activeMappings = $store->get('active-polymorphic-mappings') ?: [];
|
|
557
|
+
if (!is_array($activeMappings)) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
foreach ($activeMappings as $mapping) {
|
|
562
|
+
$devFpHeader = strtolower($mapping['headers']['x-device-fingerprint'] ?? '');
|
|
563
|
+
$behaviorHeader = strtolower($mapping['headers']['x-behavior-metrics'] ?? '');
|
|
564
|
+
|
|
565
|
+
if (!empty($devFpHeader) && isset($context->headers[$devFpHeader])) {
|
|
566
|
+
$context->headers['x-device-fingerprint'] = $context->headers[$devFpHeader];
|
|
567
|
+
if (!empty($behaviorHeader) && isset($context->headers[$behaviorHeader])) {
|
|
568
|
+
$context->headers['x-behavior-metrics'] = $context->headers[$behaviorHeader];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
$clientFp = $context->headers['x-device-fingerprint'];
|
|
572
|
+
if ($clientFp && is_string($clientFp)) {
|
|
573
|
+
$context->headers['x-device-fingerprint'] = $this->decodePolymorphicFingerprint($clientFp, $mapping);
|
|
574
|
+
}
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
514
580
|
/**
|
|
515
581
|
* Traite une requête entrante et retourne une décision.
|
|
516
582
|
* @param RequestContext $context Le contexte de la requête.
|
|
@@ -518,8 +584,21 @@
|
|
|
518
584
|
*/
|
|
519
585
|
public function processRequest(RequestContext $context): array
|
|
520
586
|
{
|
|
587
|
+
$this->translatePolymorphicHeaders($context);
|
|
588
|
+
|
|
521
589
|
// Initialiser le vecteur de suspicion pour éviter les erreurs de type.
|
|
522
590
|
$suspicionVector = [];
|
|
591
|
+
|
|
592
|
+
$this->log('Processing request', ['clientIp' => $context->clientIp, 'path' => $context->path]);
|
|
593
|
+
|
|
594
|
+
// Parse GraphQL query if applicable
|
|
595
|
+
if ($context->path === '/graphql' && !empty($context->body)) {
|
|
596
|
+
$gqlInfo = RequestUtils::parseGraphQLQuery(is_array($context->body) ? $context->body : []);
|
|
597
|
+
if ($gqlInfo) {
|
|
598
|
+
$context->graphqlOperation = $gqlInfo;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
523
602
|
|
|
524
603
|
$this->log('Processing request', ['clientIp' => $context->clientIp, 'path' => $context->path]);
|
|
525
604
|
|
|
@@ -545,6 +624,7 @@
|
|
|
545
624
|
$isChallengeSubmission = $powNonce && (
|
|
546
625
|
isset($context->query['pow_solution']) ||
|
|
547
626
|
isset($context->query['pow_solution_cpu']) ||
|
|
627
|
+
isset($context->query['pow_solution_space']) ||
|
|
548
628
|
(isset($context->query['pow_type']) && $context->query['pow_type'] === 'useful_work_task')
|
|
549
629
|
);
|
|
550
630
|
if ($isChallengeSubmission) {
|
|
@@ -583,6 +663,28 @@
|
|
|
583
663
|
$isValid = $isValid && $isMemValid;
|
|
584
664
|
}
|
|
585
665
|
}
|
|
666
|
+
} elseif ($powType === 'pospace') {
|
|
667
|
+
$powSolutionSpace = $context->query['pow_solution_space'] ?? null;
|
|
668
|
+
if ($powSolutionSpace && isset($challengeContext['queries'])) {
|
|
669
|
+
$isSpaceValid = ChallengeUtils::verifySpacePoW(
|
|
670
|
+
$powNonce,
|
|
671
|
+
$powSolutionSpace,
|
|
672
|
+
$challengeContext['queries'],
|
|
673
|
+
$powNonce . ":" . $challengeContext['clientSecret'],
|
|
674
|
+
$challengeContext['clientSecret']
|
|
675
|
+
);
|
|
676
|
+
$isValid = $isSpaceValid;
|
|
677
|
+
if ($isValid) {
|
|
678
|
+
$ticketTtl = $this->securityConfig['ticketMaxAge'] ?? 3600000;
|
|
679
|
+
$expiry = (int)floor(microtime(true) * 1000) + $ticketTtl;
|
|
680
|
+
$ticket = ChallengeUtils::generateStatelessTicket([
|
|
681
|
+
'expiry' => $expiry,
|
|
682
|
+
'originalIp' => $context->clientIp,
|
|
683
|
+
'deviceId' => '',
|
|
684
|
+
'deviceHash' => ''
|
|
685
|
+
]);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
586
688
|
} elseif ($powType === 'useful_work_task') {
|
|
587
689
|
$problemId = $context->query['pow_problem_id'] ?? null;
|
|
588
690
|
$workResultJson = $context->query['pow_solution_work_result'] ?? null;
|
|
@@ -618,10 +720,6 @@
|
|
|
618
720
|
$this->log('Challenge solution valid - issuing ticket', ['ticketMaxAge' => $ticketTtl]);
|
|
619
721
|
|
|
620
722
|
return [
|
|
621
|
-
// ... (le reste de la logique de redirection)
|
|
622
|
-
'action' => 'redirect',
|
|
623
|
-
'path' => RequestUtils::cleanUrlFromPowParams($challengeContext['originalPath'] ?? '/', $context->query),
|
|
624
|
-
'score' => 0.0,
|
|
625
723
|
'action' => 'redirect',
|
|
626
724
|
'path' => RequestUtils::cleanUrlFromPowParams($challengeContext['originalPath'] ?? '/', $context->query),
|
|
627
725
|
'score' => 0.0,
|
|
@@ -787,7 +885,11 @@
|
|
|
787
885
|
|
|
788
886
|
$nonce = bin2hex(random_bytes(16));
|
|
789
887
|
$clientSecret = bin2hex(random_bytes(16));
|
|
790
|
-
|
|
888
|
+
$highThreshold = $thresholds['high'] ?? 75;
|
|
889
|
+
|
|
890
|
+
$suspicionFactor = ($finalScore - $lowThreshold) / (($thresholds['high'] ?? 75) - $lowThreshold);
|
|
891
|
+
$suspicionFactor = max(0, min(1.5, $suspicionFactor));
|
|
892
|
+
|
|
791
893
|
// --- NOUVELLE LOGIQUE uPoW ---
|
|
792
894
|
$shouldUseUsefulWork = ($this->securityConfig['enableUsefulWork'] ?? false) && (
|
|
793
895
|
($this->securityConfig['forceUsefulWork'] ?? false) || (random_int(0, 255) / 255) > 0.5
|
|
@@ -839,9 +941,39 @@
|
|
|
839
941
|
|
|
840
942
|
// --- FIN DE LA LOGIQUE uPoW (le reste est le fallback) ---
|
|
841
943
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
944
|
+
if ($this->securityConfig['enableProofOfSpace'] ?? false) {
|
|
945
|
+
$spaceChallenge = ChallengeUtils::generateSpaceChallenge($context->clientIp, $nonce, $suspicionFactor, $context->path, $this->securityConfig);
|
|
946
|
+
$store->set("secret:{$nonce}", [
|
|
947
|
+
'clientSecret' => $clientSecret,
|
|
948
|
+
'suspicionScore' => $finalScore,
|
|
949
|
+
'queries' => $spaceChallenge['queries'],
|
|
950
|
+
'sizeMb' => $spaceChallenge['sizeMb'],
|
|
951
|
+
'fingerprint' => RequestUtils::getCompositeDeviceHash($context),
|
|
952
|
+
'originalPath' => $context->path,
|
|
953
|
+
], $this->securityConfig['challengeTtl'] ?? 300);
|
|
954
|
+
|
|
955
|
+
if ($deviceData) {
|
|
956
|
+
$deviceData['lastChallengeNonce'] = $nonce;
|
|
957
|
+
$store->set("device:{$deviceId}", $deviceData); // @phpstan-ignore-line
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if ($isApiRequest) {
|
|
961
|
+
$decision['body'] = [
|
|
962
|
+
'challenge' => [
|
|
963
|
+
'type' => 'pospace',
|
|
964
|
+
'nonce' => $nonce,
|
|
965
|
+
'clientSecret' => $clientSecret,
|
|
966
|
+
'queries' => $spaceChallenge['queries'],
|
|
967
|
+
'sizeMb' => $spaceChallenge['sizeMb'],
|
|
968
|
+
]
|
|
969
|
+
];
|
|
970
|
+
} else {
|
|
971
|
+
$page = ChallengeUtils::generateSpaceChallengePage($spaceChallenge, $clientSecret, $this->securityConfig);
|
|
972
|
+
$decision['body'] = $page;
|
|
973
|
+
}
|
|
974
|
+
return $decision;
|
|
975
|
+
}
|
|
976
|
+
|
|
845
977
|
$cpuChallengeDetails = [
|
|
846
978
|
'type' => 'cpu_target',
|
|
847
979
|
'nonce' => $nonce,
|