@anonympins/fingerprint 0.7.4 → 0.8.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 +59 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/anonympins-bot-mitigation-pow.zip +0 -0
- package/src/js/dynamic-wasm.js +218 -218
- package/src/js/fingerprint.client.js +1784 -1738
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +489 -99
- package/src/js/fingerprint.utils.js +252 -2
- package/src/js/pow.worker.js +2 -2
- package/src/js/tests/cross_parity.test.js +3 -3
- package/src/js/tests/dns-circuit-breaker.test.js +6 -6
- package/src/js/tests/dynamic-wasm.test.js +77 -77
- package/src/js/tests/fingerprint.builder.test.js +76 -78
- package/src/js/tests/fingerprint.client.init.test.js +78 -0
- package/src/js/tests/fingerprint.client.test.js +16 -17
- package/src/js/tests/fingerprint.test.js +37 -12
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
- package/src/js/tests/library.test.js +94 -95
- package/src/js/tests/obfuscation.test.js +8 -8
- package/src/js/tests/quicFingerprint.test.js +47 -0
- package/src/js/tests/tcpFingerprint.test.js +4 -4
- package/src/js/tests/threatIntel.test.js +40 -1
- package/src/js/upow-model-task.js +155 -112
- package/src/php/Challenge/ChallengeUtils.php +392 -86
- package/src/php/Config/SecurityProfiles.php +79 -61
- package/src/php/DirectFingerprint.php +55 -8
- package/src/php/FingerprintBuilder.php +13 -13
- package/src/php/FingerprintClient.php +6 -6
- package/src/php/FingerprintEngine.php +2122 -1690
- package/src/php/Ja3AnomalyDetector.php +22 -22
- package/src/php/Optimization/FunctionRegistry.php +4 -4
- package/src/php/Optimization/Optimization.php +6 -6
- package/src/php/RequestContext.php +11 -11
- package/src/php/Store/InMemoryStore.php +96 -66
- package/src/php/Store/MongoDbStore.php +2 -2
- package/src/php/Store/RedisStore.php +1 -1
- package/src/php/Store/StoreManager.php +48 -35
- package/src/php/Tests/ChallengeUtilsTest.php +31 -0
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintClientTest.php +70 -70
- package/src/php/Tests/PowTest.php +37 -39
- package/src/php/Tests/QuicFingerprintTest.php +115 -53
- package/src/php/Tests/ThreatIntelTest.php +34 -0
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/BlockList.php +13 -13
- package/src/php/Utils/Logger.php +19 -1
- package/src/php/Utils/MaliciousPatterns.php +6 -6
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +362 -1
- package/src/php/Utils/TLSClientHelloParser.php +385 -369
- package/src/php/WordPress/WpDbStore.php +7 -7
- package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1121 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
- package/src/php/WordPress/package.php +80 -101
- package/src/php/WordPress/readme.txt +21 -5
- package/src/php/bin/auto-tune.php +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\FingerprintEngine;
|
|
8
|
-
use PHPUnit\Framework\TestCase;
|
|
9
|
-
use ReflectionClass;
|
|
10
|
-
|
|
11
|
-
class DnsCircuitBreakerTest extends TestCase
|
|
12
|
-
{
|
|
13
|
-
private ReflectionClass $reflection;
|
|
14
|
-
|
|
15
|
-
protected function setUp(): void
|
|
16
|
-
{
|
|
17
|
-
parent::setUp();
|
|
18
|
-
$this->reflection = new ReflectionClass(FingerprintEngine::class);
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
$this->setCircuitBreakerState([
|
|
22
|
-
'state' => 'CLOSED',
|
|
23
|
-
'failureCount' => 0,
|
|
24
|
-
'lastStateChange' => 0,
|
|
25
|
-
'threshold' => 5,
|
|
26
|
-
'cooldown' => 1, // 1
|
|
27
|
-
]);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
private function getCircuitBreakerState(): array
|
|
31
|
-
{
|
|
32
|
-
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
33
|
-
if (PHP_VERSION_ID < 80100) {
|
|
34
|
-
$property->setAccessible(true);
|
|
35
|
-
}
|
|
36
|
-
return $property->getValue();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private function setCircuitBreakerState(array $state): void
|
|
40
|
-
{
|
|
41
|
-
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
42
|
-
if (PHP_VERSION_ID < 80100) {
|
|
43
|
-
$property->setAccessible(true);
|
|
44
|
-
}
|
|
45
|
-
$property->setValue(null, $state);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private function invokeMethod(string $methodName, array $args = [])
|
|
49
|
-
{
|
|
50
|
-
$method = $this->reflection->getMethod($methodName);
|
|
51
|
-
if (PHP_VERSION_ID < 80100) { // PHP 8.1.0
|
|
52
|
-
$method->setAccessible(true);
|
|
53
|
-
}
|
|
54
|
-
return $method->invokeArgs(null, $args);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
public function testInitialStateIsClosed(): void
|
|
58
|
-
{
|
|
59
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
60
|
-
$state = $this->getCircuitBreakerState();
|
|
61
|
-
$this->assertSame('CLOSED', $state['state']);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public function testFailureThresholdTripsBreaker(): void
|
|
65
|
-
{
|
|
66
|
-
for ($i = 0; $i < 4; $i++) {
|
|
67
|
-
$this->invokeMethod('recordDnsFailure');
|
|
68
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
69
|
-
$state = $this->getCircuitBreakerState();
|
|
70
|
-
$this->assertSame('CLOSED', $state['state']);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
$this->invokeMethod('recordDnsFailure');
|
|
74
|
-
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
75
|
-
$state = $this->getCircuitBreakerState();
|
|
76
|
-
$this->assertSame('OPEN', $state['state']);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
public function testCooldownTransitionsToHalfOpen(): void
|
|
80
|
-
{
|
|
81
|
-
for ($i = 0; $i < 5; $i++) {
|
|
82
|
-
$this->invokeMethod('recordDnsFailure');
|
|
83
|
-
}
|
|
84
|
-
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
85
|
-
|
|
86
|
-
usleep(1100000); // 1.1s
|
|
87
|
-
|
|
88
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
89
|
-
$state = $this->getCircuitBreakerState();
|
|
90
|
-
$this->assertSame('HALF-OPEN', $state['state']);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public function testSuccessOnHalfOpenResetsBreaker(): void
|
|
94
|
-
{
|
|
95
|
-
for ($i = 0; $i < 5; $i++) {
|
|
96
|
-
$this->invokeMethod('recordDnsFailure');
|
|
97
|
-
}
|
|
98
|
-
usleep(1100000);
|
|
99
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
100
|
-
|
|
101
|
-
$this->invokeMethod('recordDnsSuccess');
|
|
102
|
-
$state = $this->getCircuitBreakerState();
|
|
103
|
-
$this->assertSame('CLOSED', $state['state']);
|
|
104
|
-
$this->assertSame(0, $state['failureCount']);
|
|
105
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\FingerprintEngine;
|
|
8
|
+
use PHPUnit\Framework\TestCase;
|
|
9
|
+
use ReflectionClass;
|
|
10
|
+
|
|
11
|
+
class DnsCircuitBreakerTest extends TestCase
|
|
12
|
+
{
|
|
13
|
+
private ReflectionClass $reflection;
|
|
14
|
+
|
|
15
|
+
protected function setUp(): void
|
|
16
|
+
{
|
|
17
|
+
parent::setUp();
|
|
18
|
+
$this->reflection = new ReflectionClass(FingerprintEngine::class);
|
|
19
|
+
|
|
20
|
+
// Reset static circuit breaker state before each test
|
|
21
|
+
$this->setCircuitBreakerState([
|
|
22
|
+
'state' => 'CLOSED',
|
|
23
|
+
'failureCount' => 0,
|
|
24
|
+
'lastStateChange' => 0,
|
|
25
|
+
'threshold' => 5,
|
|
26
|
+
'cooldown' => 1, // 1 second cooldown for faster test execution
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private function getCircuitBreakerState(): array
|
|
31
|
+
{
|
|
32
|
+
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
33
|
+
if (PHP_VERSION_ID < 80100) {
|
|
34
|
+
$property->setAccessible(true);
|
|
35
|
+
}
|
|
36
|
+
return $property->getValue();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private function setCircuitBreakerState(array $state): void
|
|
40
|
+
{
|
|
41
|
+
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
42
|
+
if (PHP_VERSION_ID < 80100) {
|
|
43
|
+
$property->setAccessible(true);
|
|
44
|
+
}
|
|
45
|
+
$property->setValue(null, $state);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private function invokeMethod(string $methodName, array $args = [])
|
|
49
|
+
{
|
|
50
|
+
$method = $this->reflection->getMethod($methodName);
|
|
51
|
+
if (PHP_VERSION_ID < 80100) { // PHP 8.1.0
|
|
52
|
+
$method->setAccessible(true);
|
|
53
|
+
}
|
|
54
|
+
return $method->invokeArgs(null, $args);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public function testInitialStateIsClosed(): void
|
|
58
|
+
{
|
|
59
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
60
|
+
$state = $this->getCircuitBreakerState();
|
|
61
|
+
$this->assertSame('CLOSED', $state['state']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public function testFailureThresholdTripsBreaker(): void
|
|
65
|
+
{
|
|
66
|
+
for ($i = 0; $i < 4; $i++) {
|
|
67
|
+
$this->invokeMethod('recordDnsFailure');
|
|
68
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
69
|
+
$state = $this->getCircuitBreakerState();
|
|
70
|
+
$this->assertSame('CLOSED', $state['state']);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
$this->invokeMethod('recordDnsFailure');
|
|
74
|
+
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
75
|
+
$state = $this->getCircuitBreakerState();
|
|
76
|
+
$this->assertSame('OPEN', $state['state']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public function testCooldownTransitionsToHalfOpen(): void
|
|
80
|
+
{
|
|
81
|
+
for ($i = 0; $i < 5; $i++) {
|
|
82
|
+
$this->invokeMethod('recordDnsFailure');
|
|
83
|
+
}
|
|
84
|
+
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
85
|
+
|
|
86
|
+
usleep(1100000); // 1.1s
|
|
87
|
+
|
|
88
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
89
|
+
$state = $this->getCircuitBreakerState();
|
|
90
|
+
$this->assertSame('HALF-OPEN', $state['state']);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public function testSuccessOnHalfOpenResetsBreaker(): void
|
|
94
|
+
{
|
|
95
|
+
for ($i = 0; $i < 5; $i++) {
|
|
96
|
+
$this->invokeMethod('recordDnsFailure');
|
|
97
|
+
}
|
|
98
|
+
usleep(1100000);
|
|
99
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
100
|
+
|
|
101
|
+
$this->invokeMethod('recordDnsSuccess');
|
|
102
|
+
$state = $this->getCircuitBreakerState();
|
|
103
|
+
$this->assertSame('CLOSED', $state['state']);
|
|
104
|
+
$this->assertSame(0, $state['failureCount']);
|
|
105
|
+
}
|
|
106
106
|
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\FingerprintClient;
|
|
8
|
-
use PHPUnit\Framework\TestCase;
|
|
9
|
-
|
|
10
|
-
class FingerprintClientTest extends TestCase
|
|
11
|
-
{
|
|
12
|
-
public function testDefaultConfigurationIncludesWasmAndDefaultPath(): void
|
|
13
|
-
{
|
|
14
|
-
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
15
|
-
$scriptTag = $client->getScriptTag();
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
$this->assertStringContainsString('src="/js/fingerprint.client.js"', $scriptTag);
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
$this->assertStringContainsString('"wasm":true', $scriptTag);
|
|
22
|
-
$this->assertStringContainsString('"wasmPath":"\/fp.js"', $scriptTag);
|
|
23
|
-
$this->assertStringContainsString('wasmScript.src = config.wasmPath;', $scriptTag);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public function testWasmCanBeOverriddenOrDisabled(): void
|
|
27
|
-
{
|
|
28
|
-
$client = new FingerprintClient('/js/fingerprint.client.js', [
|
|
29
|
-
'wasm' => false,
|
|
30
|
-
'wasmPath' => '/custom/path/to/wasm.js'
|
|
31
|
-
]);
|
|
32
|
-
$scriptTag = $client->getScriptTag();
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
$this->assertStringContainsString('"wasm":false', $scriptTag);
|
|
36
|
-
$this->assertStringContainsString('"wasmPath":"\/custom\/path\/to\/wasm.js"', $scriptTag);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public function testGenerateHoneypotFieldAddsFieldToConfig(): void
|
|
40
|
-
{
|
|
41
|
-
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
42
|
-
|
|
43
|
-
$honeypotFieldHtml = $client->generateHoneypotField('confirm_email_trap');
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
$this->assertStringContainsString('name="confirm_email_trap"', $honeypotFieldHtml);
|
|
47
|
-
$this->assertStringContainsString('tabindex="-1"', $honeypotFieldHtml);
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
$scriptTag = $client->getScriptTag();
|
|
51
|
-
$this->assertStringContainsString('"honeypots":["confirm_email_trap"]', $scriptTag);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public function testNonceIsGeneratedAndAppliedToScripts(): void
|
|
55
|
-
{
|
|
56
|
-
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
57
|
-
$nonce = $client->getNonce();
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
$this->assertNotNull($nonce);
|
|
61
|
-
$this->assertEquals(32, strlen($nonce));
|
|
62
|
-
|
|
63
|
-
$scriptTag = $client->getScriptTag();
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
$this->assertStringContainsString('nonce="' . $nonce . '"', $scriptTag);
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
$this->assertStringContainsString('wasmScript.nonce = \'' . $nonce . '\'', $scriptTag);
|
|
70
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\FingerprintClient;
|
|
8
|
+
use PHPUnit\Framework\TestCase;
|
|
9
|
+
|
|
10
|
+
class FingerprintClientTest extends TestCase
|
|
11
|
+
{
|
|
12
|
+
public function testDefaultConfigurationIncludesWasmAndDefaultPath(): void
|
|
13
|
+
{
|
|
14
|
+
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
15
|
+
$scriptTag = $client->getScriptTag();
|
|
16
|
+
|
|
17
|
+
// Verify main script tag is present
|
|
18
|
+
$this->assertStringContainsString('src="/js/fingerprint.client.js"', $scriptTag);
|
|
19
|
+
|
|
20
|
+
// Verify default WASM configuration serialized for JS runtime
|
|
21
|
+
$this->assertStringContainsString('"wasm":true', $scriptTag);
|
|
22
|
+
$this->assertStringContainsString('"wasmPath":"\/fp.js"', $scriptTag);
|
|
23
|
+
$this->assertStringContainsString('wasmScript.src = config.wasmPath;', $scriptTag);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public function testWasmCanBeOverriddenOrDisabled(): void
|
|
27
|
+
{
|
|
28
|
+
$client = new FingerprintClient('/js/fingerprint.client.js', [
|
|
29
|
+
'wasm' => false,
|
|
30
|
+
'wasmPath' => '/custom/path/to/wasm.js'
|
|
31
|
+
]);
|
|
32
|
+
$scriptTag = $client->getScriptTag();
|
|
33
|
+
|
|
34
|
+
// Verify configuration overrides are properly applied
|
|
35
|
+
$this->assertStringContainsString('"wasm":false', $scriptTag);
|
|
36
|
+
$this->assertStringContainsString('"wasmPath":"\/custom\/path\/to\/wasm.js"', $scriptTag);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public function testGenerateHoneypotFieldAddsFieldToConfig(): void
|
|
40
|
+
{
|
|
41
|
+
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
42
|
+
|
|
43
|
+
$honeypotFieldHtml = $client->generateHoneypotField('confirm_email_trap');
|
|
44
|
+
|
|
45
|
+
// Verify HTML markup of the honeypot field
|
|
46
|
+
$this->assertStringContainsString('name="confirm_email_trap"', $honeypotFieldHtml);
|
|
47
|
+
$this->assertStringContainsString('tabindex="-1"', $honeypotFieldHtml);
|
|
48
|
+
|
|
49
|
+
// Verify field dynamically registers with client-side configuration
|
|
50
|
+
$scriptTag = $client->getScriptTag();
|
|
51
|
+
$this->assertStringContainsString('"honeypots":["confirm_email_trap"]', $scriptTag);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public function testNonceIsGeneratedAndAppliedToScripts(): void
|
|
55
|
+
{
|
|
56
|
+
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
57
|
+
$nonce = $client->getNonce();
|
|
58
|
+
|
|
59
|
+
// Verify nonce generation format (32 hex characters)
|
|
60
|
+
$this->assertNotNull($nonce);
|
|
61
|
+
$this->assertEquals(32, strlen($nonce));
|
|
62
|
+
|
|
63
|
+
$scriptTag = $client->getScriptTag();
|
|
64
|
+
|
|
65
|
+
// Nonce must be attached to HTML script tag
|
|
66
|
+
$this->assertStringContainsString('nonce="' . $nonce . '"', $scriptTag);
|
|
67
|
+
|
|
68
|
+
// And dynamically set during WASM loader script injection
|
|
69
|
+
$this->assertStringContainsString('wasmScript.nonce = \'' . $nonce . '\'', $scriptTag);
|
|
70
|
+
}
|
|
71
71
|
}
|
|
@@ -1,40 +1,38 @@
|
|
|
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.
|
|
21
|
-
$store = new InMemoryStore();
|
|
22
|
-
StoreManager::configureStore($store);
|
|
23
|
-
|
|
24
|
-
// 2.
|
|
25
|
-
|
|
26
|
-
$configPath
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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. Configure an in-memory store for test isolation.
|
|
21
|
+
$store = new InMemoryStore();
|
|
22
|
+
StoreManager::configureStore($store);
|
|
23
|
+
|
|
24
|
+
// 2. Initialize ProblemManager with a valid configuration and store.
|
|
25
|
+
$configPath = dirname(__FILE__) . '/config/problems.config.json';
|
|
26
|
+
ProblemManager::getInstance($configPath, $store);
|
|
27
|
+
|
|
28
|
+
// 3. Create engine instance.
|
|
29
|
+
$this->engine = new FingerprintEngine(SecurityProfiles::createSecurityProfile('balanced'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public function testGetProblemsIsExposedForTesting(): void
|
|
33
|
+
{
|
|
34
|
+
// Calls ProblemManager::getInstance() internally; setUp() ensures valid initialization.
|
|
35
|
+
$problems = $this->engine->getProblems();
|
|
36
|
+
$this->assertIsArray($problems);
|
|
37
|
+
}
|
|
40
38
|
}
|
|
@@ -1,54 +1,116 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
-
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
9
|
-
use PHPUnit\Framework\TestCase;
|
|
10
|
-
|
|
11
|
-
class QuicFingerprintTest extends TestCase
|
|
12
|
-
{
|
|
13
|
-
public function testGetProtocolAnomalyScoreReturnsZeroIfNoFp(): void
|
|
14
|
-
{
|
|
15
|
-
$context = $this->createMock(RequestContext::class);
|
|
16
|
-
$context->method('getHeader')->willReturn(null);
|
|
17
|
-
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
18
|
-
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public function testGetProtocolAnomalyScoreDetectsSpoofedChrome(): void
|
|
22
|
-
{
|
|
23
|
-
$context = $this->createMock(RequestContext::class);
|
|
24
|
-
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
25
|
-
if ($name === 'user-agent') {
|
|
26
|
-
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
27
|
-
}
|
|
28
|
-
if ($name === 'x-quic-fp') {
|
|
29
|
-
return '1;1=65536,4=50;i=0';
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
35
|
-
$this->assertEquals(100.0, $res['protocolAnomalyScore']);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
public function testGetProtocolAnomalyScoreAllowsLegitChrome(): void
|
|
39
|
-
{
|
|
40
|
-
$context = $this->createMock(RequestContext::class);
|
|
41
|
-
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
42
|
-
if ($name === 'user-agent') {
|
|
43
|
-
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
44
|
-
}
|
|
45
|
-
if ($name === 'x-quic-fp') {
|
|
46
|
-
return '1;1=1572864,4=100;u=2,i';
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
52
|
-
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
53
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
+
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
9
|
+
use PHPUnit\Framework\TestCase;
|
|
10
|
+
|
|
11
|
+
class QuicFingerprintTest extends TestCase
|
|
12
|
+
{
|
|
13
|
+
public function testGetProtocolAnomalyScoreReturnsZeroIfNoFp(): void
|
|
14
|
+
{
|
|
15
|
+
$context = $this->createMock(RequestContext::class);
|
|
16
|
+
$context->method('getHeader')->willReturn(null);
|
|
17
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
18
|
+
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public function testGetProtocolAnomalyScoreDetectsSpoofedChrome(): void
|
|
22
|
+
{
|
|
23
|
+
$context = $this->createMock(RequestContext::class);
|
|
24
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
25
|
+
if ($name === 'user-agent') {
|
|
26
|
+
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
27
|
+
}
|
|
28
|
+
if ($name === 'x-quic-fp') {
|
|
29
|
+
return '1;1=65536,4=50;i=0';
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
35
|
+
$this->assertEquals(100.0, $res['protocolAnomalyScore']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function testGetProtocolAnomalyScoreAllowsLegitChrome(): void
|
|
39
|
+
{
|
|
40
|
+
$context = $this->createMock(RequestContext::class);
|
|
41
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
42
|
+
if ($name === 'user-agent') {
|
|
43
|
+
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
44
|
+
}
|
|
45
|
+
if ($name === 'x-quic-fp') {
|
|
46
|
+
return '1;1=1572864,4=100;u=2,i';
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
52
|
+
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public function testRfc9218PriorityWithoutIncrementalFlag(): void
|
|
56
|
+
{
|
|
57
|
+
$context = $this->createMock(RequestContext::class);
|
|
58
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
59
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
60
|
+
if ($name === 'x-quic-fp') return '1;1=1572864,4=100;u=2'; // Manque le drapeau 'i'
|
|
61
|
+
return null;
|
|
62
|
+
});
|
|
63
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
64
|
+
$this->assertEquals(40.0, $res['quicAnomalyScore']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public function testCompressionRatioPenalty(): void
|
|
68
|
+
{
|
|
69
|
+
$context = $this->createMock(RequestContext::class);
|
|
70
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
71
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
72
|
+
if ($name === 'x-compression-info') return 'req_count:5,hpack_ratio:0.2';
|
|
73
|
+
return null;
|
|
74
|
+
});
|
|
75
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
76
|
+
$this->assertEquals(40.0, $res['http2AnomalyScore']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public function testRfc9218UrgencyOutOfRange(): void
|
|
80
|
+
{
|
|
81
|
+
$context = $this->createMock(RequestContext::class);
|
|
82
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
83
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
84
|
+
if ($name === 'x-quic-fp') return '1;1=1572864,4=100;u=9,i';
|
|
85
|
+
return null;
|
|
86
|
+
});
|
|
87
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
88
|
+
$this->assertGreaterThanOrEqual(50.0, $res['quicAnomalyScore']);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public function testHttp2DefaultGoScraperDependencyTree(): void
|
|
92
|
+
{
|
|
93
|
+
$context = $this->createMock(RequestContext::class);
|
|
94
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
95
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
96
|
+
if ($name === 'x-http2-fingerprint') return 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:16|m,a,s,p|p:3,w:4,c:1';
|
|
97
|
+
return null;
|
|
98
|
+
});
|
|
99
|
+
$context->httpVersion = '2.0';
|
|
100
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
101
|
+
$this->assertGreaterThanOrEqual(55.0, $res['http2AnomalyScore']);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public function testScraperWithZeroDynamicTableEntries(): void
|
|
105
|
+
{
|
|
106
|
+
$context = $this->createMock(RequestContext::class);
|
|
107
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
108
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
109
|
+
if ($name === 'x-compression-info') return 'req_count:5,dynamic_table_entries:0';
|
|
110
|
+
return null;
|
|
111
|
+
});
|
|
112
|
+
$context->httpVersion = '2.0';
|
|
113
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
114
|
+
$this->assertGreaterThanOrEqual(45.0, $res['http2AnomalyScore']);
|
|
115
|
+
}
|
|
54
116
|
}
|
|
@@ -61,4 +61,38 @@ class ThreatIntelTest extends TestCase
|
|
|
61
61
|
$this->assertGreaterThan(75.0, $score);
|
|
62
62
|
$this->assertLessThanOrEqual(95.0, $score);
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
public function testDifferentialPrivacyDecoyInjectionAndTimestampNoise(): void
|
|
66
|
+
{
|
|
67
|
+
$config = [
|
|
68
|
+
'federatedPeers' => ['https://peer1.example.com'],
|
|
69
|
+
'federationSecret' => 'test-secret-key-32-chars-long!!',
|
|
70
|
+
'differentialPrivacy' => [
|
|
71
|
+
'enabled' => true,
|
|
72
|
+
'epsilon' => 1.0,
|
|
73
|
+
'dummyRate' => 1.0 // Force decoy injection for test
|
|
74
|
+
]
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
$postedCalls = [];
|
|
78
|
+
$engineSub = new class($config, $postedCalls) extends \Anonympins\Fingerprint\FingerprintEngine {
|
|
79
|
+
public array $calls = [];
|
|
80
|
+
public function __construct(array $cfg, array &$callsRef) {
|
|
81
|
+
$this->calls = &$callsRef;
|
|
82
|
+
parent::__construct($cfg);
|
|
83
|
+
}
|
|
84
|
+
protected function asyncPost(string $url, array $params): void {
|
|
85
|
+
$this->calls[] = ['url' => $url, 'params' => $params];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
$refSub = new \ReflectionClass($engineSub);
|
|
90
|
+
$broadcastMethod = $refSub->getMethod('broadcastBannedZkp');
|
|
91
|
+
$broadcastMethod->setAccessible(true);
|
|
92
|
+
|
|
93
|
+
$realZkpY = 'realzkpykey12345';
|
|
94
|
+
$broadcastMethod->invoke($engineSub, $realZkpY);
|
|
95
|
+
|
|
96
|
+
$this->assertCount(2, $postedCalls, 'Should broadcast both real and decoy keys');
|
|
97
|
+
}
|
|
64
98
|
}
|