PluginProbe
Patchstack – WordPress & Plugins Security / 2.3.7
Patchstack – WordPress & Plugins Security v2.3.7
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 +131 -32 2.1.192.3.7 View file →
@@ -1,15 +1,16 @@
1 1 <?php
2 2 /**
3 3 * Plugin Name: Patchstack Security
4 - * Plugin URI: https://patchstack.com
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.19
7 + * Version: 2.3.7
7 8 * Author: Patchstack
8 9 * License: GPLv3
9 10 * Text Domain: patchstack
10 11 * Domain Path: /languages
11 - * Requires at least: 4.4
12 + * Requires at least: 5.2
12 13 * Requires PHP: 5.6
13 14 */
14 15
15 16 // Do not allow the file to be called directly.
@@ -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.19';
62 + const VERSION = '2.3.7';
62 63
63 64 /**
64 65 * API URL of Patchstack to communicate with.
65 66 *
@@ -113,9 +114,9 @@
113 114 * Detailed activation error messages.
114 115 *
115 116 * @var array
116 117 */
117 - protected $activation_errors = array();
118 + protected $activation_errors = [];
118 119
119 120 /**
120 121 * Singleton instance of plugin.
121 122 *
@@ -142,9 +143,8 @@
142 143 protected $hide_login;
143 144 protected $listener;
144 145 protected $event_log;
145 146 protected $multisite;
146 - protected $notice;
147 147 protected $admin_ajax;
148 148 protected $admin_general;
149 149 protected $admin_menu;
150 150 protected $admin_options;
@@ -169,8 +169,15 @@
169 169 $this->basename = plugin_basename( __FILE__ );
170 170 $this->url = plugin_dir_url( __FILE__ );
171 171 $names = explode( '/', $this->basename );
172 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 + \WP_CLI::add_command( 'patchstack deactivate', [ $this, 'cli_deactivate' ] );
178 + \WP_CLI::add_command( 'patchstack status', [ $this, 'cli_status' ] );
179 + }
173 180 }
174 181
175 182 /**
176 183 * Call the constructor of all the Patchstack related classes.
@@ -178,9 +185,9 @@
178 185 * @return void
179 186 */
180 187 public function plugin_classes() {
181 188 // Define the array of the classes.
182 - foreach ( array(
189 + foreach ( [
183 190 'admin_options' => 'P_Admin_Options',
184 191 'cron' => 'P_Cron',
185 192 'api' => 'P_Api',
186 193 'login' => 'P_Login',
@@ -190,20 +197,20 @@
190 197 'hacker_log' => 'P_Hacker_Log',
191 198 'upload' => 'P_Upload',
192 199 'rules' => 'P_Rules',
193 200 'hide_login' => 'P_Hide_Login',
194 - 'listener' => 'P_Listener',
195 201 'event_log' => 'P_Event_Log',
196 202 'activation' => 'P_Activation',
203 + 'listener' => 'P_Listener',
197 204 'multisite' => 'P_Multisite',
198 - 'notice' => 'P_Cookie_Notice',
199 205 'admin_ajax' => 'P_Admin_Ajax',
200 206 'admin_general' => 'P_Admin_General',
201 207 'admin_menu' => 'P_Admin_Menu',
202 - ) as $var => $class ) {
208 + ] as $var => $class ) {
203 209 $this->$var = new $class( $this );
204 210 }
205 211
212 + // Load firewall base functionality.
206 213 $this->firewall_base = new P_Firewall( true, $this, true );
207 214 }
208 215
209 216 /**
@@ -216,8 +223,86 @@
216 223 $this->activation->activate( $this );
217 224 }
218 225
219 226 /**
227 + * Connects the Patchstack plugin to the API with the license id and secret key.
228 + *
229 + * Returns an error if the connection was not successful.
230 + *
231 + * ## OPTIONS
232 + *
233 + * [<id>]
234 + * : The API client id.
235 + *
236 + * [<secret>]
237 + * : The API secret key.
238 + *
239 + * <secret-id>
240 + * : The API client id and secret key merged together, found in the App. E.g. 2b072e8b60402e30d481df351fc08183906254e0-123456
241 + *
242 + * ## EXAMPLES
243 + *
244 + * $ wp patchstack activate 123456 2b072e8b60402e30d481df351fc08183906254e0
245 + * Success: The Patchstack plugin has been successfully connected.
246 + *
247 + * or
248 + *
249 + * $ wp patchstack activate 2b072e8b60402e30d481df351fc08183906254e0-123456
250 + * Success: The Patchstack plugin has been successfully connected.
251 + */
252 + public function cli_activate( $args ) {
253 + // Handle both ways to activate the plugin.
254 + if ( count( $args ) === 1 && strpos( $args[0], '-' ) !== false ) {
255 + list( $secret, $id ) = explode( '-', $args[0] );
256 + } else {
257 + $id = isset( $args[0] ) ? trim( $args[0] ) : '';
258 + $secret = isset( $args[1] ) ? trim( $args[1] ) : '';
259 + }
260 +
261 + $result = $this->activation->alter_license( $id, $secret, 'activate' );
262 + if ( $result['result'] == 'error' ) {
263 + \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'] );
264 + return;
265 + }
266 +
267 + \WP_CLI::success( 'The Patchstack plugin has been successfully connected.' );
268 + }
269 +
270 + /**
271 + * Disconnects the Patchstack plugin from the API and removes the API key.
272 + *
273 + * ## EXAMPLES
274 + *
275 + * $ wp patchstack deactivate
276 + * Success: The Patchstack plugin has been successfully disconnected.
277 + */
278 + public function cli_deactivate() {
279 + $this->activation->deactivate();
280 + $this->activation->alter_license( '', '', 'deactivate' );
281 +
282 + \WP_CLI::success( 'The Patchstack plugin has been successfully disconnected.' );
283 + }
284 +
285 + /**
286 + * Gets the current API connection status from the Patchstack plugin.
287 + *
288 + * ## EXAMPLES
289 + *
290 + * $ wp patchstack status
291 + * Success: The Patchstack plugin is currently connected to the API.
292 + *
293 + * $ wp patchstack status
294 + * Warning: The Patchstack plugin is not connected to the API.
295 + */
296 + public function cli_status() {
297 + if ( $this->api->is_connected() ) {
298 + \WP_CLI::success( __( 'The Patchstack plugin is currently connected to the API.', 'patchstack' ) );
299 + } else {
300 + \WP_CLI::warning( __( 'The Patchstack plugin is not connected to the API.', 'patchstack' ) );
301 + }
302 + }
303 +
304 + /**
220 305 * Deactivate the plugin.
221 306 *
222 307 * @return void
223 308 */
@@ -226,24 +311,23 @@
226 311 $this->activation->deactivate();
227 312 }
228 313
229 314 /**
230 - * Boot Patchstack and its classes.
315 + * Load translated strings for the plugin.
231 316 *
232 317 * @return void
233 318 */
234 - public function hooks() {
235 - add_action( 'init', array( $this, 'init' ), ~PHP_INT_MAX );
319 + public function load_textdomain () {
320 + load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
236 321 }
237 322
238 323 /**
239 - * Boot Patchstack
324 + * Boot Patchstack.
240 325 *
241 326 * @return void
242 327 */
243 328 public function init() {
244 - // Load translated strings for plugin.
245 - load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
329 + add_action( 'init', [ $this, 'load_textdomain' ] );
246 330
247 331 // Initialize plugin classes.
248 332 $this->plugin_classes();
249 333
@@ -254,9 +338,9 @@
254 338 if ( get_option( 'patchstack_api_token', '' ) == '' && get_option( 'patchstack_license_expiry', '' ) == '' ) {
255 339 $this->api->update_license_status();
256 340 }
257 341
258 - // Determine if the license is activated and not expired.
342 + // Run firewall if not disabled and license activated.
259 343 if ( get_option( 'patchstack_license_activated', 0 ) == 1 && get_option( 'patchstack_basic_firewall', 0 ) == 1 && get_option( 'patchstack_license_free', 0 ) == 0 ) {
260 344 $this->firewall = new P_Firewall( true, $this );
261 345 }
262 346 }
@@ -311,22 +395,35 @@
311 395 * @return void
312 396 */
313 397 function patchstack_uninstall() {
314 398 // Delete most of the Patchstack options.
315 - $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' );
316 - foreach ( $options as $option ) {
317 - delete_option( $option );
399 + global $wpdb;
400 + $options = $wpdb->get_results( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'patchstack_%'" );
318 401
402 + // Few options we want to keep.
403 + $keep = ['patchstack_hits_last_30', 'patchstack_hits_all_time', 'patchstack_clientid', 'patchstack_secretkey', 'patchstack_secretkey_nonce', 'patchstack_api_token'];
404 +
405 + // Delete everything else.
406 + foreach( $options as $option ) {
407 + if ( in_array( $option->option_name, $keep ) || stripos( $option->option_name, 'patchstack_captcha_' ) !== false ) {
408 + continue;
409 + }
410 +
411 + delete_option( $option->option_name );
412 +
319 413 if ( is_multisite() ) {
320 - delete_site_option( $option );
414 + delete_site_option( $option->option_name );
321 415 }
322 416 }
323 417
324 - // Drop all Patchstack tables.
418 + // Drop all tables.
325 419 global $wpdb;
326 - $tables = array( 'patchstack_user_log', 'patchstack_visitor_log', 'patchstack_firewall_log', 'patchstack_file_hashes', 'patchstack_logic', 'patchstack_ip', 'patchstack_event_log' );
327 - foreach ( $tables as $table ) {
328 - $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . $table );
420 + $prefixes = ['patchstack_', 'webarx_'];
421 + foreach ( $prefixes as $prefix ) {
422 + $tables = [ 'user_log', 'visitor_log', 'firewall_log', 'file_hashes', 'logic', 'ip', 'event_log' ];
423 + foreach ( $tables as $table ) {
424 + $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . $prefix . $table );
425 + }
329 426 }
330 427 }
331 428 }
332 429
@@ -340,11 +437,13 @@
340 437 return patchstack::get_instance();
341 438 }
342 439 }
343 440
344 -// Kick it off.
345 -add_action( 'plugins_loaded', array( patchstack(), 'hooks' ) );
441 +if ( ! has_action( 'plugins_loaded', [ patchstack(), 'init' ] ) ) {
442 + // Kick it off.
443 + add_action( 'plugins_loaded', [ patchstack(), 'init' ] );
346 444
347 -// Activation and deactivation hooks.
348 -register_activation_hook( __FILE__, array( patchstack(), 'activate' ) );
349 -register_deactivation_hook( __FILE__, array( patchstack(), 'deactivate' ) );
350 -register_uninstall_hook( __FILE__, 'patchstack_uninstall' );
445 + // Activation and deactivation hooks.
446 + register_activation_hook( __FILE__, [ patchstack(), 'activate' ] );
447 + register_deactivation_hook( __FILE__, [ patchstack(), 'deactivate' ] );
448 + register_uninstall_hook( __FILE__, 'patchstack_uninstall' );
449 +}