@anonympins/fingerprint 0.4.3 → 0.4.4
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 +21 -0
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/js/fingerprint.client.js +831 -635
- package/src/js/fingerprint.js +239 -29
- package/src/js/tests/fingerprint.client.init.test.js +140 -119
- package/src/js/tests/fingerprint.test.js +110 -10
- package/src/php/Challenge/ChallengeUtils.php +415 -361
- package/src/php/Config/SecurityProfiles.php +276 -271
- package/src/php/FingerprintClient.php +131 -131
- package/src/php/FingerprintEngine.php +26 -0
- package/src/php/RequestContext.php +90 -90
- package/src/php/Store/IStore.php +41 -41
- package/src/php/Store/RedisStore.php +53 -53
- package/src/php/Tests/RequestUtilsTest.php +130 -0
- package/src/php/Utils/RequestUtils.php +200 -16
|
@@ -1,120 +1,141 @@
|
|
|
1
|
-
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
2
|
-
import {JSDOM} from 'jsdom';
|
|
3
|
-
import ClientLibrary from '../fingerprint.client.js';
|
|
4
|
-
|
|
5
|
-
// --- Setup JSDOM Environment ---
|
|
6
|
-
// Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
|
|
7
|
-
// ici rend le test explicite et portable.
|
|
8
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
9
|
-
url: 'http://localhost',
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
// In recent Node.js versions, some global properties like 'navigator', 'performance',
|
|
13
|
-
// and 'screen' are read-only. To ensure our JSDOM environment works correctly across
|
|
14
|
-
// all versions, we use Object.defineProperty to make these properties writable.
|
|
15
|
-
Object.defineProperty(global, 'window', { value: dom.window, writable: true });
|
|
16
|
-
Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
|
|
17
|
-
Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
|
|
18
|
-
Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
|
|
19
|
-
Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
|
|
20
|
-
|
|
21
|
-
global.fetch = vi.fn(); // Mock global fetch
|
|
22
|
-
global.Headers = dom.window.Headers;
|
|
23
|
-
global.Request = dom.window.Request;
|
|
24
|
-
global.URL = dom.window.URL;
|
|
25
|
-
global.TextEncoder = dom.window.TextEncoder;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
describe('ClientLibrary.initializeClient', () => {
|
|
29
|
-
|
|
30
|
-
// On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
|
|
31
|
-
let startMouseSpy, startKeystrokeSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy;
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
// Réinitialiser l'état du client avant chaque test
|
|
35
|
-
ClientLibrary._resetCache();
|
|
36
|
-
|
|
37
|
-
// Créer les espions sur les méthodes internes
|
|
38
|
-
startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
|
|
39
|
-
startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
expect(
|
|
61
|
-
expect(
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
expect(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
2
|
+
import {JSDOM} from 'jsdom';
|
|
3
|
+
import ClientLibrary from '../fingerprint.client.js';
|
|
4
|
+
|
|
5
|
+
// --- Setup JSDOM Environment ---
|
|
6
|
+
// Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
|
|
7
|
+
// ici rend le test explicite et portable.
|
|
8
|
+
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
9
|
+
url: 'http://localhost',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// In recent Node.js versions, some global properties like 'navigator', 'performance',
|
|
13
|
+
// and 'screen' are read-only. To ensure our JSDOM environment works correctly across
|
|
14
|
+
// all versions, we use Object.defineProperty to make these properties writable.
|
|
15
|
+
Object.defineProperty(global, 'window', { value: dom.window, writable: true });
|
|
16
|
+
Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
|
|
17
|
+
Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
|
|
18
|
+
Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
|
|
19
|
+
Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
|
|
20
|
+
|
|
21
|
+
global.fetch = vi.fn(); // Mock global fetch
|
|
22
|
+
global.Headers = dom.window.Headers;
|
|
23
|
+
global.Request = dom.window.Request;
|
|
24
|
+
global.URL = dom.window.URL;
|
|
25
|
+
global.TextEncoder = dom.window.TextEncoder;
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
describe('ClientLibrary.initializeClient', () => {
|
|
29
|
+
|
|
30
|
+
// On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
|
|
31
|
+
let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
// Réinitialiser l'état du client avant chaque test
|
|
35
|
+
ClientLibrary._resetCache();
|
|
36
|
+
|
|
37
|
+
// Créer les espions sur les méthodes internes
|
|
38
|
+
startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
|
|
39
|
+
startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
|
|
40
|
+
startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
|
|
41
|
+
startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
|
|
42
|
+
initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
|
|
43
|
+
initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
|
|
44
|
+
injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
|
|
45
|
+
initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
|
|
46
|
+
injectPhantomTrapsSpy = vi.spyOn(ClientLibrary, 'injectPhantomTraps');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
// Restaurer les espions après chaque test pour ne pas affecter les autres tests
|
|
51
|
+
vi.restoreAllMocks();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should enable all trackers by default', () => {
|
|
55
|
+
ClientLibrary.initializeClient();
|
|
56
|
+
|
|
57
|
+
expect(startMouseSpy).toHaveBeenCalled();
|
|
58
|
+
expect(startKeystrokeSpy).toHaveBeenCalled();
|
|
59
|
+
expect(startClickSpy).toHaveBeenCalled();
|
|
60
|
+
expect(startTouchSpy).toHaveBeenCalled();
|
|
61
|
+
expect(injectPhantomTrapsSpy).toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should disable trackers when configured', () => {
|
|
65
|
+
ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false });
|
|
66
|
+
|
|
67
|
+
expect(startMouseSpy).not.toHaveBeenCalled();
|
|
68
|
+
expect(startKeystrokeSpy).not.toHaveBeenCalled();
|
|
69
|
+
expect(startClickSpy).not.toHaveBeenCalled();
|
|
70
|
+
expect(startTouchSpy).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should initialize WASM if wasmPath is configured', () => {
|
|
74
|
+
ClientLibrary.initializeClient({ wasmPath: '/fp.js' });
|
|
75
|
+
|
|
76
|
+
expect(initWasmSpy).toHaveBeenCalledWith('/fp.js');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should disable phantom traps when configured', () => {
|
|
80
|
+
ClientLibrary.initializeClient({ phantomTraps: false });
|
|
81
|
+
|
|
82
|
+
expect(injectPhantomTrapsSpy).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should initialize honeypots with the provided field names', () => {
|
|
86
|
+
const honeypotFields = ['email_confirm', 'user_nickname'];
|
|
87
|
+
ClientLibrary.initializeClient({ honeypots: honeypotFields });
|
|
88
|
+
|
|
89
|
+
expect(initHoneypotsSpy).toHaveBeenCalledWith(honeypotFields);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should not initialize honeypots if the array is empty', () => {
|
|
93
|
+
ClientLibrary.initializeClient({ honeypots: [] });
|
|
94
|
+
|
|
95
|
+
expect(initHoneypotsSpy).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should inject trap URLs when provided', () => {
|
|
99
|
+
const urls = ['/trap1?sig=123', '/trap2?sig=456'];
|
|
100
|
+
ClientLibrary.initializeClient({ trapUrls: urls });
|
|
101
|
+
|
|
102
|
+
expect(injectTrapsSpy).toHaveBeenCalledWith(urls);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should not inject trap URLs if the array is empty', () => {
|
|
106
|
+
ClientLibrary.initializeClient({ trapUrls: [] });
|
|
107
|
+
|
|
108
|
+
expect(injectTrapsSpy).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should initialize fetch interception when fetch config is present', () => {
|
|
112
|
+
const targetDomains = ['api.example.com'];
|
|
113
|
+
ClientLibrary.initializeClient({ fetch: { targetDomains } });
|
|
114
|
+
|
|
115
|
+
expect(initFetchSpy).toHaveBeenCalledWith(targetDomains);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should not initialize fetch interception if fetch config is absent', () => {
|
|
119
|
+
ClientLibrary.initializeClient(); // No fetch config
|
|
120
|
+
|
|
121
|
+
expect(initFetchSpy).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should call all initializers correctly when a full config is provided', () => {
|
|
125
|
+
const config = {
|
|
126
|
+
mouse: true,
|
|
127
|
+
keystrokes: true,
|
|
128
|
+
honeypots: ['field1'],
|
|
129
|
+
trapUrls: ['/trap1'],
|
|
130
|
+
fetch: { targetDomains: ['api.com'] }
|
|
131
|
+
};
|
|
132
|
+
ClientLibrary.initializeClient(config);
|
|
133
|
+
|
|
134
|
+
expect(startMouseSpy).toHaveBeenCalled();
|
|
135
|
+
expect(startKeystrokeSpy).toHaveBeenCalled();
|
|
136
|
+
expect(initHoneypotsSpy).toHaveBeenCalledWith(config.honeypots);
|
|
137
|
+
expect(injectTrapsSpy).toHaveBeenCalledWith(config.trapUrls);
|
|
138
|
+
expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
|
|
139
|
+
});
|
|
140
|
+
|
|
120
141
|
});
|
|
@@ -1747,7 +1747,9 @@ describe('getRequestPatternScore', () => {
|
|
|
1747
1747
|
dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(10000);
|
|
1748
1748
|
|
|
1749
1749
|
const { requestPatternScore } = getRequestPatternScore(context, deviceData, patternConfig);
|
|
1750
|
-
|
|
1750
|
+
// regularityScore = 1.0 (stdDev = 0). regularityRatio = 0.4.
|
|
1751
|
+
// instantScore = 1.0 * 0.4 * 80 = 32.
|
|
1752
|
+
expect(requestPatternScore).toBe(32);
|
|
1751
1753
|
});
|
|
1752
1754
|
|
|
1753
1755
|
test('should assign a high pattern score for non-natural (Benford-violating) timings', () => {
|
|
@@ -1760,7 +1762,11 @@ describe('getRequestPatternScore', () => {
|
|
|
1760
1762
|
dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(10000);
|
|
1761
1763
|
|
|
1762
1764
|
const { requestPatternScore } = getRequestPatternScore(context, deviceData, patternConfig);
|
|
1763
|
-
|
|
1765
|
+
// stdDev ~ 31.29. regularityScore = 1 - (31.29 / 50) = ~0.374.
|
|
1766
|
+
// benfordDeviation ~ 2.07. benfordScore = 1.0 (capped).
|
|
1767
|
+
// weightedScore = (0.374 * 0.4) + (1.0 * 0.3) = ~0.4496.
|
|
1768
|
+
// instantScore = 0.4496 * 80 = ~35.97.
|
|
1769
|
+
expect(requestPatternScore).toBeCloseTo(35.97, 1);
|
|
1764
1770
|
});
|
|
1765
1771
|
|
|
1766
1772
|
test('should apply decay factor to the score over time', () => {
|
|
@@ -1963,8 +1969,9 @@ describe('Regularity Detection (Standard Deviation)', () => {
|
|
|
1963
1969
|
|
|
1964
1970
|
const { requestPatternScore } = getRequestPatternScore(context, deviceData, regularityConfig);
|
|
1965
1971
|
|
|
1966
|
-
|
|
1967
|
-
|
|
1972
|
+
// stdDev is 0 (regularityScore = 1.0). regularityRatio = 0.4.
|
|
1973
|
+
// Expected score: 1.0 * 0.4 * 60 = 24.
|
|
1974
|
+
expect(requestPatternScore).toBe(24);
|
|
1968
1975
|
});
|
|
1969
1976
|
|
|
1970
1977
|
it('should apply a low penalty for slightly irregular requests', () => {
|
|
@@ -1975,7 +1982,9 @@ describe('Regularity Detection (Standard Deviation)', () => {
|
|
|
1975
1982
|
|
|
1976
1983
|
const { requestPatternScore } = getRequestPatternScore(context, deviceData, localConfig);
|
|
1977
1984
|
|
|
1978
|
-
|
|
1985
|
+
// stdDev is ~7.07 (regularityScore = 1 - 7.07/10 = 0.293).
|
|
1986
|
+
// Expected score: 0.293 * 0.4 * 60 = 7.03.
|
|
1987
|
+
expect(requestPatternScore).toBeCloseTo(7.03, 1);
|
|
1979
1988
|
});
|
|
1980
1989
|
|
|
1981
1990
|
it('should apply no penalty for highly irregular (human-like) requests', () => {
|
|
@@ -2125,19 +2134,27 @@ describe('Subnet Scoring (Node.js)', () => {
|
|
|
2125
2134
|
});
|
|
2126
2135
|
|
|
2127
2136
|
it('updateSubnetMetrics should create and update subnet data in the store', async () => {
|
|
2128
|
-
const
|
|
2129
|
-
await __internal.updateSubnetMetrics(
|
|
2137
|
+
const context1 = { clientIp: '10.0.0.25', headers: { 'user-agent': 'device-1' } };
|
|
2138
|
+
await __internal.updateSubnetMetrics(context1, 'device-1', 50);
|
|
2139
|
+
|
|
2140
|
+
const fp1 = new FingerprintBuilder().add('ua', 'device-1').toString();
|
|
2141
|
+
const expectedId1 = cyrb53(fp1).toString();
|
|
2130
2142
|
|
|
2131
2143
|
const subnetData = await inMemoryStore.get('subnet:10.0.0.0/24');
|
|
2132
2144
|
expect(subnetData).toBeDefined();
|
|
2133
2145
|
expect(subnetData.highScoreCount).toBe(1);
|
|
2134
|
-
expect(subnetData.deviceIds).toEqual([
|
|
2146
|
+
expect(subnetData.deviceIds).toEqual([expectedId1]);
|
|
2135
2147
|
|
|
2136
2148
|
// Second update
|
|
2137
|
-
|
|
2149
|
+
const context2 = { clientIp: '10.0.0.25', headers: { 'user-agent': 'device-2' } };
|
|
2150
|
+
await __internal.updateSubnetMetrics(context2, 'device-2', 60);
|
|
2151
|
+
|
|
2152
|
+
const fp2 = new FingerprintBuilder().add('ua', 'device-2').toString();
|
|
2153
|
+
const expectedId2 = cyrb53(fp2).toString();
|
|
2154
|
+
|
|
2138
2155
|
const updatedSubnetData = await inMemoryStore.get('subnet:10.0.0.0/24');
|
|
2139
2156
|
expect(updatedSubnetData.highScoreCount).toBe(2);
|
|
2140
|
-
expect(updatedSubnetData.deviceIds).toEqual([
|
|
2157
|
+
expect(updatedSubnetData.deviceIds).toEqual([expectedId1, expectedId2]);
|
|
2141
2158
|
});
|
|
2142
2159
|
|
|
2143
2160
|
it('getSubnetScore should calculate score based on stored metrics', async () => {
|
|
@@ -2349,3 +2366,86 @@ describe('Additional Suspicion Vectors Coverage', () => {
|
|
|
2349
2366
|
expect(crossLayerInconsistencyScore).toBe(50);
|
|
2350
2367
|
});
|
|
2351
2368
|
});
|
|
2369
|
+
|
|
2370
|
+
describe('Botnet Cluster Scoring (Node.js)', () => {
|
|
2371
|
+
const inMemoryStore = {
|
|
2372
|
+
_map: new Map(),
|
|
2373
|
+
async get(key) { return this._map.get(key); },
|
|
2374
|
+
async set(key, value) { this._map.set(key, value); },
|
|
2375
|
+
clear() { this._map.clear(); }
|
|
2376
|
+
};
|
|
2377
|
+
|
|
2378
|
+
beforeEach(async () => {
|
|
2379
|
+
inMemoryStore.clear();
|
|
2380
|
+
await configureStore(inMemoryStore);
|
|
2381
|
+
});
|
|
2382
|
+
|
|
2383
|
+
it('should calculate botnetClusterScore based on unique IPs within 10 minutes', async () => {
|
|
2384
|
+
const { getBotnetClusterScore } = __internal;
|
|
2385
|
+
const stableFpHash = 'test-stable-hash';
|
|
2386
|
+
|
|
2387
|
+
// 1. Première IP
|
|
2388
|
+
let scoreData = await getBotnetClusterScore({ clientIp: '192.168.1.1' }, stableFpHash);
|
|
2389
|
+
expect(scoreData.botnetClusterScore).toBe(0);
|
|
2390
|
+
|
|
2391
|
+
// 2. Ajout de 2 IPs uniques (total 3)
|
|
2392
|
+
await getBotnetClusterScore({ clientIp: '192.168.1.2' }, stableFpHash);
|
|
2393
|
+
scoreData = await getBotnetClusterScore({ clientIp: '192.168.1.3' }, stableFpHash);
|
|
2394
|
+
expect(scoreData.botnetClusterScore).toBe(50.3);
|
|
2395
|
+
|
|
2396
|
+
// 3. Ajout de 2 IPs uniques (total 5)
|
|
2397
|
+
await getBotnetClusterScore({ clientIp: '192.168.1.4' }, stableFpHash);
|
|
2398
|
+
scoreData = await getBotnetClusterScore({ clientIp: '192.168.1.5' }, stableFpHash);
|
|
2399
|
+
expect(scoreData.botnetClusterScore).toBe(75.3);
|
|
2400
|
+
|
|
2401
|
+
// 4. Ajout de 5 IPs uniques (total 10)
|
|
2402
|
+
for (let i = 6; i <= 10; i++) {
|
|
2403
|
+
scoreData = await getBotnetClusterScore({ clientIp: `192.168.1.${i}` }, stableFpHash);
|
|
2404
|
+
}
|
|
2405
|
+
expect(scoreData.botnetClusterScore).toBe(95.7);
|
|
2406
|
+
});
|
|
2407
|
+
|
|
2408
|
+
it('should realistically group PS4 consoles with volatile differences (different IPs/cookies) under the same cluster score', async () => {
|
|
2409
|
+
const { getSuspicionVector } = __internal;
|
|
2410
|
+
|
|
2411
|
+
const ps4Headers = {
|
|
2412
|
+
'user-agent': 'Mozilla/5.0 (PlayStation 4 11.50) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.50 Safari/605.1.15',
|
|
2413
|
+
'x-ja3-hash': '76993ef93bf89104037599723ab9f201',
|
|
2414
|
+
'x-ja4-hash': 't13d1516h2_8daaf6152771_390237aa04be',
|
|
2415
|
+
'x-http2-fingerprint': '1:65536;3:1000;4:6291456;6:65536',
|
|
2416
|
+
'x-tcp-fingerprint': '64240:128:1:mss,nop,ws,nop,nop,sok:df:0'
|
|
2417
|
+
};
|
|
2418
|
+
|
|
2419
|
+
// Simule des requêtes provenant de 10 consoles PlayStation 4 infectées différentes (IPs différentes, pas de cookies communs)
|
|
2420
|
+
for (let i = 1; i <= 10; i++) {
|
|
2421
|
+
const context = {
|
|
2422
|
+
clientIp: `185.15.20.${i}`,
|
|
2423
|
+
path: '/api/login',
|
|
2424
|
+
headers: {
|
|
2425
|
+
...ps4Headers,
|
|
2426
|
+
'cookie_keys': `session_id=fake_sess_${i}` // Élément volatil
|
|
2427
|
+
},
|
|
2428
|
+
cookies: {}, // Pas de cookie device_id partagé pour simuler des terminaux distincts
|
|
2429
|
+
query: {},
|
|
2430
|
+
httpVersion: '2.0',
|
|
2431
|
+
requestTimestamp: Date.now()
|
|
2432
|
+
};
|
|
2433
|
+
|
|
2434
|
+
const vector = await getSuspicionVector(context, { honeypot: {}, patterns: {} });
|
|
2435
|
+
|
|
2436
|
+
if (i === 1) {
|
|
2437
|
+
expect(vector.botnetClusterScore).toBe(0);
|
|
2438
|
+
} else if (i === 2) {
|
|
2439
|
+
expect(vector.botnetClusterScore).toBe(29.5);
|
|
2440
|
+
} else if (i === 3) {
|
|
2441
|
+
expect(vector.botnetClusterScore).toBe(50.3);
|
|
2442
|
+
} else if (i === 4) {
|
|
2443
|
+
expect(vector.botnetClusterScore).toBe(65);
|
|
2444
|
+
} else if (i === 5) {
|
|
2445
|
+
expect(vector.botnetClusterScore).toBe(75.3);
|
|
2446
|
+
} else if (i === 10) {
|
|
2447
|
+
expect(vector.botnetClusterScore).toBe(95.7);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
});
|