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 +133 -39 2.1.112.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.11
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.11';
62 + const VERSION = '2.3.7';
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 *
@@ -145,12 +139,12 @@
145 139 protected $htaccess;
146 140 protected $hacker_log;
147 141 protected $upload;
148 142 protected $rules;
143 + protected $hide_login;
149 144 protected $listener;
150 145 protected $event_log;
151 146 protected $multisite;
152 - protected $notice;
153 147 protected $admin_ajax;
154 148 protected $admin_general;
155 149 protected $admin_menu;
156 150 protected $admin_options;
@@ -175,8 +169,15 @@
175 169 $this->basename = plugin_basename( __FILE__ );
176 170 $this->url = plugin_dir_url( __FILE__ );
177 171 $names = explode( '/', $this->basename );
178 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 + }
179 180 }
180 181
181 182 /**
182 183 * Call the constructor of all the Patchstack related classes.
@@ -184,9 +185,9 @@
184 185 * @return void
185 186 */
186 187 public function plugin_classes() {
187 188 // Define the array of the classes.
188 - foreach ( array(
189 + foreach ( [
189 190 'admin_options' => 'P_Admin_Options',
190 191 'cron' => 'P_Cron',
191 192 'api' => 'P_Api',
192 193 'login' => 'P_Login',
@@ -195,20 +196,21 @@
195 196 'htaccess' => 'P_Htaccess',
196 197 'hacker_log' => 'P_Hacker_Log',
197 198 'upload' => 'P_Upload',
198 199 'rules' => 'P_Rules',
199 - 'listener' => 'P_Listener',
200 + 'hide_login' => 'P_Hide_Login',
200 201 'event_log' => 'P_Event_Log',
201 202 'activation' => 'P_Activation',
203 + 'listener' => 'P_Listener',
202 204 'multisite' => 'P_Multisite',
203 - 'notice' => 'P_Cookie_Notice',
204 205 'admin_ajax' => 'P_Admin_Ajax',
205 206 'admin_general' => 'P_Admin_General',
206 207 'admin_menu' => 'P_Admin_Menu',
207 - ) as $var => $class ) {
208 + ] as $var => $class ) {
208 209 $this->$var = new $class( $this );
209 210 }
210 211
212 + // Load firewall base functionality.
211 213 $this->firewall_base = new P_Firewall( true, $this, true );
212 214 }
213 215
214 216 /**
@@ -221,8 +223,86 @@
221 223 $this->activation->activate( $this );
222 224 }
223 225
224 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 + /**
225 305 * Deactivate the plugin.
226 306 *
227 307 * @return void
228 308 */
@@ -231,24 +311,23 @@
231 311 $this->activation->deactivate();
232 312 }
233 313
234 314 /**
235 - * Boot Patchstack and its classes.
315 + * Load translated strings for the plugin.
236 316 *
237 317 * @return void
238 318 */
239 - public function hooks() {
240 - add_action( 'init', array( $this, 'init' ), ~PHP_INT_MAX );
319 + public function load_textdomain () {
320 + load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
241 321 }
242 322
243 323 /**
244 - * Boot Patchstack
324 + * Boot Patchstack.
245 325 *
246 326 * @return void
247 327 */
248 328 public function init() {
249 - // Load translated strings for plugin.
250 - load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
329 + add_action( 'init', [ $this, 'load_textdomain' ] );
251 330
252 331 // Initialize plugin classes.
253 332 $this->plugin_classes();
254 333
@@ -259,9 +338,9 @@
259 338 if ( get_option( 'patchstack_api_token', '' ) == '' && get_option( 'patchstack_license_expiry', '' ) == '' ) {
260 339 $this->api->update_license_status();
261 340 }
262 341
263 - // Determine if the license is activated and not expired.
342 + // Run firewall if not disabled and license activated.
264 343 if ( get_option( 'patchstack_license_activated', 0 ) == 1 && get_option( 'patchstack_basic_firewall', 0 ) == 1 && get_option( 'patchstack_license_free', 0 ) == 0 ) {
265 344 $this->firewall = new P_Firewall( true, $this );
266 345 }
267 346 }
@@ -316,22 +395,35 @@
316 395 * @return void
317 396 */
318 397 function patchstack_uninstall() {
319 398 // Delete most of the Patchstack options.
320 - $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' );
321 - foreach ( $options as $option ) {
322 - delete_option( $option );
399 + global $wpdb;
400 + $options = $wpdb->get_results( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'patchstack_%'" );
323 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 +
324 413 if ( is_multisite() ) {
325 - delete_site_option( $option );
414 + delete_site_option( $option->option_name );
326 415 }
327 416 }
328 417
329 - // Drop all Patchstack tables.
418 + // Drop all tables.
330 419 global $wpdb;
331 - $tables = array( 'patchstack_user_log', 'patchstack_visitor_log', 'patchstack_firewall_log', 'patchstack_file_hashes', 'patchstack_logic', 'patchstack_ip', 'patchstack_event_log' );
332 - foreach ( $tables as $table ) {
333 - $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 + }
334 426 }
335 427 }
336 428 }
337 429
@@ -345,11 +437,13 @@
345 437 return patchstack::get_instance();
346 438 }
347 439 }
348 440
349 -// Kick it off.
350 -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' ] );
351 444
352 -// Activation and deactivation hooks.
353 -register_activation_hook( __FILE__, array( patchstack(), 'activate' ) );
354 -register_deactivation_hook( __FILE__, array( patchstack(), 'deactivate' ) );
355 -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 +}