PluginProbe
The WP Remote WordPress Plugin / 6.76
The WP Remote WordPress Plugin v6.76
6.76 6.72 6.69 6.65 6.62 6.48 6.47 4.87 4.97 5.05 5.09 5.16 5.22 5.24 5.25 5.38 5.41 5.42 5.45 5.47 5.53 5.56 5.65 5.68 5.72 All 54 releases
← All changes | callback/wings/security.php +257 -30 5.726.76 View file →
@@ -1,8 +1,16 @@
1 1 <?php
2 2 if (!defined('ABSPATH')) exit;
3 -if (!class_exists('BVSecurityCallback')) :
4 - class BVSecurityCallback extends BVCallbackBase {
3 +if (!class_exists('WPRSecurityCallback')) :
4 + class WPRSecurityCallback extends WPRCallbackBase {
5 + private $settings;
6 +
7 + public function __construct() {
8 + $this->settings = new WPRWPSettings();
9 + }
10 +
11 + // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fread
12 + // Here we need fread as we are using popen which returns a handler
5 13 function getCrontab() {
6 14 $resp = array();
7 15
8 16 if (function_exists('exec')) {
@@ -31,34 +39,92 @@
31 39 }
32 40
33 41 return $resp;
34 42 }
43 + // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fread
35 44
36 - public function setupWP2FA($user_id, $secret, $to_encrypt = true, $cipher_algo = null) {
37 - if ($to_encrypt === true) {
38 - if (empty($cipher_algo)) {
39 - $cipher_algo = WPRWP2FA::$cipher_algo;
45 + public function setupWP2FA($secrets_by_uids, $to_encrypt, $cipher_algo, $enabled) {
46 + if (!is_array($secrets_by_uids) || !is_bool($to_encrypt) ||
47 + (!is_null($cipher_algo) && !is_string($cipher_algo)) ||
48 + (!is_null($enabled) && !is_bool($enabled))) {
49 + return array("status" => false, "message" => "Invalid parameters.");
50 + }
51 + if (count($secrets_by_uids) < 1) {
52 + return array("status" => false, "message" => "Invalid parameters.");
53 + }
54 + foreach ($secrets_by_uids as $user_id => $secret) {
55 + if (!$this->isValidUserId($user_id) || !is_string($secret)) {
56 + return array("status" => false, "message" => "Invalid parameters.");
40 57 }
58 + }
41 59
42 - if (defined('SECURE_AUTH_KEY')) {
43 - $encryption_result = WPRHelper::opensslEncrypt($secret, $cipher_algo, SECURE_AUTH_KEY);
44 - if ($encryption_result[0] === false) {
45 - return array("status" => false, "message" => $encryption_result[1]);
60 + $result = array();
61 + $status = true;
62 + foreach ($secrets_by_uids as $user_id => $secret) {
63 + if ($to_encrypt === true) {
64 + if (empty($cipher_algo)) {
65 + $cipher_algo = WPRWP2FA::$cipher_algo;
46 66 }
47 - $secret = $encryption_result[1];
48 - } else {
49 - return array("status" => false, "message" => "Encryption key not found.");
67 +
68 + if (defined('SECURE_AUTH_KEY')) {
69 + $encryption_result = WPRHelper::opensslEncrypt($secret, $cipher_algo, SECURE_AUTH_KEY);
70 + if ($encryption_result[0] === false) {
71 + return array("status" => false, "message" => $encryption_result[1]);
72 + }
73 + $secret = $encryption_result[1];
74 + } else {
75 + return array("status" => false, "message" => "Encryption key not found.");
76 + }
50 77 }
78 +
79 + $secret_info = array(
80 + "secret" => base64_encode($secret),
81 + "is_encrypted" => $to_encrypt
82 + );
83 +
84 + $email_state_cleared = WPRWP2FAEmailOTP::revoke($user_id);
85 + $attempt_state_cleared = WPRWP2FATimeOTPLogin::clearState($user_id);
86 + $result[$user_id][WPRWP2FA::EMAIL_CHALLENGE_META_KEY] = $email_state_cleared;
87 + if (!$email_state_cleared || !$attempt_state_cleared) {
88 + $status = false;
89 + continue;
90 + }
91 +
92 + update_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, $secret_info);
93 + $secret_saved = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true) === $secret_info;
94 + $result[$user_id][WPRWP2FA::SECRET_META_KEY] = $secret_saved;
95 + if (!$secret_saved) {
96 + $status = false;
97 + continue;
98 + }
99 +
100 + update_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, 'totp');
101 + $method_saved = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true) === 'totp';
102 + $result[$user_id][WPRWP2FA::METHOD_META_KEY] = $method_saved;
103 + if (!$method_saved) {
104 + $status = false;
105 + continue;
106 + }
107 +
108 + update_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
109 + $flag_saved = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true) === '1';
110 + $result[$user_id][WPRWP2FA::FLAG_META_KEY] = $flag_saved;
111 + if (!$flag_saved) {
112 + $status = false;
113 + }
51 114 }
52 115
53 - $secret_info = array(
54 - "secret" => base64_encode($secret),
55 - "is_encrypted" => $to_encrypt
56 - );
116 + if (is_bool($enabled)) {
117 + $config = array("enabled" => $enabled);
118 + $this->settings->updateOption(WPRWP2FA::$wp_2fa_option, $config);
119 + $option_saved = WPRWP2FA::isEnabled($this->settings) === $enabled;
120 + $result[WPRWP2FA::$wp_2fa_option] = $option_saved;
121 + if (!$option_saved) {
122 + $status = false;
123 + }
124 + }
57 125
58 - update_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, $secret_info);
59 - update_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
60 - return array("status" => true);
126 + return array("status" => $status, "result" => $result);
61 127 }
62 128
63 129 public function verifyWP2FACode($user_id, $code, $cipher_algo = null) {
64 130 $encoded_secret_info = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true);
@@ -86,9 +152,9 @@
86 152 return array("status" => false, "message" => "Decryption key not found.");
87 153 }
88 154 }
89 155
90 - return array("status" => WPRWP2FAAuthenticator::verifyCode($secret, $code, 2));
156 + return array("status" => WPRWP2FATimeOTP::verifyCode($secret, $code, 2));
91 157 }
92 158
93 159 public function readWP2FAKeys($user_id) {
94 160 $secret = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true);
@@ -98,32 +164,193 @@
98 164 "enabled" => $enabled
99 165 );
100 166 }
101 167
102 - public function deleteWP2FAKeys($user_ids) {
168 + public function deleteWP2FAKeys($user_ids, $is_disable = false) {
169 + $result = array();
170 + $status = true;
171 +
103 172 foreach ($user_ids as $user_id) {
104 - delete_user_meta($user_id, WPRWP2FA::FLAG_META_KEY);
105 - delete_user_meta($user_id, WPRWP2FA::SECRET_META_KEY);
173 + $secret_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::SECRET_META_KEY);
174 + $flag_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::FLAG_META_KEY);
175 + $method_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::METHOD_META_KEY);
176 + $email_state_deleted = WPRWP2FAEmailOTP::revoke($user_id);
177 + $totp_state_deleted = WPRWP2FATimeOTPLogin::clearState($user_id);
178 + $status = $status && $secret_deleted && $flag_deleted && $method_deleted &&
179 + $email_state_deleted && $totp_state_deleted;
180 + $result[$user_id] = array(
181 + WPRWP2FA::SECRET_META_KEY => $secret_deleted,
182 + WPRWP2FA::FLAG_META_KEY => $flag_deleted,
183 + WPRWP2FA::METHOD_META_KEY => $method_deleted,
184 + WPRWP2FA::EMAIL_CHALLENGE_META_KEY => $email_state_deleted
185 + );
106 186 }
107 - return array("status" => true);
187 +
188 + if ($is_disable === true) {
189 + $this->settings->deleteOption(WPRWP2FA::$wp_2fa_option);
190 + $option_deleted = $this->settings->getOption(WPRWP2FA::$wp_2fa_option) === false;
191 + $result[WPRWP2FA::$wp_2fa_option] = $option_deleted;
192 + $status = $status && $option_deleted;
193 + }
194 +
195 + return array("status" => $status, "result" => $result);
108 196 }
109 197
198 + private function deleteUserMetaState($user_id, $key) {
199 + delete_user_meta($user_id, $key);
200 + return !metadata_exists('user', $user_id, $key);
201 + }
202 +
203 + private function restoreEmailWP2FAMeta($user_id, $key, $value) {
204 + if ($value === '') {
205 + delete_user_meta($user_id, $key);
206 + if (get_user_meta($user_id, $key, true) !== '') update_user_meta($user_id, $key, '');
207 + } else {
208 + update_user_meta($user_id, $key, $value);
209 + }
210 + return get_user_meta($user_id, $key, true) === $value;
211 + }
212 +
213 + private function restoreEmailWP2FAUserState($user_id, $method, $flag) {
214 + $method_restored = $this->restoreEmailWP2FAMeta($user_id, WPRWP2FA::METHOD_META_KEY, $method);
215 + $flag_restored = $this->restoreEmailWP2FAMeta($user_id, WPRWP2FA::FLAG_META_KEY, $flag);
216 + return $method_restored && $flag_restored;
217 + }
218 +
219 + private function clearAuthenticatorState($user_id) {
220 + $secret_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::SECRET_META_KEY);
221 + $attempt_state_deleted = WPRWP2FATimeOTPLogin::clearState($user_id);
222 + return $secret_deleted && $attempt_state_deleted;
223 + }
224 +
225 + private function isValidUserId($user_id) {
226 + $is_integer = is_int($user_id);
227 + $is_integer_string = is_string($user_id) && ctype_digit($user_id);
228 + return ($is_integer || $is_integer_string) && intval($user_id) > 0;
229 + }
230 +
231 + public function setupEmailWP2FA($capability_version, $enabled, $targets) {
232 + if (!is_int($capability_version) || $capability_version !== 1 || $enabled !== true || !is_array($targets) || count($targets) < 1 || count($targets) > 100) return array('status' => false, 'outcomes' => array());
233 + $seen_user_ids = array();
234 + foreach ($targets as $target) {
235 + if (!is_array($target) || !isset($target['user_id']) || !is_int($target['user_id']) || $target['user_id'] < 1 || !array_key_exists('replace_existing', $target) || !is_bool($target['replace_existing']) || isset($seen_user_ids[$target['user_id']])) return array('status' => false, 'outcomes' => array());
236 + $seen_user_ids[$target['user_id']] = true;
237 + }
238 + if (!WPRWP2FAEmailOTP::hasSiteSecret()) {
239 + $outcomes = array();
240 + foreach ($targets as $target) {
241 + $outcomes[] = array('user_id' => $target['user_id'], 'status' => 'rejected', 'reason' => 'secure_secret_unavailable');
242 + }
243 + return array('status' => true, 'outcomes' => $outcomes);
244 + }
245 + $config = $this->settings->getOption(WPRWP2FA::$wp_2fa_option);
246 + if (!is_array($config)) $config = array();
247 + $config['enabled'] = true;
248 + $this->settings->updateOption(WPRWP2FA::$wp_2fa_option, $config);
249 + if (!WPRWP2FA::isEnabled($this->settings)) return array('status' => false, 'outcomes' => array());
250 + $outcomes = array();
251 + foreach ($targets as $target) {
252 + $user_id = isset($target['user_id']) ? $target['user_id'] : null;
253 + $replace = isset($target['replace_existing']) && $target['replace_existing'] === true;
254 + if (!is_int($user_id) || $user_id < 1) continue;
255 + $user = get_userdata($user_id);
256 + if (!$user) { $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'not_found'); continue; }
257 + if (!is_email($user->user_email)) { $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'invalid_email'); continue; }
258 + $current = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true);
259 + $has_2fa = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true) === '1';
260 + $current = ($has_2fa && $current === '') ? 'totp' : $current;
261 + if ($has_2fa && $current === 'email_otp') {
262 + $authenticator_state_cleared = $this->clearAuthenticatorState($user_id);
263 + $outcomes[] = array(
264 + 'user_id' => $user_id,
265 + 'status' => $authenticator_state_cleared ? 'already_configured' : 'rejected',
266 + 'reason' => $authenticator_state_cleared ? null : 'persistence_failed'
267 + );
268 + continue;
269 + }
270 + if ($has_2fa && !$replace) { $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'replacement_required'); continue; }
271 + $previous_method = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true);
272 + $previous_flag = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
273 + if (!WPRWP2FAEmailOTP::revoke($user_id)) {
274 + $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'persistence_failed');
275 + continue;
276 + }
277 + update_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, 'email_otp');
278 + update_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
279 + $method_persisted = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true) === 'email_otp';
280 + $flag_persisted = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true) === '1';
281 + if (!$method_persisted || !$flag_persisted) {
282 + $rollback_restored = $this->restoreEmailWP2FAUserState($user_id, $previous_method, $previous_flag);
283 + $reason = $rollback_restored ? 'persistence_failed' : 'rollback_failed';
284 + $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => $reason);
285 + continue;
286 + }
287 + # Dropped only once the switch has stuck, so the rollback above still has it.
288 + if (!$this->clearAuthenticatorState($user_id)) {
289 + $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'persistence_failed');
290 + continue;
291 + }
292 + $outcomes[] = array('user_id' => $user_id, 'status' => 'configured', 'reason' => null);
293 + }
294 + return array('status' => true, 'outcomes' => $outcomes);
295 + }
296 +
110 297 public function process($request) {
298 + $params = isset($request->params) && is_array($request->params) ? $request->params : array();
299 + $invalid_params = array('status' => false, 'message' => 'Invalid parameters.');
300 +
111 301 switch ($request->method) {
112 302 case "gtcrntb":
113 303 $resp = $this->getCrontab();
114 304 break;
115 305 case "stupwp2fa":
116 - $resp = $this->setupWP2FA($request->params['user_id'], $request->params['secret'], $request->params['to_encrypt'], $request->params['cipher_algo']);
306 + $secrets_by_uids = array_key_exists('secrets_by_uids', $params) ? $params['secrets_by_uids'] : null;
307 + $to_encrypt = array_key_exists('to_encrypt', $params) ? $params['to_encrypt'] : null;
308 + $cipher_algo = array_key_exists('cipher_algo', $params) ? $params['cipher_algo'] : null;
309 + $enable_wp_2fa = array_key_exists('enable_wp_2fa', $params) ? $params['enable_wp_2fa'] : null;
310 + if (!is_array($secrets_by_uids) || !is_bool($to_encrypt) ||
311 + (!is_null($cipher_algo) && !is_string($cipher_algo)) ||
312 + (!is_null($enable_wp_2fa) && !is_bool($enable_wp_2fa))) {
313 + $resp = $invalid_params;
314 + break;
315 + }
316 + $resp = $this->setupWP2FA($secrets_by_uids, $to_encrypt, $cipher_algo, $enable_wp_2fa);
117 317 break;
318 + case "stupemail2fa":
319 + $capability_version = array_key_exists('capability_version', $params) ? $params['capability_version'] : null;
320 + $enable_wp_2fa = array_key_exists('enable_wp_2fa', $params) ? $params['enable_wp_2fa'] : null;
321 + $targets = array_key_exists('targets', $params) ? $params['targets'] : null;
322 + $resp = $this->setupEmailWP2FA($capability_version, $enable_wp_2fa, $targets);
323 + break;
118 324 case "vrfywp2fa":
119 - $resp = $this->verifyWP2FACode($request->params['user_id'], $request->params['code'], $request->params['cipher_algo']);
325 + $user_id = array_key_exists('user_id', $params) ? $params['user_id'] : null;
326 + $code = array_key_exists('code', $params) ? $params['code'] : null;
327 + $cipher_algo = array_key_exists('cipher_algo', $params) ? $params['cipher_algo'] : null;
328 + if (!$this->isValidUserId($user_id) || !is_string($code) ||
329 + (!is_null($cipher_algo) && !is_string($cipher_algo))) {
330 + $resp = $invalid_params;
331 + break;
332 + }
333 + $resp = $this->verifyWP2FACode($user_id, $code, $cipher_algo);
120 334 break;
121 335 case "rdwp2fa":
122 - $resp = $this->readWP2FAKeys($request->params['user_id']);
336 + $user_id = array_key_exists('user_id', $params) ? $params['user_id'] : null;
337 + $resp = $this->isValidUserId($user_id) ? $this->readWP2FAKeys($user_id) : $invalid_params;
123 338 break;
124 339 case "dltewp2fa":
125 - $resp = $this->deleteWP2FAKeys($request->params['user_ids']);
340 + $user_ids = array_key_exists('user_ids', $params) ? $params['user_ids'] : null;
341 + $is_disable = array_key_exists('is_disable', $params) ? $params['is_disable'] : null;
342 + $valid_user_ids = is_array($user_ids);
343 + if ($valid_user_ids) {
344 + foreach ($user_ids as $user_id) {
345 + if (!$this->isValidUserId($user_id)) {
346 + $valid_user_ids = false;
347 + break;
348 + }
349 + }
350 + }
351 + $resp = ($valid_user_ids && is_bool($is_disable)) ?
352 + $this->deleteWP2FAKeys($user_ids, $is_disable) : $invalid_params;
126 353 break;
127 354 default:
128 355 $resp = false;
129 356 }
@@ -130,5 +357,5 @@
130 357
131 358 return $resp;
132 359 }
133 360 }
134 -endif;
361 +endif;