@anonympins/fingerprint 0.7.1 → 0.7.3

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +28 -3
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/public/fingerprint-anti-bot.zip +0 -0
  5. package/src/js/fingerprint.js +106 -29
  6. package/src/js/fingerprint.utils.js +32 -5
  7. package/src/js/tests/fingerprint.test.js +91 -4
  8. package/src/js/tests/threatIntel.test.js +76 -0
  9. package/src/php/AutoTuner.php +36 -18
  10. package/src/php/Challenge/ChallengeUtils.php +107 -225
  11. package/src/php/DirectFingerprint.php +16 -4
  12. package/src/php/FingerprintClient.php +17 -19
  13. package/src/php/FingerprintEngine.php +114 -51
  14. package/src/php/Ja3AnomalyDetector.php +20 -12
  15. package/src/php/Optimization/Optimization.php +5 -1
  16. package/src/php/Optimization/OptimizationOperators.php +5 -1
  17. package/src/php/Optimization/ProblemInitializers.php +16 -4
  18. package/src/php/ProblemManager.php +20 -14
  19. package/src/php/Tests/FingerprintEngineTest.php +705 -662
  20. package/src/php/Tests/ThreatIntelTest.php +64 -0
  21. package/src/php/Utils/BlockList.php +99 -99
  22. package/src/php/Utils/Env.php +51 -49
  23. package/src/php/Utils/MaliciousPatterns.php +74 -74
  24. package/src/php/Utils/RequestUtils.php +22 -5
  25. package/src/php/WordPress/WpDbStore.php +4 -0
  26. package/src/php/WordPress/fingerprint-anti-bot.php +559 -0
  27. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  28. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
  29. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  30. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
  31. package/src/php/WordPress/package.php +102 -4
  32. package/src/php/WordPress/readme.txt +36 -0
  33. package/public/fingerprint-wordpress.zip +0 -0
  34. package/src/js/fingerprint.client.obfuscated.js +0 -1
  35. package/src/php/WordPress/fingerprint-wordpress.php +0 -217
@@ -0,0 +1,64 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Tests;
6
+
7
+ use Anonympins\Fingerprint\Ja3AnomalyDetector;
8
+ use Anonympins\Fingerprint\RequestContext;
9
+ use Anonympins\Fingerprint\Store\InMemoryStore;
10
+ use Anonympins\Fingerprint\Store\StoreManager;
11
+ use Anonympins\Fingerprint\Utils\RequestUtils;
12
+ use PHPUnit\Framework\TestCase;
13
+
14
+ class ThreatIntelTest extends TestCase
15
+ {
16
+ protected function setUp(): void
17
+ {
18
+ parent::setUp();
19
+ StoreManager::configureStore(new InMemoryStore());
20
+ }
21
+
22
+ public function testRttProxyScoreReturnsZeroForCoherentTurnaround(): void
23
+ {
24
+ $reqTime = 1700000000000;
25
+ $context = new RequestContext(
26
+ '127.0.0.1', '/',
27
+ [
28
+ 'x-tcp-rtt' => '15',
29
+ 'x-behavior-metrics' => json_encode(['clientTimestamp' => $reqTime - 30])
30
+ ],
31
+ [], null, [], '1.1', $reqTime
32
+ );
33
+
34
+ $score = Ja3AnomalyDetector::getRttProxyScore($context);
35
+ $this->assertEquals(0.0, $score);
36
+ }
37
+
38
+ public function testRttProxyScoreIgnoresSlowConnectionWithoutTcpRtt(): void
39
+ {
40
+ $reqTime = 1700000000000;
41
+ $context = new RequestContext(
42
+ '127.0.0.1', '/',
43
+ ['x-behavior-metrics' => json_encode(['clientTimestamp' => $reqTime - 450])],
44
+ [], null, [], '1.1', $reqTime
45
+ );
46
+
47
+ $score = Ja3AnomalyDetector::getRttProxyScore($context);
48
+ $this->assertEquals(0.0, $score, 'A slow connection without TCP RTT context should not trigger a false positive.');
49
+ }
50
+
51
+ public function testRttProxyScoreContinuouslyPenalizesResidentialTunneling(): void
52
+ {
53
+ $reqTime = 1700000000000;
54
+ $context = new RequestContext(
55
+ '127.0.0.1', '/',
56
+ ['x-tcp-rtt' => '10', 'x-behavior-metrics' => json_encode(['clientTimestamp' => $reqTime - 300])],
57
+ [], null, [], '1.1', $reqTime
58
+ );
59
+
60
+ $score = Ja3AnomalyDetector::getRttProxyScore($context);
61
+ $this->assertGreaterThan(75.0, $score);
62
+ $this->assertLessThanOrEqual(95.0, $score);
63
+ }
64
+ }
@@ -1,100 +1,100 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Utils;
6
-
7
- /**
8
- * Implémentation PHP d'une liste de blocage IP/CIDR, similaire à Node.js `net.BlockList`.
9
- */
10
- class BlockList
11
- {
12
- /**
13
- * @var array<string> Liste des IPs ou CIDR à bloquer.
14
- */
15
- private array $entries = [];
16
-
17
- /**
18
- * Ajoute une adresse IP ou une plage CIDR à la liste.
19
- * @param string $entry Une adresse IP (ex: '192.168.1.1') ou une plage CIDR (ex: '192.168.1.0/24').
20
- * @return void
21
- */
22
- public function add(string $entry): void
23
- {
24
- $this->entries[] = $entry;
25
- }
26
-
27
- /**
28
- * Vérifie si une adresse IP est présente dans la liste de blocage.
29
- * @param string $ip L'adresse IP à vérifier.
30
- * @return bool True si l'IP est bloquée, false sinon.
31
- */
32
- public function check(string $ip): bool
33
- {
34
- foreach ($this->entries as $entry) {
35
- if (str_contains($entry, '/')) {
36
- // C'est une plage CIDR
37
- if ($this->ipInCidr($ip, $entry)) {
38
- return true;
39
- }
40
- } else {
41
- // C'est une IP directe
42
- if ($ip === $entry) {
43
- return true;
44
- }
45
- }
46
- }
47
- return false;
48
- }
49
-
50
- /**
51
- * Vérifie si une adresse IP se trouve dans une plage CIDR donnée.
52
- * @param string $ip L'adresse IP à vérifier.
53
- * @param string $cidr La plage CIDR (ex: '192.168.1.0/24').
54
- * @return bool True si l'IP est dans la plage, false sinon.
55
- */
56
- private function ipInCidr(string $ip, string $cidr): bool
57
- {
58
- [$network, $mask] = explode('/', $cidr);
59
- $mask = (int)$mask;
60
-
61
- if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && filter_var($network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
62
- // IPv6
63
- $ipBinary = inet_pton($ip);
64
- $networkBinary = inet_pton($network);
65
-
66
- if ($ipBinary === false || $networkBinary === false) {
67
- return false;
68
- }
69
-
70
- $ipHex = bin2hex($ipBinary);
71
- $networkHex = bin2hex($networkBinary);
72
-
73
- $numBytes = (int)ceil($mask / 8);
74
- $compareLength = $numBytes * 2;
75
-
76
- if (substr($ipHex, 0, $compareLength) !== substr($networkHex, 0, $compareLength)) {
77
- return false;
78
- }
79
-
80
- if ($mask % 8 !== 0) {
81
- $bitMask = (0xFF << (8 - ($mask % 8))) & 0xFF;
82
- $ipByte = hexdec(substr($ipHex, $numBytes * 2 - 2, 2));
83
- $networkByte = hexdec(substr($networkHex, $numBytes * 2 - 2, 2));
84
- return ($ipByte & $bitMask) === ($networkByte & $bitMask);
85
- }
86
-
87
- return true;
88
-
89
- } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && filter_var($network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
90
- // IPv4
91
- $ipLong = ip2long($ip);
92
- $networkLong = ip2long($network);
93
- $wildcard = pow(2, (32 - $mask)) - 1;
94
- $netmask = ~$wildcard;
95
- return (($ipLong & $netmask) === ($networkLong & $netmask));
96
- }
97
-
98
- return false;
99
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Utils;
6
+
7
+ /**
8
+ * Implémentation PHP d'une liste de blocage IP/CIDR, similaire à Node.js `net.BlockList`.
9
+ */
10
+ class BlockList
11
+ {
12
+ /**
13
+ * @var array<string> Liste des IPs ou CIDR à bloquer.
14
+ */
15
+ private array $entries = [];
16
+
17
+ /**
18
+ * Ajoute une adresse IP ou une plage CIDR à la liste.
19
+ * @param string $entry Une adresse IP (ex: '192.168.1.1') ou une plage CIDR (ex: '192.168.1.0/24').
20
+ * @return void
21
+ */
22
+ public function add(string $entry): void
23
+ {
24
+ $this->entries[] = $entry;
25
+ }
26
+
27
+ /**
28
+ * Vérifie si une adresse IP est présente dans la liste de blocage.
29
+ * @param string $ip L'adresse IP à vérifier.
30
+ * @return bool True si l'IP est bloquée, false sinon.
31
+ */
32
+ public function check(string $ip): bool
33
+ {
34
+ foreach ($this->entries as $entry) {
35
+ if (str_contains($entry, '/')) {
36
+ // C'est une plage CIDR
37
+ if ($this->ipInCidr($ip, $entry)) {
38
+ return true;
39
+ }
40
+ } else {
41
+ // C'est une IP directe
42
+ if ($ip === $entry) {
43
+ return true;
44
+ }
45
+ }
46
+ }
47
+ return false;
48
+ }
49
+
50
+ /**
51
+ * Vérifie si une adresse IP se trouve dans une plage CIDR donnée.
52
+ * @param string $ip L'adresse IP à vérifier.
53
+ * @param string $cidr La plage CIDR (ex: '192.168.1.0/24').
54
+ * @return bool True si l'IP est dans la plage, false sinon.
55
+ */
56
+ private function ipInCidr(string $ip, string $cidr): bool
57
+ {
58
+ // Handle IPv4-mapped IPv6 addresses by converting them to IPv4
59
+ if (str_starts_with(strtolower($ip), '::ffff:') && filter_var(substr($ip, 7), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
60
+ $ip = substr($ip, 7);
61
+ }
62
+
63
+ [$network, $mask] = explode('/', $cidr, 2);
64
+ $mask = (int)$mask;
65
+
66
+ // Now, both $ip and $network should be in the same family for a valid comparison
67
+ if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && filter_var($network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
68
+ if ($mask < 0 || $mask > 32) { return false; }
69
+ $ipLong = ip2long($ip);
70
+ $networkLong = ip2long($network);
71
+ if ($ipLong === false || $networkLong === false) return false;
72
+ $netmask = -1 << (32 - $mask);
73
+ return ($ipLong & $netmask) === ($networkLong & $netmask);
74
+ } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && filter_var($network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
75
+ // IPv6 vs IPv6 CIDR
76
+ if ($mask < 0 || $mask > 128) { return false; }
77
+ $ipBinary = inet_pton($ip);
78
+ $networkBinary = inet_pton($network);
79
+ if ($ipBinary === false || $networkBinary === false) return false;
80
+
81
+ $bytesToCompare = (int)floor($mask / 8);
82
+ if (strncmp($ipBinary, $networkBinary, $bytesToCompare) !== 0) {
83
+ return false;
84
+ }
85
+
86
+ $bitsToCompare = $mask % 8;
87
+ if ($bitsToCompare > 0) {
88
+ $byteIndex = $bytesToCompare;
89
+ $bitmask = (0xFF << (8 - $bitsToCompare)) & 0xFF;
90
+ if ((ord($ipBinary[$byteIndex]) & $bitmask) !== (ord($networkBinary[$byteIndex]) & $bitmask)) {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ return true;
96
+ }
97
+
98
+ return false;
99
+ }
100
100
  }
@@ -1,50 +1,52 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Utils;
6
-
7
- class Env
8
- {
9
- /** @var array<string, mixed> */
10
- private static array $localCache = [];
11
-
12
- /**
13
- * @param string $key
14
- * @param mixed $default
15
- * @return mixed
16
- */
17
- public static function get(string $key, $default = null)
18
- {
19
- if (array_key_exists($key, self::$localCache)) {
20
- return self::$localCache[$key];
21
- }
22
- if (isset($_ENV[$key]) && $_ENV[$key] !== '') {
23
- return $_ENV[$key];
24
- }
25
- if (isset($_SERVER[$key]) && $_SERVER[$key] !== '') {
26
- return $_SERVER[$key];
27
- }
28
- $val = getenv($key);
29
- if ($val !== false && $val !== '') {
30
- return $val;
31
- }
32
- return $default;
33
- }
34
-
35
- public static function set(string $key, $value): void
36
- {
37
- self::$localCache[$key] = $value;
38
- $_ENV[$key] = $value;
39
- $_SERVER[$key] = $value;
40
- @putenv("{$key}={$value}");
41
- }
42
-
43
- public static function clear(string $key): void
44
- {
45
- unset(self::$localCache[$key]);
46
- unset($_ENV[$key]);
47
- unset($_SERVER[$key]);
48
- @putenv($key);
49
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Utils;
6
+
7
+ class Env
8
+ {
9
+ /** @var array<string, mixed> */
10
+ private static array $localCache = [];
11
+
12
+ /**
13
+ * @param string $key
14
+ * @param mixed $default
15
+ * @return mixed
16
+ */
17
+ public static function get(string $key, $default = null)
18
+ {
19
+ if (array_key_exists($key, self::$localCache)) {
20
+ return self::$localCache[$key];
21
+ }
22
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Environment and server variables may contain raw cryptographic keys.
23
+ if (isset($_ENV[$key]) && $_ENV[$key] !== '') {
24
+ return $_ENV[$key];
25
+ }
26
+ if (isset($_SERVER[$key]) && $_SERVER[$key] !== '') {
27
+ return $_SERVER[$key];
28
+ }
29
+ // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
30
+ $val = getenv($key);
31
+ if ($val !== false && $val !== '') {
32
+ return $val;
33
+ }
34
+ return $default;
35
+ }
36
+
37
+ public static function set(string $key, $value): void
38
+ {
39
+ self::$localCache[$key] = $value;
40
+ $_ENV[$key] = $value;
41
+ $_SERVER[$key] = $value;
42
+ @putenv("{$key}={$value}");
43
+ }
44
+
45
+ public static function clear(string $key): void
46
+ {
47
+ unset(self::$localCache[$key]);
48
+ unset($_ENV[$key]);
49
+ unset($_SERVER[$key]);
50
+ @putenv($key);
51
+ }
50
52
  }
@@ -1,75 +1,75 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Utils;
6
-
7
- /**
8
- * Fournit des fonctions pour détecter les patterns d'injection malveillants.
9
- */
10
- class MaliciousPatterns
11
- {
12
- /**
13
- * @var array<string, string> Map des patterns malveillants regroupés par type.
14
- */
15
- private const INJECTION_PATTERNS = [
16
- // SQL/NoSQL injections, including time-based attacks
17
- 'sql' => '/(\$ne|\' *OR *\'1\'=\'1|[\'";]\s*--|; ?(DROP|TRUNCATE|DELETE)|UNION SELECT|(?:SLEEP|BENCHMARK)\s*\(|WAITFOR DELAY)/i',
18
- // Log4Shell (JNDI injection)
19
- 'log4shell' => '/\$\{jndi:(ldap|rmi|dns):/i',
20
- // Server-Side Template Injection (SSTI) for engines like Jinja2, Twig, etc.
21
- 'ssti' => '/\{\{.*\}\}|\{%.*%\}/',
22
- // XML External Entity (XXE) injection
23
- 'xxe' => '/<!ENTITY\s+.*SYSTEM/i',
24
- // Path Traversal
25
- 'traversal' => '/(\.\.\/|\.\.)/',
26
- // Remote Command Execution (RCE)
27
- // The original regex had an issue with `|cmd` being interpreted as a modifier in some PCRE versions.
28
- // Using a non-capturing group (?:...) makes it more robust and fixes the compilation error.
29
- 'rce' => '/`.*`|(?:^|[\n;&|]\s*)(?:ping|ls|whoami|cat|rm|ncat|nc|bash|sh|powershell|cmd)\b/i',
30
- // Server-Side Request Forgery (SSRF)
31
- 'ssrf' => '/(?:https?:\/\/)?(?:127\.\d+\.\d+\.\d+|169\.254\.169\.254|10\.\d+\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|localhost|0\.0\.0\.0|\[[0:]+1\])\b/i',
32
- // Carriage Return Line Feed (CRLF) Injection
33
- 'crlf' => '/[\r\n]|%0[ad]/i',
34
- // Cross-Site Scripting (XSS)
35
- 'xss' => '/(<script|javascript:|on\w+\s*=|alert\s*\(|confirm\s*\(|prompt\s*\(|<img\s+src[^>]+onerror|<iframe)/i',
36
- // Open Redirect
37
- 'openRedirect' => '/^(https?:)?\/\/(?![^\/]*?(localhost|127\.0\.0\.1))[^\s\/]+/i',
38
- // Local/Remote File Inclusion (LFI/RFI)
39
- 'lfi' => '/(?:etc\/passwd|win\.ini|boot\.ini|php:\/\/filter|data:\/\/|zip:\/\/)/i',
40
- // Shellshock (CVE-2014-6271)
41
- 'shellshock' => '/\(\)\s*\{\s*:\s*;\s*\}\s*/i',
42
- // NoSQL Injection (MongoDB query operators)
43
- 'nosql' => '/\$(?:eq|ne|gt|gte|lt|lte|in|nin|and|or|nor|not|expr|jsonSchema|mod|regex|text|where|elemMatch)/i',
44
- ];
45
-
46
- /**
47
- * Vérifie si une chaîne de caractères contient des patterns d'injection connus.
48
- * @param string $str La chaîne à vérifier.
49
- * @param array<string> $typesToDetect Les types d'injections à détecter (par défaut, tous).
50
- * @return bool True si un pattern malveillant est détecté, false sinon.
51
- */
52
- public static function isMalicious(string $str, array $typesToDetect = []): bool
53
- {
54
- if (empty($typesToDetect)) {
55
- $typesToDetect = array_filter(array_keys(self::INJECTION_PATTERNS), function ($key) {
56
- return $key !== 'openRedirect';
57
- });
58
- }
59
-
60
- foreach ($typesToDetect as $type) {
61
- if (isset(self::INJECTION_PATTERNS[$type])) {
62
- try {
63
- if (preg_match(self::INJECTION_PATTERNS[$type], $str)) {
64
- return true;
65
- }
66
- }catch (\Exception $e){
67
- error_log($type);
68
- error_log($e->getMessage());
69
- }
70
- }
71
- }
72
-
73
- return false;
74
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Utils;
6
+
7
+ /**
8
+ * Fournit des fonctions pour détecter les patterns d'injection malveillants.
9
+ */
10
+ class MaliciousPatterns
11
+ {
12
+ /**
13
+ * @var array<string, string> Map des patterns malveillants regroupés par type.
14
+ */
15
+ private const INJECTION_PATTERNS = [
16
+ // SQL/NoSQL injections, including time-based attacks
17
+ 'sql' => '/(\$ne|\' *OR *\'1\'=\'1|[\'";]\s*--|; ?(DROP|TRUNCATE|DELETE)|UNION SELECT|(?:SLEEP|BENCHMARK)\s*\(|WAITFOR DELAY)/i',
18
+ // Log4Shell (JNDI injection)
19
+ 'log4shell' => '/\$\{jndi:(ldap|rmi|dns):/i',
20
+ // Server-Side Template Injection (SSTI) for engines like Jinja2, Twig, etc.
21
+ 'ssti' => '/\{\{.*\}\}|\{%.*%\}/',
22
+ // XML External Entity (XXE) injection
23
+ 'xxe' => '/<!ENTITY\s+.*SYSTEM/i',
24
+ // Path Traversal
25
+ 'traversal' => '/(\.\.\/|\.\.)/',
26
+ // Remote Command Execution (RCE)
27
+ // The original regex had an issue with `|cmd` being interpreted as a modifier in some PCRE versions.
28
+ // Using a non-capturing group (?:...) makes it more robust and fixes the compilation error.
29
+ 'rce' => '/`.*`|(?:^|[\n;&|]\s*)(?:ping|ls|whoami|cat|rm|ncat|nc|bash|sh|powershell|cmd)\b/i',
30
+ // Server-Side Request Forgery (SSRF)
31
+ 'ssrf' => '/(?:https?:\/\/)?(?:127\.\d+\.\d+\.\d+|169\.254\.169\.254|10\.\d+\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|localhost|0\.0\.0\.0|\[[0:]+1\])\b/i',
32
+ // Carriage Return Line Feed (CRLF) Injection
33
+ 'crlf' => '/[\r\n]|%0[ad]/i',
34
+ // Cross-Site Scripting (XSS)
35
+ 'xss' => '/(<script|javascript:|on\w+\s*=|alert\s*\(|confirm\s*\(|prompt\s*\(|<img\s+src[^>]+onerror|<iframe)/i',
36
+ // Open Redirect
37
+ 'openRedirect' => '/^(https?:)?\/\/(?![^\/]*?(localhost|127\.0\.0\.1))[^\s\/]+/i',
38
+ // Local/Remote File Inclusion (LFI/RFI)
39
+ 'lfi' => '/(?:etc\/passwd|win\.ini|boot\.ini|php:\/\/filter|data:\/\/|zip:\/\/)/i',
40
+ // Shellshock (CVE-2014-6271)
41
+ 'shellshock' => '/\(\)\s*\{\s*:\s*;\s*\}\s*/i',
42
+ // NoSQL Injection (MongoDB query operators)
43
+ 'nosql' => '/\$(?:eq|ne|gt|gte|lt|lte|in|nin|and|or|nor|not|expr|jsonSchema|mod|regex|text|where|elemMatch)/i',
44
+ ];
45
+
46
+ /**
47
+ * Vérifie si une chaîne de caractères contient des patterns d'injection connus.
48
+ * @param string $str La chaîne à vérifier.
49
+ * @param array<string> $typesToDetect Les types d'injections à détecter (par défaut, tous).
50
+ * @return bool True si un pattern malveillant est détecté, false sinon.
51
+ */
52
+ public static function isMalicious(string $str, array $typesToDetect = []): bool
53
+ {
54
+ if (empty($typesToDetect)) {
55
+ $typesToDetect = array_filter(array_keys(self::INJECTION_PATTERNS), function ($key) {
56
+ return $key !== 'openRedirect';
57
+ });
58
+ }
59
+
60
+ foreach ($typesToDetect as $type) {
61
+ if (isset(self::INJECTION_PATTERNS[$type])) {
62
+ try {
63
+ if (preg_match(self::INJECTION_PATTERNS[$type], $str)) {
64
+ return true;
65
+ }
66
+ } catch (\Exception $e) {
67
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- WAF regex validation diagnostic
68
+ error_log("Pattern inspection failed for {$type}: " . $e->getMessage());
69
+ }
70
+ }
71
+ }
72
+
73
+ return false;
74
+ }
75
75
  }
@@ -1038,19 +1038,34 @@ class RequestUtils
1038
1038
  public static function getThreatIntelScore(RequestContext $context, array $threatIntelConfig): array
1039
1039
  {
1040
1040
  $score = 0.0;
1041
+ $signals = [];
1042
+
1041
1043
  $zkpY = $context->zkpY;
1042
1044
  if (!empty($zkpY)) {
1043
1045
  $store = StoreManager::getStore();
1044
1046
  $isBanned = $store->has("banned-zkp-y:{$zkpY}");
1045
1047
  if ($isBanned) {
1046
1048
  $score = 100.0;
1049
+ $signals[] = [
1050
+ 'ruleId' => 'FEDERATED_ZKP_BANNED',
1051
+ 'confidence' => 1.0,
1052
+ 'score' => 100.0,
1053
+ 'rationale' => 'Cryptographic identity matched federated banned list consensus'
1054
+ ];
1047
1055
  }
1048
1056
  }
1049
1057
 
1050
- $rttProxyScore = \Anonympins\Fingerprint\Ja3AnomalyDetector::getRttProxyScore($context);
1051
- $score = max($score, (float)$rttProxyScore);
1058
+ $rttProxyScore = (float)\Anonympins\Fingerprint\Ja3AnomalyDetector::getRttProxyScore($context);
1059
+ if ($rttProxyScore > 0.0) {
1060
+ $score = max($score, $rttProxyScore);
1061
+ $signals[] = [
1062
+ 'ruleId' => 'RESIDENTIAL_PROXY_RTT_DISCREPANCY',
1063
+ 'score' => $rttProxyScore,
1064
+ 'rationale' => 'Physical discrepancy between TCP edge RTT and application transit latency'
1065
+ ];
1066
+ }
1052
1067
 
1053
- return ['threatIntelScore' => $score];
1068
+ return ['threatIntelScore' => $score, 'threatIntelSignals' => $signals];
1054
1069
  }
1055
1070
 
1056
1071
  /**
@@ -1145,7 +1160,8 @@ class RequestUtils
1145
1160
  */
1146
1161
  public static function cleanUrlFromPowParams(string $originalPath, array $incomingQuery): string
1147
1162
  {
1148
- $urlParts = parse_url($originalPath);
1163
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Uses wp_parse_url when running inside WordPress
1164
+ $urlParts = function_exists('wp_parse_url') ? wp_parse_url($originalPath) : parse_url($originalPath);
1149
1165
  $path = $urlParts['path'] ?? '/';
1150
1166
  $finalQuery = $incomingQuery;
1151
1167
 
@@ -1326,7 +1342,8 @@ class RequestUtils
1326
1342
 
1327
1343
  // Utilisation de la partie stable du fingerprint matériel plutôt que l'ID de cookie volatil
1328
1344
  $currentDeviceHash = self::getCompositeDeviceHash($context);
1329
- $stableFpId = FingerprintBuilder::cyrb53(self::extractStablePart($currentDeviceHash));
1345
+ $stablePart = self::extractStablePart($currentDeviceHash);
1346
+ $stableFpId = !empty($stablePart) ? (string)FingerprintBuilder::cyrb53($stablePart) : (!empty($deviceId) ? $deviceId : (string)FingerprintBuilder::cyrb53($currentDeviceHash));
1330
1347
 
1331
1348
  $currentDeviceContributions = $subnetData['highScoreDevices'][$stableFpId] ?? 0;
1332
1349
  if ($currentDeviceContributions < 1 && $finalScore < 95) {
@@ -4,6 +4,10 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\WordPress;
6
6
 
7
+ if (!defined('ABSPATH')) {
8
+ exit;
9
+ }
10
+
7
11
  use Anonympins\Fingerprint\Store\IStore;
8
12
 
9
13
  // Polyfill pour ARRAY_A si chargé hors du cycle de vie standard de WordPress