@anonympins/fingerprint 0.6.4 → 0.6.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 +11 -0
- package/package.json +1 -1
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +76 -6
- package/src/js/gpu_pow.solver.js +34 -3
- package/src/js/library.js +188 -97
- package/src/js/tests/gpu_pow.test.js +9 -7
- package/src/php/AutoTuner.php +41 -0
- package/src/php/Challenge/ChallengeUtils.php +38 -4
- package/src/php/FingerprintEngine.php +13 -2
- package/src/php/Optimization/OptimizationOperators.php +96 -28
- package/src/php/Tests/ChallengeUtilsTest.php +6 -2
package/src/php/AutoTuner.php
CHANGED
|
@@ -53,6 +53,47 @@ class AutoTuner
|
|
|
53
53
|
$this->onCleanup = $options['onCleanup'] ?? null;
|
|
54
54
|
$this->savePath = $options['savePath'] ?? null;
|
|
55
55
|
$this->validationTolerance = $options['validationTolerance'] ?? 0.15;
|
|
56
|
+
|
|
57
|
+
if (isset($this->securityConfig['logger']) && is_callable($this->securityConfig['logger']) && !isset($this->securityConfig['logger_wrapped'])) {
|
|
58
|
+
$originalLogger = $this->securityConfig['logger'];
|
|
59
|
+
$maxPercentage = $this->securityConfig['autotuning']['maxDensityPercentage'] ?? 0.02;
|
|
60
|
+
|
|
61
|
+
$this->securityConfig['logger'] = function (array $log) use ($originalLogger, $maxPercentage) {
|
|
62
|
+
$ip = $log['clientIp'] ?? $log['ip'] ?? null;
|
|
63
|
+
$subnet = $ip ? RequestUtils::getIpSubnet($ip, 24, 48) : null;
|
|
64
|
+
$fp = $log['deviceHash'] ?? $log['fingerprint'] ?? $log['deviceFingerprint'] ?? null;
|
|
65
|
+
$stableFp = $fp ? RequestUtils::extractStablePart($fp) : null;
|
|
66
|
+
|
|
67
|
+
if (count($this->trafficData) > 0) {
|
|
68
|
+
$total = count($this->trafficData);
|
|
69
|
+
$ipMatchCount = 0;
|
|
70
|
+
$fpMatchCount = 0;
|
|
71
|
+
$matchedKey = null;
|
|
72
|
+
|
|
73
|
+
foreach ($this->trafficData as $key => $existingLog) {
|
|
74
|
+
$logIp = $existingLog['clientIp'] ?? $existingLog['ip'] ?? null;
|
|
75
|
+
$logSubnet = $logIp ? RequestUtils::getIpSubnet($logIp, 24, 48) : null;
|
|
76
|
+
$logFp = $existingLog['deviceHash'] ?? $existingLog['fingerprint'] ?? $existingLog['deviceFingerprint'] ?? null;
|
|
77
|
+
$logStableFp = $logFp ? RequestUtils::extractStablePart($logFp) : null;
|
|
78
|
+
|
|
79
|
+
$isIpMatch = $subnet && $logSubnet === $subnet;
|
|
80
|
+
$isFpMatch = $stableFp && $logStableFp === $stableFp;
|
|
81
|
+
if ($isIpMatch) $ipMatchCount++;
|
|
82
|
+
if ($isFpMatch) $fpMatchCount++;
|
|
83
|
+
if ($isIpMatch || $isFpMatch) $matchedKey = $key;
|
|
84
|
+
}
|
|
85
|
+
if (($subnet && ($ipMatchCount / $total) > $maxPercentage) || ($stableFp && ($fpMatchCount / $total) > $maxPercentage)) {
|
|
86
|
+
if ($matchedKey !== null) {
|
|
87
|
+
$this->trafficData[$matchedKey]['instancesCount'] = ($this->trafficData[$matchedKey]['instancesCount'] ?? 1) + 1;
|
|
88
|
+
$this->trafficData[$matchedKey]['weight'] = ($this->trafficData[$matchedKey]['weight'] ?? 1.0) + 1.0;
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
$originalLogger($log);
|
|
94
|
+
};
|
|
95
|
+
$this->securityConfig['logger_wrapped'] = true;
|
|
96
|
+
}
|
|
56
97
|
}
|
|
57
98
|
|
|
58
99
|
/**
|
|
@@ -32,12 +32,33 @@ class ChallengeUtils
|
|
|
32
32
|
return abs($hash % 1000000) / 1000000;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
public static function
|
|
35
|
+
public static function deriveSampleIndices(string $clientIp, string $secret): array
|
|
36
|
+
{
|
|
37
|
+
$timeWindow = (int)floor(time() / (60 * 5));
|
|
38
|
+
$message = "{$clientIp}:{$timeWindow}";
|
|
39
|
+
$hmacHex = hash_hmac('sha256', $message, $secret ?: 'gpu-pow-salt');
|
|
40
|
+
$hmacBytes = array_values(unpack('C*', hex2bin($hmacHex)));
|
|
41
|
+
|
|
42
|
+
$indices = [];
|
|
43
|
+
for ($i = 0; $i < 4; $i++) {
|
|
44
|
+
$offset = ($i * 2) % count($hmacBytes);
|
|
45
|
+
$value = ($hmacBytes[$offset] << 8) | $hmacBytes[($offset + 1) % count($hmacBytes)];
|
|
46
|
+
$idx = $value % 64;
|
|
47
|
+
while (in_array($idx, $indices, true)) {
|
|
48
|
+
$idx = ($idx + 1) % 64;
|
|
49
|
+
}
|
|
50
|
+
$indices[] = $idx;
|
|
51
|
+
}
|
|
52
|
+
return $indices;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public static function verifyGpuPow(string $seed, int $iterations, string $solution, string $clientIp = '127.0.0.1', string $secret = 'gpu-pow-salt'): bool
|
|
36
56
|
{
|
|
37
57
|
$values = explode(',', $solution);
|
|
38
58
|
if (count($values) !== 64) {
|
|
39
59
|
return false;
|
|
40
60
|
}
|
|
61
|
+
$sampleIndices = self::deriveSampleIndices($clientIp, $secret);
|
|
41
62
|
$numericSeed = self::hashSeedToFloat($seed);
|
|
42
63
|
$r = 3.9999;
|
|
43
64
|
foreach ($sampleIndices as $idx) {
|
|
@@ -203,9 +224,22 @@ class ChallengeUtils
|
|
|
203
224
|
return ['error' => 'Missing signature'];
|
|
204
225
|
}
|
|
205
226
|
|
|
206
|
-
|
|
207
|
-
$store->
|
|
208
|
-
|
|
227
|
+
$peersKey = "fed-peers:{$zkpY}";
|
|
228
|
+
$reportedPeers = $store->get($peersKey) ?: [];
|
|
229
|
+
if (!is_array($reportedPeers)) {
|
|
230
|
+
$reportedPeers = [];
|
|
231
|
+
}
|
|
232
|
+
if (!in_array($clientIp, $reportedPeers, true)) {
|
|
233
|
+
$reportedPeers[] = $clientIp;
|
|
234
|
+
$store->set($peersKey, $reportedPeers, 86400 * 30);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
$threshold = $config['federationConsensusThreshold'] ?? 3;
|
|
238
|
+
if (count($reportedPeers) >= $threshold) {
|
|
239
|
+
$store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
|
|
240
|
+
return ['status' => 'synchronized', 'banned' => true];
|
|
241
|
+
}
|
|
242
|
+
return ['status' => 'synchronized', 'banned' => false, 'reportsCount' => count($reportedPeers)];
|
|
209
243
|
}
|
|
210
244
|
|
|
211
245
|
$nodeId = $params['node_id'] ?? '';
|
|
@@ -1249,10 +1249,21 @@
|
|
|
1249
1249
|
// 1. Valider cryptographiquement la preuve avant de bannir/diffuser
|
|
1250
1250
|
if (ChallengeUtils::verifyZkpProof($zkpY, $zkpT, $zkpS)) {
|
|
1251
1251
|
// 2. Dédoublonner : Ne diffuser que si la clé n'est pas déjà bannie
|
|
1252
|
-
|
|
1252
|
+
$peersKey = "fed-peers:{$zkpY}";
|
|
1253
|
+
$reportedPeers = $store->get($peersKey) ?: [];
|
|
1254
|
+
if (!is_array($reportedPeers)) {
|
|
1255
|
+
$reportedPeers = [];
|
|
1256
|
+
}
|
|
1257
|
+
if (!in_array('local', $reportedPeers, true)) {
|
|
1258
|
+
$reportedPeers[] = 'local';
|
|
1259
|
+
$store->set($peersKey, $reportedPeers, 86400 * 30);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
$threshold = $this->securityConfig['federationConsensusThreshold'] ?? 3;
|
|
1263
|
+
if (count($reportedPeers) >= $threshold) {
|
|
1253
1264
|
$store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
|
|
1254
|
-
$this->broadcastBannedZkp($zkpY);
|
|
1255
1265
|
}
|
|
1266
|
+
$this->broadcastBannedZkp($zkpY);
|
|
1256
1267
|
}
|
|
1257
1268
|
}
|
|
1258
1269
|
|
|
@@ -74,10 +74,43 @@ class OptimizationOperators
|
|
|
74
74
|
$currentConfig = $context['currentConfig'] ?? null;
|
|
75
75
|
|
|
76
76
|
return function (array $config) use ($trafficData, $currentConfig): array {
|
|
77
|
-
$
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
$threatProfiles = [
|
|
78
|
+
'account_takeover' => [
|
|
79
|
+
'importance' => 10.0,
|
|
80
|
+
'ux_vs_security_ratio' => 0.1,
|
|
81
|
+
'target_threshold' => 'block',
|
|
82
|
+
'indicators' => ['requestPatternScore', 'behaviorScore', 'timeInconsistencyScore', 'clickVarianceScore']
|
|
83
|
+
],
|
|
84
|
+
'active_exploitation' => [
|
|
85
|
+
'importance' => 8.0,
|
|
86
|
+
'ux_vs_security_ratio' => 0.2,
|
|
87
|
+
'target_threshold' => 'block',
|
|
88
|
+
'indicators' => ['honeypotScore', 'headerAnomalyScore']
|
|
89
|
+
],
|
|
90
|
+
'mass_scraping' => [
|
|
91
|
+
'importance' => 3.0,
|
|
92
|
+
'ux_vs_security_ratio' => 0.8,
|
|
93
|
+
'target_threshold' => 'low',
|
|
94
|
+
'indicators' => ['requestPatternScore', 'renderingAnomalyScore', 'clientHintsInconsistencyScore']
|
|
95
|
+
],
|
|
96
|
+
'distributed_botnets' => [
|
|
97
|
+
'importance' => 6.0,
|
|
98
|
+
'ux_vs_security_ratio' => 0.5,
|
|
99
|
+
'target_threshold' => 'high',
|
|
100
|
+
'indicators' => ['subnetScore', 'botnetClusterScore', 'ipReputationScore', 'tlsSpoofingScore']
|
|
101
|
+
],
|
|
102
|
+
'basic_automation' => [
|
|
103
|
+
'importance' => 5.0,
|
|
104
|
+
'ux_vs_security_ratio' => 0.4,
|
|
105
|
+
'target_threshold' => 'medium',
|
|
106
|
+
'indicators' => ['botScore', 'tlsSpoofingScore', 'tcpAnomalyScore']
|
|
107
|
+
]
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
$threatStats = [];
|
|
111
|
+
foreach ($threatProfiles as $name => $profile) {
|
|
112
|
+
$threatStats[$name] = ['fp' => 0.0, 'fn' => 0.0, 'totalHumans' => 0.0, 'totalBots' => 0.0];
|
|
113
|
+
}
|
|
81
114
|
|
|
82
115
|
$maxHumanScore = 0.0;
|
|
83
116
|
$minBotScore = 100.0;
|
|
@@ -108,22 +141,56 @@ class OptimizationOperators
|
|
|
108
141
|
$score = $calculateScore($log);
|
|
109
142
|
|
|
110
143
|
if ($isLikelyBot) {
|
|
111
|
-
$totalBots += $confidence;
|
|
112
144
|
$minBotScore = min($minBotScore, $score);
|
|
113
|
-
if ($score < $config['thresholds']['low']) {
|
|
114
|
-
$falseNegatives += $confidence;
|
|
115
|
-
}
|
|
116
145
|
} elseif ($isLikelyHuman) {
|
|
117
|
-
$totalHumans += $confidence;
|
|
118
146
|
$maxHumanScore = max($maxHumanScore, $score);
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
foreach ($threatProfiles as $name => $profile) {
|
|
150
|
+
$threatActivity = 0.0;
|
|
151
|
+
foreach ($profile['indicators'] as $indicator) {
|
|
152
|
+
$threatActivity += (float)($log['vector'][$indicator] ?? 0.0);
|
|
153
|
+
}
|
|
154
|
+
if ($threatActivity <= 0.0) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
$effectiveConfidence = $confidence * ($threatActivity / 100.0);
|
|
158
|
+
$targetThreshold = (float)($config['thresholds'][$profile['target_threshold']] ?? 20.0);
|
|
159
|
+
|
|
160
|
+
if ($isLikelyBot) {
|
|
161
|
+
$threatStats[$name]['totalBots'] += $effectiveConfidence;
|
|
162
|
+
if ($score < $targetThreshold) {
|
|
163
|
+
$threatStats[$name]['fn'] += $effectiveConfidence;
|
|
164
|
+
}
|
|
165
|
+
} elseif ($isLikelyHuman) {
|
|
166
|
+
$threatStats[$name]['totalHumans'] += $effectiveConfidence;
|
|
167
|
+
if ($score >= $targetThreshold) {
|
|
168
|
+
$threatStats[$name]['fp'] += $effectiveConfidence;
|
|
169
|
+
}
|
|
121
170
|
}
|
|
122
171
|
}
|
|
123
172
|
}
|
|
124
173
|
|
|
125
|
-
$
|
|
126
|
-
$
|
|
174
|
+
$weightedFpr = 0.0;
|
|
175
|
+
$weightedFnr = 0.0;
|
|
176
|
+
$totalImportance = 0.0;
|
|
177
|
+
|
|
178
|
+
foreach ($threatProfiles as $name => $profile) {
|
|
179
|
+
$totalImportance += $profile['importance'];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
foreach ($threatProfiles as $name => $profile) {
|
|
183
|
+
$stats = $threatStats[$name];
|
|
184
|
+
$fpr = $stats['totalHumans'] > 0 ? $stats['fp'] / $stats['totalHumans'] : 0.0;
|
|
185
|
+
$fnr = $stats['totalBots'] > 0 ? $stats['fn'] / $stats['totalBots'] : 0.0;
|
|
186
|
+
|
|
187
|
+
$importanceWeight = $profile['importance'] / $totalImportance;
|
|
188
|
+
$uxRatio = $profile['ux_vs_security_ratio'];
|
|
189
|
+
$secRatio = 1.0 - $uxRatio;
|
|
190
|
+
|
|
191
|
+
$weightedFpr += $fpr * $importanceWeight * $uxRatio;
|
|
192
|
+
$weightedFnr += $fnr * $importanceWeight * $secRatio;
|
|
193
|
+
}
|
|
127
194
|
|
|
128
195
|
// 3. Pénalité de dérive d'échelle (L2 Regularization par rapport au profil d'origine)
|
|
129
196
|
$regularizationPenalty = 0.0;
|
|
@@ -138,8 +205,8 @@ class OptimizationOperators
|
|
|
138
205
|
$marginOverlap = max(0.0, $maxHumanScore - $minBotScore);
|
|
139
206
|
$marginPenalty = $marginOverlap / 100.0;
|
|
140
207
|
|
|
141
|
-
$obj1 = $
|
|
142
|
-
$obj2 = $
|
|
208
|
+
$obj1 = $weightedFpr + ($regularizationPenalty * 0.05);
|
|
209
|
+
$obj2 = $weightedFnr + $marginPenalty;
|
|
143
210
|
|
|
144
211
|
return [$obj1, $obj2];
|
|
145
212
|
};
|
|
@@ -160,9 +227,7 @@ class OptimizationOperators
|
|
|
160
227
|
$child = $c1;
|
|
161
228
|
foreach (['thresholds', 'weights', 'patterns'] as $section) {
|
|
162
229
|
foreach ($child[$section] as $key => $value) {
|
|
163
|
-
|
|
164
|
-
$child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
|
|
165
|
-
}
|
|
230
|
+
$child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
|
|
166
231
|
}
|
|
167
232
|
}
|
|
168
233
|
return $child;
|
|
@@ -177,7 +242,7 @@ class OptimizationOperators
|
|
|
177
242
|
foreach (['thresholds', 'weights', 'patterns'] as $section) {
|
|
178
243
|
if (isset($config[$section]) && is_array($config[$section])) {
|
|
179
244
|
foreach ($config[$section] as $k => $v) {
|
|
180
|
-
if (is_numeric($v)
|
|
245
|
+
if (is_numeric($v)) {
|
|
181
246
|
$randomVariation = 1.0 + (self::secureRandom() - 0.5) * 0.5; // Variation de +/- 25%
|
|
182
247
|
$ind[$section][$k] = $v * $randomVariation;
|
|
183
248
|
} else {
|
|
@@ -197,9 +262,9 @@ class OptimizationOperators
|
|
|
197
262
|
$mutate = function (array $c, ?array $currentConfigRef = null) use ($currentConfig): array {
|
|
198
263
|
$newConfig = $c;
|
|
199
264
|
$sections = [
|
|
200
|
-
['name' => 'patterns', 'weight' => 0.
|
|
201
|
-
['name' => '
|
|
202
|
-
['name' => '
|
|
265
|
+
['name' => 'patterns', 'weight' => 0.50],
|
|
266
|
+
['name' => 'thresholds', 'weight' => 0.25],
|
|
267
|
+
['name' => 'weights', 'weight' => 0.25]
|
|
203
268
|
];
|
|
204
269
|
$rand = self::secureRandom();
|
|
205
270
|
$cumulativeWeight = 0;
|
|
@@ -215,13 +280,16 @@ class OptimizationOperators
|
|
|
215
280
|
$keys = array_keys($newConfig[$sectionToMutate]);
|
|
216
281
|
$keyToMutate = $keys[random_int(0, count($keys) - 1)];
|
|
217
282
|
|
|
218
|
-
if ($keyToMutate === 'honeypotScore') return $newConfig;
|
|
219
|
-
|
|
220
|
-
$mutationAmount = (self::secureRandom() - 0.5) * 0.4;
|
|
221
|
-
$newConfig[$sectionToMutate][$keyToMutate] *= (1 + $mutationAmount);
|
|
222
|
-
|
|
223
283
|
if ($sectionToMutate === 'weights') {
|
|
224
|
-
$newConfig[$sectionToMutate][$keyToMutate] = max(0, min(1.5, $newConfig[$sectionToMutate][$keyToMutate]));
|
|
284
|
+
$newConfig[$sectionToMutate][$keyToMutate] = max(0.05, min(1.5, $newConfig[$sectionToMutate][$keyToMutate] + (self::secureRandom() - 0.5) * 0.1));
|
|
285
|
+
} elseif ($sectionToMutate === 'thresholds') {
|
|
286
|
+
$newConfig[$sectionToMutate][$keyToMutate] = (int)round($newConfig[$sectionToMutate][$keyToMutate] + (self::secureRandom() - 0.5) * 5.0);
|
|
287
|
+
} else {
|
|
288
|
+
if ($keyToMutate === 'decayFactor') {
|
|
289
|
+
$newConfig['patterns']['decayFactor'] = max(0.8, min(0.999, $newConfig['patterns']['decayFactor'] + (self::secureRandom() - 0.5) * 0.05));
|
|
290
|
+
} elseif (str_contains($keyToMutate, 'Threshold') || str_contains($keyToMutate, 'Reset')) {
|
|
291
|
+
$newConfig['patterns'][$keyToMutate] = max(50.0, $newConfig['patterns'][$keyToMutate] + (self::secureRandom() - 0.5) * 50.0);
|
|
292
|
+
}
|
|
225
293
|
}
|
|
226
294
|
|
|
227
295
|
$refConfig = $currentConfigRef ?? $currentConfig;
|
|
@@ -53,7 +53,9 @@ class ChallengeUtilsTest extends TestCase
|
|
|
53
53
|
|
|
54
54
|
// Tamper with value on verified sample index 12
|
|
55
55
|
$tampered = $solutions;
|
|
56
|
-
$
|
|
56
|
+
$sampleIndices = ChallengeUtils::deriveSampleIndices('127.0.0.1', 'gpu-pow-salt');
|
|
57
|
+
$tamperedIndex = $sampleIndices[0];
|
|
58
|
+
$tampered[$tamperedIndex] = (string)((float)$tampered[$tamperedIndex] + 0.0002); // Out of tolerance
|
|
57
59
|
$this->assertFalse(ChallengeUtils::verifyGpuPow($seed, $iterations, implode(',', $tampered)));
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -85,8 +87,10 @@ class ChallengeUtilsTest extends TestCase
|
|
|
85
87
|
}
|
|
86
88
|
$solutionString = implode(',', $solutions);
|
|
87
89
|
$this->assertTrue(ChallengeUtils::verifyGpuPow($seed, $iterations, $solutionString));
|
|
90
|
+
$sampleIndices = ChallengeUtils::deriveSampleIndices('127.0.0.1', 'gpu-pow-salt');
|
|
91
|
+
$tamperedIndex = $sampleIndices[0];
|
|
88
92
|
$tamperedSolutions = $solutions;
|
|
89
|
-
$tamperedSolutions[
|
|
93
|
+
$tamperedSolutions[$tamperedIndex] = (string)((float)$tamperedSolutions[$tamperedIndex] + 0.1);
|
|
90
94
|
$this->assertFalse(ChallengeUtils::verifyGpuPow($seed, $iterations, implode(',', $tamperedSolutions)));
|
|
91
95
|
}
|
|
92
96
|
|