PluginProbe
Patchstack – WordPress & Plugins Security / 2.3.5
Patchstack – WordPress & Plugins Security v2.3.5
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 | patchstack.php +91 -37 2.1.12.3.5 View file →
@@ -1,10 +1,11 @@
1 1 <?php
2 2 /**
3 - * Plugin Name: Patchstack
4 - * Plugin URI: https://patchstack.com
3 + * Plugin Name: Patchstack Security
4 + * Plugin URI: https://patchstack.com/?utm_medium=wp&utm_source=dashboard&utm_campaign=patchstack%20plugin
5 + * Author URI: https://patchstack.com/?utm_medium=wp&utm_source=dashboard&utm_campaign=patchstack%20plugin
5 6 * Description: Patchstack identifies security vulnerabilities in WordPress plugins, themes, and core.
6 - * Version: 2.1.1
7 + * Version: 2.3.5
7 8 * Author: Patchstack
8 9 * License: GPLv3
9 10 * Text Domain: patchstack
10 11 * Domain Path: /languages
@@ -32,9 +33,9 @@
32 33
33 34 // Set up our filename.
34 35 $file_name = strtolower( str_replace( '_', '-', substr( $class_name, strlen( 'P_' ) ) ) );
35 36 $dir = trailingslashit( dirname( __FILE__ ) ) . 'includes/';
36 - $target = array( $dir . $file_name . '.php', $dir . 'admin/' . str_replace( 'admin-', '', $file_name ) . '.php' );
37 + $target = [ $dir . $file_name . '.php', $dir . 'admin/' . str_replace( 'admin-', '', $file_name ) . '.php' ];
37 38
38 39 // Attempt each target and load if it exists.
39 40 foreach ( $target as $file ) {
40 41 if ( file_exists( $file ) ) {
@@ -57,9 +58,9 @@
57 58 * The plugin version.
58 59 *
59 60 * @var string
60 61 */
61 - const VERSION = '2.1.1';
62 + const VERSION = '2.3.5';
62 63
63 64 /**
64 65 * API URL of Patchstack to communicate with.
65 66 *
@@ -88,15 +89,8 @@
88 89 */
89 90 const PRIVATE_KEY = 'PATCHSTACK_PRIVATE_KEY';
90 91
91 92 /**
92 - * Known IP addresses.
93 - *
94 - * @var array
95 - */
96 - protected $ips = array( '18.221.197.243', '52.15.237.250', '3.19.3.34', '3.18.238.17', '13.58.49.77', '18.222.191.77', '3.131.108.250', '3.23.157.140', '18.220.70.233', '3.140.84.221', '185.212.171.100' );
97 -
98 - /**
99 93 * URL of the plugin directory.
100 94 *
101 95 * @var string
102 96 */
@@ -120,9 +114,9 @@
120 114 * Detailed activation error messages.
121 115 *
122 116 * @var array
123 117 */
124 - protected $activation_errors = array();
118 + protected $activation_errors = [];
125 119
126 120 /**
127 121 * Singleton instance of plugin.
128 122 *
@@ -149,9 +143,8 @@
149 143 protected $hide_login;
150 144 protected $listener;
151 145 protected $event_log;
152 146 protected $multisite;
153 - protected $notice;
154 147 protected $admin_ajax;
155 148 protected $admin_general;
156 149 protected $admin_menu;
157 150 protected $admin_options;
@@ -176,8 +169,13 @@
176 169 $this->basename = plugin_basename( __FILE__ );
177 170 $this->url = plugin_dir_url( __FILE__ );
178 171 $names = explode( '/', $this->basename );
179 172 $this->name = $names[0];
173 +
174 + // Define WP_CLI command.
175 + if ( defined( 'WP_CLI' ) && WP_CLI && method_exists('\WP_CLI', 'add_command')) {
176 + \WP_CLI::add_command( 'patchstack activate', [ $this, 'cli_activate' ] );
177 + }
180 178 }
181 179
182 180 /**
183 181 * Call the constructor of all the Patchstack related classes.
@@ -185,9 +183,9 @@
185 183 * @return void
186 184 */
187 185 public function plugin_classes() {
188 186 // Define the array of the classes.
189 - foreach ( array(
187 + foreach ( [
190 188 'admin_options' => 'P_Admin_Options',
191 189 'cron' => 'P_Cron',
192 190 'api' => 'P_Api',
193 191 'login' => 'P_Login',
@@ -197,20 +195,20 @@
197 195 'hacker_log' => 'P_Hacker_Log',
198 196 'upload' => 'P_Upload',
199 197 'rules' => 'P_Rules',
200 198 'hide_login' => 'P_Hide_Login',
201 - 'listener' => 'P_Listener',
202 199 'event_log' => 'P_Event_Log',
203 200 'activation' => 'P_Activation',
201 + 'listener' => 'P_Listener',
204 202 'multisite' => 'P_Multisite',
205 - 'notice' => 'P_Cookie_Notice',
206 203 'admin_ajax' => 'P_Admin_Ajax',
207 204 'admin_general' => 'P_Admin_General',
208 205 'admin_menu' => 'P_Admin_Menu',
209 - ) as $var => $class ) {
206 + ] as $var => $class ) {
210 207 $this->$var = new $class( $this );
211 208 }
212 209
210 + // Load firewall base functionality.
213 211 $this->firewall_base = new P_Firewall( true, $this, true );
214 212 }
215 213
216 214 /**
@@ -223,8 +221,52 @@
223 221 $this->activation->activate( $this );
224 222 }
225 223
226 224 /**
225 + * Connects the Patchstack plugin to the API with the license id and secret key.
226 + *
227 + * Returns an error if the connection was not successful.
228 + *
229 + * ## OPTIONS
230 + *
231 + * [<id>]
232 + * : The API client id.
233 + *
234 + * [<secret>]
235 + * : The API secret key.
236 + *
237 + * <secret-id>
238 + * : The API client id and secret key merged together, found in the App. E.g. 2b072e8b60402e30d481df351fc08183906254e0-123456
239 + *
240 + * ## EXAMPLES
241 + *
242 + * $ wp patchstack activate 123456 2b072e8b60402e30d481df351fc08183906254e0
243 + * Success: The Patchstack plugin has been successfully connected.
244 + *
245 + * or
246 + *
247 + * $ wp patchstack activate 2b072e8b60402e30d481df351fc08183906254e0-123456
248 + * Success: The Patchstack plugin has been successfully connected.
249 + */
250 + public function cli_activate( $args ) {
251 + // Handle both ways to activate the plugin.
252 + if ( count( $args ) === 1 && strpos( $args[0], '-' ) !== false ) {
253 + list( $secret, $id ) = explode( '-', $args[0] );
254 + } else {
255 + $id = isset( $args[0] ) ? trim( $args[0] ) : '';
256 + $secret = isset( $args[1] ) ? trim( $args[1] ) : '';
257 + }
258 +
259 + $result = $this->activation->alter_license( $id, $secret, 'activate' );
260 + if ( $result['result'] == 'error' ) {
261 + \WP_CLI::error( "The Patchstack plugin could not be connected. Make sure the id and secret key are valid and that api.patchstack.com is not blocked. Additional information:\n" . $result['body'] );
262 + return;
263 + }
264 +
265 + \WP_CLI::success( 'The Patchstack plugin has been successfully connected.' );
266 + }
267 +
268 + /**
227 269 * Deactivate the plugin.
228 270 *
229 271 * @return void
230 272 */
@@ -233,24 +275,23 @@
233 275 $this->activation->deactivate();
234 276 }
235 277
236 278 /**
237 - * Boot Patchstack and its classes.
238 - *
279 + * Load translated strings for the plugin.
280 + *
239 281 * @return void
240 282 */
241 - public function hooks() {
242 - add_action( 'init', array( $this, 'init' ), ~PHP_INT_MAX );
283 + public function load_textdomain () {
284 + load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
243 285 }
244 286
245 287 /**
246 - * Boot Patchstack
288 + * Boot Patchstack.
247 289 *
248 290 * @return void
249 291 */
250 292 public function init() {
251 - // Load translated strings for plugin.
252 - load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
293 + add_action( 'init', [ $this, 'load_textdomain' ] );
253 294
254 295 // Initialize plugin classes.
255 296 $this->plugin_classes();
256 297
@@ -261,9 +302,9 @@
261 302 if ( get_option( 'patchstack_api_token', '' ) == '' && get_option( 'patchstack_license_expiry', '' ) == '' ) {
262 303 $this->api->update_license_status();
263 304 }
264 305
265 - // Determine if the license is activated and not expired.
306 + // Run firewall if not disabled and license activated.
266 307 if ( get_option( 'patchstack_license_activated', 0 ) == 1 && get_option( 'patchstack_basic_firewall', 0 ) == 1 && get_option( 'patchstack_license_free', 0 ) == 0 ) {
267 308 $this->firewall = new P_Firewall( true, $this );
268 309 }
269 310 }
@@ -318,22 +359,35 @@
318 359 * @return void
319 360 */
320 361 function patchstack_uninstall() {
321 362 // Delete most of the Patchstack options.
322 - $options = array( 'patchstack_eventlog_lastid', 'patchstack_api_token', 'patchstack_dashboardlock', 'patchstack_pluginedit', 'patchstack_move_logs', 'patchstack_userenum', 'patchstack_basicscanblock', 'patchstack_hidewpcontent', 'patchstack_hidewpversionk', 'patchstack_prevent_default_file_access', 'patchstack_basic_firewall', 'patchstack_known_blacklist', 'patchstack_block_debug_log_access', 'patchstack_block_fake_bots', 'patchstack_index_views', 'patchstack_proxy_comment_posting', 'patchstack_bad_query_strings', 'patchstack_advanced_character_string_filter', 'patchstack_advanced_blacklist_firewall', 'patchstack_forbid_rfi', 'patchstack_image_hotlinking', 'patchstack_add_security_headers', 'patchstack_firewall_log_lastid', 'patchstack_user_log_lastid', 'patchstack_captcha_public_key', 'patchstack_captcha_private_key', 'patchstack_scan_interval', 'patchstack_scan_day', 'patchstack_scan_time', 'patchstack_hackers_log', 'patchstack_users_log', 'patchstack_visitors_log', 'external_updates-webarx', 'patchstack_wp_stats', 'patchstack_captcha_login_form', 'patchstack_license_activated', 'patchstack_license_expiry', 'patchstack_software_data_hash', 'patchstack_mv_wp_login', 'patchstack_rename_wp_login', 'patchstack_googledrive_backup_is_running', 'patchstack_googledrive_upload_state', 'patchstack_googledrive_access_token', 'patchstack_googledrive_refresh_token', 'patchstack_cron_offset', 'patchstack_htaccess_rules_hash' );
323 - foreach ( $options as $option ) {
324 - delete_option( $option );
363 + global $wpdb;
364 + $options = $wpdb->get_results( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'patchstack_%'" );
325 365
366 + // Few options we want to keep.
367 + $keep = ['patchstack_hits_last_30', 'patchstack_hits_all_time', 'patchstack_clientid', 'patchstack_secretkey', 'patchstack_secretkey_nonce', 'patchstack_api_token'];
368 +
369 + // Delete everything else.
370 + foreach( $options as $option ) {
371 + if ( in_array( $option->option_name, $keep ) || stripos( $option->option_name, 'patchstack_captcha_' ) !== false ) {
372 + continue;
373 + }
374 +
375 + delete_option( $option->option_name );
376 +
326 377 if ( is_multisite() ) {
327 - delete_site_option( $option );
378 + delete_site_option( $option->option_name );
328 379 }
329 380 }
330 381
331 - // Drop all Patchstack tables.
382 + // Drop all tables.
332 383 global $wpdb;
333 - $tables = array( 'patchstack_user_log', 'patchstack_visitor_log', 'patchstack_firewall_log', 'patchstack_file_hashes', 'patchstack_logic', 'patchstack_ip', 'patchstack_event_log' );
334 - foreach ( $tables as $table ) {
335 - $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . $table );
384 + $prefixes = ['patchstack_', 'webarx_'];
385 + foreach ( $prefixes as $prefix ) {
386 + $tables = [ 'user_log', 'visitor_log', 'firewall_log', 'file_hashes', 'logic', 'ip', 'event_log' ];
387 + foreach ( $tables as $table ) {
388 + $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . $prefix . $table );
389 + }
336 390 }
337 391 }
338 392 }
339 393
@@ -348,10 +402,10 @@
348 402 }
349 403 }
350 404
351 405 // Kick it off.
352 -add_action( 'plugins_loaded', array( patchstack(), 'hooks' ) );
406 +add_action( 'plugins_loaded', [ patchstack(), 'init' ] );
353 407
354 408 // Activation and deactivation hooks.
355 -register_activation_hook( __FILE__, array( patchstack(), 'activate' ) );
356 -register_deactivation_hook( __FILE__, array( patchstack(), 'deactivate' ) );
409 +register_activation_hook( __FILE__, [ patchstack(), 'activate' ] );
410 +register_deactivation_hook( __FILE__, [ patchstack(), 'deactivate' ] );
357 411 register_uninstall_hook( __FILE__, 'patchstack_uninstall' );