@anonympins/fingerprint 0.2.3 → 0.3.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/README.md +19 -10
- package/fingerprint.builder.js +171 -160
- package/fingerprint.client.js +5 -0
- package/fingerprint.js +317 -113
- package/library.js +1727 -1694
- package/package.json +1 -1
- package/problem-manager.js +523 -423
package/README.md
CHANGED
|
@@ -14,6 +14,10 @@ This system identifies and slows down bots and automated scripts by evaluating t
|
|
|
14
14
|
The process unfolds in three steps:
|
|
15
15
|
|
|
16
16
|
1. **Identification & Fingerprinting**: A unique fingerprint is generated for each device. This combines a client-side browser fingerprint, server-side request headers, and the **JA3 fingerprint** from the TLS handshake, which reliably identifies the underlying HTTP client library (e.g., Chrome vs. a Python script). A `device_id` cookie is used to track the device over time.
|
|
17
|
+
* **Advanced TLS Fingerprinting (JA4/JA4H)**: Beyond JA3, the system can leverage JA4/JA4H (if provided by a reverse proxy like Cloudflare or Akamai) for a more robust and modern TLS fingerprint, especially for HTTP/2 traffic.
|
|
18
|
+
* **HTTP/2 Fingerprinting**: Analyzes HTTP/2 specific characteristics (settings frame, priority, window update) to identify client libraries.
|
|
19
|
+
* **TCP/IP Fingerprinting**: If available (e.g., from a specialized reverse proxy), low-level TCP/IP stack characteristics (TTL, window size, options) are used for identification.
|
|
20
|
+
* A `device_id` cookie is used to track the device over time.
|
|
17
21
|
2. **Suspicion Score Calculation**: Several indicators are analyzed to calculate a suspicion score:
|
|
18
22
|
* **Header Anomalies**: Missing `User-Agent`, `Accept-Language`, etc.
|
|
19
23
|
* **Device Behavior**: Rapid fingerprint changes (User-Agent rotation).
|
|
@@ -22,10 +26,11 @@ The process unfolds in three steps:
|
|
|
22
26
|
* **Cross-Layer Inconsistency**: Mismatches between client-side data (e.g., OS reported by the browser) and server-side headers (e.g., `User-Agent`).
|
|
23
27
|
* **Request Patterns**: Repetitive, rapid-fire, or sequential requests typical of scraping bots. The parameters for detecting these patterns (e.g., request velocity, burst detection) are dynamically adjusted by the auto-tuner for optimal performance.
|
|
24
28
|
* **Honeypot Trap**: Detection of bots that automatically fill hidden form fields or probe for common but unused URL parameters (e.g., `?debug=true`).
|
|
25
|
-
|
|
29
|
+
* **TLS Fingerprint Spoofing**: Detects inconsistencies between the TLS fingerprint (JA3/JA4) and other HTTP headers (e.g., User-Agent), indicating an attempt to disguise the client.
|
|
30
|
+
3. **Dynamic Challenge**: If the suspicion score exceeds a certain threshold, a challenge is presented. The difficulty and type of challenge depend on the score:
|
|
26
31
|
* **Low to Medium Suspicion**: A combined **CPU and Memory Proof-of-Work (PoW)** challenge is issued. The difficulty of both the CPU (hash calculation) and Memory (allocation and computation) components scales progressively with the suspicion score. For low scores, the memory challenge is negligible, making it primarily a CPU task.
|
|
27
|
-
* **High Suspicion**: For the most suspicious requests, the system issues a high-difficulty combined CPU/Memory challenge. The architecture allows for plugging in more complex challenges like CAPTCHAs if needed.
|
|
28
|
-
* **New Devices**: To increase the cost for bots that simply clear their cookies, new (unseen) devices are systematically presented with a minimal, almost imperceptible challenge on their first visit
|
|
32
|
+
* **High Suspicion**: For the most suspicious requests, the system issues a high-difficulty combined CPU/Memory challenge or a "Useful Proof-of-Work" task (see below). The architecture allows for plugging in more complex challenges like CAPTCHAs if needed.
|
|
33
|
+
* **New Devices**: To increase the cost for bots that simply clear their cookies, new (unseen) devices are systematically presented with a minimal, almost imperceptible challenge on their first visit. This behavior is enabled by default in `strict` and `ecommerce` profiles and can be configured with the `challengeNewDevices` option.
|
|
29
34
|
|
|
30
35
|
Once the challenge is solved, a clearance "ticket" is issued via a secure cookie, exempting the user from new challenges. The duration of this ticket is dynamic:
|
|
31
36
|
- **Probationary Ticket**: If the request was moderately suspicious, a very short-lived "probationary" ticket (e.g., 30 seconds) is issued. This forces the client to be re-evaluated quickly, increasing security.
|
|
@@ -38,10 +43,11 @@ For API clients, the challenge is delivered as a `404` JSON response, and the cl
|
|
|
38
43
|
- **Multi-Factor Fingerprinting**: Combines client-side data (`hardwareConcurrency`, `deviceMemory`, `screen`, `canvas`, `webgl`) and server-side data (`User-Agent`, `Client-Hints`).
|
|
39
44
|
- **Secure Ticket System**: Uses HMAC-SHA256 signatures to validate clearances and prevent tampering.
|
|
40
45
|
- **Pluggable Datastore**: Supports external datastores like Redis for state persistence and scalability across multiple server instances.
|
|
41
|
-
- **Express.js Middleware**: Easy integration into an Express application with `powMiddleware`.
|
|
46
|
+
- **Express.js Middleware**: Easy integration into an Express application with `powMiddleware`.
|
|
42
47
|
- **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure validation of tickets and other signatures.
|
|
43
48
|
- **Bot Whitelisting**: Includes a DNS-based verification mechanism to reliably identify and whitelist legitimate crawlers like Googlebot and Bingbot, preventing them from being challenged. The results are cached for optimal performance.
|
|
44
|
-
- **Automatic Parameter Tuning**: Includes a genetic algorithm-based optimizer (`startThresholdAutoTuning`) that analyzes real traffic to dynamically adjust
|
|
49
|
+
- **Automatic Parameter Tuning**: Includes a genetic algorithm-based optimizer (`startThresholdAutoTuning`) that analyzes real traffic to dynamically adjust suspicion thresholds, weights, and behavioral pattern detection parameters, improving accuracy and reducing false positives over time. The tuner is hardened against data poisoning attempts.
|
|
50
|
+
- **Hardened Security**: Protects against various attacks, including DoS via memory exhaustion, invalid nonce submission, and uses cryptographically secure randomness for all sensitive operations.
|
|
45
51
|
|
|
46
52
|
## Installation and Usage
|
|
47
53
|
|
|
@@ -130,7 +136,7 @@ app.listen(3000, () => console.log('Server started on port 3000'));
|
|
|
130
136
|
|
|
131
137
|
### Full Configuration Example
|
|
132
138
|
|
|
133
|
-
If you prefer to define the entire configuration manually instead of using a profile, you can create a `securityConfig` object with all the parameters. All parameters are optional.
|
|
139
|
+
If you prefer to define the entire configuration manually instead of using a profile, you can create a `securityConfig` object with all the parameters. All parameters are optional, but it is highly recommended to review and adjust them for your specific needs. The engine will warn you about any unknown keys in this configuration, helping you catch typos.
|
|
134
140
|
|
|
135
141
|
```javascript
|
|
136
142
|
import { default_whitelist, default_analyzers } from './fingerprint.js';
|
|
@@ -156,7 +162,8 @@ const securityConfig = {
|
|
|
156
162
|
behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
|
|
157
163
|
honeypotScore: 1.0, // Strongly penalizes bots filling hidden form fields
|
|
158
164
|
crossLayerInconsistencyScore: 0.4, // Penalizes mismatches between client-side data (e.g., OS) and server-side headers (e.g., User-Agent)
|
|
159
|
-
timeInconsistencyScore: 0.9 // Strongly penalizes large time gaps between client metric collection and server reception (replay attack)
|
|
165
|
+
timeInconsistencyScore: 0.9, // Strongly penalizes large time gaps between client metric collection and server reception (replay attack)
|
|
166
|
+
tlsSpoofingScore: 0.8 // Penalizes mismatches between the TLS fingerprint (JA3/JA4) and the User-Agent (client spoofing)
|
|
160
167
|
},
|
|
161
168
|
thresholds: {
|
|
162
169
|
low: 20, // Score from which a CPU challenge is issued
|
|
@@ -264,7 +271,8 @@ const securityConfig = {
|
|
|
264
271
|
maxDataPoints: 20000 // Minimum requests before starting an optimization cycle.
|
|
265
272
|
},
|
|
266
273
|
// Enables problem solving for suspicious activity (configurable in problems.config.json)
|
|
267
|
-
enableUsefulWork: true
|
|
274
|
+
enableUsefulWork: true,
|
|
275
|
+
usefulWorkConfigPath: './path/to/your/problems.config.json' // (Optional) Path to the useful work configuration.
|
|
268
276
|
};
|
|
269
277
|
```
|
|
270
278
|
|
|
@@ -607,9 +615,9 @@ The engine is a named export from the main module.
|
|
|
607
615
|
|
|
608
616
|
### Useful Proof-of-Work (`ProblemManager`)
|
|
609
617
|
|
|
610
|
-
Instead of issuing a generic Proof-of-Work, the system can dispatch a "useful" computational problem to a suspicious client. This allows harnessing the client's CPU cycles to solve complex problems (like optimization tasks) over time. This feature is managed by the `ProblemManager` class, which is enabled via the `enableUsefulWork: true` flag in the security configuration.
|
|
618
|
+
Instead of issuing a generic Proof-of-Work, the system can dispatch a "useful" computational problem to a suspicious client. This allows harnessing the client's CPU cycles to solve complex problems (like optimization tasks) over time. This feature is managed by the `ProblemManager` class, which is enabled via the `enableUsefulWork: true` flag in the security configuration. The state of these problems is persisted via the configured datastore, allowing a cluster of servers to collaborate on solving them.
|
|
611
619
|
|
|
612
|
-
The `ProblemManager` reads its configuration from `problems.config.json`, which defines the problems to be solved, the type of work units, and the
|
|
620
|
+
The `ProblemManager` reads its configuration asynchronously from `problems.config.json`, which defines the problems to be solved, the type of work units, and the initial state of the solutions.
|
|
613
621
|
|
|
614
622
|
While you typically won't interact with it directly, its methods are exported and can be used for monitoring or manual administration. The main instance is exported as `problemManager`.
|
|
615
623
|
|
|
@@ -661,6 +669,7 @@ Updates the payload (parameters) of a specific problem by its ID. This allows fo
|
|
|
661
669
|
|
|
662
670
|
---
|
|
663
671
|
|
|
672
|
+
## NodeJS raw integration
|
|
664
673
|
**Workflow:**
|
|
665
674
|
|
|
666
675
|
1. **Instantiate the Engine**: Create an instance with your `securityConfig`.
|
package/fingerprint.builder.js
CHANGED
|
@@ -1,161 +1,172 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Algorithme de hachage cyrb53 (rapide et faible taux de collision).
|
|
3
|
-
*/
|
|
4
|
-
export const cyrb53 = (str, seed = 0) => {
|
|
5
|
-
let h1 = 0xdeadbeef ^ seed,
|
|
6
|
-
h2 = 0x41c6ce57 ^ seed;
|
|
7
|
-
for (let i = 0, ch; i < str.length; i++) {
|
|
8
|
-
ch = str.charCodeAt(i);
|
|
9
|
-
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
10
|
-
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
11
|
-
}
|
|
12
|
-
h1 =
|
|
13
|
-
Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
|
|
14
|
-
Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
15
|
-
h2 =
|
|
16
|
-
Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
|
|
17
|
-
Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
18
|
-
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Classe pour construire une empreinte composite (Multi-Hash).
|
|
23
|
-
* Format de sortie : "grp1:hash1|grp2:hash2|grp3:hash3"
|
|
24
|
-
*/
|
|
25
|
-
export class FingerprintBuilder {
|
|
26
|
-
constructor() {
|
|
27
|
-
this.components = new Map();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Ajoute un composant au hash global.
|
|
32
|
-
* @param {string} group - Le nom du groupe (ex: 'hw', 'screen', 'geo')
|
|
33
|
-
* @param {string|number|boolean} value - La valeur brute à hasher
|
|
34
|
-
*/
|
|
35
|
-
add(group, value) {
|
|
36
|
-
if (value === undefined || value === null) return this;
|
|
37
|
-
// On hash la valeur individuellement pour l'anonymiser et réduire sa taille
|
|
38
|
-
this.components.set(group, cyrb53(String(value)));
|
|
39
|
-
return this;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Adds a raw component without hashing it.
|
|
44
|
-
* Useful for metrics that need to be read on the server.
|
|
45
|
-
* @param {string} group - The name of the group.
|
|
46
|
-
* @param {string|number} value - The raw value.
|
|
47
|
-
*/
|
|
48
|
-
addRaw(group, value) {
|
|
49
|
-
if (value === undefined || value === null) return this;
|
|
50
|
-
this.components.set(group, value);
|
|
51
|
-
return this;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Affiche les composants actuels dans la console.
|
|
56
|
-
* @param {string} [title='FingerprintBuilder Components'] - Un titre pour le log.
|
|
57
|
-
*/
|
|
58
|
-
log(title = 'FingerprintBuilder Components') {
|
|
59
|
-
console.log(`--- ${title} ---`);
|
|
60
|
-
const sortedComponents = Array.from(this.components.entries())
|
|
61
|
-
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
62
|
-
|
|
63
|
-
console.table(Object.fromEntries(sortedComponents));
|
|
64
|
-
console.log(`Final string: ${this.toString()}`);
|
|
65
|
-
console.log(`---------------------------------${'-'.repeat(title.length)}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Génère la chaîne de signature finale.
|
|
70
|
-
* Trie les clés pour garantir un ordre déterministe.
|
|
71
|
-
*/
|
|
72
|
-
toString() {
|
|
73
|
-
return Array.from(this.components.entries())
|
|
74
|
-
.sort((a, b) => a[0].localeCompare(b[0])) // Tri alphabétique des clés
|
|
75
|
-
.map(([key, hash]) => `${key}:${hash}`)
|
|
76
|
-
.join("|");
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Adds a raw component without hashing it.
|
|
80
|
-
* Useful for metrics that need to be read on the server.
|
|
81
|
-
* @param {string} group - The name of the group.
|
|
82
|
-
* @param {string|number} value - The raw value.
|
|
83
|
-
*/
|
|
84
|
-
addRaw(group, value) {
|
|
85
|
-
if (value === undefined || value === null) return this;
|
|
86
|
-
this.components.set(group, value);
|
|
87
|
-
return this;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Compares two fingerprints and returns a similarity score (0 to 1).
|
|
92
|
-
* Uses weights to give more importance to strong invariants (Canvas, GPU).
|
|
93
|
-
* @param {string} fpString1 - Fingerprint A
|
|
94
|
-
* @param {string} fpString2 - Fingerprint B
|
|
95
|
-
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
'
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Algorithme de hachage cyrb53 (rapide et faible taux de collision).
|
|
3
|
+
*/
|
|
4
|
+
export const cyrb53 = (str, seed = 0) => {
|
|
5
|
+
let h1 = 0xdeadbeef ^ seed,
|
|
6
|
+
h2 = 0x41c6ce57 ^ seed;
|
|
7
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
8
|
+
ch = str.charCodeAt(i);
|
|
9
|
+
h1 = Math.imul(h1 ^ ch, 2654435761); // Use Math.imul for 32-bit multiplication
|
|
10
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
11
|
+
}
|
|
12
|
+
h1 =
|
|
13
|
+
Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
|
|
14
|
+
Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
15
|
+
h2 =
|
|
16
|
+
Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
|
|
17
|
+
Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
18
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Classe pour construire une empreinte composite (Multi-Hash).
|
|
23
|
+
* Format de sortie : "grp1:hash1|grp2:hash2|grp3:hash3"
|
|
24
|
+
*/
|
|
25
|
+
export class FingerprintBuilder {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.components = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ajoute un composant au hash global.
|
|
32
|
+
* @param {string} group - Le nom du groupe (ex: 'hw', 'screen', 'geo')
|
|
33
|
+
* @param {string|number|boolean} value - La valeur brute à hasher
|
|
34
|
+
*/
|
|
35
|
+
add(group, value) {
|
|
36
|
+
if (value === undefined || value === null) return this;
|
|
37
|
+
// On hash la valeur individuellement pour l'anonymiser et réduire sa taille
|
|
38
|
+
this.components.set(group, cyrb53(String(value)));
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Adds a raw component without hashing it.
|
|
44
|
+
* Useful for metrics that need to be read on the server.
|
|
45
|
+
* @param {string} group - The name of the group.
|
|
46
|
+
* @param {string|number} value - The raw value.
|
|
47
|
+
*/
|
|
48
|
+
addRaw(group, value) {
|
|
49
|
+
if (value === undefined || value === null) return this;
|
|
50
|
+
this.components.set(group, value);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Affiche les composants actuels dans la console.
|
|
56
|
+
* @param {string} [title='FingerprintBuilder Components'] - Un titre pour le log.
|
|
57
|
+
*/
|
|
58
|
+
log(title = 'FingerprintBuilder Components') {
|
|
59
|
+
console.log(`--- ${title} ---`);
|
|
60
|
+
const sortedComponents = Array.from(this.components.entries())
|
|
61
|
+
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
62
|
+
|
|
63
|
+
console.table(Object.fromEntries(sortedComponents));
|
|
64
|
+
console.log(`Final string: ${this.toString()}`);
|
|
65
|
+
console.log(`---------------------------------${'-'.repeat(title.length)}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Génère la chaîne de signature finale.
|
|
70
|
+
* Trie les clés pour garantir un ordre déterministe.
|
|
71
|
+
*/
|
|
72
|
+
toString() {
|
|
73
|
+
return Array.from(this.components.entries())
|
|
74
|
+
.sort((a, b) => a[0].localeCompare(b[0])) // Tri alphabétique des clés
|
|
75
|
+
.map(([key, hash]) => `${key}:${hash}`)
|
|
76
|
+
.join("|");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Adds a raw component without hashing it.
|
|
80
|
+
* Useful for metrics that need to be read on the server.
|
|
81
|
+
* @param {string} group - The name of the group.
|
|
82
|
+
* @param {string|number} value - The raw value.
|
|
83
|
+
*/
|
|
84
|
+
addRaw(group, value) {
|
|
85
|
+
if (value === undefined || value === null) return this;
|
|
86
|
+
this.components.set(group, value);
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Compares two fingerprints and returns a similarity score (0 to 1).
|
|
92
|
+
* Uses weights to give more importance to strong invariants (Canvas, GPU).
|
|
93
|
+
* @param {string} fpString1 - Fingerprint A
|
|
94
|
+
* @param {string} fpString2 - Fingerprint B
|
|
95
|
+
*/
|
|
96
|
+
// Note on `volatileKeys`: These keys are ignored during the comparison between the fingerprint
|
|
97
|
+
// of the request that *triggered* a challenge and the fingerprint of the request that *submits*
|
|
98
|
+
// the solution. This is because headers like Client-Hints (ch_*), cookie presence, and upgrade-insecure-requests
|
|
99
|
+
// can legitimately change or be absent on the subsequent request, especially after a redirect.
|
|
100
|
+
// By ignoring them, we focus the comparison on more stable identifiers like UA, JA3, GPU, etc.
|
|
101
|
+
static compare(fpString1, fpString2) {
|
|
102
|
+
if (!fpString1 || !fpString2) return 0;
|
|
103
|
+
|
|
104
|
+
const parse = (str) => new Map(str.split("|").map(part => part.split(":")).filter(([k,v]) => k && v));
|
|
105
|
+
|
|
106
|
+
const map1 = parse(fpString1);
|
|
107
|
+
const map2 = parse(fpString2);
|
|
108
|
+
|
|
109
|
+
// Keys to ignore when comparing the initial request fingerprint with the challenge solver's fingerprint.
|
|
110
|
+
// Headers like Client-Hints (ch_*), cookie presence (cookie_keys), and upgrade-insecure-requests
|
|
111
|
+
// can vary or be absent on the subsequent request that submits the solution, especially after a redirect.
|
|
112
|
+
// By ignoring them, we focus the comparison on more stable identifiers like UA, JA3, GPU, etc.
|
|
113
|
+
const volatileKeys = new Set([
|
|
114
|
+
'ch_ua', 'ch_platform', 'ch_mobile', 'ch_model', 'ch_arch', 'ch_bitness',
|
|
115
|
+
'cookie_keys', 'upgrade',
|
|
116
|
+
// Also ignore network and http version as they can change between requests (e.g., proxy, protocol upgrade)
|
|
117
|
+
'network', 'http_ver',
|
|
118
|
+
// Ignore proxy-related headers as they are not stable client identifiers
|
|
119
|
+
'x_forwarded_for', 'x_real_ip', 'cf_connecting_ip'
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
// Poids de "véracité" (Entropie/Stabilité)
|
|
123
|
+
// Les poids sont augmentés pour donner plus d'importance aux signaux forts.
|
|
124
|
+
const weights = {
|
|
125
|
+
// --- Signaux très forts (difficiles à usurper) ---
|
|
126
|
+
cvs: 5.0, // Canvas: Très haute entropie (Rendu unique du GPU/driver)
|
|
127
|
+
gpu: 4.0, // GPU: Haute entropie (Matériel spécifique)
|
|
128
|
+
ja3: 3.5, // JA3: Identifie la librairie TLS (très stable pour un client donné)
|
|
129
|
+
ja4: 4.0, // JA4: Plus moderne, inclut HTTP/2
|
|
130
|
+
h2_settings: 3.0, // HTTP/2 settings frame fingerprint
|
|
131
|
+
tcp_fp: 2.5, // TCP/IP fingerprint
|
|
132
|
+
ua: 2.0, // User-Agent: Signal fort, bien que modifiable
|
|
133
|
+
|
|
134
|
+
// --- Signaux composites et dérivés ---
|
|
135
|
+
client_fp_hash: 3.0, // Le hash de l'empreinte client est un signal très fort.
|
|
136
|
+
browser: 1.5, // Le navigateur extrait du UA.
|
|
137
|
+
os_version: 1.5, // L'OS extrait du UA.
|
|
138
|
+
device_type: 1.0, // Le type d'appareil extrait du UA.
|
|
139
|
+
|
|
140
|
+
// --- Signaux moyens ---
|
|
141
|
+
hw: 1.5, // Hardware (CPU, RAM): Stabilité moyenne
|
|
142
|
+
scr: 1.0, // Screen: Stabilité moyenne
|
|
143
|
+
// 'os' est souvent la même chose que 'ch_platform', on peut le déprécier ou lui donner un poids faible.
|
|
144
|
+
os: 0.8, // OS (nav.platform): Assez stable
|
|
145
|
+
geo: 0.5, // Geo/Langue: Peut changer (VPN, voyage)
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let weightedMatches = 0;
|
|
149
|
+
let totalWeight = 0;
|
|
150
|
+
|
|
151
|
+
const allKeys = new Set([...map1.keys(), ...map2.keys()]);
|
|
152
|
+
|
|
153
|
+
allKeys.forEach((key) => {
|
|
154
|
+
// On ignore les clés volatiles pour cette comparaison spécifique.
|
|
155
|
+
if (volatileKeys.has(key)) return;
|
|
156
|
+
|
|
157
|
+
// On ne compare que les clés qui ont un poids défini.
|
|
158
|
+
const weight = weights[key];
|
|
159
|
+
// The check must be for `undefined` to allow keys that have a legitimate weight of 0.
|
|
160
|
+
// The previous `!weight` check would incorrectly exclude them, potentially leading to a totalWeight of 0
|
|
161
|
+
// and a similarity score of NaN, which evaluates to 0. This is the fix.
|
|
162
|
+
if (weight === undefined) return;
|
|
163
|
+
|
|
164
|
+
totalWeight += weight; // N'incrémenter que si la clé est pertinente.
|
|
165
|
+
if (map1.get(key) === map2.get(key)) {
|
|
166
|
+
weightedMatches += weight;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return totalWeight === 0 ? 0 : weightedMatches / totalWeight;
|
|
171
|
+
}
|
|
161
172
|
}
|
package/fingerprint.client.js
CHANGED
|
@@ -220,6 +220,9 @@ const ClientLibrary = {
|
|
|
220
220
|
* @returns {ClientBehaviorMetrics}
|
|
221
221
|
*/
|
|
222
222
|
getClientBehaviorMetrics() {
|
|
223
|
+
// Add history length as a behavioral signal.
|
|
224
|
+
metrics.historyLength = window.history.length;
|
|
225
|
+
|
|
223
226
|
// Ajoute un timestamp au moment de la collecte pour la détection de rejeu.
|
|
224
227
|
metrics.clientTimestamp = Date.now();
|
|
225
228
|
|
|
@@ -438,6 +441,7 @@ const ClientLibrary = {
|
|
|
438
441
|
* @property {number} mouseEntropy - Entropie des mouvements de la souris.
|
|
439
442
|
* @property {number} keystrokeLatency - Latence moyenne entre les frappes.
|
|
440
443
|
* @property {boolean} honeypotInteraction - Vrai si un honeypot a été touché.
|
|
444
|
+
* @property {number} historyLength - La longueur de l'historique de session du navigateur (`window.history.length`).
|
|
441
445
|
* @property {number} clientTimestamp - Timestamp (Date.now()) de la collecte des métriques.
|
|
442
446
|
*/
|
|
443
447
|
|
|
@@ -446,6 +450,7 @@ const metrics = {
|
|
|
446
450
|
mouseEntropy: 0,
|
|
447
451
|
keystrokeLatency: 0,
|
|
448
452
|
honeypotInteraction: false,
|
|
453
|
+
historyLength: 0,
|
|
449
454
|
clientTimestamp: 0,
|
|
450
455
|
};
|
|
451
456
|
|