PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.1
Patchstack – WordPress & Plugins Security v2.2.1
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
← All changes | includes/core.php +279 -65 2.1.222.2.1 View file →
@@ -29,68 +29,68 @@
29 29 * Allowed HTML for the wp_kses function used to render certain paragraphs of texts.
30 30 *
31 31 * @var array
32 32 */
33 - public $allowed_html = array(
34 - 'a' => array(
35 - 'href' => array(),
36 - 'title' => array(),
37 - 'target' => array()
38 - ),
39 - 'p' => array(
40 - 'style' => array()
41 - ),
42 - 'span' => array(
43 - 'style' => array()
44 - ),
45 - 'br' => array(),
46 - 'strong' => array(),
47 - 'b' => array(),
48 - 'i' => array(
49 - 'style' => array()
50 - ),
51 - 'label' => array(
52 - 'for' => array(),
53 - 'style' => array()
54 - ),
55 - 'input' => array(
56 - 'type' => array(),
57 - 'class' => array(),
58 - 'name' => array(),
59 - 'id' => array(),
60 - 'value' => array(),
61 - 'checked' => array(),
62 - 'style' => array()
63 - ),
64 - 'textarea' => array(
65 - 'rows' => array(),
66 - 'id' => array(),
67 - 'name' => array()
68 - ),
69 - 'select' => array(
70 - 'name' => array(),
71 - 'id' => array(),
72 - 'data-selected' => array()
73 - ),
74 - 'option' => array(
75 - 'value' => array(),
76 - 'selected' => array()
77 - ),
78 - 'table' => array(
79 - 'class' => array(),
80 - 'style' => array()
81 - ),
82 - 'thead' => array(),
83 - 'th' => array(
84 - 'style' => array()
85 - ),
86 - 'tr' => array(),
87 - 'td' => array(),
88 - 'div' => array(
89 - 'class' => array(),
90 - 'style' => array()
91 - )
92 - );
33 + public $allowed_html = [
34 + 'a' => [
35 + 'href' => [],
36 + 'title' => [],
37 + 'target' => []
38 + ],
39 + 'p' => [
40 + 'style' => []
41 + ],
42 + 'span' => [
43 + 'style' => []
44 + ],
45 + 'br' => [],
46 + 'strong' => [],
47 + 'b' => [],
48 + 'i' => [
49 + 'style' => []
50 + ],
51 + 'label' => [
52 + 'for' => [],
53 + 'style' => []
54 + ],
55 + 'input' => [
56 + 'type' => [],
57 + 'class' => [],
58 + 'name' => [],
59 + 'id' => [],
60 + 'value' => [],
61 + 'checked' => [],
62 + 'style' => []
63 + ],
64 + 'textarea' => [
65 + 'rows' => [],
66 + 'id' => [],
67 + 'name' => []
68 + ],
69 + 'select' => [
70 + 'name' => [],
71 + 'id' => [],
72 + 'data-selected' => []
73 + ],
74 + 'option' => [
75 + 'value' => [],
76 + 'selected' => []
77 + ],
78 + 'table' => [
79 + 'class' => [],
80 + 'style' => []
81 + ],
82 + 'thead' => [],
83 + 'th' => [
84 + 'style' => []
85 + ],
86 + 'tr' => [],
87 + 'td' => [],
88 + 'div' => [
89 + 'class' => [],
90 + 'style' => []
91 + ]
92 + ];
93 93
94 94 /**
95 95 * Some of the IP addresses of Patchstack.
96 96 *
@@ -95,9 +95,9 @@
95 95 * Some of the IP addresses of Patchstack.
96 96 *
97 97 * @var array
98 98 */
99 - public $ips = array(
99 + public $ips = [
100 100 '18.221.197.243',
101 101 '52.15.237.250',
102 102 '3.19.3.34',
103 103 '3.18.238.17',
@@ -110,9 +110,9 @@
110 110 '185.212.171.100',
111 111 '3.133.121.93',
112 112 '18.219.61.133',
113 113 '3.14.29.150'
114 - );
114 + ];
115 115
116 116 /**
117 117 * @param Patchstack $plugin
118 118 * @return void
@@ -131,9 +131,9 @@
131 131 * @return mixed
132 132 */
133 133 public function get_option( $name, $default = false ) {
134 134 // We always want to return the site option on the default settings management page.
135 - if ( isset( $_GET['page'] ) && $_GET['page'] == 'patchstack-multisite-settings' && is_super_admin() ) {
135 + if ( isset( $_GET['page'] ) && $_GET['page'] == 'patchstack-multisite-settings' && function_exists( 'wp_get_current_user' ) && is_super_admin() ) {
136 136 return get_site_option( $name, $default );
137 137 }
138 138
139 139 // Get the setting of the current site.
@@ -197,14 +197,78 @@
197 197 return false;
198 198 }
199 199
200 200 /**
201 - * Attempt to get the client IP by checking all possible IP (proxy) headers.
201 + * Convert the subscription class name to its text variant.
202 + *
203 + * @param int $class
204 + * @return string
205 + */
206 + public function get_subscription_name( $class ) {
207 + switch ( $class ) {
208 + case 0:
209 + return 'Community';
210 + case 1:
211 + case 6:
212 + return 'Developer';
213 + case 7:
214 + return 'Business';
215 + default:
216 + return 'Unknown';
217 + }
218 + }
219 +
220 + /**
221 + * Determine if the user is a community user.
222 + *
223 + * @return boolean
224 + */
225 + public function is_community() {
226 + $class = get_option( 'patchstack_subscription_class', '');
227 + return $class != '' && (int) $class === 0;
228 + }
229 +
230 + /**
231 + * Determine if the plugin is connected to the API.
232 + *
233 + * @return boolean
234 + */
235 + public function is_connected() {
236 + // Determine if the API client id is set.
237 + if ( $this->plugin->client_id == 'PATCHSTACK_CLIENT_ID' && get_option( 'patchstack_clientid', false ) === false ) {
238 + return false;
239 + }
240 +
241 + // Determine if we have an API token.
242 + if ( get_option( 'patchstack_api_token', '' ) == '' ) {
243 + return false;
244 + }
245 +
246 + // Determine if we have a last license check set.
247 + $last_license_check = get_option( 'patchstack_last_license_check', 0 );
248 + if ( !empty( $last_license_check ) && time() - $last_license_check >= 604800 ) {
249 + return false;
250 + }
251 +
252 + return true;
253 + }
254 +
255 + /**
256 + * Determine if the plugin provides protection.
257 + *
258 + * @return boolean
259 + */
260 + public function is_protected() {
261 + return get_option( 'patchstack_license_free', false) == 0;
262 + }
263 +
264 + /**
265 + * Grab the IP address of the user. Give the override IP header priority.
266 + * If this does not exist, we should always default to REMOTE_ADDR.
202 267 *
203 268 * @return string
204 269 */
205 270 public function get_ip() {
206 - // IP address header override set?
207 271 $override = get_site_option( 'patchstack_firewall_ip_header', '' );
208 272 if ( $override != '' && isset( $_SERVER[ $override ] ) ) {
209 273 return $_SERVER[ $override ];
210 274 }
@@ -209,6 +273,156 @@
209 273 return $_SERVER[ $override ];
210 274 }
211 275
212 276 return isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
277 + }
278 +
279 + /**
280 + * Grab the secret key used for API communication.
281 + *
282 + * @param string $custom
283 + * @return string
284 + */
285 + public function get_secret_key( $custom = '' ) {
286 + if ( $custom != '' ) {
287 + return $this->encrypt( $custom );
288 + }
289 +
290 + $secret = get_option( 'patchstack_secretkey', '' );
291 + if ( ! $secret ) {
292 + return '';
293 + }
294 +
295 + if ( strlen( $secret ) === 40 ) {
296 + $enc = $this->encrypt( $secret );
297 +
298 + update_option( 'patchstack_secretkey', $enc['cipher'] );
299 + update_option( 'patchstack_secretkey_nonce', $enc['nonce'] );
300 +
301 + return $secret;
302 + }
303 +
304 + $nonce = get_option( 'patchstack_secretkey_nonce' );
305 + return $this->decrypt( $secret, $nonce );
306 + }
307 +
308 + /**
309 + * Set the secret key used for API communication.
310 + *
311 + * @param string $secret
312 + * @return void
313 + */
314 + public function set_secret_key( $secret ) {
315 + $enc = $this->encrypt( $secret );
316 +
317 + update_option( 'patchstack_secretkey', $enc['cipher'] );
318 + update_option( 'patchstack_secretkey_nonce', $enc['nonce'] );
319 + }
320 +
321 + /**
322 + * Determine which encryption dependency we can use.
323 + *
324 + * @return string
325 + */
326 + public function get_enc_type() {
327 + if ( function_exists('sodium_crypto_generichash') ) {
328 + return 'native';
329 + }
330 +
331 + return 'compat';
332 + }
333 +
334 + /**
335 + * Get the unique nonce that is used for the secretbox.
336 + *
337 + * @return string
338 + */
339 + public function get_enc_nonce() {
340 + if ( function_exists('random_bytes') ) {
341 + return random_bytes( 24 );
342 + }
343 +
344 + require_once dirname( __FILE__ ) . '/2fa/polyfill/lib/random.php';
345 + return random_bytes( 24 );
346 + }
347 +
348 + /**
349 + * Encrypt a string.
350 + *
351 + * @param string $message
352 + * @return array
353 + */
354 + public function encrypt( $message ) {
355 + if ( is_null( $message ) || ! defined( 'AUTH_KEY' ) ) {
356 + return [
357 + 'cipher' => $message,
358 + 'nonce' => ''
359 + ];
360 + }
361 +
362 + $enc_type = $this->get_enc_type();
363 + $nonce = $this->get_enc_nonce();
364 +
365 + try {
366 + // Use the PHP native encryption functions.
367 + if ( $enc_type == 'native' ) {
368 + $key = sodium_crypto_generichash( AUTH_KEY );
369 +
370 + return [
371 + 'cipher' => sodium_bin2hex( sodium_crypto_secretbox( $message, $nonce, $key ) ),
372 + 'nonce' => sodium_bin2hex( $nonce )
373 + ];
374 + }
375 +
376 + // Use the Sodium polyfill library part of WordPress core.
377 + require_once ABSPATH . WPINC . '/sodium_compat/autoload.php';
378 + $key = \Sodium\crypto_generichash( AUTH_KEY );
379 +
380 + return [
381 + 'cipher' => \Sodium\bin2hex( \Sodium\crypto_secretbox( $message, $nonce, $key ) ),
382 + 'nonce' => \Sodium\bin2hex( $nonce )
383 + ];
384 + } catch ( Exception $e ) {
385 + return [
386 + 'cipher' => $message,
387 + 'nonce' => ''
388 + ];
389 + }
390 + }
391 +
392 + /**
393 + * Decrypt a cipher to plain-text.
394 + *
395 + * @param string $cipher
396 + * @param string $nonce
397 + * @return string
398 + */
399 + public function decrypt( $cipher, $nonce ) {
400 + $enc_type = $this->get_enc_type();
401 +
402 + // If we received an empty nonce, we assume it was never properly encrypted to begin with.
403 + if ( $nonce == '' || ! defined( 'AUTH_KEY' ) ) {
404 + return $cipher;
405 + }
406 +
407 + try {
408 + // Determine if we should use native or polyfill functions.
409 + if ( $enc_type == 'native' ) {
410 + $key = sodium_crypto_generichash( AUTH_KEY );
411 + $dec = sodium_crypto_secretbox_open( sodium_hex2bin( $cipher ), sodium_hex2bin( $nonce ), $key );
412 + } else {
413 + require_once ABSPATH . WPINC . '/sodium_compat/autoload.php';
414 + $key = \Sodium\crypto_generichash( AUTH_KEY );
415 + $dec = \Sodium\crypto_secretbox_open( sodium_hex2bin( $cipher ), sodium_hex2bin( $nonce ), $key );
416 + }
417 + } catch ( Exception $e ) {
418 + return $cipher;
419 + }
420 +
421 + // In case decryption failed, return null.
422 + if ( ! $dec ) {
423 + return null;
424 + }
425 +
426 + return $dec;
213 427 }
214 428 }