PluginProbe
The WP Remote WordPress Plugin / 6.72
The WP Remote WordPress Plugin v6.72
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 5.73 All 53 releases
wpremote / callback / wings / security.php

security.php in The WP Remote WordPress Plugin 6.72, at callback/wings/security.php

362 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) exit;
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
13 function getCrontab() {
14 $resp = array();
15
16 if (function_exists('exec')) {
17 $output = array();
18 $retval = -1;
19 $execRes = exec('crontab -l', $output, $retval);
20 if ($execRes !== false && $execRes !== null) {
21 $resp["content"] = implode("\n", $output);
22 $resp["status"] = "success";
23 $resp["code"] = $retval;
24 }
25 }
26 if (empty($resp) && function_exists('popen')) {
27 $handle = popen('crontab -l', 'rb');
28 if ($handle) {
29 $output = '';
30 while (!feof($handle)) {
31 $output .= fread($handle, 8192);
32 }
33 $resp["content"] = $output;
34 $resp["status"] = "success";
35 pclose($handle);
36 } else {
37 $resp["status"] = "failed";
38 }
39 }
40
41 return $resp;
42 }
43 // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fread
44
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.");
57 }
58 }
59
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;
66 }
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 }
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 }
114 }
115
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 }
125
126 return array("status" => $status, "result" => $result);
127 }
128
129 public function verifyWP2FACode($user_id, $code, $cipher_algo = null) {
130 $encoded_secret_info = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true);
131
132 $secret_info = WPRWP2FAUtils::getSecretInfo($encoded_secret_info);
133 $secret = $secret_info['secret'];
134 $is_secret_encrypted = $secret_info['is_encrypted'];
135
136 if (is_null($secret) || is_null($is_secret_encrypted)) {
137 return array("status" => false, "message" => "Secret and encryption status not found.");
138 }
139
140 if ($is_secret_encrypted === true) {
141 if (empty($cipher_algo)) {
142 $cipher_algo = WPRWP2FA::$cipher_algo;
143 }
144
145 if (defined('SECURE_AUTH_KEY')) {
146 $decryption_result = WPRHelper::opensslDecrypt($secret, $cipher_algo, SECURE_AUTH_KEY);
147 if ($decryption_result[0] === false) {
148 return array("status" => false, "message" => $decryption_result[1]);
149 }
150 $secret = $decryption_result[1];
151 } else {
152 return array("status" => false, "message" => "Decryption key not found.");
153 }
154 }
155
156 return array("status" => WPRWP2FATimeOTP::verifyCode($secret, $code, 2));
157 }
158
159 public function readWP2FAKeys($user_id) {
160 $secret = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true);
161 $enabled = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
162 return array(
163 "secret" => $secret,
164 "enabled" => $enabled
165 );
166 }
167
168 public function deleteWP2FAKeys($user_ids, $is_disable = false) {
169 $result = array();
170 $status = true;
171
172 foreach ($user_ids as $user_id) {
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 );
186 }
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);
196 }
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
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
301 switch ($request->method) {
302 case "gtcrntb":
303 $resp = $this->getCrontab();
304 break;
305 case "stupwp2fa":
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);
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;
324 case "vrfywp2fa":
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);
334 break;
335 case "rdwp2fa":
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;
338 break;
339 case "dltewp2fa":
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;
353 break;
354 default:
355 $resp = false;
356 }
357
358 return $resp;
359 }
360 }
361 endif;
362