@anonympins/fingerprint 0.3.7 → 0.4.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/CHANGELOG.md +331 -244
- package/README.md +63 -1201
- package/composer.json +38 -38
- package/index.js +4 -4
- package/package.json +103 -103
- package/phpunit.xml +20 -20
- package/public/fp.js +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +1 -1
- package/src/js/fingerprint.client.js +2 -0
- package/src/js/fingerprint.js +4429 -4220
- package/src/js/mongodb-store.js +79 -79
- package/src/js/pow.solver.inline.js +31 -0
- package/src/js/pow.solver.js +31 -0
- package/src/js/tests/fingerprint.builder.test.js +79 -0
- package/src/js/tests/fingerprint.client.init.test.js +120 -0
- package/src/js/tests/fingerprint.client.test.js +105 -0
- package/src/js/tests/fingerprint.engine.test.js +371 -0
- package/src/js/tests/fingerprint.isMalicious.test.js +117 -0
- package/src/js/tests/fingerprint.test.js +2319 -0
- package/src/js/tests/ip-reputation.test.js +132 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -0
- package/src/js/tests/library.test.js +96 -0
- package/src/js/tests/metrics.test.js +104 -0
- package/src/js/tests/pow.solver.test.js +198 -0
- package/src/js/tests/problem-manager.test.js +323 -0
- package/src/js/tests/stores.test.js +118 -0
- package/src/php/Challenge/ChallengeUtils.php +361 -305
- package/src/php/Config/SecurityProfiles.php +271 -266
- package/src/php/FingerprintBuilder.php +185 -185
- package/src/php/FingerprintClient.php +131 -131
- package/src/php/FingerprintEngine.php +1006 -1006
- package/src/php/Ja3AnomalyDetector.php +227 -227
- package/src/php/Optimization/FunctionRegistry.php +62 -62
- package/src/php/Optimization/Optimization.php +255 -255
- package/src/php/Optimization/OptimizationOperators.php +304 -304
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +104 -104
- package/src/php/Store/RedisStore.php +53 -53
- package/src/php/Tests/ChallengeUtilsTest.php +81 -81
- package/src/php/Tests/FingerprintBuilderTest.php +57 -57
- package/src/php/Tests/FingerprintClientTest.php +71 -0
- package/src/php/Tests/FingerprintEngineTest.php +299 -299
- package/src/php/Tests/IpReputationTest.php +156 -156
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +179 -179
- package/src/php/Tests/MetricsTest.php +45 -45
- package/src/php/Tests/PowTest.php +39 -39
- package/src/php/Tests/ProblemManagerTest.php +296 -296
- package/src/php/Tests/RequestUtilsTest.php +253 -145
- package/src/php/Tests/TLSClientHelloParserTest.php +118 -0
- package/src/php/Tests/problems.config.json +8 -8
- package/src/php/Utils/BigInt.php +144 -144
- package/src/php/Utils/Logger.php +29 -29
- package/src/php/Utils/MaliciousPatterns.php +58 -58
- package/src/php/Utils/MetricsManager.php +166 -166
- package/src/php/Utils/RequestUtils.php +31 -4
- package/src/php/Utils/TLSClientHelloParser.php +117 -0
- package/src/php/bin/auto-tune.php +117 -117
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\Ja3AnomalyDetector;
|
|
8
|
-
use PHPUnit\Framework\TestCase;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Tests unitaires pour la classe Ja3AnomalyDetector.
|
|
12
|
-
*/
|
|
13
|
-
class Ja3AnomalyDetectorTest extends TestCase
|
|
14
|
-
{
|
|
15
|
-
/**
|
|
16
|
-
* Teste le parsing d'une chaîne JA3 brute valide.
|
|
17
|
-
*/
|
|
18
|
-
public function testParseJa3ValidString(): void
|
|
19
|
-
{
|
|
20
|
-
$ja3 = '771,4865-4866-4867,0-23-65281-10-11,29-23-24,0';
|
|
21
|
-
$parsed = Ja3AnomalyDetector::parseJa3($ja3);
|
|
22
|
-
|
|
23
|
-
$this->assertNotNull($parsed);
|
|
24
|
-
$this->assertSame(771, $parsed['tlsVersion']);
|
|
25
|
-
$this->assertSame([4865, 4866, 4867], $parsed['ciphers']);
|
|
26
|
-
$this->assertSame([0, 23, 65281, 10, 11], $parsed['extensions']);
|
|
27
|
-
$this->assertSame([29, 23, 24], $parsed['curves']);
|
|
28
|
-
$this->assertSame([0], $parsed['points']);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Teste le parsing avec des valeurs manquantes ou malformées.
|
|
33
|
-
*/
|
|
34
|
-
public function testParseJa3InvalidString(): void
|
|
35
|
-
{
|
|
36
|
-
$this->assertNull(Ja3AnomalyDetector::parseJa3(''));
|
|
37
|
-
$this->assertNull(Ja3AnomalyDetector::parseJa3('771,4865,0-23')); // Moins de 5 parties
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Teste la détection du mécanisme GREASE.
|
|
42
|
-
*/
|
|
43
|
-
public function testHasGrease(): void
|
|
44
|
-
{
|
|
45
|
-
// Contient la valeur GREASE 2570 (0x0A0A)
|
|
46
|
-
$this->assertTrue(Ja3AnomalyDetector::hasGrease([4865, 2570, 4866]));
|
|
47
|
-
// Ne contient pas de valeur GREASE
|
|
48
|
-
$this->assertFalse(Ja3AnomalyDetector::hasGrease([4865, 4866, 4867]));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Teste l'extraction de la famille du User-Agent.
|
|
53
|
-
*/
|
|
54
|
-
public function testGetBrowserFamily(): void
|
|
55
|
-
{
|
|
56
|
-
$this->assertSame('Chrome', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'));
|
|
57
|
-
$this->assertSame('Edge', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'));
|
|
58
|
-
$this->assertSame('Firefox', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0'));
|
|
59
|
-
$this->assertSame('Safari', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15'));
|
|
60
|
-
$this->assertNull(Ja3AnomalyDetector::getBrowserFamily('curl/7.68.0'));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Teste la détection d'usurpation d'une bibliothèque connue (ex: Python/Go/curl).
|
|
65
|
-
*/
|
|
66
|
-
public function testSpoofingLibraryDetected(): void
|
|
67
|
-
{
|
|
68
|
-
$pythonJa3 = '47344a349b75c4e82333475553b5f358'; // Signature Python dans DB
|
|
69
|
-
$userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
70
|
-
|
|
71
|
-
$score = Ja3AnomalyDetector::getJa3AnomalyScore($pythonJa3, null, $userAgent, 'HTTP/2');
|
|
72
|
-
|
|
73
|
-
// Devrait retourner un score élevé (90) car une signature python se fait passer pour Chrome
|
|
74
|
-
$this->assertEquals(90, $score);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Teste la détection d'une incohérence de navigateur (ex: signature Firefox avec UA Chrome).
|
|
79
|
-
*/
|
|
80
|
-
public function testBrowserMismatchDetected(): void
|
|
81
|
-
{
|
|
82
|
-
$firefoxJa3 = 'b386946a5a586163c7c533636b45c355'; // Signature Firefox dans DB
|
|
83
|
-
$userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
84
|
-
|
|
85
|
-
$score = Ja3AnomalyDetector::getJa3AnomalyScore($firefoxJa3, null, $userAgent, 'HTTP/2');
|
|
86
|
-
|
|
87
|
-
$this->assertEquals(80, $score);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Teste la détection de stagnation de hash avec rotation d'User-Agents.
|
|
92
|
-
*/
|
|
93
|
-
public function testStagnationWithRotatingUserAgents(): void
|
|
94
|
-
{
|
|
95
|
-
// Mock d'un stockage de cache minimal en mémoire
|
|
96
|
-
$cache = new class {
|
|
97
|
-
private array $store = [];
|
|
98
|
-
public function get(string $key): ?string { return $this->store[$key] ?? null; }
|
|
99
|
-
public function set(string $key, string $val, int $ttl = 0): void { $this->store[$key] = $val; }
|
|
100
|
-
public function setex(string $key, int $ttl, string $val): void { $this->store[$key] = $val; }
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
$unknownJa3 = '00000000000000000000000000000000';
|
|
104
|
-
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
105
|
-
$firefoxUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/120.0';
|
|
106
|
-
|
|
107
|
-
// Premier appel avec Chrome -> pas d'anomalie de stagnation encore
|
|
108
|
-
$score1 = Ja3AnomalyDetector::getJa3AnomalyScore($unknownJa3, null, $chromeUA, 'HTTP/2', $cache);
|
|
109
|
-
$this->assertLessThan(85, $score1);
|
|
110
|
-
|
|
111
|
-
// Deuxième appel avec la même stack TLS mais un UA Firefox -> Détection de rotation !
|
|
112
|
-
$score2 = Ja3AnomalyDetector::getJa3AnomalyScore($unknownJa3, null, $firefoxUA, 'HTTP/2', $cache);
|
|
113
|
-
$this->assertEquals(85, $score2);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Teste l'absence de valeurs GREASE pour un navigateur Chromium.
|
|
118
|
-
*/
|
|
119
|
-
public function testMissingGreaseForChrome(): void
|
|
120
|
-
{
|
|
121
|
-
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
122
|
-
// JA3 brut valide mais sans AUCUNE valeur GREASE dans les extensions ou les ciphers
|
|
123
|
-
$ja3RawWithoutGrease = '771,4865-4866,0-23-10,29,0';
|
|
124
|
-
|
|
125
|
-
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawWithoutGrease, $chromeUA, 'HTTP/2');
|
|
126
|
-
$this->assertEquals(75, $score);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Teste le cas légitime d'un navigateur Chromium disposant de GREASE.
|
|
131
|
-
*/
|
|
132
|
-
public function testLegitimateChromeWithGrease(): void
|
|
133
|
-
{
|
|
134
|
-
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
135
|
-
// 2570 est une valeur GREASE valide
|
|
136
|
-
$ja3RawWithGrease = '771,4865-2570,0-23-10,29,0';
|
|
137
|
-
|
|
138
|
-
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawWithGrease, $chromeUA, 'HTTP/2');
|
|
139
|
-
$this->assertLessThan(75, $score);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Teste la détection d'absence d'ALPN lors de connexions HTTP/2.
|
|
144
|
-
*/
|
|
145
|
-
public function testHttp2WithoutAlpnExtension(): void
|
|
146
|
-
{
|
|
147
|
-
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
148
|
-
// Pas d'extension 16 (ALPN) dans les extensions
|
|
149
|
-
$ja3RawNoAlpn = '771,4865-2570,0-23-10,29,0';
|
|
150
|
-
|
|
151
|
-
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawNoAlpn, $chromeUA, 'HTTP/2');
|
|
152
|
-
$this->assertEquals(70, $score);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Teste la présence d'ALPN lors de connexions HTTP/2 (cas normal).
|
|
157
|
-
*/
|
|
158
|
-
public function testHttp2WithAlpnExtension(): void
|
|
159
|
-
{
|
|
160
|
-
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
161
|
-
// L'extension 16 est présente
|
|
162
|
-
$ja3RawWithAlpn = '771,4865-2570,0-23-10-16,29,0';
|
|
163
|
-
|
|
164
|
-
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawWithAlpn, $chromeUA, 'HTTP/2');
|
|
165
|
-
$this->assertLessThan(70, $score);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Teste l'utilisation d'une version obsolète de TLS par un navigateur récent.
|
|
170
|
-
*/
|
|
171
|
-
public function testObsoleteTlsVersionWithModernBrowser(): void
|
|
172
|
-
{
|
|
173
|
-
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
174
|
-
// Version TLS 769 (TLS 1.0) initiée par un Chrome récent
|
|
175
|
-
$obsoleteTlsJa3 = '769,4865-2570,0-23-10-16,29,0';
|
|
176
|
-
|
|
177
|
-
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $obsoleteTlsJa3, $chromeUA, 'HTTP/2');
|
|
178
|
-
$this->assertEquals(80, $score);
|
|
179
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\Ja3AnomalyDetector;
|
|
8
|
+
use PHPUnit\Framework\TestCase;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tests unitaires pour la classe Ja3AnomalyDetector.
|
|
12
|
+
*/
|
|
13
|
+
class Ja3AnomalyDetectorTest extends TestCase
|
|
14
|
+
{
|
|
15
|
+
/**
|
|
16
|
+
* Teste le parsing d'une chaîne JA3 brute valide.
|
|
17
|
+
*/
|
|
18
|
+
public function testParseJa3ValidString(): void
|
|
19
|
+
{
|
|
20
|
+
$ja3 = '771,4865-4866-4867,0-23-65281-10-11,29-23-24,0';
|
|
21
|
+
$parsed = Ja3AnomalyDetector::parseJa3($ja3);
|
|
22
|
+
|
|
23
|
+
$this->assertNotNull($parsed);
|
|
24
|
+
$this->assertSame(771, $parsed['tlsVersion']);
|
|
25
|
+
$this->assertSame([4865, 4866, 4867], $parsed['ciphers']);
|
|
26
|
+
$this->assertSame([0, 23, 65281, 10, 11], $parsed['extensions']);
|
|
27
|
+
$this->assertSame([29, 23, 24], $parsed['curves']);
|
|
28
|
+
$this->assertSame([0], $parsed['points']);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Teste le parsing avec des valeurs manquantes ou malformées.
|
|
33
|
+
*/
|
|
34
|
+
public function testParseJa3InvalidString(): void
|
|
35
|
+
{
|
|
36
|
+
$this->assertNull(Ja3AnomalyDetector::parseJa3(''));
|
|
37
|
+
$this->assertNull(Ja3AnomalyDetector::parseJa3('771,4865,0-23')); // Moins de 5 parties
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Teste la détection du mécanisme GREASE.
|
|
42
|
+
*/
|
|
43
|
+
public function testHasGrease(): void
|
|
44
|
+
{
|
|
45
|
+
// Contient la valeur GREASE 2570 (0x0A0A)
|
|
46
|
+
$this->assertTrue(Ja3AnomalyDetector::hasGrease([4865, 2570, 4866]));
|
|
47
|
+
// Ne contient pas de valeur GREASE
|
|
48
|
+
$this->assertFalse(Ja3AnomalyDetector::hasGrease([4865, 4866, 4867]));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Teste l'extraction de la famille du User-Agent.
|
|
53
|
+
*/
|
|
54
|
+
public function testGetBrowserFamily(): void
|
|
55
|
+
{
|
|
56
|
+
$this->assertSame('Chrome', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'));
|
|
57
|
+
$this->assertSame('Edge', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'));
|
|
58
|
+
$this->assertSame('Firefox', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0'));
|
|
59
|
+
$this->assertSame('Safari', Ja3AnomalyDetector::getBrowserFamily('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15'));
|
|
60
|
+
$this->assertNull(Ja3AnomalyDetector::getBrowserFamily('curl/7.68.0'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Teste la détection d'usurpation d'une bibliothèque connue (ex: Python/Go/curl).
|
|
65
|
+
*/
|
|
66
|
+
public function testSpoofingLibraryDetected(): void
|
|
67
|
+
{
|
|
68
|
+
$pythonJa3 = '47344a349b75c4e82333475553b5f358'; // Signature Python dans DB
|
|
69
|
+
$userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
70
|
+
|
|
71
|
+
$score = Ja3AnomalyDetector::getJa3AnomalyScore($pythonJa3, null, $userAgent, 'HTTP/2');
|
|
72
|
+
|
|
73
|
+
// Devrait retourner un score élevé (90) car une signature python se fait passer pour Chrome
|
|
74
|
+
$this->assertEquals(90, $score);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Teste la détection d'une incohérence de navigateur (ex: signature Firefox avec UA Chrome).
|
|
79
|
+
*/
|
|
80
|
+
public function testBrowserMismatchDetected(): void
|
|
81
|
+
{
|
|
82
|
+
$firefoxJa3 = 'b386946a5a586163c7c533636b45c355'; // Signature Firefox dans DB
|
|
83
|
+
$userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
84
|
+
|
|
85
|
+
$score = Ja3AnomalyDetector::getJa3AnomalyScore($firefoxJa3, null, $userAgent, 'HTTP/2');
|
|
86
|
+
|
|
87
|
+
$this->assertEquals(80, $score);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Teste la détection de stagnation de hash avec rotation d'User-Agents.
|
|
92
|
+
*/
|
|
93
|
+
public function testStagnationWithRotatingUserAgents(): void
|
|
94
|
+
{
|
|
95
|
+
// Mock d'un stockage de cache minimal en mémoire
|
|
96
|
+
$cache = new class {
|
|
97
|
+
private array $store = [];
|
|
98
|
+
public function get(string $key): ?string { return $this->store[$key] ?? null; }
|
|
99
|
+
public function set(string $key, string $val, int $ttl = 0): void { $this->store[$key] = $val; }
|
|
100
|
+
public function setex(string $key, int $ttl, string $val): void { $this->store[$key] = $val; }
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
$unknownJa3 = '00000000000000000000000000000000';
|
|
104
|
+
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
105
|
+
$firefoxUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/120.0';
|
|
106
|
+
|
|
107
|
+
// Premier appel avec Chrome -> pas d'anomalie de stagnation encore
|
|
108
|
+
$score1 = Ja3AnomalyDetector::getJa3AnomalyScore($unknownJa3, null, $chromeUA, 'HTTP/2', $cache);
|
|
109
|
+
$this->assertLessThan(85, $score1);
|
|
110
|
+
|
|
111
|
+
// Deuxième appel avec la même stack TLS mais un UA Firefox -> Détection de rotation !
|
|
112
|
+
$score2 = Ja3AnomalyDetector::getJa3AnomalyScore($unknownJa3, null, $firefoxUA, 'HTTP/2', $cache);
|
|
113
|
+
$this->assertEquals(85, $score2);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Teste l'absence de valeurs GREASE pour un navigateur Chromium.
|
|
118
|
+
*/
|
|
119
|
+
public function testMissingGreaseForChrome(): void
|
|
120
|
+
{
|
|
121
|
+
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
122
|
+
// JA3 brut valide mais sans AUCUNE valeur GREASE dans les extensions ou les ciphers
|
|
123
|
+
$ja3RawWithoutGrease = '771,4865-4866,0-23-10,29,0';
|
|
124
|
+
|
|
125
|
+
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawWithoutGrease, $chromeUA, 'HTTP/2');
|
|
126
|
+
$this->assertEquals(75, $score);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Teste le cas légitime d'un navigateur Chromium disposant de GREASE.
|
|
131
|
+
*/
|
|
132
|
+
public function testLegitimateChromeWithGrease(): void
|
|
133
|
+
{
|
|
134
|
+
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
135
|
+
// 2570 est une valeur GREASE valide
|
|
136
|
+
$ja3RawWithGrease = '771,4865-2570,0-23-10,29,0';
|
|
137
|
+
|
|
138
|
+
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawWithGrease, $chromeUA, 'HTTP/2');
|
|
139
|
+
$this->assertLessThan(75, $score);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Teste la détection d'absence d'ALPN lors de connexions HTTP/2.
|
|
144
|
+
*/
|
|
145
|
+
public function testHttp2WithoutAlpnExtension(): void
|
|
146
|
+
{
|
|
147
|
+
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
148
|
+
// Pas d'extension 16 (ALPN) dans les extensions
|
|
149
|
+
$ja3RawNoAlpn = '771,4865-2570,0-23-10,29,0';
|
|
150
|
+
|
|
151
|
+
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawNoAlpn, $chromeUA, 'HTTP/2');
|
|
152
|
+
$this->assertEquals(70, $score);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Teste la présence d'ALPN lors de connexions HTTP/2 (cas normal).
|
|
157
|
+
*/
|
|
158
|
+
public function testHttp2WithAlpnExtension(): void
|
|
159
|
+
{
|
|
160
|
+
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
161
|
+
// L'extension 16 est présente
|
|
162
|
+
$ja3RawWithAlpn = '771,4865-2570,0-23-10-16,29,0';
|
|
163
|
+
|
|
164
|
+
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $ja3RawWithAlpn, $chromeUA, 'HTTP/2');
|
|
165
|
+
$this->assertLessThan(70, $score);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Teste l'utilisation d'une version obsolète de TLS par un navigateur récent.
|
|
170
|
+
*/
|
|
171
|
+
public function testObsoleteTlsVersionWithModernBrowser(): void
|
|
172
|
+
{
|
|
173
|
+
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
|
|
174
|
+
// Version TLS 769 (TLS 1.0) initiée par un Chrome récent
|
|
175
|
+
$obsoleteTlsJa3 = '769,4865-2570,0-23-10-16,29,0';
|
|
176
|
+
|
|
177
|
+
$score = Ja3AnomalyDetector::getJa3AnomalyScore(null, $obsoleteTlsJa3, $chromeUA, 'HTTP/2');
|
|
178
|
+
$this->assertEquals(80, $score);
|
|
179
|
+
}
|
|
180
180
|
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
-
use Anonympins\Fingerprint\DirectFingerprint;
|
|
9
|
-
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
10
|
-
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
-
use PHPUnit\Framework\TestCase;
|
|
13
|
-
|
|
14
|
-
class MetricsTest extends TestCase
|
|
15
|
-
{
|
|
16
|
-
protected function setUp(): void
|
|
17
|
-
{
|
|
18
|
-
parent::setUp();
|
|
19
|
-
StoreManager::configureStore(new InMemoryStore());
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public function testIncrementCounterAndGetPrometheusMetrics(): void
|
|
23
|
-
{
|
|
24
|
-
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
25
|
-
$metrics = MetricsManager::getPrometheusMetrics();
|
|
26
|
-
|
|
27
|
-
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
28
|
-
$this->assertStringContainsString('status="passed"', $metrics);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
public function testGetPrometheusMetricsViaDirectFingerprint(): void
|
|
32
|
-
{
|
|
33
|
-
$config = [
|
|
34
|
-
'metricsAuthorizationCallback' => function (RequestContext $context) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
$direct = new DirectFingerprint($config);
|
|
40
|
-
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
41
|
-
$metrics = $direct->getPrometheusMetrics();
|
|
42
|
-
|
|
43
|
-
$this->assertNotNull($metrics);
|
|
44
|
-
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
45
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
+
use Anonympins\Fingerprint\DirectFingerprint;
|
|
9
|
+
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
10
|
+
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
+
use PHPUnit\Framework\TestCase;
|
|
13
|
+
|
|
14
|
+
class MetricsTest extends TestCase
|
|
15
|
+
{
|
|
16
|
+
protected function setUp(): void
|
|
17
|
+
{
|
|
18
|
+
parent::setUp();
|
|
19
|
+
StoreManager::configureStore(new InMemoryStore());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function testIncrementCounterAndGetPrometheusMetrics(): void
|
|
23
|
+
{
|
|
24
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
25
|
+
$metrics = MetricsManager::getPrometheusMetrics();
|
|
26
|
+
|
|
27
|
+
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
28
|
+
$this->assertStringContainsString('status="passed"', $metrics);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public function testGetPrometheusMetricsViaDirectFingerprint(): void
|
|
32
|
+
{
|
|
33
|
+
$config = [
|
|
34
|
+
'metricsAuthorizationCallback' => function (RequestContext $context) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
$direct = new DirectFingerprint($config);
|
|
40
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
41
|
+
$metrics = $direct->getPrometheusMetrics();
|
|
42
|
+
|
|
43
|
+
$this->assertNotNull($metrics);
|
|
44
|
+
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
45
|
+
}
|
|
46
46
|
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
8
|
-
use Anonympins\Fingerprint\FingerprintEngine;
|
|
9
|
-
use Anonympins\Fingerprint\ProblemManager;
|
|
10
|
-
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
-
use PHPUnit\Framework\TestCase;
|
|
13
|
-
|
|
14
|
-
class PowTest extends TestCase
|
|
15
|
-
{
|
|
16
|
-
private FingerprintEngine $engine;
|
|
17
|
-
|
|
18
|
-
protected function setUp(): void
|
|
19
|
-
{
|
|
20
|
-
// 1. Configurer un store en mémoire pour l'isolation des tests.
|
|
21
|
-
$store = new InMemoryStore();
|
|
22
|
-
StoreManager::configureStore($store);
|
|
23
|
-
|
|
24
|
-
// 2. Initialiser le ProblemManager avec une configuration et un store valides.
|
|
25
|
-
// C'est l'étape cruciale qui manquait.
|
|
26
|
-
$configPath = dirname(__FILE__) . '/problems.config.json';
|
|
27
|
-
ProblemManager::getInstance($configPath, $store);
|
|
28
|
-
|
|
29
|
-
// 3. Créer l'instance du moteur.
|
|
30
|
-
$this->engine = new FingerprintEngine(SecurityProfiles::createSecurityProfile('balanced'));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public function testGetProblemsIsExposedForTesting(): void
|
|
34
|
-
{
|
|
35
|
-
// Cette méthode appelle ProblemManager::getInstance() en interne.
|
|
36
|
-
// Grâce au setUp(), l'instance est déjà initialisée et le test passe.
|
|
37
|
-
$problems = $this->engine->getProblems();
|
|
38
|
-
$this->assertIsArray($problems);
|
|
39
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
8
|
+
use Anonympins\Fingerprint\FingerprintEngine;
|
|
9
|
+
use Anonympins\Fingerprint\ProblemManager;
|
|
10
|
+
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
+
use PHPUnit\Framework\TestCase;
|
|
13
|
+
|
|
14
|
+
class PowTest extends TestCase
|
|
15
|
+
{
|
|
16
|
+
private FingerprintEngine $engine;
|
|
17
|
+
|
|
18
|
+
protected function setUp(): void
|
|
19
|
+
{
|
|
20
|
+
// 1. Configurer un store en mémoire pour l'isolation des tests.
|
|
21
|
+
$store = new InMemoryStore();
|
|
22
|
+
StoreManager::configureStore($store);
|
|
23
|
+
|
|
24
|
+
// 2. Initialiser le ProblemManager avec une configuration et un store valides.
|
|
25
|
+
// C'est l'étape cruciale qui manquait.
|
|
26
|
+
$configPath = dirname(__FILE__) . '/problems.config.json';
|
|
27
|
+
ProblemManager::getInstance($configPath, $store);
|
|
28
|
+
|
|
29
|
+
// 3. Créer l'instance du moteur.
|
|
30
|
+
$this->engine = new FingerprintEngine(SecurityProfiles::createSecurityProfile('balanced'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public function testGetProblemsIsExposedForTesting(): void
|
|
34
|
+
{
|
|
35
|
+
// Cette méthode appelle ProblemManager::getInstance() en interne.
|
|
36
|
+
// Grâce au setUp(), l'instance est déjà initialisée et le test passe.
|
|
37
|
+
$problems = $this->engine->getProblems();
|
|
38
|
+
$this->assertIsArray($problems);
|
|
39
|
+
}
|
|
40
40
|
}
|