@anonympins/fingerprint 0.3.7 → 0.4.0
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 +331 -244
- package/README.md +63 -1201
- package/composer.json +38 -38
- package/index.js +4 -4
- package/package.json +103 -103
- package/phpunit.xml +20 -20
- package/public/fp.js +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +1 -1
- package/src/js/fingerprint.client.js +2 -0
- package/src/js/fingerprint.js +4429 -4220
- package/src/js/mongodb-store.js +79 -79
- package/src/js/pow.solver.inline.js +31 -0
- package/src/js/pow.solver.js +31 -0
- package/src/js/tests/fingerprint.builder.test.js +79 -0
- package/src/js/tests/fingerprint.client.init.test.js +120 -0
- package/src/js/tests/fingerprint.client.test.js +105 -0
- package/src/js/tests/fingerprint.engine.test.js +371 -0
- package/src/js/tests/fingerprint.isMalicious.test.js +117 -0
- package/src/js/tests/fingerprint.test.js +2319 -0
- package/src/js/tests/ip-reputation.test.js +132 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -0
- package/src/js/tests/library.test.js +96 -0
- package/src/js/tests/metrics.test.js +104 -0
- package/src/js/tests/pow.solver.test.js +198 -0
- package/src/js/tests/problem-manager.test.js +323 -0
- package/src/js/tests/stores.test.js +118 -0
- package/src/php/Challenge/ChallengeUtils.php +361 -305
- package/src/php/Config/SecurityProfiles.php +271 -266
- package/src/php/FingerprintBuilder.php +185 -185
- package/src/php/FingerprintClient.php +131 -131
- package/src/php/FingerprintEngine.php +1006 -1006
- package/src/php/Ja3AnomalyDetector.php +227 -227
- package/src/php/Optimization/FunctionRegistry.php +62 -62
- package/src/php/Optimization/Optimization.php +255 -255
- package/src/php/Optimization/OptimizationOperators.php +304 -304
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +104 -104
- package/src/php/Store/RedisStore.php +53 -53
- package/src/php/Tests/ChallengeUtilsTest.php +81 -81
- package/src/php/Tests/FingerprintBuilderTest.php +57 -57
- package/src/php/Tests/FingerprintClientTest.php +71 -0
- package/src/php/Tests/FingerprintEngineTest.php +299 -299
- package/src/php/Tests/IpReputationTest.php +156 -156
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +179 -179
- package/src/php/Tests/MetricsTest.php +45 -45
- package/src/php/Tests/PowTest.php +39 -39
- package/src/php/Tests/ProblemManagerTest.php +296 -296
- package/src/php/Tests/RequestUtilsTest.php +253 -145
- package/src/php/Tests/TLSClientHelloParserTest.php +118 -0
- package/src/php/Tests/problems.config.json +8 -8
- package/src/php/Utils/BigInt.php +144 -144
- package/src/php/Utils/Logger.php +29 -29
- package/src/php/Utils/MaliciousPatterns.php +58 -58
- package/src/php/Utils/MetricsManager.php +166 -166
- package/src/php/Utils/RequestUtils.php +31 -4
- package/src/php/Utils/TLSClientHelloParser.php +117 -0
- package/src/php/bin/auto-tune.php +117 -117
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
-
|
|
7
|
-
class MetricsManager
|
|
8
|
-
{
|
|
9
|
-
/** @var array Stockage temporaire des compteurs Prometheus */
|
|
10
|
-
private static array $counters = [];
|
|
11
|
-
|
|
12
|
-
/** @var array Stockage temporaire des observations Prometheus */
|
|
13
|
-
private static array $observations = [];
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Incrémente un compteur Prometheus.
|
|
17
|
-
*
|
|
18
|
-
* @param string $name Nom du compteur.
|
|
19
|
-
* @param array $labels Libellés/Labels associés.
|
|
20
|
-
*/
|
|
21
|
-
public static function incrementCounter(string $name, array $labels = []): void
|
|
22
|
-
{
|
|
23
|
-
if (strpos($name, 'fingerprint_') !== 0) {
|
|
24
|
-
$name = 'fingerprint_' . $name;
|
|
25
|
-
}
|
|
26
|
-
ksort($labels);
|
|
27
|
-
$labelPairs = [];
|
|
28
|
-
foreach ($labels as $k => $v) {
|
|
29
|
-
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
30
|
-
}
|
|
31
|
-
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
32
|
-
$key = $name . $labelsStr;
|
|
33
|
-
|
|
34
|
-
if (!isset(self::$counters[$key])) {
|
|
35
|
-
self::$counters[$key] = [
|
|
36
|
-
'name' => $name,
|
|
37
|
-
'labelsStr' => $labelsStr,
|
|
38
|
-
'value' => 0
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
self::$counters[$key]['value']++;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Enregistre une observation de valeur (ex: temps d'exécution, score).
|
|
46
|
-
*
|
|
47
|
-
* @param string $name Nom de la métrique.
|
|
48
|
-
* @param float $value Valeur observée.
|
|
49
|
-
* @param array $labels Libellés/Labels associés.
|
|
50
|
-
*/
|
|
51
|
-
public static function observeValue(string $name, float $value, array $labels = []): void
|
|
52
|
-
{
|
|
53
|
-
if (strpos($name, 'fingerprint_') !== 0) {
|
|
54
|
-
$name = 'fingerprint_' . $name;
|
|
55
|
-
}
|
|
56
|
-
ksort($labels);
|
|
57
|
-
$labelPairs = [];
|
|
58
|
-
foreach ($labels as $k => $v) {
|
|
59
|
-
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
60
|
-
}
|
|
61
|
-
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
62
|
-
$key = $name . $labelsStr;
|
|
63
|
-
|
|
64
|
-
self::$observations[$key] = [
|
|
65
|
-
'name' => $name,
|
|
66
|
-
'labelsStr' => $labelsStr,
|
|
67
|
-
'value' => $value
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Réinitialise les compteurs enregistrés (utile pour l'isolation des tests).
|
|
73
|
-
*/
|
|
74
|
-
public static function clearMetrics(): void
|
|
75
|
-
{
|
|
76
|
-
self::$counters = [];
|
|
77
|
-
self::$observations = [];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Génère les métriques au format Prometheus text/plain.
|
|
82
|
-
*
|
|
83
|
-
* @param array $securityConfig La configuration de sécurité active.
|
|
84
|
-
* @param array|null $lastBestSolution La dernière solution calculée par l'Auto-Tuner.
|
|
85
|
-
* @return string
|
|
86
|
-
*/
|
|
87
|
-
public static function getPrometheusMetrics(array $securityConfig = [], ?array $lastBestSolution = null): string
|
|
88
|
-
{
|
|
89
|
-
$metrics = "";
|
|
90
|
-
|
|
91
|
-
if (empty(self::$counters)) {
|
|
92
|
-
$metrics .= "# HELP fingerprint_requests_total Total requests processed.\n";
|
|
93
|
-
$metrics .= "# TYPE fingerprint_requests_total counter\n";
|
|
94
|
-
$metrics .= "fingerprint_requests_total{status=\"passed\"} 1\n";
|
|
95
|
-
} else {
|
|
96
|
-
$grouped = [];
|
|
97
|
-
foreach (self::$counters as $c) {
|
|
98
|
-
$grouped[$c['name']][] = $c;
|
|
99
|
-
}
|
|
100
|
-
foreach ($grouped as $name => $instances) {
|
|
101
|
-
$metrics .= "# HELP {$name} Total requests processed.\n";
|
|
102
|
-
$metrics .= "# TYPE {$name} counter\n";
|
|
103
|
-
foreach ($instances as $instance) {
|
|
104
|
-
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Export des observations (Gauges)
|
|
110
|
-
if (!empty(self::$observations)) {
|
|
111
|
-
$groupedObs = [];
|
|
112
|
-
foreach (self::$observations as $obs) {
|
|
113
|
-
$groupedObs[$obs['name']][] = $obs;
|
|
114
|
-
}
|
|
115
|
-
foreach ($groupedObs as $name => $instances) {
|
|
116
|
-
$metrics .= "\n# HELP {$name} Value observation.\n";
|
|
117
|
-
$metrics .= "# TYPE {$name} gauge\n";
|
|
118
|
-
foreach ($instances as $instance) {
|
|
119
|
-
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// 1. Export des poids actifs (Weights)
|
|
125
|
-
if (isset($securityConfig['weights']) && is_array($securityConfig['weights'])) {
|
|
126
|
-
$metrics .= "\n# HELP fingerprint_security_weight Active weight for each suspicion indicator.\n";
|
|
127
|
-
$metrics .= "# TYPE fingerprint_security_weight gauge\n";
|
|
128
|
-
foreach ($securityConfig['weights'] as $indicator => $weight) {
|
|
129
|
-
if (is_numeric($weight)) {
|
|
130
|
-
$metrics .= "fingerprint_security_weight{indicator=\"{$indicator}\"} {$weight}\n";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 2. Export des seuils actifs (Thresholds)
|
|
136
|
-
if (isset($securityConfig['thresholds']) && is_array($securityConfig['thresholds'])) {
|
|
137
|
-
$metrics .= "\n# HELP fingerprint_security_threshold Active score threshold for each enforcement action level.\n";
|
|
138
|
-
$metrics .= "# TYPE fingerprint_security_threshold gauge\n";
|
|
139
|
-
foreach ($securityConfig['thresholds'] as $level => $threshold) {
|
|
140
|
-
if (is_numeric($threshold)) {
|
|
141
|
-
$metrics .= "fingerprint_security_threshold{level=\"{$level}\"} {$threshold}\n";
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 3. Récupération auto de la dernière solution d'auto-tuning depuis le cache (savePath) si non fournie
|
|
147
|
-
if ($lastBestSolution === null && isset($securityConfig['autotuning']['savePath'])) {
|
|
148
|
-
$savePath = $securityConfig['autotuning']['savePath'];
|
|
149
|
-
if (file_exists($savePath)) {
|
|
150
|
-
$savedData = json_decode(file_get_contents($savePath), true);
|
|
151
|
-
if (is_array($savedData) && isset($savedData['objectives'])) {
|
|
152
|
-
$lastBestSolution = $savedData;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 4. Export des objectifs d'Auto-Tuning (Faux positifs & Faux négatifs calculés)
|
|
158
|
-
if ($lastBestSolution !== null && isset($lastBestSolution['objectives']) && is_array($lastBestSolution['objectives'])) {
|
|
159
|
-
$fpr = $lastBestSolution['objectives'][0] ?? 0.0;
|
|
160
|
-
$fnr = $lastBestSolution['objectives'][1] ?? 0.0;
|
|
161
|
-
$metrics .= "\n# HELP fingerprint_autotuning_false_positive_rate Current false positive rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_positive_rate gauge\nfingerprint_autotuning_false_positive_rate {$fpr}\n";
|
|
162
|
-
$metrics .= "\n# HELP fingerprint_autotuning_false_negative_rate Current false negative rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_negative_rate gauge\nfingerprint_autotuning_false_negative_rate {$fnr}\n";
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return $metrics;
|
|
166
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
class MetricsManager
|
|
8
|
+
{
|
|
9
|
+
/** @var array Stockage temporaire des compteurs Prometheus */
|
|
10
|
+
private static array $counters = [];
|
|
11
|
+
|
|
12
|
+
/** @var array Stockage temporaire des observations Prometheus */
|
|
13
|
+
private static array $observations = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Incrémente un compteur Prometheus.
|
|
17
|
+
*
|
|
18
|
+
* @param string $name Nom du compteur.
|
|
19
|
+
* @param array $labels Libellés/Labels associés.
|
|
20
|
+
*/
|
|
21
|
+
public static function incrementCounter(string $name, array $labels = []): void
|
|
22
|
+
{
|
|
23
|
+
if (strpos($name, 'fingerprint_') !== 0) {
|
|
24
|
+
$name = 'fingerprint_' . $name;
|
|
25
|
+
}
|
|
26
|
+
ksort($labels);
|
|
27
|
+
$labelPairs = [];
|
|
28
|
+
foreach ($labels as $k => $v) {
|
|
29
|
+
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
30
|
+
}
|
|
31
|
+
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
32
|
+
$key = $name . $labelsStr;
|
|
33
|
+
|
|
34
|
+
if (!isset(self::$counters[$key])) {
|
|
35
|
+
self::$counters[$key] = [
|
|
36
|
+
'name' => $name,
|
|
37
|
+
'labelsStr' => $labelsStr,
|
|
38
|
+
'value' => 0
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
self::$counters[$key]['value']++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Enregistre une observation de valeur (ex: temps d'exécution, score).
|
|
46
|
+
*
|
|
47
|
+
* @param string $name Nom de la métrique.
|
|
48
|
+
* @param float $value Valeur observée.
|
|
49
|
+
* @param array $labels Libellés/Labels associés.
|
|
50
|
+
*/
|
|
51
|
+
public static function observeValue(string $name, float $value, array $labels = []): void
|
|
52
|
+
{
|
|
53
|
+
if (strpos($name, 'fingerprint_') !== 0) {
|
|
54
|
+
$name = 'fingerprint_' . $name;
|
|
55
|
+
}
|
|
56
|
+
ksort($labels);
|
|
57
|
+
$labelPairs = [];
|
|
58
|
+
foreach ($labels as $k => $v) {
|
|
59
|
+
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
60
|
+
}
|
|
61
|
+
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
62
|
+
$key = $name . $labelsStr;
|
|
63
|
+
|
|
64
|
+
self::$observations[$key] = [
|
|
65
|
+
'name' => $name,
|
|
66
|
+
'labelsStr' => $labelsStr,
|
|
67
|
+
'value' => $value
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Réinitialise les compteurs enregistrés (utile pour l'isolation des tests).
|
|
73
|
+
*/
|
|
74
|
+
public static function clearMetrics(): void
|
|
75
|
+
{
|
|
76
|
+
self::$counters = [];
|
|
77
|
+
self::$observations = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Génère les métriques au format Prometheus text/plain.
|
|
82
|
+
*
|
|
83
|
+
* @param array $securityConfig La configuration de sécurité active.
|
|
84
|
+
* @param array|null $lastBestSolution La dernière solution calculée par l'Auto-Tuner.
|
|
85
|
+
* @return string
|
|
86
|
+
*/
|
|
87
|
+
public static function getPrometheusMetrics(array $securityConfig = [], ?array $lastBestSolution = null): string
|
|
88
|
+
{
|
|
89
|
+
$metrics = "";
|
|
90
|
+
|
|
91
|
+
if (empty(self::$counters)) {
|
|
92
|
+
$metrics .= "# HELP fingerprint_requests_total Total requests processed.\n";
|
|
93
|
+
$metrics .= "# TYPE fingerprint_requests_total counter\n";
|
|
94
|
+
$metrics .= "fingerprint_requests_total{status=\"passed\"} 1\n";
|
|
95
|
+
} else {
|
|
96
|
+
$grouped = [];
|
|
97
|
+
foreach (self::$counters as $c) {
|
|
98
|
+
$grouped[$c['name']][] = $c;
|
|
99
|
+
}
|
|
100
|
+
foreach ($grouped as $name => $instances) {
|
|
101
|
+
$metrics .= "# HELP {$name} Total requests processed.\n";
|
|
102
|
+
$metrics .= "# TYPE {$name} counter\n";
|
|
103
|
+
foreach ($instances as $instance) {
|
|
104
|
+
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Export des observations (Gauges)
|
|
110
|
+
if (!empty(self::$observations)) {
|
|
111
|
+
$groupedObs = [];
|
|
112
|
+
foreach (self::$observations as $obs) {
|
|
113
|
+
$groupedObs[$obs['name']][] = $obs;
|
|
114
|
+
}
|
|
115
|
+
foreach ($groupedObs as $name => $instances) {
|
|
116
|
+
$metrics .= "\n# HELP {$name} Value observation.\n";
|
|
117
|
+
$metrics .= "# TYPE {$name} gauge\n";
|
|
118
|
+
foreach ($instances as $instance) {
|
|
119
|
+
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 1. Export des poids actifs (Weights)
|
|
125
|
+
if (isset($securityConfig['weights']) && is_array($securityConfig['weights'])) {
|
|
126
|
+
$metrics .= "\n# HELP fingerprint_security_weight Active weight for each suspicion indicator.\n";
|
|
127
|
+
$metrics .= "# TYPE fingerprint_security_weight gauge\n";
|
|
128
|
+
foreach ($securityConfig['weights'] as $indicator => $weight) {
|
|
129
|
+
if (is_numeric($weight)) {
|
|
130
|
+
$metrics .= "fingerprint_security_weight{indicator=\"{$indicator}\"} {$weight}\n";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2. Export des seuils actifs (Thresholds)
|
|
136
|
+
if (isset($securityConfig['thresholds']) && is_array($securityConfig['thresholds'])) {
|
|
137
|
+
$metrics .= "\n# HELP fingerprint_security_threshold Active score threshold for each enforcement action level.\n";
|
|
138
|
+
$metrics .= "# TYPE fingerprint_security_threshold gauge\n";
|
|
139
|
+
foreach ($securityConfig['thresholds'] as $level => $threshold) {
|
|
140
|
+
if (is_numeric($threshold)) {
|
|
141
|
+
$metrics .= "fingerprint_security_threshold{level=\"{$level}\"} {$threshold}\n";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 3. Récupération auto de la dernière solution d'auto-tuning depuis le cache (savePath) si non fournie
|
|
147
|
+
if ($lastBestSolution === null && isset($securityConfig['autotuning']['savePath'])) {
|
|
148
|
+
$savePath = $securityConfig['autotuning']['savePath'];
|
|
149
|
+
if (file_exists($savePath)) {
|
|
150
|
+
$savedData = json_decode(file_get_contents($savePath), true);
|
|
151
|
+
if (is_array($savedData) && isset($savedData['objectives'])) {
|
|
152
|
+
$lastBestSolution = $savedData;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 4. Export des objectifs d'Auto-Tuning (Faux positifs & Faux négatifs calculés)
|
|
158
|
+
if ($lastBestSolution !== null && isset($lastBestSolution['objectives']) && is_array($lastBestSolution['objectives'])) {
|
|
159
|
+
$fpr = $lastBestSolution['objectives'][0] ?? 0.0;
|
|
160
|
+
$fnr = $lastBestSolution['objectives'][1] ?? 0.0;
|
|
161
|
+
$metrics .= "\n# HELP fingerprint_autotuning_false_positive_rate Current false positive rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_positive_rate gauge\nfingerprint_autotuning_false_positive_rate {$fpr}\n";
|
|
162
|
+
$metrics .= "\n# HELP fingerprint_autotuning_false_negative_rate Current false negative rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_negative_rate gauge\nfingerprint_autotuning_false_negative_rate {$fnr}\n";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return $metrics;
|
|
166
|
+
}
|
|
167
167
|
}
|
|
@@ -906,10 +906,20 @@ class RequestUtils
|
|
|
906
906
|
$subnetData = $store->get($key) ?? [
|
|
907
907
|
'highScoreCount' => 0,
|
|
908
908
|
'deviceIds' => [],
|
|
909
|
+
'highScoreDevices' => [],
|
|
909
910
|
'lastActivity' => 0
|
|
910
911
|
];
|
|
911
912
|
|
|
912
|
-
$subnetData['
|
|
913
|
+
if (!isset($subnetData['highScoreDevices'])) {
|
|
914
|
+
$subnetData['highScoreDevices'] = [];
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
$currentDeviceContributions = $subnetData['highScoreDevices'][$deviceId] ?? 0;
|
|
918
|
+
if ($currentDeviceContributions < 5 && $finalScore < 95) {
|
|
919
|
+
$subnetData['highScoreDevices'][$deviceId] = $currentDeviceContributions + 1;
|
|
920
|
+
$subnetData['highScoreCount']++;
|
|
921
|
+
}
|
|
922
|
+
|
|
913
923
|
if (!in_array($deviceId, $subnetData['deviceIds'])) {
|
|
914
924
|
$subnetData['deviceIds'][] = $deviceId;
|
|
915
925
|
}
|
|
@@ -917,7 +927,12 @@ class RequestUtils
|
|
|
917
927
|
|
|
918
928
|
// Limiter la taille du tableau des deviceIds pour éviter une consommation mémoire excessive.
|
|
919
929
|
if (count($subnetData['deviceIds']) > 100) {
|
|
920
|
-
array_shift($subnetData['deviceIds']);
|
|
930
|
+
$oldDeviceId = array_shift($subnetData['deviceIds']);
|
|
931
|
+
if (isset($subnetData['highScoreDevices'][$oldDeviceId])) {
|
|
932
|
+
$oldContributions = $subnetData['highScoreDevices'][$oldDeviceId];
|
|
933
|
+
$subnetData['highScoreCount'] = max(0, $subnetData['highScoreCount'] - $oldContributions);
|
|
934
|
+
unset($subnetData['highScoreDevices'][$oldDeviceId]);
|
|
935
|
+
}
|
|
921
936
|
}
|
|
922
937
|
|
|
923
938
|
// TTL de 24 heures pour les données de sous-réseau.
|
|
@@ -945,16 +960,28 @@ class RequestUtils
|
|
|
945
960
|
return ['subnetScore' => 0.0];
|
|
946
961
|
}
|
|
947
962
|
|
|
963
|
+
// Application d'une décroissance temporelle (demi-vie de 30 minutes soit 1800 secondes)
|
|
964
|
+
$now = time();
|
|
965
|
+
$inactivitySec = $now - ($subnetData['lastActivity'] ?? $now);
|
|
966
|
+
$halfLives = (int)floor($inactivitySec / 1800);
|
|
967
|
+
|
|
968
|
+
$highScoreCount = $subnetData['highScoreCount'] ?? 0;
|
|
969
|
+
$deviceCount = isset($subnetData['deviceIds']) ? count($subnetData['deviceIds']) : 0;
|
|
970
|
+
|
|
971
|
+
if ($halfLives > 0) {
|
|
972
|
+
$highScoreCount = max(0, (int)floor($highScoreCount / pow(2, $halfLives)));
|
|
973
|
+
$deviceCount = max(0, (int)floor($deviceCount / pow(2, $halfLives)));
|
|
974
|
+
}
|
|
975
|
+
|
|
948
976
|
$score = 0.0;
|
|
949
977
|
|
|
950
978
|
// Pénalité basée sur le nombre de devices uniques vus depuis ce sous-réseau.
|
|
951
|
-
$deviceCount = count($subnetData['deviceIds']);
|
|
952
979
|
if ($deviceCount > 10) {
|
|
953
980
|
$score += min(80.0, ($deviceCount - 10) * 5);
|
|
954
981
|
}
|
|
955
982
|
|
|
956
983
|
// Pénalité basée sur le nombre de scores élevés enregistrés.
|
|
957
|
-
$score += min(40.0, $
|
|
984
|
+
$score += min(40.0, $highScoreCount * 2);
|
|
958
985
|
|
|
959
986
|
return ['subnetScore' => min(100.0, $score)];
|
|
960
987
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Décode nativement un paquet TLS Client Hello binaire pour calculer l'empreinte JA3/JA4.
|
|
9
|
+
*/
|
|
10
|
+
class TLSClientHelloParser
|
|
11
|
+
{
|
|
12
|
+
private const GREASE_VALUES = [
|
|
13
|
+
2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
|
|
14
|
+
35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse le Client Hello brut et retourne l'empreinte JA3 et une approximation JA4.
|
|
19
|
+
*
|
|
20
|
+
* @param string $binary Le premier paquet TCP reçu sur la socket.
|
|
21
|
+
* @return array{ja3_string: string, ja3_hash: string, ja4_raw: string}|null
|
|
22
|
+
*/
|
|
23
|
+
public static function parse(string $binary): ?array
|
|
24
|
+
{
|
|
25
|
+
$len = strlen($binary);
|
|
26
|
+
if ($len < 43) {
|
|
27
|
+
return null; // Paquet trop court
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 1. Vérification du Record Layer Type (0x16 = Handshake)
|
|
31
|
+
if (ord($binary[0]) !== 0x16) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Vérification du Handshake Type (0x01 = Client Hello)
|
|
36
|
+
if (ord($binary[5]) !== 0x01) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
$offset = 43; // Sauter l'en-tête, la version et le Random Client (32 octets)
|
|
41
|
+
if ($len < $offset + 1) return null;
|
|
42
|
+
|
|
43
|
+
// 3. Lecture du Session ID
|
|
44
|
+
$sessionLen = ord($binary[$offset]);
|
|
45
|
+
$offset += 1 + $sessionLen;
|
|
46
|
+
if ($len < $offset + 2) return null;
|
|
47
|
+
|
|
48
|
+
// 4. Lecture des Cipher Suites
|
|
49
|
+
$ciphersLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
50
|
+
$offset += 2;
|
|
51
|
+
if ($len < $offset + $ciphersLen + 1) return null;
|
|
52
|
+
|
|
53
|
+
$ciphers = [];
|
|
54
|
+
for ($i = 0; $i < $ciphersLen; $i += 2) {
|
|
55
|
+
$ciphers[] = unpack('n', substr($binary, $offset + $i, 2))[1];
|
|
56
|
+
}
|
|
57
|
+
$offset += $ciphersLen;
|
|
58
|
+
|
|
59
|
+
// 5. Lecture des Compression Methods
|
|
60
|
+
$compressionLen = ord($binary[$offset]);
|
|
61
|
+
$offset += 1 + $compressionLen;
|
|
62
|
+
if ($len < $offset + 2) return null;
|
|
63
|
+
|
|
64
|
+
// 6. Lecture des Extensions
|
|
65
|
+
$extensionsLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
66
|
+
$offset += 2;
|
|
67
|
+
|
|
68
|
+
$extensions = [];
|
|
69
|
+
$curves = [];
|
|
70
|
+
$points = [];
|
|
71
|
+
|
|
72
|
+
$extLimit = $offset + $extensionsLen;
|
|
73
|
+
while ($offset < $extLimit && $offset + 4 <= $len) {
|
|
74
|
+
$extType = unpack('n', substr($binary, $offset, 2))[1];
|
|
75
|
+
$extLen = unpack('n', substr($binary, $offset + 2, 2))[1];
|
|
76
|
+
$offset += 4;
|
|
77
|
+
|
|
78
|
+
if ($offset + $extLen > $len) break;
|
|
79
|
+
|
|
80
|
+
$extensions[] = $extType;
|
|
81
|
+
|
|
82
|
+
if ($extType === 10) { // Extension Supported Groups (Elliptic Curves)
|
|
83
|
+
if ($extLen >= 2) {
|
|
84
|
+
$curvesLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
85
|
+
for ($j = 2; $j < $curvesLen + 2; $j += 2) {
|
|
86
|
+
$curves[] = unpack('n', substr($binary, $offset + $j, 2))[1];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} elseif ($extType === 11) { // Extension EC Point Formats
|
|
90
|
+
if ($extLen >= 1) {
|
|
91
|
+
$pointsLen = ord($binary[$offset]);
|
|
92
|
+
for ($j = 1; $j < $pointsLen + 1; $j++) {
|
|
93
|
+
$points[] = ord($binary[$offset + $j]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
$offset += $extLen;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Nettoyage des valeurs GREASE (RFC 8701) pour la conformité JA3
|
|
101
|
+
$filterGrease = fn(array $arr) => array_values(array_filter($arr, fn($v) => !in_array($v, self::GREASE_VALUES, true)));
|
|
102
|
+
|
|
103
|
+
$sslVersion = unpack('n', substr($binary, 9, 2))[1];
|
|
104
|
+
$ja3String = implode(',', [
|
|
105
|
+
$sslVersion,
|
|
106
|
+
implode('-', $filterGrease($ciphers)),
|
|
107
|
+
implode('-', $filterGrease($extensions)),
|
|
108
|
+
implode('-', $filterGrease($curves)),
|
|
109
|
+
implode('-', $filterGrease($points))
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
'ja3_string' => $ja3String,
|
|
114
|
+
'ja3_hash' => md5($ja3String)
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
}
|