@anonympins/fingerprint 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/src/js/fingerprint.client.js +1271 -1191
- package/src/js/fingerprint.js +202 -108
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +24 -8
- package/src/js/mongodb-store.js +165 -79
- package/src/js/pow.solver.inline.js +500 -14
- package/src/js/pow.solver.js +323 -14
- package/src/js/problem-manager.js +1 -1
- package/src/js/redis-store.js +114 -42
- package/src/js/tests/dns-circuit-breaker.test.js +55 -0
- package/src/js/tests/fingerprint.client.init.test.js +143 -143
- package/src/js/tests/fingerprint.client.test.js +128 -128
- package/src/js/tests/fingerprint.test.js +32 -2
- package/src/js/tests/ip-reputation.test.js +1 -1
- package/src/js/tests/pow.solver.test.js +5 -2
- package/src/js/tests/usefulWorkSolver.test.js +162 -0
- package/src/php/Challenge/ChallengeUtils.php +21 -8
- package/src/php/FingerprintClient.php +22 -5
- package/src/php/FingerprintEngine.php +232 -56
- package/src/php/Optimization/Optimization.php +23 -16
- package/src/php/Optimization/OptimizationOperators.php +27 -0
- package/src/php/ProblemManager.php +1 -1
- package/src/php/Store/MongoDbStore.php +184 -104
- package/src/php/Store/RedisStore.php +91 -11
- package/src/php/Tests/ChallengeUtilsTest.php +285 -285
- package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
- package/src/php/Tests/IpReputationTest.php +1 -1
- package/src/php/Tests/PowTest.php +1 -1
- package/src/php/Tests/RequestUtilsTest.php +29 -0
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/RequestUtils.php +74 -21
- package/src/php/Utils/TLSClientHelloParser.php +212 -116
|
@@ -1,117 +1,213 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Décode nativement un paquet TLS Client Hello binaire pour calculer l'empreinte JA3/JA4.
|
|
9
|
-
*/
|
|
10
|
-
class TLSClientHelloParser
|
|
11
|
-
{
|
|
12
|
-
private const GREASE_VALUES = [
|
|
13
|
-
2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
|
|
14
|
-
35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Parse le Client Hello brut et retourne l'empreinte JA3 et une approximation JA4.
|
|
19
|
-
*
|
|
20
|
-
* @param string $binary Le premier paquet TCP reçu sur la socket.
|
|
21
|
-
* @return array{ja3_string: string, ja3_hash: string, ja4_raw: string}|null
|
|
22
|
-
*/
|
|
23
|
-
public static function parse(string $binary): ?array
|
|
24
|
-
{
|
|
25
|
-
$len = strlen($binary);
|
|
26
|
-
if ($len < 43) {
|
|
27
|
-
return null; // Paquet trop court
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// 1. Vérification du Record Layer Type (0x16 = Handshake)
|
|
31
|
-
if (ord($binary[0]) !== 0x16) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 2. Vérification du Handshake Type (0x01 = Client Hello)
|
|
36
|
-
if (ord($binary[5]) !== 0x01) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
$offset = 43; // Sauter l'en-tête, la version et le Random Client (32 octets)
|
|
41
|
-
if ($len < $offset + 1) return null;
|
|
42
|
-
|
|
43
|
-
// 3. Lecture du Session ID
|
|
44
|
-
$sessionLen = ord($binary[$offset]);
|
|
45
|
-
$offset += 1 + $sessionLen;
|
|
46
|
-
if ($len < $offset + 2) return null;
|
|
47
|
-
|
|
48
|
-
// 4. Lecture des Cipher Suites
|
|
49
|
-
$ciphersLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
50
|
-
$offset += 2;
|
|
51
|
-
if ($len < $offset + $ciphersLen + 1) return null;
|
|
52
|
-
|
|
53
|
-
$ciphers = [];
|
|
54
|
-
for ($i = 0; $i < $ciphersLen; $i += 2) {
|
|
55
|
-
$ciphers[] = unpack('n', substr($binary, $offset + $i, 2))[1];
|
|
56
|
-
}
|
|
57
|
-
$offset += $ciphersLen;
|
|
58
|
-
|
|
59
|
-
// 5. Lecture des Compression Methods
|
|
60
|
-
$compressionLen = ord($binary[$offset]);
|
|
61
|
-
$offset += 1 + $compressionLen;
|
|
62
|
-
if ($len < $offset + 2) return null;
|
|
63
|
-
|
|
64
|
-
// 6. Lecture des Extensions
|
|
65
|
-
$extensionsLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
66
|
-
$offset += 2;
|
|
67
|
-
|
|
68
|
-
$extensions = [];
|
|
69
|
-
$curves = [];
|
|
70
|
-
$points = [];
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
$
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
$
|
|
81
|
-
|
|
82
|
-
if ($
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
$
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Décode nativement un paquet TLS Client Hello binaire pour calculer l'empreinte JA3/JA4.
|
|
9
|
+
*/
|
|
10
|
+
class TLSClientHelloParser
|
|
11
|
+
{
|
|
12
|
+
private const GREASE_VALUES = [
|
|
13
|
+
2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
|
|
14
|
+
35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse le Client Hello brut et retourne l'empreinte JA3 et une approximation JA4.
|
|
19
|
+
*
|
|
20
|
+
* @param string $binary Le premier paquet TCP reçu sur la socket.
|
|
21
|
+
* @return array{ja3_string: string, ja3_hash: string, ja4_raw: string}|null
|
|
22
|
+
*/
|
|
23
|
+
public static function parse(string $binary): ?array
|
|
24
|
+
{
|
|
25
|
+
$len = strlen($binary);
|
|
26
|
+
if ($len < 43) {
|
|
27
|
+
return null; // Paquet trop court
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 1. Vérification du Record Layer Type (0x16 = Handshake)
|
|
31
|
+
if (ord($binary[0]) !== 0x16) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Vérification du Handshake Type (0x01 = Client Hello)
|
|
36
|
+
if (ord($binary[5]) !== 0x01) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
$offset = 43; // Sauter l'en-tête, la version et le Random Client (32 octets)
|
|
41
|
+
if ($len < $offset + 1) return null;
|
|
42
|
+
|
|
43
|
+
// 3. Lecture du Session ID
|
|
44
|
+
$sessionLen = ord($binary[$offset]);
|
|
45
|
+
$offset += 1 + $sessionLen;
|
|
46
|
+
if ($len < $offset + 2) return null;
|
|
47
|
+
|
|
48
|
+
// 4. Lecture des Cipher Suites
|
|
49
|
+
$ciphersLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
50
|
+
$offset += 2;
|
|
51
|
+
if ($len < $offset + $ciphersLen + 1) return null;
|
|
52
|
+
|
|
53
|
+
$ciphers = [];
|
|
54
|
+
for ($i = 0; $i < $ciphersLen; $i += 2) {
|
|
55
|
+
$ciphers[] = unpack('n', substr($binary, $offset + $i, 2))[1];
|
|
56
|
+
}
|
|
57
|
+
$offset += $ciphersLen;
|
|
58
|
+
|
|
59
|
+
// 5. Lecture des Compression Methods
|
|
60
|
+
$compressionLen = ord($binary[$offset]);
|
|
61
|
+
$offset += 1 + $compressionLen;
|
|
62
|
+
if ($len < $offset + 2) return null;
|
|
63
|
+
|
|
64
|
+
// 6. Lecture des Extensions
|
|
65
|
+
$extensionsLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
66
|
+
$offset += 2;
|
|
67
|
+
|
|
68
|
+
$extensions = [];
|
|
69
|
+
$curves = [];
|
|
70
|
+
$points = [];
|
|
71
|
+
$sigAlgs = [];
|
|
72
|
+
$supportedVersions = [];
|
|
73
|
+
$hasSni = false;
|
|
74
|
+
$alpnProtocol = '';
|
|
75
|
+
|
|
76
|
+
$extLimit = $offset + $extensionsLen;
|
|
77
|
+
while ($offset < $extLimit && $offset + 4 <= $len) {
|
|
78
|
+
$extType = unpack('n', substr($binary, $offset, 2))[1];
|
|
79
|
+
$extLen = unpack('n', substr($binary, $offset + 2, 2))[1];
|
|
80
|
+
$offset += 4;
|
|
81
|
+
|
|
82
|
+
if ($offset + $extLen > $len) break;
|
|
83
|
+
|
|
84
|
+
$extensions[] = $extType;
|
|
85
|
+
|
|
86
|
+
if ($extType === 0) {
|
|
87
|
+
$hasSni = true;
|
|
88
|
+
} elseif ($extType === 10) { // Extension Supported Groups (Elliptic Curves)
|
|
89
|
+
if ($extLen >= 2) {
|
|
90
|
+
$curvesLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
91
|
+
for ($j = 2; $j < $curvesLen + 2; $j += 2) {
|
|
92
|
+
$curves[] = unpack('n', substr($binary, $offset + $j, 2))[1];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} elseif ($extType === 11) { // Extension EC Point Formats
|
|
96
|
+
if ($extLen >= 1) {
|
|
97
|
+
$pointsLen = ord($binary[$offset]);
|
|
98
|
+
for ($j = 1; $j < $pointsLen + 1; $j++) {
|
|
99
|
+
$points[] = ord($binary[$offset + $j]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} elseif ($extType === 13) { // Signature Algorithms
|
|
103
|
+
if ($extLen >= 2 && $offset + 2 <= $len) {
|
|
104
|
+
$sigAlgsLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
105
|
+
for ($j = 2; $j < $sigAlgsLen + 2; $j += 2) {
|
|
106
|
+
if ($offset + $j + 2 <= $len && $j + 2 <= $extLen) {
|
|
107
|
+
$sigAlgs[] = unpack('n', substr($binary, $offset + $j, 2))[1];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} elseif ($extType === 16) { // ALPN
|
|
112
|
+
if ($extLen >= 3 && $offset + 2 <= $len) {
|
|
113
|
+
$alpnListLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
114
|
+
if ($extLen >= 2 + $alpnListLen && $offset + 2 + $alpnListLen <= $len) {
|
|
115
|
+
$alpnStrLen = ord($binary[$offset + 2]);
|
|
116
|
+
if ($alpnListLen >= 1 + $alpnStrLen) {
|
|
117
|
+
$alpnProtocol = substr($binary, $offset + 3, $alpnStrLen);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} elseif ($extType === 43) { // Supported Versions
|
|
122
|
+
if ($extLen >= 1 && $offset + 1 <= $len) {
|
|
123
|
+
$versionsLen = ord($binary[$offset]);
|
|
124
|
+
for ($j = 1; $j < $versionsLen + 1; $j += 2) {
|
|
125
|
+
if ($offset + $j + 2 <= $len && $j + 2 <= $extLen) {
|
|
126
|
+
$supportedVersions[] = unpack('n', substr($binary, $offset + $j, 2))[1];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
$offset += $extLen;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Nettoyage des valeurs GREASE (RFC 8701) pour la conformité JA3
|
|
135
|
+
$filterGrease = fn(array $arr) => array_values(array_filter($arr, fn($v) => !in_array($v, self::GREASE_VALUES, true)));
|
|
136
|
+
|
|
137
|
+
$cleanCiphers = $filterGrease($ciphers);
|
|
138
|
+
$cleanExtensions = $filterGrease($extensions);
|
|
139
|
+
$cleanCurves = $filterGrease($curves);
|
|
140
|
+
$cleanPoints = $filterGrease($points);
|
|
141
|
+
$cleanSigAlgs = $filterGrease($sigAlgs);
|
|
142
|
+
$cleanSupportedVersions = $filterGrease($supportedVersions);
|
|
143
|
+
|
|
144
|
+
$sslVersion = unpack('n', substr($binary, 9, 2))[1];
|
|
145
|
+
$ja3String = implode(',', [
|
|
146
|
+
$sslVersion,
|
|
147
|
+
implode('-', $cleanCiphers),
|
|
148
|
+
implode('-', $cleanExtensions),
|
|
149
|
+
implode('-', $cleanCurves),
|
|
150
|
+
implode('-', $cleanPoints)
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
// Calcul natif de JA4
|
|
154
|
+
$highestVersion = $sslVersion;
|
|
155
|
+
if (!empty($cleanSupportedVersions)) {
|
|
156
|
+
$highestVersion = max($cleanSupportedVersions);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$ja4Version = "12";
|
|
160
|
+
if ($highestVersion === 0x0304) {
|
|
161
|
+
$ja4Version = "13";
|
|
162
|
+
} elseif ($highestVersion === 0x0303) {
|
|
163
|
+
$ja4Version = "12";
|
|
164
|
+
} elseif ($highestVersion === 0x0302) {
|
|
165
|
+
$ja4Version = "11";
|
|
166
|
+
} elseif ($highestVersion === 0x0301) {
|
|
167
|
+
$ja4Version = "10";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
$sniStatus = $hasSni ? "d" : "i";
|
|
171
|
+
$numCiphers = min(99, count($cleanCiphers));
|
|
172
|
+
$numExtensions = min(99, count($cleanExtensions));
|
|
173
|
+
|
|
174
|
+
$ja4Alpn = "00";
|
|
175
|
+
if ($alpnProtocol !== '') {
|
|
176
|
+
$lenAlpn = strlen($alpnProtocol);
|
|
177
|
+
if ($lenAlpn === 1) {
|
|
178
|
+
$ja4Alpn = $alpnProtocol . $alpnProtocol;
|
|
179
|
+
} else {
|
|
180
|
+
$ja4Alpn = $alpnProtocol[0] . $alpnProtocol[$lenAlpn - 1];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
$ja4_a = "t" . $ja4Version . $sniStatus . sprintf("%02d", $numCiphers) . sprintf("%02d", $numExtensions) . $ja4Alpn;
|
|
185
|
+
|
|
186
|
+
$sortedCiphers = $cleanCiphers;
|
|
187
|
+
sort($sortedCiphers);
|
|
188
|
+
$ciphersHex = array_map(fn($c) => sprintf('%04x', $c), $sortedCiphers);
|
|
189
|
+
$ciphersString = implode(',', $ciphersHex);
|
|
190
|
+
$ja4_b = substr(hash('sha256', $ciphersString), 0, 12);
|
|
191
|
+
|
|
192
|
+
$sortedExtensions = $cleanExtensions;
|
|
193
|
+
sort($sortedExtensions);
|
|
194
|
+
$extensionsHex = array_map(fn($e) => sprintf('%04x', $e), $sortedExtensions);
|
|
195
|
+
$extensionsString = implode(',', $extensionsHex);
|
|
196
|
+
|
|
197
|
+
$sortedSigAlgs = $cleanSigAlgs;
|
|
198
|
+
sort($sortedSigAlgs);
|
|
199
|
+
$sigAlgsHex = array_map(fn($s) => sprintf('%04x', $s), $sortedSigAlgs);
|
|
200
|
+
$sigAlgsString = implode(',', $sigAlgsHex);
|
|
201
|
+
|
|
202
|
+
$ja4_c_input = $extensionsString . '_' . $sigAlgsString;
|
|
203
|
+
$ja4_c = substr(hash('sha256', $ja4_c_input), 0, 12);
|
|
204
|
+
|
|
205
|
+
$ja4_hash = $ja4_a . "_" . $ja4_b . "_" . $ja4_c;
|
|
206
|
+
|
|
207
|
+
return [
|
|
208
|
+
'ja3_string' => $ja3String,
|
|
209
|
+
'ja3_hash' => md5($ja3String),
|
|
210
|
+
'ja4_raw' => $ja4_hash
|
|
211
|
+
];
|
|
212
|
+
}
|
|
117
213
|
}
|