@anonympins/fingerprint 0.7.0 → 0.7.2
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 +1 -1
- package/composer.json +8 -1
- package/package.json +1 -1
- package/public/fingerprint-wordpress.zip +0 -0
- package/src/js/build-client.js +8 -10
- package/src/js/fingerprint.builder.js +37 -38
- package/src/js/fingerprint.client.js +1531 -1463
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/src/js/fingerprint.js +190 -16
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +340 -376
- package/src/js/mongodb-store.js +1 -1
- package/src/js/optimization.worker.js +27 -27
- package/src/js/pow.solver.inline.js +154 -56
- package/src/js/pow.solver.js +180 -92
- package/src/js/pow.worker.js +48 -48
- package/src/js/redis-store.js +1 -1
- package/src/js/tests/fingerprint.client.init.test.js +146 -143
- package/src/js/tests/fingerprint.test.js +124 -10
- package/src/js/tests/ip-reputation.test.js +1 -0
- package/src/js/upow-model-task.js +14 -15
- package/src/php/AutoTuner.php +34 -34
- package/src/php/Challenge/ChallengeUtils.php +57 -11
- package/src/php/Config/SecurityProfiles.php +20 -0
- package/src/php/DirectFingerprint.php +190 -145
- package/src/php/FingerprintBuilder.php +29 -30
- package/src/php/FingerprintClient.php +147 -148
- package/src/php/FingerprintEngine.php +44 -17
- package/src/php/Ja3AnomalyDetector.php +33 -32
- package/src/php/Optimization/FunctionRegistry.php +7 -7
- package/src/php/Optimization/Optimization.php +24 -24
- package/src/php/Optimization/OptimizationOperators.php +26 -25
- package/src/php/Optimization/ProblemInitializers.php +52 -52
- package/src/php/ProblemManager.php +31 -34
- package/src/php/RequestContext.php +8 -1
- package/src/php/Store/IStore.php +7 -8
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +5 -5
- package/src/php/Store/RedisStore.php +5 -5
- package/src/php/Store/StoreManager.php +35 -35
- package/src/php/Tests/ChallengeUtilsTest.php +453 -453
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintEngineTest.php +38 -0
- package/src/php/Tests/IpReputationTest.php +10 -0
- package/src/php/Tests/MaliciousPatternsTest.php +103 -103
- package/src/php/Tests/MetricsTest.php +97 -97
- package/src/php/Tests/ProblemManagerTest.php +376 -376
- package/src/php/Tests/QuicFingerprintTest.php +53 -53
- package/src/php/Tests/RequestUtilsTest.php +471 -471
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
- package/src/php/Tests/config/ed25519_key.json +3 -3
- package/src/php/Utils/BlockList.php +99 -99
- package/src/php/Utils/Env.php +49 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +129 -7
- package/src/php/Utils/TLSClientHelloParser.php +369 -369
- package/src/php/WordPress/WpDbStore.php +155 -0
- package/src/php/WordPress/fingerprint-wordpress.php +554 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
- package/src/php/WordPress/package.php +218 -0
- package/src/php/bin/auto-tune.php +118 -118
package/src/js/fingerprint.js
CHANGED
|
@@ -2347,7 +2347,9 @@ function getBehaviorScore(context) {
|
|
|
2347
2347
|
if (metrics.honeypotInteraction) {
|
|
2348
2348
|
return { behaviorScore: 100 };
|
|
2349
2349
|
}
|
|
2350
|
-
|
|
2350
|
+
if (metrics.prototypeTampered) {
|
|
2351
|
+
score += 80; // Altération flagrante de l'environnement JS
|
|
2352
|
+
}
|
|
2351
2353
|
// 2. Analyse des mouvements de la souris
|
|
2352
2354
|
const { avgSpeed, avgAcceleration, straightness, pauses, segments } = analyzeMouseMovements(metrics.mouseMovementsHistory);
|
|
2353
2355
|
const touchAnalysis = analyzeTouchMovements(metrics.touchMovementsHistory);
|
|
@@ -2399,6 +2401,14 @@ function getBehaviorScore(context) {
|
|
|
2399
2401
|
const benfordDev = Optimization.Operators.benfordTest(touch.segments);
|
|
2400
2402
|
if (benfordDev > 0.18) score += 35;
|
|
2401
2403
|
}
|
|
2404
|
+
|
|
2405
|
+
// Détection de ferme mobile : Touch actif sur mobile sans aucune vibration physique (châssis/rack ADB)
|
|
2406
|
+
const isMobileDevice = (context.headers['user-agent'] || '').includes('Mobile');
|
|
2407
|
+
if (isMobileDevice && touchHistory.length >= 5 && typeof metrics.motionVariance === 'number') {
|
|
2408
|
+
if (metrics.motionVariance === 0) {
|
|
2409
|
+
score += 50; // Terminal fixé sur un châssis mécanique (rack ADB)
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2402
2412
|
}
|
|
2403
2413
|
|
|
2404
2414
|
// Plausibilité de la latence de frappe
|
|
@@ -3032,6 +3042,62 @@ function getIpSubnet(ip, ipv4Prefix = 24, ipv6Prefix = 48) { // eslint-disable-l
|
|
|
3032
3042
|
return null;
|
|
3033
3043
|
}
|
|
3034
3044
|
|
|
3045
|
+
/**
|
|
3046
|
+
* Calcule la longueur du préfixe commun (en bits) entre deux adresses IP (IPv4 ou IPv6).
|
|
3047
|
+
* @param {string} ip1
|
|
3048
|
+
* @param {string} ip2
|
|
3049
|
+
* @returns {number} Nombre de bits identiques en tête (0 à 32 pour IPv4, 0 à 128 pour IPv6).
|
|
3050
|
+
*/
|
|
3051
|
+
function getIpCommonPrefixLength(ip1, ip2) {
|
|
3052
|
+
if (!ip1 || !ip2) return 0;
|
|
3053
|
+
if (isIPv4(ip1) && isIPv4(ip2)) {
|
|
3054
|
+
const b1 = ip1.split('.').map(Number);
|
|
3055
|
+
const b2 = ip2.split('.').map(Number);
|
|
3056
|
+
const int1 = ((b1[0] << 24) | (b1[1] << 16) | (b1[2] << 8) | b1[3]) >>> 0;
|
|
3057
|
+
const int2 = ((b2[0] << 24) | (b2[1] << 16) | (b2[2] << 8) | b2[3]) >>> 0;
|
|
3058
|
+
const xor = (int1 ^ int2) >>> 0;
|
|
3059
|
+
return xor === 0 ? 32 : Math.clz32(xor);
|
|
3060
|
+
}
|
|
3061
|
+
if (isIPv6(ip1) && isIPv6(ip2)) {
|
|
3062
|
+
const normalize = (ip) => {
|
|
3063
|
+
let normalized = ip.trim().toLowerCase();
|
|
3064
|
+
if (normalized.includes("::")) {
|
|
3065
|
+
const parts = normalized.split("::");
|
|
3066
|
+
const left = parts[0] ? parts[0].split(":") : [];
|
|
3067
|
+
const right = parts[1] ? parts[1].split(":") : [];
|
|
3068
|
+
const missing = 8 - (left.length + right.length);
|
|
3069
|
+
const middle = Array(missing).fill("0000");
|
|
3070
|
+
normalized = [...left, ...middle, ...right].join(":");
|
|
3071
|
+
} else {
|
|
3072
|
+
const parts = normalized.split(":");
|
|
3073
|
+
if (parts.length !== 8) return null;
|
|
3074
|
+
}
|
|
3075
|
+
return normalized.split(":").map(g => parseInt(g, 16) || 0);
|
|
3076
|
+
};
|
|
3077
|
+
|
|
3078
|
+
try {
|
|
3079
|
+
const g1 = normalize(ip1);
|
|
3080
|
+
const g2 = normalize(ip2);
|
|
3081
|
+
if (!g1 || !g2) return 0;
|
|
3082
|
+
let prefix = 0;
|
|
3083
|
+
for (let i = 0; i < 8; i++) {
|
|
3084
|
+
const xor = (g1[i] ^ g2[i]) & 0xffff;
|
|
3085
|
+
if (xor === 0) {
|
|
3086
|
+
prefix += 16;
|
|
3087
|
+
} else {
|
|
3088
|
+
// clz32 opère sur des entiers 32 bits, ajuster pour 16 bits
|
|
3089
|
+
prefix += (Math.clz32(xor) - 16);
|
|
3090
|
+
break;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
return prefix;
|
|
3094
|
+
} catch (e) {
|
|
3095
|
+
return 0;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return 0;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3035
3101
|
/**
|
|
3036
3102
|
* Applies temporal decay (half-life of 30 minutes) to subnet metrics.
|
|
3037
3103
|
* @private
|
|
@@ -3079,6 +3145,11 @@ function decaySubnetData(subnetData, now) {
|
|
|
3079
3145
|
subnetData.uas = subnetData.uas.slice(0, newLen);
|
|
3080
3146
|
}
|
|
3081
3147
|
|
|
3148
|
+
if (Array.isArray(subnetData.attackerIps)) {
|
|
3149
|
+
const maxAttackerAgeMs = 60 * 60 * 1000; // 1 heure max
|
|
3150
|
+
subnetData.attackerIps = subnetData.attackerIps.filter(a => (now - (a.lastSeen || now)) < maxAttackerAgeMs);
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3082
3153
|
subnetData.lastActivity = now - (inactivityMs % (30 * 60 * 1000));
|
|
3083
3154
|
}
|
|
3084
3155
|
return subnetData;
|
|
@@ -3101,7 +3172,8 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
|
|
|
3101
3172
|
highScoreDevices: {},
|
|
3102
3173
|
lastActivity: 0,
|
|
3103
3174
|
ips: [],
|
|
3104
|
-
uas: []
|
|
3175
|
+
uas: [],
|
|
3176
|
+
attackerIps: []
|
|
3105
3177
|
};
|
|
3106
3178
|
|
|
3107
3179
|
if (!subnetData.highScoreDevices) {
|
|
@@ -3113,6 +3185,9 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
|
|
|
3113
3185
|
subnetData.ips = [];
|
|
3114
3186
|
}
|
|
3115
3187
|
if (!subnetData.uas) subnetData.uas = [];
|
|
3188
|
+
if (!Array.isArray(subnetData.attackerIps)) {
|
|
3189
|
+
subnetData.attackerIps = [];
|
|
3190
|
+
}
|
|
3116
3191
|
|
|
3117
3192
|
const now = Date.now();
|
|
3118
3193
|
decaySubnetData(subnetData, now);
|
|
@@ -3136,6 +3211,20 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
|
|
|
3136
3211
|
}
|
|
3137
3212
|
|
|
3138
3213
|
const userAgent = context.headers?.['user-agent'] || '';
|
|
3214
|
+
|
|
3215
|
+
if (finalScore >= 70) {
|
|
3216
|
+
const existingAttacker = subnetData.attackerIps.find(a => a.ip === context.clientIp);
|
|
3217
|
+
if (existingAttacker) {
|
|
3218
|
+
existingAttacker.lastSeen = now;
|
|
3219
|
+
existingAttacker.score = Math.max(existingAttacker.score || 0, finalScore);
|
|
3220
|
+
} else {
|
|
3221
|
+
subnetData.attackerIps.push({ ip: context.clientIp, lastSeen: now, score: finalScore });
|
|
3222
|
+
if (subnetData.attackerIps.length > 30) {
|
|
3223
|
+
subnetData.attackerIps.shift();
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3139
3228
|
if (userAgent && !subnetData.uas.includes(userAgent)) {
|
|
3140
3229
|
subnetData.uas.push(userAgent);
|
|
3141
3230
|
}
|
|
@@ -3178,9 +3267,14 @@ export function calculateAnalogInconsistencyScore(consistencyScore, inflectionPo
|
|
|
3178
3267
|
/**
|
|
3179
3268
|
* Calculates a suspicion score based on the historical activity of the IP subnet.
|
|
3180
3269
|
* @param {object} context The request context.
|
|
3270
|
+
* @param {string} [deviceId=''] The device ID.
|
|
3271
|
+
* @param {object} [securityConfig=null] Security configuration containing thresholds.
|
|
3181
3272
|
* @returns {Promise<{subnetScore: number}>}
|
|
3182
3273
|
*/
|
|
3183
|
-
async function getSubnetScore(context) {
|
|
3274
|
+
async function getSubnetScore(context, deviceId = '', securityConfig = null) {
|
|
3275
|
+
if (typeof deviceId === 'object' && deviceId !== null && !securityConfig) {
|
|
3276
|
+
securityConfig = deviceId;
|
|
3277
|
+
}
|
|
3184
3278
|
const subnet = getIpSubnet(context.clientIp);
|
|
3185
3279
|
if (!subnet) return { subnetScore: 0 };
|
|
3186
3280
|
const key = `subnet:${subnet}`;
|
|
@@ -3223,14 +3317,61 @@ async function getSubnetScore(context) {
|
|
|
3223
3317
|
const uaDispersion = Math.min(3.0, Math.max(1, uaCount) / deviceCount);
|
|
3224
3318
|
const uaMultiplier = 0.7 + 0.3 * Math.tanh(uaDispersion - 1.0);
|
|
3225
3319
|
|
|
3226
|
-
// 4. Intensité brute continue de la menace
|
|
3320
|
+
// 4. Intensité brute continue de la menace
|
|
3227
3321
|
const rawThreatIntensity = highScoreCount * bayesianDensity * ipMultiplier * uaMultiplier;
|
|
3228
3322
|
|
|
3229
|
-
// 5.
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
const
|
|
3323
|
+
// 5. Composante 1 : Score ambiant plafonné en zone Medium (asymptote au seuil medium)
|
|
3324
|
+
const mediumThreshold = (securityConfig?.thresholds?.medium !== undefined)
|
|
3325
|
+
? Number(securityConfig.thresholds.medium)
|
|
3326
|
+
: 45.0;
|
|
3327
|
+
const ambientAsymptote = mediumThreshold;
|
|
3328
|
+
const maxProximityBoost = Math.max(0.0, 100.0 - ambientAsymptote);
|
|
3329
|
+
const scaleFactor = 10.0;
|
|
3330
|
+
const ambientScore = ambientAsymptote * Math.tanh(rawThreatIntensity / scaleFactor);
|
|
3331
|
+
|
|
3332
|
+
// 6. Composante 2 : Boost de proximité micro-réseau avec des attaquants récents (le reste va jusqu'à 100)
|
|
3333
|
+
let proximityBoost = 0.0;
|
|
3334
|
+
const attackerIps = subnetData.attackerIps || [];
|
|
3335
|
+
const clientIp = context.clientIp;
|
|
3336
|
+
|
|
3337
|
+
if (attackerIps.length > 0 && clientIp) {
|
|
3338
|
+
let maxCommonPrefix = 0;
|
|
3339
|
+
const isClientV4 = isIPv4(clientIp);
|
|
3340
|
+
const isClientV6 = isIPv6(clientIp);
|
|
3341
|
+
|
|
3342
|
+
for (const att of attackerIps) {
|
|
3343
|
+
if (att.ip && (now - (att.lastSeen || now)) <= 30 * 60 * 1000) {
|
|
3344
|
+
const prefixLen = getIpCommonPrefixLength(clientIp, att.ip);
|
|
3345
|
+
if (prefixLen > maxCommonPrefix) {
|
|
3346
|
+
maxCommonPrefix = prefixLen;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
if (isClientV4) {
|
|
3352
|
+
if (maxCommonPrefix >= 32) {
|
|
3353
|
+
proximityBoost = maxProximityBoost; // Même adresse IP exacte
|
|
3354
|
+
} else if (maxCommonPrefix >= 30) {
|
|
3355
|
+
proximityBoost = maxProximityBoost * (45.0 / 55.0); // Même /30 (écart <= 3 adresses)
|
|
3356
|
+
} else if (maxCommonPrefix >= 28) {
|
|
3357
|
+
proximityBoost = maxProximityBoost * (30.0 / 55.0); // Même /28 (bloc de 16 adresses)
|
|
3358
|
+
} else if (maxCommonPrefix >= 26) {
|
|
3359
|
+
proximityBoost = maxProximityBoost * (15.0 / 55.0); // Même /26 (bloc de 64 adresses)
|
|
3360
|
+
}
|
|
3361
|
+
} else if (isClientV6) {
|
|
3362
|
+
if (maxCommonPrefix >= 128) {
|
|
3363
|
+
proximityBoost = maxProximityBoost; // Même IPv6 exacte
|
|
3364
|
+
} else if (maxCommonPrefix >= 120) {
|
|
3365
|
+
proximityBoost = maxProximityBoost * (45.0 / 55.0); // Même /120
|
|
3366
|
+
} else if (maxCommonPrefix >= 112) {
|
|
3367
|
+
proximityBoost = maxProximityBoost * (30.0 / 55.0); // Même /112
|
|
3368
|
+
} else if (maxCommonPrefix >= 96) {
|
|
3369
|
+
proximityBoost = maxProximityBoost * (15.0 / 55.0); // Même /96
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
const finalScore = Math.min(100.0, Math.round((ambientScore + proximityBoost) * 10) / 10);
|
|
3234
3375
|
|
|
3235
3376
|
return { subnetScore: finalScore };
|
|
3236
3377
|
}
|
|
@@ -3809,7 +3950,7 @@ export const getSuspicionVector = async (context, securityConfig) => {
|
|
|
3809
3950
|
getBehavioralIndicators(context, deviceData), // This modifies deviceData, so it must be done before saving deviceData
|
|
3810
3951
|
getThreatIntelScore(context, zkpY), // NOUVEAU: Score de Threat Intelligence Fédéré
|
|
3811
3952
|
getTlsSpoofingScore(context),
|
|
3812
|
-
getSubnetScore(context, deviceId),
|
|
3953
|
+
getSubnetScore(context, deviceId, securityConfig),
|
|
3813
3954
|
getIpReputationScore(clientIp),
|
|
3814
3955
|
getBotnetClusterScore(context, stableFpHash),
|
|
3815
3956
|
store.set(`ip-device:${clientIp}`, deviceId, 600) // Link the IP to the device for 10 minutes
|
|
@@ -4331,6 +4472,17 @@ export class FingerprintEngine {
|
|
|
4331
4472
|
}
|
|
4332
4473
|
}
|
|
4333
4474
|
}
|
|
4475
|
+
|
|
4476
|
+
// If no security config is provided at all, create a base one.
|
|
4477
|
+
if (!finalConfig) {
|
|
4478
|
+
finalConfig = {};
|
|
4479
|
+
}
|
|
4480
|
+
// If the whitelist is not explicitly provided in the configuration,
|
|
4481
|
+
// apply the default whitelist which includes common search engine bots.
|
|
4482
|
+
if (!finalConfig.whitelist) {
|
|
4483
|
+
finalConfig.whitelist = default_whitelist();
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4334
4486
|
this.securityConfig = finalConfig;
|
|
4335
4487
|
this.isProduction = isProduction;
|
|
4336
4488
|
this._allowlist = this._buildAllowlist();
|
|
@@ -4529,7 +4681,8 @@ export class FingerprintEngine {
|
|
|
4529
4681
|
return false;
|
|
4530
4682
|
}
|
|
4531
4683
|
_isIpInAllowlist(clientIp) {
|
|
4532
|
-
|
|
4684
|
+
const family = isIPv6(clientIp) ? 'ipv6' : 'ipv4';
|
|
4685
|
+
return this._allowlist.check(clientIp, family);
|
|
4533
4686
|
}
|
|
4534
4687
|
/**
|
|
4535
4688
|
* Checks if the request's host and path match an entry in the host+path allowlist.
|
|
@@ -4668,7 +4821,10 @@ export class FingerprintEngine {
|
|
|
4668
4821
|
return false;
|
|
4669
4822
|
}
|
|
4670
4823
|
|
|
4824
|
+
const botName = matchedRule.userAgent; // e.g., 'Googlebot'
|
|
4825
|
+
|
|
4671
4826
|
if (!canAttemptDns()) {
|
|
4827
|
+
this._log(`[Bot Verification] DNS circuit breaker is open. Skipping check for ${botName}.`, { clientIp });
|
|
4672
4828
|
return false;
|
|
4673
4829
|
}
|
|
4674
4830
|
|
|
@@ -4676,9 +4832,11 @@ export class FingerprintEngine {
|
|
|
4676
4832
|
const cachedStatus = await store.get(cacheKey);
|
|
4677
4833
|
|
|
4678
4834
|
if (cachedStatus === 'verified') {
|
|
4835
|
+
this._log(`[Bot Verification] PASSED (cached): ${botName}`, { clientIp });
|
|
4679
4836
|
return true;
|
|
4680
4837
|
}
|
|
4681
4838
|
if (cachedStatus === 'failed') {
|
|
4839
|
+
this._log(`[Bot Verification] FAILED (cached): ${botName}`, { clientIp });
|
|
4682
4840
|
return false;
|
|
4683
4841
|
}
|
|
4684
4842
|
|
|
@@ -4688,25 +4846,35 @@ export class FingerprintEngine {
|
|
|
4688
4846
|
const validHostname = hostnames.find(h => h.endsWith(matchedRule.hostnameSuffix));
|
|
4689
4847
|
|
|
4690
4848
|
if (!validHostname) {
|
|
4849
|
+
this._log(`[Bot Verification] FAILED: Reverse DNS lookup for ${clientIp} did not yield a valid hostname ending in '${matchedRule.hostnameSuffix}'.`, { clientIp, resolvedHostnames: hostnames });
|
|
4691
4850
|
await store.set(cacheKey, 'failed', 300); // Temporary negative caching (5 minutes)
|
|
4692
4851
|
return false;
|
|
4693
4852
|
}
|
|
4694
4853
|
|
|
4854
|
+
this._log(`[Bot Verification] Reverse DNS OK for ${botName}.`, { clientIp, validHostname });
|
|
4855
|
+
|
|
4695
4856
|
// 2. Forward DNS lookup
|
|
4696
4857
|
let addresses = [];
|
|
4697
4858
|
try {
|
|
4698
4859
|
addresses = await withTimeout(dns.resolve(validHostname), 500);
|
|
4699
|
-
} catch (e) {
|
|
4860
|
+
} catch (e) {
|
|
4861
|
+
this._log(`[Bot Verification] Forward DNS (A) lookup FAILED for ${validHostname}.`, { error: e.message });
|
|
4862
|
+
}
|
|
4700
4863
|
try {
|
|
4701
4864
|
const ipv6 = await withTimeout(dns.resolve(validHostname, 'AAAA'), 500);
|
|
4702
4865
|
addresses = addresses.concat(ipv6);
|
|
4703
|
-
} catch (e) {
|
|
4866
|
+
} catch (e) {
|
|
4867
|
+
this._log(`[Bot Verification] Forward DNS (AAAA) lookup FAILED for ${validHostname}.`, { error: e.message });
|
|
4868
|
+
}
|
|
4704
4869
|
if (addresses.includes(clientIp)) {
|
|
4870
|
+
this._log(`[Bot Verification] PASSED: Forward DNS IP matches original IP for ${botName}.`, { clientIp, validHostname, resolvedIps: addresses });
|
|
4705
4871
|
recordDnsSuccess();
|
|
4706
4872
|
await store.set(cacheKey, 'verified', 86400); // Cache success for 24h (TTL in seconds)
|
|
4707
4873
|
return true;
|
|
4708
4874
|
}
|
|
4875
|
+
this._log(`[Bot Verification] FAILED: IP mismatch for ${botName}. Original IP not in resolved addresses.`, { clientIp, validHostname, resolvedIps: addresses });
|
|
4709
4876
|
} catch (error) {
|
|
4877
|
+
this._log(`[Bot Verification] FAILED: DNS lookup error for ${botName}.`, { clientIp, error: error.message });
|
|
4710
4878
|
recordDnsFailure();
|
|
4711
4879
|
await store.set(cacheKey, 'failed', 300); // Temporary negative caching (5 minutes)
|
|
4712
4880
|
return false;
|
|
@@ -4824,15 +4992,15 @@ export class FingerprintEngine {
|
|
|
4824
4992
|
}
|
|
4825
4993
|
}
|
|
4826
4994
|
|
|
4827
|
-
|
|
4828
|
-
if (
|
|
4995
|
+
const botVerificationResult = await this._verifyWhitelistedBot(requestContext);
|
|
4996
|
+
if (botVerificationResult === true) {
|
|
4829
4997
|
const filterWhitelist = this.securityConfig.filterWhitelist || false;
|
|
4830
4998
|
let bypassWhitelist = false;
|
|
4831
4999
|
if (filterWhitelist === true) {
|
|
4832
5000
|
bypassWhitelist = this._hasCertainAttack(requestContext);
|
|
4833
5001
|
} else if (typeof filterWhitelist === 'number') {
|
|
4834
5002
|
const res = await getScoreAndVector();
|
|
4835
|
-
if (res.score > filterWhitelist) {
|
|
5003
|
+
if (res.score > filterWhitelist) { // NOSONAR
|
|
4836
5004
|
bypassWhitelist = true;
|
|
4837
5005
|
}
|
|
4838
5006
|
}
|
|
@@ -4872,6 +5040,11 @@ export class FingerprintEngine {
|
|
|
4872
5040
|
|
|
4873
5041
|
// The engine now works with the context directly, no more rawReq dependency here.
|
|
4874
5042
|
const suspicionVector = preCalculatedVector || await __internal.getSuspicionVector(requestContext, this.securityConfig);
|
|
5043
|
+
if (botVerificationResult === false) {
|
|
5044
|
+
// This means it claimed to be a bot but failed verification.
|
|
5045
|
+
// This is a strong signal of spoofing.
|
|
5046
|
+
suspicionVector.tlsSpoofingScore = Math.max(suspicionVector.tlsSpoofingScore || 0, 95);
|
|
5047
|
+
}
|
|
4875
5048
|
let finalScore = preCalculatedScore !== null ? preCalculatedScore : this.calculateFinalScore(suspicionVector);
|
|
4876
5049
|
|
|
4877
5050
|
this._log('Suspicion vector calculated', {
|
|
@@ -6774,6 +6947,7 @@ export const __internal = {
|
|
|
6774
6947
|
generateCombinedPoWChallengePage,
|
|
6775
6948
|
problemManager, // Re-export the problemManager promise
|
|
6776
6949
|
getIpSubnet, // Expose for testing
|
|
6950
|
+
getIpCommonPrefixLength, // Expose for testing
|
|
6777
6951
|
updateSubnetMetrics, // Expose for testing
|
|
6778
6952
|
getSubnetScore, // Expose for testing
|
|
6779
6953
|
getIpReputationScore, // Expose for testing
|