@anonympins/fingerprint 0.7.2 → 0.7.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 +36 -9
- package/README.md +1 -1
- package/package.json +109 -107
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/fingerprint.client.js +252 -45
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +265 -185
- package/src/js/fingerprint.utils.js +32 -5
- package/src/js/fingerprint.worker.js +81 -0
- package/src/js/library.js +6 -2
- package/src/js/pow.solver.js +2 -3
- package/src/js/problem-manager.js +113 -113
- package/src/js/tests/fingerprint.client.init.test.js +30 -0
- package/src/js/tests/fingerprint.test.js +136 -4
- package/src/js/tests/threatIntel.test.js +76 -0
- package/src/php/AutoTuner.php +36 -18
- package/src/php/Challenge/ChallengeUtils.php +121 -240
- package/src/php/DirectFingerprint.php +16 -4
- package/src/php/FingerprintClient.php +17 -19
- package/src/php/FingerprintEngine.php +77 -39
- package/src/php/Ja3AnomalyDetector.php +20 -12
- package/src/php/Optimization/Optimization.php +5 -1
- package/src/php/Optimization/OptimizationOperators.php +5 -1
- package/src/php/Optimization/ProblemInitializers.php +16 -4
- package/src/php/ProblemManager.php +20 -14
- package/src/php/Tests/ChallengeUtilsTest.php +469 -453
- package/src/php/Tests/FingerprintEngineTest.php +6 -1
- package/src/php/Tests/RequestUtilsTest.php +1 -1
- package/src/php/Tests/ThreatIntelTest.php +64 -0
- package/src/php/Utils/Env.php +51 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/RequestUtils.php +231 -132
- package/src/php/WordPress/WpDbStore.php +4 -0
- package/src/php/WordPress/{fingerprint-wordpress.php → fingerprint-anti-bot.php} +131 -126
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
- package/src/php/WordPress/package.php +6 -3
- package/src/php/WordPress/readme.txt +36 -0
- package/public/fingerprint-wordpress.zip +0 -0
|
@@ -178,7 +178,8 @@ class ChallengeUtils
|
|
|
178
178
|
$peers = $config['federatedPeers'] ?? [];
|
|
179
179
|
if (!empty($peers)) {
|
|
180
180
|
$allowedHosts = array_map(function ($url) {
|
|
181
|
-
|
|
181
|
+
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
|
|
182
|
+
$host = function_exists('wp_parse_url') ? wp_parse_url($url, PHP_URL_HOST) : parse_url($url, PHP_URL_HOST);
|
|
182
183
|
return !empty($host) ? $host : $url;
|
|
183
184
|
}, $peers);
|
|
184
185
|
|
|
@@ -188,9 +189,9 @@ class ChallengeUtils
|
|
|
188
189
|
}
|
|
189
190
|
$zkpY = $params['zkpY'] ?? '';
|
|
190
191
|
$headers = function_exists('getallheaders') ? array_change_key_case(getallheaders(), CASE_LOWER) : [];
|
|
191
|
-
$sigEd25519 = $params['signature_ed25519'] ?? $headers['x-federation-signature-ed25519'] ??
|
|
192
|
-
$sigHmac = $params['signature'] ?? $headers['x-federation-signature'] ??
|
|
193
|
-
$timestamp = (int)($params['timestamp'] ?? $headers['x-federation-timestamp'] ??
|
|
192
|
+
$sigEd25519 = $params['signature_ed25519'] ?? $headers['x-federation-signature-ed25519'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_SIGNATURE_ED25519', '');
|
|
193
|
+
$sigHmac = $params['signature'] ?? $headers['x-federation-signature'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_SIGNATURE', '');
|
|
194
|
+
$timestamp = (int)($params['timestamp'] ?? $headers['x-federation-timestamp'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_TIMESTAMP', 0));
|
|
194
195
|
|
|
195
196
|
if (empty($zkpY) || empty($timestamp)) {
|
|
196
197
|
return ['error' => 'Missing threat intel parameters'];
|
|
@@ -205,7 +206,7 @@ class ChallengeUtils
|
|
|
205
206
|
$msg = "{$timestamp}:{$zkpY}";
|
|
206
207
|
|
|
207
208
|
if (!empty($sigEd25519)) {
|
|
208
|
-
$publicKey = $config['ed25519_public_key'] ??
|
|
209
|
+
$publicKey = $config['ed25519_public_key'] ?? Env::get('ED25519_PUBLIC_KEY');
|
|
209
210
|
if (!$publicKey) {
|
|
210
211
|
return ['error' => 'Missing public key for asymmetric verification'];
|
|
211
212
|
}
|
|
@@ -298,8 +299,8 @@ class ChallengeUtils
|
|
|
298
299
|
|
|
299
300
|
switch ($op) {
|
|
300
301
|
case 'register':
|
|
301
|
-
$clientIp =
|
|
302
|
-
$seed = $params['seed'] ?? '';
|
|
302
|
+
$clientIp = self::getSanitizedServerVar('REMOTE_ADDR', '127.0.0.1');
|
|
303
|
+
$seed = $params['seed'] ?? ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
303
304
|
self::registerCooperativeNode($clientIp, $nodeId, $seed);
|
|
304
305
|
return ['status' => 'registered'];
|
|
305
306
|
|
|
@@ -504,7 +505,7 @@ class ChallengeUtils
|
|
|
504
505
|
return 'ed25519.' . $base64UrlEncode($serialized) . '.' . $base64UrlEncode($signature);
|
|
505
506
|
}
|
|
506
507
|
} catch (\Throwable $e) {
|
|
507
|
-
|
|
508
|
+
self::logError("[ChallengeUtils] Ed25519 signing failed: " . $e->getMessage());
|
|
508
509
|
}
|
|
509
510
|
}
|
|
510
511
|
|
|
@@ -540,7 +541,7 @@ class ChallengeUtils
|
|
|
540
541
|
|
|
541
542
|
$ed25519PubKey = Env::get('ED25519_PUBLIC_KEY');
|
|
542
543
|
if (!$ed25519PubKey) {
|
|
543
|
-
|
|
544
|
+
self::logError("[ChallengeUtils] ED25519_PUBLIC_KEY is not defined in environment.");
|
|
544
545
|
return null;
|
|
545
546
|
}
|
|
546
547
|
|
|
@@ -551,7 +552,7 @@ class ChallengeUtils
|
|
|
551
552
|
return null;
|
|
552
553
|
}
|
|
553
554
|
} catch (\Throwable $e) {
|
|
554
|
-
|
|
555
|
+
self::logError("[ChallengeUtils] Ed25519 verification failed: " . $e->getMessage());
|
|
555
556
|
return null;
|
|
556
557
|
}
|
|
557
558
|
|
|
@@ -737,7 +738,7 @@ class ChallengeUtils
|
|
|
737
738
|
): ?string {
|
|
738
739
|
// En mode HTTP (non sécurisé), aucun calcul SHA-256 n'est exigé
|
|
739
740
|
if (!empty($challengeContext['isHttp'])) {
|
|
740
|
-
|
|
741
|
+
self::logError('[FP Server Verify] Mode HTTP détecté (insecure policy) : validation sans SHA-256 acceptée.');
|
|
741
742
|
$expiry = (int)floor(microtime(true) * 1000) + $ticketTtl;
|
|
742
743
|
return self::generateStatelessTicket([
|
|
743
744
|
'expiry' => $expiry,
|
|
@@ -751,7 +752,7 @@ class ChallengeUtils
|
|
|
751
752
|
$baseBlock = $challengeContext['baseBlock'] ?? null;
|
|
752
753
|
|
|
753
754
|
if ($cpuTargetHex === null || $baseBlock === null) {
|
|
754
|
-
|
|
755
|
+
self::logError('[FP Server Verify] Invalid challenge context. Missing cpuTarget or baseBlock.');
|
|
755
756
|
return null;
|
|
756
757
|
}
|
|
757
758
|
|
|
@@ -763,7 +764,7 @@ class ChallengeUtils
|
|
|
763
764
|
$isValid = strcmp($hash, $paddedTarget) < 0;
|
|
764
765
|
|
|
765
766
|
if ($isValid) {
|
|
766
|
-
|
|
767
|
+
self::logError('[FP Server Verify] CPU PoW verification PASSED.');
|
|
767
768
|
|
|
768
769
|
$expiry = (int)floor(microtime(true) * 1000) + $ticketTtl;
|
|
769
770
|
$payload = [
|
|
@@ -776,7 +777,7 @@ class ChallengeUtils
|
|
|
776
777
|
}
|
|
777
778
|
|
|
778
779
|
// Log details on failure
|
|
779
|
-
|
|
780
|
+
self::logError(sprintf(
|
|
780
781
|
'[FP Server Verify] CPU PoW verification FAILED. Details: hashCalculated=0x%s, target=0x%s',
|
|
781
782
|
$hash,
|
|
782
783
|
$cpuTargetHex
|
|
@@ -788,39 +789,38 @@ class ChallengeUtils
|
|
|
788
789
|
/**
|
|
789
790
|
* Vérifie le limiteur de débit Token Bucket pour les demandes de challenge d'un sous-réseau.
|
|
790
791
|
*/
|
|
791
|
-
public static function checkChallengeRateLimit(string $clientIp): bool
|
|
792
|
+
public static function checkChallengeRateLimit(string $clientIp, float $capacity = 5.0, float $refillRate = 0.1): bool
|
|
792
793
|
{
|
|
793
|
-
$subnet = RequestUtils::getIpSubnet($clientIp);
|
|
794
|
-
if ($subnet === null) {
|
|
795
|
-
return false;
|
|
796
|
-
}
|
|
794
|
+
$subnet = RequestUtils::getIpSubnet($clientIp) ?? $clientIp;
|
|
797
795
|
|
|
798
796
|
$store = StoreManager::getStore();
|
|
799
797
|
$key = "rate-limit:{$subnet}";
|
|
800
|
-
$rateLimitData = $store->get($key) ?? [
|
|
801
|
-
'tokens' => 5.0,
|
|
802
|
-
'lastRefill' => microtime(true)
|
|
803
|
-
];
|
|
804
|
-
|
|
805
|
-
$capacity = 5.0;
|
|
806
|
-
$refillRate = 0.1; // 1 token toutes les 10 secondes
|
|
807
798
|
$now = microtime(true);
|
|
808
799
|
|
|
809
|
-
$
|
|
810
|
-
$
|
|
800
|
+
$rateLimitData = $store->get($key);
|
|
801
|
+
if (!is_array($rateLimitData) || !isset($rateLimitData['tokens'], $rateLimitData['lastRefill'])) {
|
|
802
|
+
$rateLimitData = [
|
|
803
|
+
'tokens' => $capacity,
|
|
804
|
+
'lastRefill' => $now
|
|
805
|
+
];
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
$elapsed = max(0.0, $now - (float)$rateLimitData['lastRefill']);
|
|
809
|
+
$tokens = min($capacity, (float)$rateLimitData['tokens'] + $elapsed * $refillRate);
|
|
810
|
+
$ttl = (int)max(60, ceil($capacity / max(0.1, $refillRate)));
|
|
811
811
|
|
|
812
812
|
if ($tokens < 1.0) {
|
|
813
813
|
$store->set($key, [
|
|
814
814
|
'tokens' => $tokens,
|
|
815
815
|
'lastRefill' => $now
|
|
816
|
-
],
|
|
816
|
+
], $ttl);
|
|
817
817
|
return false;
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
$store->set($key, [
|
|
821
821
|
'tokens' => $tokens - 1.0,
|
|
822
822
|
'lastRefill' => $now
|
|
823
|
-
],
|
|
823
|
+
], $ttl);
|
|
824
824
|
|
|
825
825
|
return true;
|
|
826
826
|
}
|
|
@@ -888,7 +888,7 @@ class ChallengeUtils
|
|
|
888
888
|
}
|
|
889
889
|
$maxAllowedMemDifficulty = 128; // 128MB
|
|
890
890
|
if ($difficulty > $maxAllowedMemDifficulty) {
|
|
891
|
-
|
|
891
|
+
self::logError("[Security] Memory PoW verification attempt with excessive difficulty: {$difficulty}MB. Denied.");
|
|
892
892
|
return false;
|
|
893
893
|
}
|
|
894
894
|
|
|
@@ -1020,7 +1020,7 @@ class ChallengeUtils
|
|
|
1020
1020
|
dirname(__DIR__, 1) . '/assets/pow.solver.inline.js',
|
|
1021
1021
|
];
|
|
1022
1022
|
if (defined('ABSPATH')) {
|
|
1023
|
-
$wpPluginSolver = ABSPATH . 'wp-content/plugins/fingerprint-
|
|
1023
|
+
$wpPluginSolver = ABSPATH . 'wp-content/plugins/fingerprint-anti-bot/assets/pow.solver.inline.js';
|
|
1024
1024
|
if (file_exists($wpPluginSolver)) {
|
|
1025
1025
|
return file_get_contents($wpPluginSolver) ?: '';
|
|
1026
1026
|
}
|
|
@@ -1030,7 +1030,7 @@ class ChallengeUtils
|
|
|
1030
1030
|
return file_get_contents($solverPath) ?: '';
|
|
1031
1031
|
}
|
|
1032
1032
|
}
|
|
1033
|
-
|
|
1033
|
+
self::logError("[ChallengeUtils] Erreur: Le fichier pow.solver.inline.js n'a pas été trouvé à l'emplacement attendu.");
|
|
1034
1034
|
return '';
|
|
1035
1035
|
}
|
|
1036
1036
|
|
|
@@ -1052,173 +1052,28 @@ class ChallengeUtils
|
|
|
1052
1052
|
$safeNonce = json_encode($nonce, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
1053
1053
|
$safeClientSecret = json_encode($clientSecret, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
1054
1054
|
|
|
1055
|
-
$challengeScript =
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
await fetch(window.location.pathname + "?coop_op=webrtc_signal&node_id=" + nodeId + "&target_peer_id=" + targetId + "&signal_type=" + type + "&signal_data=" + encodeURIComponent(data) + "&coop_sig=" + sig);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
document.getElementById('loader').innerText = '⚙️ Checking persistent local storage...';
|
|
1081
|
-
await new Promise(r => setTimeout(r, 10));
|
|
1082
|
-
|
|
1083
|
-
try {
|
|
1084
|
-
await window.initializeSpace(nonce + ":" + clientSecret, sizeMb);
|
|
1085
|
-
|
|
1086
|
-
if (peerId && peerBlockIdx !== -1) {
|
|
1087
|
-
// Enregistrement coopératif
|
|
1088
|
-
const sig = await signCoop("register", nodeId, nonce + ":" + clientSecret);
|
|
1089
|
-
await fetch(window.location.pathname + "?coop_op=register&node_id=" + nodeId + "&seed=" + encodeURIComponent(nonce + ":" + clientSecret) + "&coop_sig=" + sig);
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
const peerConnections = {};
|
|
1093
|
-
|
|
1094
|
-
// Écoute des signaux WebRTC et requêtes entrantes
|
|
1095
|
-
setInterval(async () => {
|
|
1096
|
-
try {
|
|
1097
|
-
const sigWebrtc = await signCoop("poll_signals", nodeId);
|
|
1098
|
-
const resWebrtc = await fetch(window.location.pathname + "?coop_op=poll_signals&node_id=" + nodeId + "&coop_sig=" + sigWebrtc);
|
|
1099
|
-
const dataWebrtc = await resWebrtc.json();
|
|
1100
|
-
if (dataWebrtc.signals && dataWebrtc.signals.length > 0) {
|
|
1101
|
-
for (const sig of dataWebrtc.signals) {
|
|
1102
|
-
const fromId = sig.from_peer_id;
|
|
1103
|
-
if (sig.signal_type === 'offer') {
|
|
1104
|
-
const pc = new RTCPeerConnection({ iceServers: [] });
|
|
1105
|
-
peerConnections[fromId] = pc;
|
|
1106
|
-
pc.onicecandidate = (e) => {
|
|
1107
|
-
if (e.candidate) sendWebRtcSignal(fromId, 'candidate', JSON.stringify(e.candidate));
|
|
1108
|
-
};
|
|
1109
|
-
pc.ondatachannel = (e) => {
|
|
1110
|
-
const dc = e.channel;
|
|
1111
|
-
dc.onmessage = async (evt) => {
|
|
1112
|
-
try {
|
|
1113
|
-
const req = JSON.parse(evt.data);
|
|
1114
|
-
if (req.type === 'get_block') {
|
|
1115
|
-
document.getElementById('loader').innerText = '📤 Transfert direct P2P (WebRTC) du bloc vers le pair...';
|
|
1116
|
-
const blockData = await window.readSpaceBlock(req.block_idx);
|
|
1117
|
-
dc.send(JSON.stringify({ type: 'block_data', block_data: blockData }));
|
|
1118
|
-
}
|
|
1119
|
-
} catch (err) {}
|
|
1120
|
-
};
|
|
1121
|
-
};
|
|
1122
|
-
await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(sig.signal_data)));
|
|
1123
|
-
const answer = await pc.createAnswer();
|
|
1124
|
-
await pc.setLocalDescription(answer);
|
|
1125
|
-
await sendWebRtcSignal(fromId, 'answer', JSON.stringify(answer));
|
|
1126
|
-
} else if (sig.signal_type === 'answer' && peerConnections[fromId]) {
|
|
1127
|
-
await peerConnections[fromId].setRemoteDescription(new RTCSessionDescription(JSON.parse(sig.signal_data)));
|
|
1128
|
-
} else if (sig.signal_type === 'candidate' && peerConnections[fromId]) {
|
|
1129
|
-
await peerConnections[fromId].addIceCandidate(new RTCIceCandidate(JSON.parse(sig.signal_data)));
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
} catch (e) {}
|
|
1134
|
-
|
|
1135
|
-
try {
|
|
1136
|
-
const sig = await signCoop("poll_requests", nodeId);
|
|
1137
|
-
const res = await fetch(window.location.pathname + "?coop_op=poll_requests&node_id=" + nodeId + "&coop_sig=" + sig);
|
|
1138
|
-
const data = await res.json();
|
|
1139
|
-
if (data.requests && data.requests.length > 0) {
|
|
1140
|
-
for (const req of data.requests) {
|
|
1141
|
-
document.getElementById('loader').innerText = '📤 Transfert coopératif de bloc vers le pair...';
|
|
1142
|
-
const blockData = await window.readSpaceBlock(req.block_idx);
|
|
1143
|
-
const respSig = await signCoop("respond_block", nodeId, req.requester_id + ":" + req.req_id + ":" + blockData);
|
|
1144
|
-
await fetch(window.location.pathname + "?coop_op=respond_block&node_id=" + nodeId + "&requester_id=" + req.requester_id + "&req_id=" + req.req_id + "&block_data=" + encodeURIComponent(blockData) + "&coop_sig=" + respSig);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
} catch (e) {
|
|
1148
|
-
console.error("Cooperative polling error", e);
|
|
1149
|
-
}
|
|
1150
|
-
}, 800);
|
|
1151
|
-
|
|
1152
|
-
let peerBlock = "";
|
|
1153
|
-
if (peerId && peerBlockIdx !== -1) {
|
|
1154
|
-
document.getElementById('loader').innerText = '📥 Connexion WebRTC P2P directe au pair (' + peerId + ')...';
|
|
1155
|
-
|
|
1156
|
-
// 1. Échange direct P2P via WebRTC DataChannel (charge serveur = 0)
|
|
1157
|
-
const webrtcTransferPromise = new Promise(async (resolve) => {
|
|
1158
|
-
if (!window.RTCPeerConnection) return resolve(null);
|
|
1159
|
-
try {
|
|
1160
|
-
const pc = new RTCPeerConnection({ iceServers: [] });
|
|
1161
|
-
peerConnections[peerId] = pc;
|
|
1162
|
-
const dc = pc.createDataChannel("pospace-transfer");
|
|
1163
|
-
pc.onicecandidate = (e) => {
|
|
1164
|
-
if (e.candidate) sendWebRtcSignal(peerId, 'candidate', JSON.stringify(e.candidate));
|
|
1165
|
-
};
|
|
1166
|
-
dc.onopen = () => {
|
|
1167
|
-
dc.send(JSON.stringify({ type: 'get_block', block_idx: peerBlockIdx }));
|
|
1168
|
-
};
|
|
1169
|
-
dc.onmessage = (e) => {
|
|
1170
|
-
try {
|
|
1171
|
-
const msg = JSON.parse(e.data);
|
|
1172
|
-
if (msg.type === 'block_data' && msg.block_data) {
|
|
1173
|
-
resolve(msg.block_data);
|
|
1174
|
-
}
|
|
1175
|
-
} catch (err) {}
|
|
1176
|
-
};
|
|
1177
|
-
const offer = await pc.createOffer();
|
|
1178
|
-
await pc.setLocalDescription(offer);
|
|
1179
|
-
await sendWebRtcSignal(peerId, 'offer', JSON.stringify(offer));
|
|
1180
|
-
} catch (err) {
|
|
1181
|
-
resolve(null);
|
|
1182
|
-
}
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
const webrtcTimeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), 5000));
|
|
1186
|
-
peerBlock = await Promise.race([webrtcTransferPromise, webrtcTimeoutPromise]);
|
|
1187
|
-
|
|
1188
|
-
if (peerBlock) {
|
|
1189
|
-
document.getElementById('loader').innerText = '⚡ Bloc reçu en direct via WebRTC P2P sans transit serveur !';
|
|
1190
|
-
} else {
|
|
1191
|
-
// 2. Repli vers le relais HTTP si WebRTC échoue
|
|
1192
|
-
document.getElementById('loader').innerText = '⚠️ WebRTC indisponible. Téléchargement via relais HTTP...';
|
|
1193
|
-
const reqId = Math.random().toString(36).substring(2);
|
|
1194
|
-
const reqSig = await signCoop("request_peer_block", nodeId, peerId + ":" + peerBlockIdx + ":" + reqId);
|
|
1195
|
-
await fetch(window.location.pathname + "?coop_op=request_peer_block&node_id=" + nodeId + "&peer_id=" + peerId + "&block_idx=" + peerBlockIdx + "&req_id=" + reqId + "&coop_sig=" + reqSig);
|
|
1196
|
-
|
|
1197
|
-
let attempts = 0;
|
|
1198
|
-
while (attempts < coopTimeout) {
|
|
1199
|
-
const pollSig = await signCoop("poll_response", nodeId, reqId);
|
|
1200
|
-
const res = await fetch(window.location.pathname + "?coop_op=poll_response&node_id=" + nodeId + "&req_id=" + reqId + "&coop_sig=" + pollSig);
|
|
1201
|
-
const data = await res.json();
|
|
1202
|
-
if (data.status === 'ready') {
|
|
1203
|
-
peerBlock = data.block_data;
|
|
1204
|
-
break;
|
|
1205
|
-
}
|
|
1206
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
1207
|
-
attempts++;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
document.getElementById('loader').innerText = '⚙️ Génération de la Preuve d\\'Espace...';
|
|
1213
|
-
const hash = await window.solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret, peerBlock);
|
|
1214
|
-
|
|
1215
|
-
window.location.href = path + "?pow_type=pospace&pow_nonce=" + nonce + "&pow_solution_space=" + hash + (peerBlock ? "&pow_coop=1" : "");
|
|
1216
|
-
} catch(e) {
|
|
1217
|
-
document.getElementById('loader').innerText = "Error initializing local storage: " + e.message;
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
solve();
|
|
1221
|
-
JS;
|
|
1055
|
+
$challengeScript = 'async function solve() {' . "\n"
|
|
1056
|
+
. ' const nonce = ' . $safeNonce . ";\n"
|
|
1057
|
+
. ' const path = ' . $safePath . ";\n"
|
|
1058
|
+
. ' const clientSecret = ' . $safeClientSecret . ";\n"
|
|
1059
|
+
. ' const queries = ' . $queriesJson . ";\n"
|
|
1060
|
+
. ' const sizeMb = ' . $sizeMb . ";\n"
|
|
1061
|
+
. ' const nodeId = "' . $nodeId . "\";\n"
|
|
1062
|
+
. ' const peerId = "' . $peerId . "\";\n"
|
|
1063
|
+
. ' const peerBlockIdx = ' . $peerBlockIdx . ";\n"
|
|
1064
|
+
. ' const coopTimeout = ' . $coopTimeout . ";\n"
|
|
1065
|
+
. ' document.getElementById("loader").innerText = "⚙️ Checking persistent local storage...";' . "\n"
|
|
1066
|
+
. ' await new Promise(r => setTimeout(r, 10));' . "\n"
|
|
1067
|
+
. ' try {' . "\n"
|
|
1068
|
+
. ' await window.initializeSpace(nonce + ":" + clientSecret, sizeMb);' . "\n"
|
|
1069
|
+
. ' document.getElementById("loader").innerText = "⚙️ Proof of Space generation...";' . "\n"
|
|
1070
|
+
. ' const hash = await window.solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret, "");' . "\n"
|
|
1071
|
+
. ' window.location.href = path + "?pow_type=pospace&pow_nonce=" + nonce + "&pow_solution_space=" + hash;' . "\n"
|
|
1072
|
+
. ' } catch(e) {' . "\n"
|
|
1073
|
+
. ' document.getElementById("loader").innerText = "Error initializing local storage: " + e.message;' . "\n"
|
|
1074
|
+
. ' }' . "\n"
|
|
1075
|
+
. '}' . "\n"
|
|
1076
|
+
. 'solve();';
|
|
1222
1077
|
|
|
1223
1078
|
return "<html><head><title>Security Check</title></head><body style=\"font-family:sans-serif; text-align:center; padding-top:50px;\"><h1>Security Check (Level 2)</h1><p>We are verifying your storage allocation. This may take a few seconds on first load.</p><div id=\"loader\" style=\"margin:20px;\">⚙️ Initializing storage space...</div><script>{$solverCode}</script><script>{$challengeScript}</script></body></html>";
|
|
1224
1079
|
}
|
|
@@ -1267,7 +1122,7 @@ JS;
|
|
|
1267
1122
|
|
|
1268
1123
|
$trapLinks = [];
|
|
1269
1124
|
foreach ($trapUrls as $index => $url) {
|
|
1270
|
-
$nestingType =
|
|
1125
|
+
$nestingType = function_exists('wp_rand') ? wp_rand(0, 2) : random_int(0, 2);
|
|
1271
1126
|
$innerHtml = ($nestingType === 1) ? "<b>> " . ($index + 1) . "</b>" : (($nestingType === 2) ? "<i>> " . ($index + 1) . "</i>" : "<span>> " . ($index + 1) . "</span>");
|
|
1272
1127
|
$trapLinks[] = "<a href=\"{$url}\" tabindex=\"-1\">{$innerHtml}</a>";
|
|
1273
1128
|
}
|
|
@@ -1279,45 +1134,38 @@ JS;
|
|
|
1279
1134
|
$safeClientSecret = json_encode($clientSecret, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
1280
1135
|
$isHttpJs = $isHttp ? 'true' : 'false';
|
|
1281
1136
|
|
|
1282
|
-
$challengeScript =
|
|
1283
|
-
|
|
1284
|
-
const
|
|
1285
|
-
const
|
|
1286
|
-
const
|
|
1287
|
-
const
|
|
1288
|
-
const
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
const finalUrl = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=" + cpuSolution + "&pow_solution_mem=" + encodeURIComponent(JSON.stringify(memSolution));
|
|
1317
|
-
window.location.href = finalUrl;
|
|
1318
|
-
}
|
|
1319
|
-
solve();
|
|
1320
|
-
JS;
|
|
1137
|
+
$challengeScript = 'async function solve() {' . "\n"
|
|
1138
|
+
. ' const nonce = ' . $safeNonce . ";\n"
|
|
1139
|
+
. ' const path = ' . $safePath . ";\n"
|
|
1140
|
+
. ' const clientSecret = ' . $safeClientSecret . ";\n"
|
|
1141
|
+
. ' const isHttp = ' . $isHttpJs . ";\n"
|
|
1142
|
+
. ' const cpuTarget = BigInt("0x" + "' . $target . "\");\n"
|
|
1143
|
+
. ' const memDifficulty = ' . $memoryDifficulty . ";\n"
|
|
1144
|
+
. ' const baseBlock = new Uint8Array(' . $baseBlockBytes . ");\n"
|
|
1145
|
+
. ' if (isHttp) {' . "\n"
|
|
1146
|
+
. ' document.getElementById("loader").innerText = "HTTP connection: quick acknowledgment...";' . "\n"
|
|
1147
|
+
. ' await new Promise(r => setTimeout(r, 150));' . "\n"
|
|
1148
|
+
. ' window.location.href = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=http_simulacre_ack&pow_solution_mem=0";' . "\n"
|
|
1149
|
+
. ' return;' . "\n"
|
|
1150
|
+
. ' }' . "\n"
|
|
1151
|
+
. ' document.getElementById("loader").innerText = "⚙️ Performing CPU security calculation...";' . "\n"
|
|
1152
|
+
. ' const cpuSolution = await window.solveCpuChallengeInline(baseBlock, cpuTarget, (progress) => {});' . "\n"
|
|
1153
|
+
. ' if (memDifficulty > 0) {' . "\n"
|
|
1154
|
+
. ' document.getElementById("loader").innerText = "⚙️ Performing memory allocation and calculation... (" + memDifficulty + " MB)";' . "\n"
|
|
1155
|
+
. ' await new Promise(r => setTimeout(r, 10));' . "\n"
|
|
1156
|
+
. ' }' . "\n"
|
|
1157
|
+
. ' let memSolution = 0;' . "\n"
|
|
1158
|
+
. ' try {' . "\n"
|
|
1159
|
+
. ' const memSeed = ":" + nonce + ":" + clientSecret;' . "\n"
|
|
1160
|
+
. ' memSolution = await window.solveMemoryChallenge(memSeed, memDifficulty);' . "\n"
|
|
1161
|
+
. ' } catch(e) {' . "\n"
|
|
1162
|
+
. ' document.getElementById("loader").innerText = "Error: Insufficient memory. Please refresh.";' . "\n"
|
|
1163
|
+
. ' return;' . "\n"
|
|
1164
|
+
. ' }' . "\n"
|
|
1165
|
+
. ' const finalUrl = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=" + cpuSolution + "&pow_solution_mem=" + encodeURIComponent(JSON.stringify(memSolution));' . "\n"
|
|
1166
|
+
. ' window.location.href = finalUrl;' . "\n"
|
|
1167
|
+
. '}' . "\n"
|
|
1168
|
+
. 'solve();';
|
|
1321
1169
|
|
|
1322
1170
|
$htmlTemplate = '<html><head><title>Advanced Security Check</title></head><body style="font-family:sans-serif; text-align:center; padding-top:50px;"><h1>Enhanced Verification... (Level 2)</h1><p>Your activity requires an additional security check. This may take a few moments.</p><div id="loader" style="margin:20px;">⚙️ Initializing combined verification...</div><script><!-- FINGERPRINT_SOLVER_SCRIPT --></script><script><!-- FINGERPRINT_CHALLENGE_SCRIPT --></script><!-- FINGERPRINT_TRAPS --></body></html>';
|
|
1323
1171
|
$customTemplatePath = $securityConfig['challengePagePath'] ?? null;
|
|
@@ -1332,4 +1180,37 @@ JS;
|
|
|
1332
1180
|
$htmlTemplate
|
|
1333
1181
|
);
|
|
1334
1182
|
}
|
|
1183
|
+
|
|
1184
|
+
private static function logError(string $message): void
|
|
1185
|
+
{
|
|
1186
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Challenge diagnostic logging
|
|
1187
|
+
error_log($message);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Safely retrieves and sanitizes a value from the $_SERVER superglobal.
|
|
1192
|
+
* Uses WordPress functions if available, otherwise falls back to basic PHP sanitization.
|
|
1193
|
+
*
|
|
1194
|
+
* @param string $key The key to retrieve from $_SERVER.
|
|
1195
|
+
* @param mixed $default The default value to return if the key is not found.
|
|
1196
|
+
* @return mixed The sanitized value.
|
|
1197
|
+
*/
|
|
1198
|
+
private static function getSanitizedServerVar(string $key, $default = '')
|
|
1199
|
+
{
|
|
1200
|
+
if (!isset($_SERVER[$key])) {
|
|
1201
|
+
return $default;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Unslashed and sanitized immediately below
|
|
1205
|
+
$value = $_SERVER[$key];
|
|
1206
|
+
|
|
1207
|
+
if (function_exists('wp_unslash')) {
|
|
1208
|
+
$value = wp_unslash($value);
|
|
1209
|
+
}
|
|
1210
|
+
if (function_exists('sanitize_text_field')) {
|
|
1211
|
+
return sanitize_text_field($value);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
return is_scalar($value) ? (string) $value : '';
|
|
1215
|
+
}
|
|
1335
1216
|
}
|
|
@@ -34,17 +34,25 @@ class DirectFingerprint
|
|
|
34
34
|
public function protect(): ?array
|
|
35
35
|
{
|
|
36
36
|
// 1. Build request context from PHP superglobals
|
|
37
|
+
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Direct WAF inspection firewall before WP core logic
|
|
37
38
|
$body = $_POST ?: json_decode(file_get_contents('php://input'), true);
|
|
38
39
|
$headers = function_exists('getallheaders') ? getallheaders() : [];
|
|
39
40
|
|
|
41
|
+
$remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '127.0.0.1';
|
|
42
|
+
$rawUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '/';
|
|
43
|
+
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Uses wp_parse_url when available
|
|
44
|
+
$path = function_exists('wp_parse_url') ? (string)wp_parse_url($rawUri, PHP_URL_PATH) : (string)parse_url($rawUri, PHP_URL_PATH);
|
|
45
|
+
$serverProtocol = isset($_SERVER['SERVER_PROTOCOL']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_PROTOCOL'])) : '1.1';
|
|
46
|
+
|
|
40
47
|
$context = new RequestContext(
|
|
41
|
-
$
|
|
42
|
-
|
|
48
|
+
$remoteAddr,
|
|
49
|
+
!empty($path) ? $path : '/',
|
|
43
50
|
$headers,
|
|
51
|
+
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
44
52
|
$_GET,
|
|
45
53
|
$body,
|
|
46
54
|
$_COOKIE,
|
|
47
|
-
$
|
|
55
|
+
$serverProtocol
|
|
48
56
|
);
|
|
49
57
|
|
|
50
58
|
// 2. Process request with engine
|
|
@@ -62,9 +70,11 @@ class DirectFingerprint
|
|
|
62
70
|
http_response_code($decision['status'] ?? 403);
|
|
63
71
|
if (is_array($decision['body'])) {
|
|
64
72
|
header('Content-Type: application/json');
|
|
73
|
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON API challenge payload
|
|
65
74
|
echo json_encode($decision['body']);
|
|
66
75
|
} else {
|
|
67
76
|
header('Content-Type: text/html; charset=utf-8');
|
|
77
|
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Proof-of-Work challenge HTML page
|
|
68
78
|
echo $decision['body'];
|
|
69
79
|
}
|
|
70
80
|
exit();
|
|
@@ -152,7 +162,8 @@ class DirectFingerprint
|
|
|
152
162
|
switch ($decision['action']) {
|
|
153
163
|
case 'block':
|
|
154
164
|
http_response_code($decision['status'] ?? 403);
|
|
155
|
-
|
|
165
|
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
166
|
+
echo esc_html($decision['body'] ?? "Access denied.");
|
|
156
167
|
exit();
|
|
157
168
|
case 'redirect':
|
|
158
169
|
header('Location: ' . $decision['path'], true, $decision['status'] ?? 302);
|
|
@@ -176,6 +187,7 @@ class DirectFingerprint
|
|
|
176
187
|
|
|
177
188
|
// 3. If authorized, stream metrics output
|
|
178
189
|
header('Content-Type: text/plain; version=0.0.4; charset=utf-8');
|
|
190
|
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Prometheus exposition format output
|
|
179
191
|
echo MetricsManager::getPrometheusMetrics();
|
|
180
192
|
exit();
|
|
181
193
|
}
|
|
@@ -87,7 +87,7 @@ class FingerprintClient
|
|
|
87
87
|
$containerTags = ['div', 'span', 'p', 'section'];
|
|
88
88
|
$tag = $containerTags[array_rand($containerTags)];
|
|
89
89
|
|
|
90
|
-
$nestingType =
|
|
90
|
+
$nestingType = function_exists('wp_rand') ? wp_rand(0, 1) : random_int(0, 1);
|
|
91
91
|
if ($nestingType === 1) {
|
|
92
92
|
return '<' . $tag . ' style="' . $styles . '" aria-hidden="true">'
|
|
93
93
|
. '<label for="' . htmlspecialchars($fieldName) . '">' . htmlspecialchars($fieldName)
|
|
@@ -113,26 +113,24 @@ class FingerprintClient
|
|
|
113
113
|
$nonceAttr = $this->nonce ? ' nonce="' . $this->nonce . '"' : '';
|
|
114
114
|
|
|
115
115
|
// Inline initialization script embedded in HTML
|
|
116
|
-
$initScript =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
wasmScript.
|
|
123
|
-
wasmScript.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
JS;
|
|
116
|
+
$initScript = "document.addEventListener('DOMContentLoaded', function() {\n"
|
|
117
|
+
. " const config = " . $configJson . ";\n"
|
|
118
|
+
. " if (window.ClientLibrary) {\n"
|
|
119
|
+
. " if (config.wasmPath) {\n"
|
|
120
|
+
. " const wasmScript = document.createElement('script');\n"
|
|
121
|
+
. " wasmScript.src = config.wasmPath;\n"
|
|
122
|
+
. " wasmScript.async = true;\n"
|
|
123
|
+
. " wasmScript.nonce = '" . addslashes((string)$this->nonce) . "';\n"
|
|
124
|
+
. " document.head.appendChild(wasmScript);\n"
|
|
125
|
+
. " }\n"
|
|
126
|
+
. " window.ClientLibrary.initializeClient(config);\n"
|
|
127
|
+
. " } else {\n"
|
|
128
|
+
. " console.error('Fingerprint client library not loaded.');\n"
|
|
129
|
+
. " }\n"
|
|
130
|
+
. "});";
|
|
134
131
|
|
|
135
132
|
// Combine library loader script and inline initialization
|
|
133
|
+
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- Polymorphic dynamic client script tag generation
|
|
136
134
|
return '<script src="' . htmlspecialchars($this->clientScriptPath) . '"' . $nonceAttr . '></script>'
|
|
137
135
|
. '<script' . $nonceAttr . '>' . $initScript . '</script>';
|
|
138
136
|
}
|