@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,157 +1,157 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
-
use Anonympins\Fingerprint\Store\IStore;
|
|
9
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
10
|
-
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
11
|
-
use PHPUnit\Framework\TestCase;
|
|
12
|
-
|
|
13
|
-
class IpReputationTest extends TestCase
|
|
14
|
-
{
|
|
15
|
-
private $store;
|
|
16
|
-
|
|
17
|
-
protected function setUp(): void
|
|
18
|
-
{
|
|
19
|
-
parent::setUp();
|
|
20
|
-
|
|
21
|
-
// Un mock anonyme simple et en mémoire du Store pour isoler les tests
|
|
22
|
-
$this->store = new class implements IStore {
|
|
23
|
-
private array $data = [];
|
|
24
|
-
public function get(string $key) { return $this->data[$key] ?? null; }
|
|
25
|
-
public function set(string $key, $value, ?int $ttl = null): void { $this->data[$key] = $value; }
|
|
26
|
-
public function has(string $key): bool { return isset($this->data[$key]); }
|
|
27
|
-
public function delete(string $key): void { unset($this->data[$key]); }
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
StoreManager::setStore($this->store);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public function testGetScoreReturnsZeroForUnknownIp(): void
|
|
34
|
-
{
|
|
35
|
-
$score = RequestUtils::getIpReputationScore('8.8.8.8');
|
|
36
|
-
$this->assertEquals(0.0, $score);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public function testUpdateScoreChangesValueCorrectly(): void
|
|
40
|
-
{
|
|
41
|
-
$ip = '8.8.4.4';
|
|
42
|
-
RequestUtils::updateIpReputationScore($ip, 45.0);
|
|
43
|
-
$this->assertEquals(45.0, RequestUtils::getIpReputationScore($ip));
|
|
44
|
-
|
|
45
|
-
RequestUtils::updateIpReputationScore($ip, -15.0);
|
|
46
|
-
$this->assertEquals(30.0, RequestUtils::getIpReputationScore($ip));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
public function testBoundsAreClampedBetween0And100(): void
|
|
50
|
-
{
|
|
51
|
-
$ip = '1.1.1.1';
|
|
52
|
-
RequestUtils::updateIpReputationScore($ip, 120.0);
|
|
53
|
-
$this->assertEquals(100.0, RequestUtils::getIpReputationScore($ip));
|
|
54
|
-
|
|
55
|
-
RequestUtils::updateIpReputationScore($ip, -150.0);
|
|
56
|
-
$this->assertEquals(0.0, RequestUtils::getIpReputationScore($ip));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public function testTimeDecayCalculatesLossOfTwoPointsPerHour(): void
|
|
60
|
-
{
|
|
61
|
-
$ip = '2.2.2.2';
|
|
62
|
-
$now = time();
|
|
63
|
-
|
|
64
|
-
// Simulation d'un score de 80 vieux de 4 heures (4 * 2 = 8 points de perte)
|
|
65
|
-
$this->store->set("ip-reputation:{$ip}", [
|
|
66
|
-
'score' => 80.0,
|
|
67
|
-
'lastUpdate' => $now - 14400
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
$score = RequestUtils::getIpReputationScore($ip);
|
|
71
|
-
$this->assertEquals(72.0, $score);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public function testIpReputationScoreIsIntegratedIntoFinalScore(): void
|
|
75
|
-
{
|
|
76
|
-
$ip = '1.2.3.4';
|
|
77
|
-
RequestUtils::updateIpReputationScore($ip, 60.0);
|
|
78
|
-
|
|
79
|
-
$weights = [
|
|
80
|
-
'ipReputationScore' => 0.5,
|
|
81
|
-
'historyScore' => 0.0,
|
|
82
|
-
'rotationScore' => 0.0,
|
|
83
|
-
'headerAnomalyScore' => 0.0,
|
|
84
|
-
'requestPatternScore' => 0.0,
|
|
85
|
-
'inconsistencyScore' => 0.0,
|
|
86
|
-
'honeypotScore' => 0.0,
|
|
87
|
-
'behaviorScore' => 0.0,
|
|
88
|
-
'botScore' => 0.0,
|
|
89
|
-
'crossLayerInconsistencyScore' => 0.0,
|
|
90
|
-
'tlsSpoofingScore' => 0.0,
|
|
91
|
-
'timeInconsistencyScore' => 0.0,
|
|
92
|
-
'clickVarianceScore' => 0.0,
|
|
93
|
-
'clientHintsInconsistencyScore' => 0.0,
|
|
94
|
-
'subnetScore' => 0.0,
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
$ipRepScore = RequestUtils::getIpReputationScore($ip);
|
|
98
|
-
$this->assertEquals(60.0, $ipRepScore);
|
|
99
|
-
$score = $ipRepScore * $weights['ipReputationScore'];
|
|
100
|
-
$this->assertEquals(30.0, $score);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public function testGetIpSubnetIpv4AndIpv6(): void
|
|
104
|
-
{
|
|
105
|
-
$ipv4Subnet = RequestUtils::getIpSubnet('192.168.1.50', 24, 48);
|
|
106
|
-
$this->assertEquals('192.168.1.0/24', $ipv4Subnet);
|
|
107
|
-
|
|
108
|
-
$ipv6Subnet = RequestUtils::getIpSubnet('2001:db8:abcd:0012::1', 24, 48);
|
|
109
|
-
$this->assertEquals('2001:db8:abcd::/48', $ipv6Subnet);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public function testGetClientHintsInconsistencyScoreMismatch(): void
|
|
113
|
-
{
|
|
114
|
-
$context = $this->createMock(RequestContext::class);
|
|
115
|
-
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
116
|
-
if ($name === 'user-agent') {
|
|
117
|
-
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/117.0';
|
|
118
|
-
}
|
|
119
|
-
if ($name === 'sec-ch-ua') {
|
|
120
|
-
return '"Google Chrome";v="117"';
|
|
121
|
-
}
|
|
122
|
-
return null;
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
$score = RequestUtils::getClientHintsInconsistencyScore($context);
|
|
126
|
-
$this->assertEquals(90.0, $score['clientHintsInconsistencyScore']);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
public function testGetSubnetScoreCalculations(): void
|
|
130
|
-
{
|
|
131
|
-
$context = $this->createMock(RequestContext::class);
|
|
132
|
-
$context->clientIp = '192.168.1.50';
|
|
133
|
-
|
|
134
|
-
$score = RequestUtils::getSubnetScore($context, 'device-1');
|
|
135
|
-
$this->assertEquals(0.0, $score['subnetScore']);
|
|
136
|
-
|
|
137
|
-
for ($i = 1; $i <= 12; $i++) {
|
|
138
|
-
RequestUtils::updateSubnetMetrics($context, "device-{$i}", 30.0);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
$scoreWithHistory = RequestUtils::getSubnetScore($context, 'device-1');
|
|
142
|
-
$this->assertGreaterThan(0.0, $scoreWithHistory['subnetScore']);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
public function testTlsSpoofingScoreWithJa4(): void
|
|
146
|
-
{
|
|
147
|
-
$context = $this->createMock(RequestContext::class);
|
|
148
|
-
$context->ja4 = 't13d1517h2_8daaf61527d5';
|
|
149
|
-
$context->ja3 = null;
|
|
150
|
-
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
151
|
-
return $name === 'user-agent' ? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/117.0' : null;
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
$score = RequestUtils::getTlsSpoofingScore($context);
|
|
155
|
-
$this->assertEquals(90.0, $score['tlsSpoofingScore']);
|
|
156
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
+
use Anonympins\Fingerprint\Store\IStore;
|
|
9
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
10
|
+
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
11
|
+
use PHPUnit\Framework\TestCase;
|
|
12
|
+
|
|
13
|
+
class IpReputationTest extends TestCase
|
|
14
|
+
{
|
|
15
|
+
private $store;
|
|
16
|
+
|
|
17
|
+
protected function setUp(): void
|
|
18
|
+
{
|
|
19
|
+
parent::setUp();
|
|
20
|
+
|
|
21
|
+
// Un mock anonyme simple et en mémoire du Store pour isoler les tests
|
|
22
|
+
$this->store = new class implements IStore {
|
|
23
|
+
private array $data = [];
|
|
24
|
+
public function get(string $key) { return $this->data[$key] ?? null; }
|
|
25
|
+
public function set(string $key, $value, ?int $ttl = null): void { $this->data[$key] = $value; }
|
|
26
|
+
public function has(string $key): bool { return isset($this->data[$key]); }
|
|
27
|
+
public function delete(string $key): void { unset($this->data[$key]); }
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
StoreManager::setStore($this->store);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public function testGetScoreReturnsZeroForUnknownIp(): void
|
|
34
|
+
{
|
|
35
|
+
$score = RequestUtils::getIpReputationScore('8.8.8.8');
|
|
36
|
+
$this->assertEquals(0.0, $score);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public function testUpdateScoreChangesValueCorrectly(): void
|
|
40
|
+
{
|
|
41
|
+
$ip = '8.8.4.4';
|
|
42
|
+
RequestUtils::updateIpReputationScore($ip, 45.0);
|
|
43
|
+
$this->assertEquals(45.0, RequestUtils::getIpReputationScore($ip));
|
|
44
|
+
|
|
45
|
+
RequestUtils::updateIpReputationScore($ip, -15.0);
|
|
46
|
+
$this->assertEquals(30.0, RequestUtils::getIpReputationScore($ip));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public function testBoundsAreClampedBetween0And100(): void
|
|
50
|
+
{
|
|
51
|
+
$ip = '1.1.1.1';
|
|
52
|
+
RequestUtils::updateIpReputationScore($ip, 120.0);
|
|
53
|
+
$this->assertEquals(100.0, RequestUtils::getIpReputationScore($ip));
|
|
54
|
+
|
|
55
|
+
RequestUtils::updateIpReputationScore($ip, -150.0);
|
|
56
|
+
$this->assertEquals(0.0, RequestUtils::getIpReputationScore($ip));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public function testTimeDecayCalculatesLossOfTwoPointsPerHour(): void
|
|
60
|
+
{
|
|
61
|
+
$ip = '2.2.2.2';
|
|
62
|
+
$now = time();
|
|
63
|
+
|
|
64
|
+
// Simulation d'un score de 80 vieux de 4 heures (4 * 2 = 8 points de perte)
|
|
65
|
+
$this->store->set("ip-reputation:{$ip}", [
|
|
66
|
+
'score' => 80.0,
|
|
67
|
+
'lastUpdate' => $now - 14400
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
$score = RequestUtils::getIpReputationScore($ip);
|
|
71
|
+
$this->assertEquals(72.0, $score);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public function testIpReputationScoreIsIntegratedIntoFinalScore(): void
|
|
75
|
+
{
|
|
76
|
+
$ip = '1.2.3.4';
|
|
77
|
+
RequestUtils::updateIpReputationScore($ip, 60.0);
|
|
78
|
+
|
|
79
|
+
$weights = [
|
|
80
|
+
'ipReputationScore' => 0.5,
|
|
81
|
+
'historyScore' => 0.0,
|
|
82
|
+
'rotationScore' => 0.0,
|
|
83
|
+
'headerAnomalyScore' => 0.0,
|
|
84
|
+
'requestPatternScore' => 0.0,
|
|
85
|
+
'inconsistencyScore' => 0.0,
|
|
86
|
+
'honeypotScore' => 0.0,
|
|
87
|
+
'behaviorScore' => 0.0,
|
|
88
|
+
'botScore' => 0.0,
|
|
89
|
+
'crossLayerInconsistencyScore' => 0.0,
|
|
90
|
+
'tlsSpoofingScore' => 0.0,
|
|
91
|
+
'timeInconsistencyScore' => 0.0,
|
|
92
|
+
'clickVarianceScore' => 0.0,
|
|
93
|
+
'clientHintsInconsistencyScore' => 0.0,
|
|
94
|
+
'subnetScore' => 0.0,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
$ipRepScore = RequestUtils::getIpReputationScore($ip);
|
|
98
|
+
$this->assertEquals(60.0, $ipRepScore);
|
|
99
|
+
$score = $ipRepScore * $weights['ipReputationScore'];
|
|
100
|
+
$this->assertEquals(30.0, $score);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public function testGetIpSubnetIpv4AndIpv6(): void
|
|
104
|
+
{
|
|
105
|
+
$ipv4Subnet = RequestUtils::getIpSubnet('192.168.1.50', 24, 48);
|
|
106
|
+
$this->assertEquals('192.168.1.0/24', $ipv4Subnet);
|
|
107
|
+
|
|
108
|
+
$ipv6Subnet = RequestUtils::getIpSubnet('2001:db8:abcd:0012::1', 24, 48);
|
|
109
|
+
$this->assertEquals('2001:db8:abcd::/48', $ipv6Subnet);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public function testGetClientHintsInconsistencyScoreMismatch(): void
|
|
113
|
+
{
|
|
114
|
+
$context = $this->createMock(RequestContext::class);
|
|
115
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
116
|
+
if ($name === 'user-agent') {
|
|
117
|
+
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/117.0';
|
|
118
|
+
}
|
|
119
|
+
if ($name === 'sec-ch-ua') {
|
|
120
|
+
return '"Google Chrome";v="117"';
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
$score = RequestUtils::getClientHintsInconsistencyScore($context);
|
|
126
|
+
$this->assertEquals(90.0, $score['clientHintsInconsistencyScore']);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public function testGetSubnetScoreCalculations(): void
|
|
130
|
+
{
|
|
131
|
+
$context = $this->createMock(RequestContext::class);
|
|
132
|
+
$context->clientIp = '192.168.1.50';
|
|
133
|
+
|
|
134
|
+
$score = RequestUtils::getSubnetScore($context, 'device-1');
|
|
135
|
+
$this->assertEquals(0.0, $score['subnetScore']);
|
|
136
|
+
|
|
137
|
+
for ($i = 1; $i <= 12; $i++) {
|
|
138
|
+
RequestUtils::updateSubnetMetrics($context, "device-{$i}", 30.0);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
$scoreWithHistory = RequestUtils::getSubnetScore($context, 'device-1');
|
|
142
|
+
$this->assertGreaterThan(0.0, $scoreWithHistory['subnetScore']);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public function testTlsSpoofingScoreWithJa4(): void
|
|
146
|
+
{
|
|
147
|
+
$context = $this->createMock(RequestContext::class);
|
|
148
|
+
$context->ja4 = 't13d1517h2_8daaf61527d5';
|
|
149
|
+
$context->ja3 = null;
|
|
150
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
151
|
+
return $name === 'user-agent' ? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/117.0' : null;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
$score = RequestUtils::getTlsSpoofingScore($context);
|
|
155
|
+
$this->assertEquals(90.0, $score['tlsSpoofingScore']);
|
|
156
|
+
}
|
|
157
157
|
}
|