@anonympins/fingerprint 0.6.4 → 0.6.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 +29 -0
- package/README.md +2 -2
- package/package.json +4 -1
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +315 -54
- package/src/js/gpu_pow.solver.js +37 -5
- package/src/js/library.js +188 -97
- package/src/js/pow.solver.inline.js +128 -2
- package/src/js/pow.solver.js +85 -0
- package/src/js/problem-manager.js +16 -1
- package/src/js/tests/cross_parity.test.js +2 -2
- package/src/js/tests/fingerprint.test.js +12 -3
- package/src/js/tests/gpu_pow.test.js +9 -7
- package/src/js/tests/ip-reputation.test.js +1 -1
- package/src/js/tests/quicFingerprint.test.js +21 -10
- package/src/js/upow-model-task.js +114 -0
- package/src/php/AutoTuner.php +41 -0
- package/src/php/Challenge/ChallengeUtils.php +39 -5
- package/src/php/Config/SecurityProfiles.php +6 -6
- package/src/php/FingerprintEngine.php +17 -6
- package/src/php/Optimization/FunctionRegistry.php +1 -1
- package/src/php/Optimization/OptimizationOperators.php +109 -28
- package/src/php/ProblemManager.php +22 -0
- package/src/php/Tests/ChallengeUtilsTest.php +9 -4
- package/src/php/Tests/IpReputationTest.php +1 -1
- package/src/php/Tests/ProblemManagerTest.php +1 -1
- package/src/php/Tests/QuicFingerprintTest.php +9 -9
- package/src/php/Utils/RequestUtils.php +102 -45
package/src/js/fingerprint.js
CHANGED
|
@@ -9,6 +9,7 @@ import {DynamicWasmGenerator} from "./dynamic-wasm.js";
|
|
|
9
9
|
import {writeFileSync, readFileSync, existsSync} from "node:fs";
|
|
10
10
|
import {fileURLToPath} from "node:url";
|
|
11
11
|
import {dirname, join, resolve} from "node:path";
|
|
12
|
+
import {GpuPowSolver} from "./gpu_pow.solver.js";
|
|
12
13
|
import {
|
|
13
14
|
verifyZkpProof,
|
|
14
15
|
sanitizeRedirectPath,
|
|
@@ -354,46 +355,75 @@ export function generateStatelessTicket(payload) {
|
|
|
354
355
|
return `${base64UrlEncode(iv)}.${base64UrlEncode(encrypted)}.${base64UrlEncode(signature)}`;
|
|
355
356
|
}
|
|
356
357
|
/**
|
|
357
|
-
* Détecte les anomalies de
|
|
358
|
+
* Détecte les anomalies de protocole (HTTP/2 et QUIC/HTTP3) par rapport au User-Agent.
|
|
358
359
|
* @private
|
|
359
360
|
* @param {object} context - Le contexte de la requête.
|
|
360
|
-
* @returns {{
|
|
361
|
+
* @returns {{protocolAnomalyScore: number}}
|
|
361
362
|
*/
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return { quicAnomalyScore: 0.0 };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const parts = quicFp.split(';');
|
|
369
|
-
if (parts.length < 2) return { quicAnomalyScore: 0.0 };
|
|
370
|
-
|
|
371
|
-
const params = {};
|
|
372
|
-
parts[1].split(',').forEach(p => {
|
|
373
|
-
const kv = p.split('=');
|
|
374
|
-
if (kv.length === 2) params[kv[0]] = kv[1];
|
|
375
|
-
});
|
|
376
|
-
const priorityOrder = parts[2] || '';
|
|
363
|
+
function getProtocolAnomalyScore(context) {
|
|
364
|
+
let http2Anomaly = 0.0;
|
|
365
|
+
let quicAnomaly = 0.0;
|
|
377
366
|
|
|
378
367
|
const ua = context.headers?.['user-agent'] || '';
|
|
379
368
|
const uaParts = parseUserAgent(ua);
|
|
380
369
|
const browser = uaParts.browser;
|
|
381
370
|
|
|
382
|
-
if (
|
|
371
|
+
if (browser) {
|
|
372
|
+
// HTTP/2 Anomaly Logic
|
|
373
|
+
const h2Fp = context.headers?.['x-http2-fingerprint'] || context.http2Fingerprint || null;
|
|
374
|
+
if (h2Fp && typeof h2Fp === 'string') {
|
|
375
|
+
const parts = h2Fp.split('|');
|
|
376
|
+
if (parts.length >= 4) {
|
|
377
|
+
const connWindow = parseInt(parts[1], 10);
|
|
378
|
+
const headerOrder = parts[3];
|
|
379
|
+
const isChromium = browser.startsWith('Chrome') || browser.startsWith('Edge');
|
|
380
|
+
const isFirefox = browser.startsWith('Firefox');
|
|
381
|
+
const isSafari = browser.startsWith('Safari');
|
|
382
|
+
|
|
383
|
+
if (isChromium) {
|
|
384
|
+
if (headerOrder && headerOrder !== 'm,a,s,p') http2Anomaly += 60.0;
|
|
385
|
+
if (connWindow === 65535 || connWindow === 65536) http2Anomaly += 40.0;
|
|
386
|
+
} else if (isFirefox) {
|
|
387
|
+
if (headerOrder && headerOrder !== 'm,s,p,a') http2Anomaly += 60.0;
|
|
388
|
+
} else if (isSafari) {
|
|
389
|
+
if (headerOrder && headerOrder !== 'm,s,p,a') http2Anomaly += 60.0;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
383
393
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
+
// QUIC Anomaly Logic
|
|
395
|
+
const quicFp = context.headers?.['x-quic-fp'] || context.quicFingerprint || null;
|
|
396
|
+
if (quicFp && typeof quicFp === 'string') {
|
|
397
|
+
const parts = quicFp.split(';');
|
|
398
|
+
if (parts.length >= 2) {
|
|
399
|
+
const params = {};
|
|
400
|
+
parts[1].split(',').forEach(p => {
|
|
401
|
+
const kv = p.split('=');
|
|
402
|
+
if (kv.length === 2) params[kv[0]] = kv[1];
|
|
403
|
+
});
|
|
404
|
+
const priorityOrder = parts[2] || '';
|
|
405
|
+
|
|
406
|
+
if (browser.startsWith('Chrome') || browser.startsWith('Edge')) {
|
|
407
|
+
const maxData = parseInt(params['1'] || '0', 10);
|
|
408
|
+
const maxStreams = parseInt(params['4'] || '0', 10);
|
|
409
|
+
if (maxData > 0 && maxData < 1048576) quicAnomaly += 40.0;
|
|
410
|
+
if (maxStreams > 0 && maxStreams !== 100) quicAnomaly += 30.0;
|
|
411
|
+
if (priorityOrder && !priorityOrder.includes('u=')) quicAnomaly += 30.0;
|
|
412
|
+
} else if (browser.startsWith('Firefox')) {
|
|
413
|
+
const maxData = parseInt(params['1'] || '0', 10);
|
|
414
|
+
if (maxData > 0 && maxData > 5000000) quicAnomaly += 40.0;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
394
418
|
}
|
|
395
419
|
|
|
396
|
-
return {
|
|
420
|
+
return {
|
|
421
|
+
protocolAnomalyScore: Math.max(
|
|
422
|
+
0.0,
|
|
423
|
+
Math.min(100.0, http2Anomaly),
|
|
424
|
+
Math.min(100.0, quicAnomaly)
|
|
425
|
+
)
|
|
426
|
+
};
|
|
397
427
|
}
|
|
398
428
|
/**
|
|
399
429
|
* Détecte les anomalies de rendu (V-Sync, FPS, gigue) à partir des métriques d'affichage.
|
|
@@ -523,6 +553,39 @@ async function checkChallengeRateLimit(clientIp) {
|
|
|
523
553
|
return true;
|
|
524
554
|
}
|
|
525
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Génère la page HTML de challenge GPU basée sur la trajectoire d'une carte logistique chaotique.
|
|
558
|
+
* @private
|
|
559
|
+
* @param {object} challengeDetails - Les détails du challenge.
|
|
560
|
+
* @returns {string}
|
|
561
|
+
*/
|
|
562
|
+
function generateGpuChallengePage(challengeDetails) {
|
|
563
|
+
const { nonce, iterations, path } = challengeDetails;
|
|
564
|
+
const safePath = sanitizeRedirectPath(path);
|
|
565
|
+
const solverCode = getPowSolverCode();
|
|
566
|
+
return `
|
|
567
|
+
<html><head><title>GPU Hardware Verification</title></head>
|
|
568
|
+
<body style="font-family:sans-serif; text-align:center; padding-top:50px;">
|
|
569
|
+
<h1>Hardware Performance Check</h1>
|
|
570
|
+
<p>Please wait while we verify your graphics card's math precision to ensure you are a human...</p>
|
|
571
|
+
<div id="loader" style="margin:20px;">⚙️ Running parallel chaotic iterations...</div>
|
|
572
|
+
<script>${solverCode}</script>
|
|
573
|
+
<script type="module">
|
|
574
|
+
import { GpuPowSolver } from './gpu_pow.solver.js';
|
|
575
|
+
async function solve() {
|
|
576
|
+
const nonce = ${safeJsonStringify(nonce)};
|
|
577
|
+
const iterations = ${iterations};
|
|
578
|
+
try {
|
|
579
|
+
const result = await GpuPowSolver.solve(nonce, iterations);
|
|
580
|
+
window.location.href = ${safeJsonStringify(safePath)} + "?pow_type=gpu&pow_nonce=" + nonce + "&pow_solution=" + result.solution;
|
|
581
|
+
} catch(e) {
|
|
582
|
+
document.getElementById('loader').innerText = "GPU hardware acceleration is required to complete this verification.";
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
solve();
|
|
586
|
+
</script>
|
|
587
|
+
</body></html>`;
|
|
588
|
+
}
|
|
526
589
|
|
|
527
590
|
const securityProfiles = {
|
|
528
591
|
/**
|
|
@@ -546,6 +609,7 @@ const securityProfiles = {
|
|
|
546
609
|
botnetClusterScore: 0.6, // NOUVEAU: Poids pour le clustering botnet
|
|
547
610
|
tcpAnomalyScore: 0.8, // NEW: Anomalie de pile TCP/IP
|
|
548
611
|
quicAnomalyScore: 0.8, // NOUVEAU: Poids pour l'anomalie QUIC
|
|
612
|
+
protocolAnomalyScore: 0.8, // NOUVEAU: Poids pour l'anomalie HTTP/2
|
|
549
613
|
renderingAnomalyScore: 0.8, // NOUVEAU: Poids pour l'anomalie de rendu
|
|
550
614
|
threatIntelScore: 1.0, // NOUVEAU: Poids pour le réseau de Threat Intelligence Fédéré
|
|
551
615
|
},
|
|
@@ -563,9 +627,10 @@ const securityProfiles = {
|
|
|
563
627
|
inactivityReset: 5000,
|
|
564
628
|
},
|
|
565
629
|
allowCrossNetworkRoaming: true, // Profil balancé : tolérant par défaut
|
|
566
|
-
|
|
567
|
-
|
|
630
|
+
wasm: true,
|
|
631
|
+
filterWhitelist: 85.0, // Stratégie d'inspection modérée pour les IP/chemins en liste blanche
|
|
568
632
|
useAsymmetricTickets: true,
|
|
633
|
+
enableGpuPow: true, // Activer le challenge GPU
|
|
569
634
|
},
|
|
570
635
|
/**
|
|
571
636
|
* @summary **Strict Profile**
|
|
@@ -587,6 +652,7 @@ const securityProfiles = {
|
|
|
587
652
|
ipReputationScore: 0.6, // NOUVEAU: Poids pour la réputation IP
|
|
588
653
|
botnetClusterScore: 0.8, // NOUVEAU: Poids pour le clustering botnet
|
|
589
654
|
renderingAnomalyScore: 1.0, // NOUVEAU: Poids pour l'anomalie de rendu
|
|
655
|
+
protocolAnomalyScore: 1.0, // NOUVEAU
|
|
590
656
|
threatIntelScore: 1.0, // NOUVEAU: Poids pour le réseau de Threat Intelligence Fédéré
|
|
591
657
|
},
|
|
592
658
|
thresholds: { low: 10, medium: 35, high: 65, block: 90 },
|
|
@@ -607,6 +673,7 @@ const securityProfiles = {
|
|
|
607
673
|
filterWhitelist: true, // Tout comportement d'attaque certain bypass immédiatement la liste blanche
|
|
608
674
|
wasm: true,
|
|
609
675
|
useAsymmetricTickets: true,
|
|
676
|
+
enableGpuPow: true,
|
|
610
677
|
},
|
|
611
678
|
/**
|
|
612
679
|
* @summary **API Profile**
|
|
@@ -628,6 +695,7 @@ const securityProfiles = {
|
|
|
628
695
|
ipReputationScore: 0.5, // NOUVEAU: Poids pour la réputation IP
|
|
629
696
|
botnetClusterScore: 0.7, // NOUVEAU: Poids pour le clustering botnet
|
|
630
697
|
tcpAnomalyScore: 0.8, // NEW: Anomalie de pile TCP/IP
|
|
698
|
+
protocolAnomalyScore: 0.8,
|
|
631
699
|
quicAnomalyScore: 0.8 // NOUVEAU: Poids pour l'anomalie QUIC
|
|
632
700
|
},
|
|
633
701
|
thresholds: { low: 25, medium: 50, high: 80, block: 95 },
|
|
@@ -675,6 +743,7 @@ const securityProfiles = {
|
|
|
675
743
|
ipReputationScore: 0.3, // NOUVEAU: Poids pour la réputation IP
|
|
676
744
|
botnetClusterScore: 0.5, // NOUVEAU: Poids pour le clustering botnet
|
|
677
745
|
tcpAnomalyScore: 0.5, // NEW: Anomalie de pile TCP/IP
|
|
746
|
+
protocolAnomalyScore: 0.5,
|
|
678
747
|
quicAnomalyScore: 0.5, // NOUVEAU: Poids pour l'anomalie QUIC
|
|
679
748
|
renderingAnomalyScore: 0.5, // NOUVEAU: Poids pour l'anomalie de rendu
|
|
680
749
|
},
|
|
@@ -722,6 +791,7 @@ const securityProfiles = {
|
|
|
722
791
|
ipReputationScore: 0.6, // NOUVEAU: Poids pour la réputation IP
|
|
723
792
|
botnetClusterScore: 0.9, // NOUVEAU: Poids pour le clustering botnet
|
|
724
793
|
tcpAnomalyScore: 0.9, // NEW: Anomalie de pile TCP/IP
|
|
794
|
+
protocolAnomalyScore: 0.9,
|
|
725
795
|
quicAnomalyScore: 0.9, // NOUVEAU: Poids pour l'anomalie QUIC
|
|
726
796
|
renderingAnomalyScore: 0.9, // NOUVEAU: Poids pour l'anomalie de rendu,threatIntelScore: 1.0, // NOUVEAU: Poids pour le réseau de Threat Intelligence Fédéré
|
|
727
797
|
},
|
|
@@ -2298,6 +2368,7 @@ function getTimeInconsistencyScore(context, metrics, deviceData = null) {
|
|
|
2298
2368
|
function getCrossLayerInconsistency(context) {
|
|
2299
2369
|
try {
|
|
2300
2370
|
const clientFpString = context.headers['x-device-fingerprint'];
|
|
2371
|
+
const h2Fingerprint = context.headers['x-http2-fingerprint'] || context.http2Fingerprint || null;
|
|
2301
2372
|
if (!clientFpString) return { crossLayerInconsistencyScore: 0 };
|
|
2302
2373
|
|
|
2303
2374
|
const clientFpMap = new Map(clientFpString.split("|").map(part => part.split(":")));
|
|
@@ -2362,6 +2433,40 @@ function getCrossLayerInconsistency(context) {
|
|
|
2362
2433
|
}
|
|
2363
2434
|
}
|
|
2364
2435
|
|
|
2436
|
+
// 4. Incohérence des paramètres HTTP/2 (SETTINGS & WINDOW_UPDATE)
|
|
2437
|
+
// Les bots de spoofing TLS oublient souvent de modifier la signature de la couche HTTP/2
|
|
2438
|
+
if (h2Fingerprint && serverOsParts.browser) {
|
|
2439
|
+
const claimedBrowser = serverOsParts.browser.split('/')[0];
|
|
2440
|
+
|
|
2441
|
+
// Profils de paramètres HTTP/2 Settings attendus (Format standard type Akamai)
|
|
2442
|
+
// Chrome :SETTINGS_HEADER_TABLE_SIZE=65536, SETTINGS_MAX_CONCURRENT_STREAMS=1000, etc.
|
|
2443
|
+
const isChromium = ['Chrome', 'Edge'].includes(claimedBrowser);
|
|
2444
|
+
const isFirefox = claimedBrowser === 'Firefox';
|
|
2445
|
+
|
|
2446
|
+
if (isChromium) {
|
|
2447
|
+
// Chrome envoie typiquement : "1:65536;3:1000;4:6291456;6:65536" ou similaire
|
|
2448
|
+
const hasChromiumSettings = h2Fingerprint.includes('1:65536') && h2Fingerprint.includes('4:6291456');
|
|
2449
|
+
const isGoDefaultH2 = h2Fingerprint.includes('3:100') && h2Fingerprint.includes('4:1048576'); // Signature Go net/http par défaut
|
|
2450
|
+
|
|
2451
|
+
if (isGoDefaultH2) {
|
|
2452
|
+
score += 90; // Très forte suspicion d'un bot Go (tls-client) usurpant Chrome
|
|
2453
|
+
} else if (!hasChromiumSettings) {
|
|
2454
|
+
score += 40; // Anomalie de configuration HTTP/2
|
|
2455
|
+
}
|
|
2456
|
+
} else if (isFirefox) {
|
|
2457
|
+
// Firefox utilise des valeurs de fenêtres initiales et de paramètres différentes
|
|
2458
|
+
const isPythonH2 = h2Fingerprint.includes('1:4096') && h2Fingerprint.includes('4:65536'); // Signature hyper-générique type Python hyper/h2
|
|
2459
|
+
if (isPythonH2) {
|
|
2460
|
+
score += 90; // Bot Python usurpant Firefox
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
// 5. Corrélation stricte OS Réseau (TCP/IP) vs OS Applicatif (User-Agent)
|
|
2466
|
+
if (context.tcpAnomalyScore && context.tcpAnomalyScore > 70) {
|
|
2467
|
+
score += 40; // Augmente drastiquement la pénalité si l'OS réseau ne correspond pas à l'OS applicatif
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2365
2470
|
return { crossLayerInconsistencyScore: Math.min(100, score) };
|
|
2366
2471
|
} catch (e) {
|
|
2367
2472
|
return { crossLayerInconsistencyScore: 10 }; // Erreur de parsing = suspect.
|
|
@@ -2791,6 +2896,58 @@ function getIpSubnet(ip, ipv4Prefix = 24, ipv6Prefix = 48) { // eslint-disable-l
|
|
|
2791
2896
|
return null;
|
|
2792
2897
|
}
|
|
2793
2898
|
|
|
2899
|
+
/**
|
|
2900
|
+
* Applies temporal decay (half-life of 30 minutes) to subnet metrics.
|
|
2901
|
+
* @private
|
|
2902
|
+
* @param {object} subnetData The subnet data.
|
|
2903
|
+
* @param {number} now The current timestamp.
|
|
2904
|
+
* @returns {object} The decayed subnet data.
|
|
2905
|
+
*/
|
|
2906
|
+
function decaySubnetData(subnetData, now) {
|
|
2907
|
+
const inactivityMs = now - (subnetData.lastActivity || now);
|
|
2908
|
+
const halfLives = Math.floor(inactivityMs / (30 * 60 * 1000));
|
|
2909
|
+
|
|
2910
|
+
if (halfLives > 0) {
|
|
2911
|
+
const decay = Math.pow(2, halfLives);
|
|
2912
|
+
subnetData.highScoreCount = Math.max(0, Math.floor((subnetData.highScoreCount || 0) / decay));
|
|
2913
|
+
|
|
2914
|
+
if (subnetData.highScoreDevices) {
|
|
2915
|
+
for (const fpId in subnetData.highScoreDevices) {
|
|
2916
|
+
const decayedVal = Math.floor(subnetData.highScoreDevices[fpId] / decay);
|
|
2917
|
+
if (decayedVal <= 0) {
|
|
2918
|
+
delete subnetData.highScoreDevices[fpId];
|
|
2919
|
+
} else {
|
|
2920
|
+
subnetData.highScoreDevices[fpId] = decayedVal;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
if (subnetData.deviceIds) {
|
|
2926
|
+
const newLen = Math.max(0, Math.floor(subnetData.deviceIds.length / decay));
|
|
2927
|
+
subnetData.deviceIds = subnetData.deviceIds.slice(0, newLen);
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
if (subnetData.ips) {
|
|
2931
|
+
const currentLen = subnetData.ips instanceof Set ? subnetData.ips.size : (subnetData.ips.length || 0);
|
|
2932
|
+
const newLen = Math.max(0, Math.floor(currentLen / decay));
|
|
2933
|
+
if (subnetData.ips instanceof Set) {
|
|
2934
|
+
const arr = Array.from(subnetData.ips).slice(0, newLen);
|
|
2935
|
+
subnetData.ips = new Set(arr);
|
|
2936
|
+
} else {
|
|
2937
|
+
subnetData.ips = subnetData.ips.slice(0, newLen);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
if (subnetData.uas) {
|
|
2942
|
+
const newLen = Math.max(0, Math.floor(subnetData.uas.length / decay));
|
|
2943
|
+
subnetData.uas = subnetData.uas.slice(0, newLen);
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
subnetData.lastActivity = now - (inactivityMs % (30 * 60 * 1000));
|
|
2947
|
+
}
|
|
2948
|
+
return subnetData;
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2794
2951
|
/**
|
|
2795
2952
|
* Updates aggregated metrics for an IP subnet.
|
|
2796
2953
|
* @param {object} context The request context.
|
|
@@ -2821,12 +2978,15 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
|
|
|
2821
2978
|
}
|
|
2822
2979
|
if (!subnetData.uas) subnetData.uas = [];
|
|
2823
2980
|
|
|
2981
|
+
const now = Date.now();
|
|
2982
|
+
decaySubnetData(subnetData, now);
|
|
2983
|
+
|
|
2824
2984
|
// Utilisation d'un identifiant d'appareil stable (fingerprint matériel) plutôt que l'ID de cookie volatil
|
|
2825
2985
|
const currentDeviceHash = getCompositeDeviceHash(context);
|
|
2826
2986
|
const stableFpId = cyrb53(extractStablePart(currentDeviceHash)).toString();
|
|
2827
2987
|
|
|
2828
2988
|
const currentDeviceContributions = subnetData.highScoreDevices[stableFpId] || 0;
|
|
2829
|
-
if (currentDeviceContributions <
|
|
2989
|
+
if (currentDeviceContributions < 1) {
|
|
2830
2990
|
subnetData.highScoreDevices[stableFpId] = currentDeviceContributions + 1;
|
|
2831
2991
|
subnetData.highScoreCount++;
|
|
2832
2992
|
}
|
|
@@ -2844,7 +3004,7 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
|
|
|
2844
3004
|
subnetData.uas.push(userAgent);
|
|
2845
3005
|
}
|
|
2846
3006
|
|
|
2847
|
-
subnetData.lastActivity =
|
|
3007
|
+
subnetData.lastActivity = now;
|
|
2848
3008
|
|
|
2849
3009
|
if (subnetData.deviceIds.length > 100) {
|
|
2850
3010
|
const oldDeviceId = subnetData.deviceIds.shift();
|
|
@@ -2872,11 +3032,15 @@ async function getSubnetScore(context) {
|
|
|
2872
3032
|
const subnetData = await store.get(`subnet:${subnet}`);
|
|
2873
3033
|
if (!subnetData) return { subnetScore: 0 };
|
|
2874
3034
|
|
|
2875
|
-
// Application d'une décroissance temporelle (demi-vie de 30 minutes)
|
|
2876
3035
|
const now = Date.now();
|
|
2877
3036
|
const inactivityMs = now - (subnetData.lastActivity || now);
|
|
2878
3037
|
const halfLives = Math.floor(inactivityMs / (30 * 60 * 1000));
|
|
2879
3038
|
|
|
3039
|
+
if (halfLives > 0) {
|
|
3040
|
+
decaySubnetData(subnetData, now);
|
|
3041
|
+
await store.set(key, subnetData, 86400);
|
|
3042
|
+
}
|
|
3043
|
+
|
|
2880
3044
|
let highScoreCount = subnetData.highScoreCount || 0;
|
|
2881
3045
|
let deviceCount = subnetData.deviceIds ? subnetData.deviceIds.length : 0;
|
|
2882
3046
|
let ipCount = 1;
|
|
@@ -2885,14 +3049,6 @@ async function getSubnetScore(context) {
|
|
|
2885
3049
|
}
|
|
2886
3050
|
let uaCount = subnetData.uas ? subnetData.uas.length : 1;
|
|
2887
3051
|
|
|
2888
|
-
if (halfLives > 0) {
|
|
2889
|
-
const decay = Math.pow(2, halfLives);
|
|
2890
|
-
highScoreCount = Math.max(0, Math.floor(highScoreCount / decay));
|
|
2891
|
-
deviceCount = Math.max(0, Math.floor(deviceCount / decay));
|
|
2892
|
-
ipCount = Math.max(1, Math.floor(ipCount / decay));
|
|
2893
|
-
uaCount = Math.max(1, Math.floor(uaCount / decay));
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
3052
|
if (deviceCount === 0) {
|
|
2897
3053
|
return { subnetScore: 0.0 };
|
|
2898
3054
|
}
|
|
@@ -3483,8 +3639,8 @@ export const getSuspicionVector = async (context, securityConfig) => {
|
|
|
3483
3639
|
const { requestPatternScore } = getRequestPatternScore(context, deviceData, securityConfig.patterns);
|
|
3484
3640
|
|
|
3485
3641
|
const { tcpAnomalyScore } = getTcpAnomalyScore(context);
|
|
3486
|
-
|
|
3487
|
-
|
|
3642
|
+
const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
|
|
3643
|
+
const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
|
|
3488
3644
|
|
|
3489
3645
|
// Save the updated device state to the store
|
|
3490
3646
|
// Note: deviceData.ips is a Set, which may not serialize correctly in all stores (e.g., JSON). A Redis store should handle this via custom serialization or by converting to an array.
|
|
@@ -3496,7 +3652,7 @@ export const getSuspicionVector = async (context, securityConfig) => {
|
|
|
3496
3652
|
deviceData.ips = new Set(deviceData.ips);
|
|
3497
3653
|
}
|
|
3498
3654
|
// Le vecteur de suspicion est maintenant complet.
|
|
3499
|
-
return { ...behavioral, headerAnomalyScore, inconsistencyScore, behaviorScore, honeypotScore, botScore, requestPatternScore, crossLayerInconsistencyScore, timeInconsistencyScore, tlsSpoofingScore, clickVarianceScore, clientHintsInconsistencyScore, subnetScore, ipReputationScore, botnetClusterScore, tcpAnomalyScore,
|
|
3655
|
+
return { ...behavioral, headerAnomalyScore, inconsistencyScore, behaviorScore, honeypotScore, botScore, requestPatternScore, crossLayerInconsistencyScore, timeInconsistencyScore, tlsSpoofingScore, clickVarianceScore, clientHintsInconsistencyScore, subnetScore, ipReputationScore, botnetClusterScore, tcpAnomalyScore, protocolAnomalyScore, renderingAnomalyScore, threatIntelScore };
|
|
3500
3656
|
};
|
|
3501
3657
|
|
|
3502
3658
|
// A residential user can change networks (home, 4G, public wifi).
|
|
@@ -3997,7 +4153,7 @@ export class FingerprintEngine {
|
|
|
3997
4153
|
'trustedProxies',
|
|
3998
4154
|
'wasm',
|
|
3999
4155
|
'similarityThreshold', 'reset',
|
|
4000
|
-
'ed25519_private_key', 'ed25519_public_key',
|
|
4156
|
+
'ed25519_private_key', 'ed25519_public_key', 'upowModel',
|
|
4001
4157
|
'federatedPeers', 'federationSecret', 'filterWhitelist'
|
|
4002
4158
|
]);
|
|
4003
4159
|
|
|
@@ -4058,6 +4214,7 @@ export class FingerprintEngine {
|
|
|
4058
4214
|
(suspicionVector.ipReputationScore || 0) * (weights.ipReputationScore || 0) +
|
|
4059
4215
|
(suspicionVector.tcpAnomalyScore || 0) * (weights.tcpAnomalyScore || 0) +
|
|
4060
4216
|
(suspicionVector.quicAnomalyScore || 0) * (weights.quicAnomalyScore || 0) + // NOUVEAU: QUIC Anomaly
|
|
4217
|
+
(suspicionVector.http2AnomalyScore || 0) * (weights.http2AnomalyScore || 0) + // NOUVEAU: HTTP/2 Anomaly
|
|
4061
4218
|
(suspicionVector.threatIntelScore || 0) * (weights.threatIntelScore || 0) + // NOUVEAU: QUIC Anomaly
|
|
4062
4219
|
(suspicionVector.renderingAnomalyScore || 0) * (weights.renderingAnomalyScore || 0); // NOUVEAU: Rendering Anomaly
|
|
4063
4220
|
|
|
@@ -4491,7 +4648,7 @@ export class FingerprintEngine {
|
|
|
4491
4648
|
const blockThreshold = thresholds.block ?? 95;
|
|
4492
4649
|
|
|
4493
4650
|
// Mettre à jour les métriques du sous-réseau après le calcul du score final
|
|
4494
|
-
if (finalScore
|
|
4651
|
+
if (finalScore >= (thresholds.medium ?? 45) && finalScore < blockThreshold) {
|
|
4495
4652
|
await __internal.updateSubnetMetrics(requestContext, deviceId, finalScore);
|
|
4496
4653
|
}
|
|
4497
4654
|
|
|
@@ -4619,6 +4776,18 @@ export class FingerprintEngine {
|
|
|
4619
4776
|
deviceHash: currentDeviceHash
|
|
4620
4777
|
});
|
|
4621
4778
|
}
|
|
4779
|
+
} else if (pow_type === "gpu" && pow_solution) {
|
|
4780
|
+
const isGpuValid = GpuPowSolver.verify(pow_nonce, challengeContext.iterations || 200000, pow_solution, clientIp, getPowSecret());
|
|
4781
|
+
isValid = isGpuValid;
|
|
4782
|
+
if (isValid) {
|
|
4783
|
+
const ttl = finalTtl || 3600000;
|
|
4784
|
+
ticket = generateStatelessTicket({
|
|
4785
|
+
expiry: Date.now() + ttl,
|
|
4786
|
+
originalIp: clientIp,
|
|
4787
|
+
deviceId,
|
|
4788
|
+
deviceHash: currentDeviceHash
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4622
4791
|
}
|
|
4623
4792
|
}
|
|
4624
4793
|
} else {
|
|
@@ -4843,11 +5012,23 @@ export class FingerprintEngine {
|
|
|
4843
5012
|
const [zkpY, zkpT, zkpS] = parts;
|
|
4844
5013
|
// 1. Valider cryptographiquement la preuve avant de bannir/diffuser
|
|
4845
5014
|
if (verifyZkpProof(zkpY, zkpT, zkpS)) {
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
5015
|
+
const peersKey = `fed-peers:${zkpY}`;
|
|
5016
|
+
let reportedPeers = await store.get(peersKey) || [];
|
|
5017
|
+
if (!Array.isArray(reportedPeers)) {
|
|
5018
|
+
reportedPeers = [];
|
|
4850
5019
|
}
|
|
5020
|
+
if (!reportedPeers.includes('local')) {
|
|
5021
|
+
reportedPeers.push('local');
|
|
5022
|
+
await store.set(peersKey, reportedPeers, 86400 * 30);
|
|
5023
|
+
}
|
|
5024
|
+
|
|
5025
|
+
const threshold = this.securityConfig.federationConsensusThreshold || 3;
|
|
5026
|
+
if (reportedPeers.length >= threshold) {
|
|
5027
|
+
if (!(await store.has(`banned-zkp-y:${zkpY}`))) {
|
|
5028
|
+
await store.set(`banned-zkp-y:${zkpY}`, true, 86400 * 30); // Banni pour 30 jours
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
broadcastBannedZkp(zkpY, this.securityConfig).catch(() => {});
|
|
4851
5032
|
}
|
|
4852
5033
|
}
|
|
4853
5034
|
}
|
|
@@ -5070,6 +5251,28 @@ export class FingerprintEngine {
|
|
|
5070
5251
|
}
|
|
5071
5252
|
return decision;
|
|
5072
5253
|
}
|
|
5254
|
+
|
|
5255
|
+
// NOUVEAU : Challenge matériel GPU (Logistic Map floating point parity check)
|
|
5256
|
+
const shouldUseGpu = this.securityConfig.enableGpuPow && finalScore >= thresholds.high;
|
|
5257
|
+
if (shouldUseGpu) {
|
|
5258
|
+
const iterations = this.securityConfig.gpuPowIterations || 200000;
|
|
5259
|
+
await store.set(`secret:${nonce}`, {
|
|
5260
|
+
clientSecret,
|
|
5261
|
+
iterations,
|
|
5262
|
+
fingerprint: originalFingerprint,
|
|
5263
|
+
originalPath: path
|
|
5264
|
+
}, this.securityConfig.challengeTtl || 300);
|
|
5265
|
+
|
|
5266
|
+
if (isApi) {
|
|
5267
|
+
decision.body = {
|
|
5268
|
+
challenge: { type: "gpu", nonce, clientSecret, iterations }
|
|
5269
|
+
};
|
|
5270
|
+
} else {
|
|
5271
|
+
decision.body = generateGpuChallengePage({ nonce, iterations, path });
|
|
5272
|
+
}
|
|
5273
|
+
return decision;
|
|
5274
|
+
}
|
|
5275
|
+
|
|
5073
5276
|
// Generate some trap URLs to embed in the challenge page.
|
|
5074
5277
|
// These links are visually hidden but present in the DOM to trap bots.
|
|
5075
5278
|
const trapUrls = Array.from({ length: 3 }, () => generateTrapUrl(nonce)); // Génère les URL
|
|
@@ -5339,8 +5542,22 @@ export async function handleCooperativeRequest(params, clientIp = '127.0.0.1', c
|
|
|
5339
5542
|
return { error: 'Missing signature' };
|
|
5340
5543
|
}
|
|
5341
5544
|
|
|
5342
|
-
|
|
5343
|
-
|
|
5545
|
+
const peersKey = `fed-peers:${zkpY}`;
|
|
5546
|
+
let reportedPeers = await store.get(peersKey) || [];
|
|
5547
|
+
if (!Array.isArray(reportedPeers)) {
|
|
5548
|
+
reportedPeers = [];
|
|
5549
|
+
}
|
|
5550
|
+
if (!reportedPeers.includes(clientIp)) {
|
|
5551
|
+
reportedPeers.push(clientIp);
|
|
5552
|
+
await store.set(peersKey, reportedPeers, 86400 * 30);
|
|
5553
|
+
}
|
|
5554
|
+
|
|
5555
|
+
const threshold = config.federationConsensusThreshold || 3;
|
|
5556
|
+
if (reportedPeers.length >= threshold) {
|
|
5557
|
+
await store.set(`banned-zkp-y:${zkpY}`, true, 86400 * 30);
|
|
5558
|
+
return { status: 'synchronized', banned: true };
|
|
5559
|
+
}
|
|
5560
|
+
return { status: 'synchronized', banned: false, reportsCount: reportedPeers.length };
|
|
5344
5561
|
}
|
|
5345
5562
|
|
|
5346
5563
|
const nodeId = params.node_id || '';
|
|
@@ -6277,9 +6494,9 @@ export const __internal = {
|
|
|
6277
6494
|
parseTcpSyn, // Expose for testing
|
|
6278
6495
|
classifyTcpOs, // Expose for testing
|
|
6279
6496
|
getTcpAnomalyScore, // Expose for testing,
|
|
6280
|
-
getQuicAnomalyScore, // NOUVEAU: Expose pour les tests
|
|
6281
6497
|
getRenderingAnomalyScore, // NOUVEAU: Expose pour les tests
|
|
6282
6498
|
dnsCircuitBreaker,
|
|
6499
|
+
getProtocolAnomalyScore,
|
|
6283
6500
|
recordDnsSuccess,
|
|
6284
6501
|
recordDnsFailure,
|
|
6285
6502
|
canAttemptDns,
|
|
@@ -6704,6 +6921,50 @@ export function startThresholdAutoTuning(options) {
|
|
|
6704
6921
|
if (!securityConfig || !trafficData) {
|
|
6705
6922
|
throw new Error("[AutoTuning] `securityConfig` et `trafficData` sont requis.");
|
|
6706
6923
|
}
|
|
6924
|
+
if (securityConfig.logger && typeof securityConfig.logger === 'function' && !securityConfig.logger_wrapped) {
|
|
6925
|
+
const originalLogger = securityConfig.logger;
|
|
6926
|
+
const maxPercentage = securityConfig.autotuning?.maxDensityPercentage || 0.02;
|
|
6927
|
+
|
|
6928
|
+
securityConfig.logger = function (log) {
|
|
6929
|
+
const ip = log.clientIp || log.ip;
|
|
6930
|
+
const subnet = ip ? __internal.getIpSubnet(ip, 24, 48) : null;
|
|
6931
|
+
const fp = log.deviceHash || log.fingerprint || log.deviceFingerprint;
|
|
6932
|
+
const stableFp = fp ? extractStablePart(fp) : null;
|
|
6933
|
+
|
|
6934
|
+
if (trafficData.length > 0) {
|
|
6935
|
+
const total = trafficData.length;
|
|
6936
|
+
let ipMatchCount = 0;
|
|
6937
|
+
let fpMatchCount = 0;
|
|
6938
|
+
let matchedKey = null;
|
|
6939
|
+
|
|
6940
|
+
for (let i = 0; i < trafficData.length; i++) {
|
|
6941
|
+
const existingLog = trafficData[i];
|
|
6942
|
+
const logIp = existingLog.clientIp || existingLog.ip;
|
|
6943
|
+
const logSubnet = logIp ? __internal.getIpSubnet(logIp, 24, 48) : null;
|
|
6944
|
+
const logFp = existingLog.deviceHash || existingLog.fingerprint || existingLog.deviceFingerprint;
|
|
6945
|
+
const logStableFp = logFp ? extractStablePart(logFp) : null;
|
|
6946
|
+
|
|
6947
|
+
const isIpMatch = subnet && logSubnet === subnet;
|
|
6948
|
+
const isFpMatch = stableFp && logStableFp === stableFp;
|
|
6949
|
+
|
|
6950
|
+
if (isIpMatch) ipMatchCount++;
|
|
6951
|
+
if (isFpMatch) fpMatchCount++;
|
|
6952
|
+
|
|
6953
|
+
if (isIpMatch || isFpMatch) matchedKey = i;
|
|
6954
|
+
}
|
|
6955
|
+
|
|
6956
|
+
if ((subnet && (ipMatchCount / total) > maxPercentage) || (stableFp && (fpMatchCount / total) > maxPercentage)) {
|
|
6957
|
+
if (matchedKey !== null) {
|
|
6958
|
+
trafficData[matchedKey].instancesCount = (trafficData[matchedKey].instancesCount || 1) + 1;
|
|
6959
|
+
trafficData[matchedKey].weight = (trafficData[matchedKey].weight || 1.0) + 1.0;
|
|
6960
|
+
}
|
|
6961
|
+
return;
|
|
6962
|
+
}
|
|
6963
|
+
}
|
|
6964
|
+
originalLogger(log);
|
|
6965
|
+
};
|
|
6966
|
+
securityConfig.logger_wrapped = true;
|
|
6967
|
+
}
|
|
6707
6968
|
|
|
6708
6969
|
console.log(`[AutoTuning] Job d'optimisation des seuils démarré. Prochain cycle dans ${interval / 60000} minutes.`);
|
|
6709
6970
|
|
package/src/js/gpu_pow.solver.js
CHANGED
|
@@ -53,26 +53,57 @@ export class GpuPowSolver {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
static deriveSampleIndices(clientIp, secret) {
|
|
57
|
+
const timeWindow = Math.floor(Date.now() / (1000 * 60 * 5));
|
|
58
|
+
const message = `${clientIp}:${timeWindow}`;
|
|
59
|
+
let hmacBytes;
|
|
60
|
+
if (typeof window === 'undefined') {
|
|
61
|
+
try {
|
|
62
|
+
const crypto = require('node:crypto');
|
|
63
|
+
const hmac = crypto.createHmac('sha256', secret || 'gpu-pow-salt').update(message).digest();
|
|
64
|
+
hmacBytes = new Uint8Array(hmac);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
hmacBytes = new TextEncoder().encode(message);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
hmacBytes = new TextEncoder().encode(message);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const indices = [];
|
|
73
|
+
for (let i = 0; i < 4; i++) {
|
|
74
|
+
const offset = (i * 2) % hmacBytes.length;
|
|
75
|
+
const value = (hmacBytes[offset] << 8) | hmacBytes[(offset + 1) % hmacBytes.length];
|
|
76
|
+
let idx = value % 64;
|
|
77
|
+
while (indices.includes(idx)) {
|
|
78
|
+
idx = (idx + 1) % 64;
|
|
79
|
+
}
|
|
80
|
+
indices.push(idx);
|
|
81
|
+
}
|
|
82
|
+
return indices;
|
|
83
|
+
}
|
|
84
|
+
|
|
56
85
|
/**
|
|
57
86
|
* Verifies a GPU PoW solution.
|
|
58
87
|
* To prevent server-side DoS, it verifies a sample of the 64 channels.
|
|
59
88
|
* @param {string} seed - The challenge seed.
|
|
60
89
|
* @param {number} iterations - Number of iterations.
|
|
61
90
|
* @param {string} solution - The comma-separated solution string.
|
|
62
|
-
* @param {
|
|
91
|
+
* @param {string} [clientIp='127.0.0.1'] - Client IP address.
|
|
92
|
+
* @param {string} [secret='gpu-pow-salt'] - Session secret key.
|
|
63
93
|
* @returns {boolean} True if the solution is valid.
|
|
64
94
|
*/
|
|
65
|
-
static verify(seed, iterations, solution,
|
|
95
|
+
static verify(seed, iterations, solution, clientIp = '127.0.0.1', secret = 'gpu-pow-salt') {
|
|
66
96
|
if (!solution || typeof solution !== 'string') return false;
|
|
67
97
|
const values = solution.split(',');
|
|
68
98
|
if (values.length !== 64) return false;
|
|
69
99
|
|
|
100
|
+
const sampleIndices = this.deriveSampleIndices(clientIp, secret);
|
|
70
101
|
const numericSeed = this._hashSeedToFloat(seed);
|
|
71
102
|
const r = 3.9999;
|
|
72
103
|
|
|
73
104
|
for (const idx of sampleIndices) {
|
|
74
105
|
if (idx < 0 || idx >= 64) return false;
|
|
75
|
-
let x = Math.fround(numericSeed + idx * 0.015);
|
|
106
|
+
let x = Math.fround((numericSeed + idx * 0.015) % 1);
|
|
76
107
|
const rFloat = Math.fround(r);
|
|
77
108
|
for (let i = 0; i < iterations; i++) {
|
|
78
109
|
x = Math.fround(rFloat * x * Math.fround(1.0 - x));
|
|
@@ -125,7 +156,7 @@ export class GpuPowSolver {
|
|
|
125
156
|
|
|
126
157
|
const inputData = new Float32Array(64);
|
|
127
158
|
for (let i = 0; i < 64; i++) {
|
|
128
|
-
inputData[i] = seed + (i * 0.015);
|
|
159
|
+
inputData[i] = (seed + (i * 0.015)) % 1;
|
|
129
160
|
}
|
|
130
161
|
|
|
131
162
|
const gpuBuffer = device.createBuffer({
|
|
@@ -179,7 +210,8 @@ export class GpuPowSolver {
|
|
|
179
210
|
uniform int uIterations;
|
|
180
211
|
void main() {
|
|
181
212
|
float index = gl_FragCoord.x + (gl_FragCoord.y * 8.0);
|
|
182
|
-
|
|
213
|
+
float x = mod(uSeed + (index * 0.015), 1.0);
|
|
214
|
+
float x = mod(uSeed + (index * 0.015), 1.0);
|
|
183
215
|
float r = 3.9999;
|
|
184
216
|
for(int i = 0; i < uIterations; i++) {
|
|
185
217
|
x = r * x * (1.0 - x);
|