PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.11
Patchstack – WordPress & Plugins Security v2.2.11
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/upload.php +159 -73 2.1.222.2.11 View file →
@@ -20,23 +20,23 @@
20 20 public function __construct( $core ) {
21 21 parent::__construct( $core );
22 22
23 23 // In case the software has never been synchronized, force it.
24 - if ( ! get_option( 'patchstack_software_data_hash', false ) ) {
24 + if ( ! get_option( 'patchstack_software_data_hash', false ) && ! get_option( 'patchstack_software_upload_attempted', false ) ) {
25 25 $this->upload_software();
26 26 }
27 27
28 28 // Register the actions.
29 - add_action( 'patchstack_send_software_data', array( $this, 'upload_software' ) );
30 - add_action( 'patchstack_send_hacker_logs', array( $this, 'upload_firewall_logs' ) );
31 - add_action( 'patchstack_send_event_logs', array( $this, 'upload_activity_logs' ) );
29 + add_action( 'patchstack_send_software_data', [ $this, 'upload_software' ] );
30 + add_action( 'patchstack_send_hacker_logs', [ $this, 'upload_firewall_logs' ] );
31 + add_action( 'patchstack_send_event_logs', [ $this, 'upload_activity_logs' ] );
32 32
33 33 // In case a plugin or upgrade has been performed, re-synchronize with the app.
34 - add_action( 'activated_plugin', array( $this, 'upload_software' ) );
35 - add_action( 'deactivated_plugin', array( $this, 'upload_software' ) );
36 - add_action( 'deleted_plugin', array( $this, 'upload_software' ) );
37 - add_action( 'upgrader_process_complete', array( $this, 'upload_software' ) );
38 - add_action( '_core_updated_successfully', array( &$this, 'upload_software' ) );
34 + add_action( 'activated_plugin', [ $this, 'upload_software' ] );
35 + add_action( 'deactivated_plugin', [ $this, 'upload_software' ] );
36 + add_action( 'deleted_plugin', [ $this, 'upload_software' ] );
37 + add_action( 'upgrader_process_complete', [ $this, 'upload_software' ] );
38 + add_action( '_core_updated_successfully', [ &$this, 'upload_software' ] );
39 39 }
40 40
41 41 /**
42 42 * Synchronize the software data with our API.
@@ -47,14 +47,19 @@
47 47 public function upload_software() {
48 48 // Get the software data and hash.
49 49 $data = $this->get_software_data();
50 50 $hash = sha1( json_encode( $data ) );
51 - if ( ! defined( 'DOING_CRON' ) && ! isset( $_POST['webarx_secret'] ) && get_option( 'patchstack_software_data_hash', false ) === $hash && ! is_admin() ) {
51 +
52 + // Do not sync for no reason.
53 + if ( ! defined( 'DOING_CRON' ) && ! isset( $_POST['webarx_secret'] ) && get_option( 'patchstack_software_data_hash', false ) === $hash && ! is_admin() && ! defined( 'WP_CLI ' ) ) {
52 54 return;
53 55 }
54 56
57 + // Make sure to not keep calling this function.
58 + update_option( 'patchstack_software_upload_attempted', true );
59 +
55 60 // Synchronize the software list with the API.
56 - $results = $this->plugin->api->upload_software( array( 'software' => json_encode( $data ) ) );
61 + $results = $this->plugin->api->upload_software( [ 'software' => json_encode( $data ) ] );
57 62 if ( isset( $results['success'] ) ) {
58 63 update_option( 'patchstack_software_data_hash', $hash );
59 64
60 65 // The result will also contain a list of all vulnerable plugins on the site that is returned by the API.
@@ -59,13 +64,28 @@
59 64
60 65 // The result will also contain a list of all vulnerable plugins on the site that is returned by the API.
61 66 // If the auto update setting is enabled for vulnerable plugins, perform the update once the 15 minute
62 67 // scheduled task "patchstack_update_plugins" is executed.
63 - $update = get_site_option( 'patchstack_auto_update', array() );
68 + $update = get_site_option( 'patchstack_auto_update', [] );
64 69 if ( isset( $results['vulnerable'] ) && is_array( $update ) && in_array( 'vulnerable', $update ) ) {
65 70 update_site_option( 'patchstack_vulnerable_plugins', $results['vulnerable'] );
66 71 }
67 72
73 + // If we have vulnerable plugins, determine if we had them before and if not, pull latest firewall rules.
74 + if ( isset( $results['vulnerable'] ) && count( $results['vulnerable'] ) > 0 ) {
75 + $prev = get_site_option( 'patchstack_latest_vulnerable', [] );
76 + foreach ( $results['vulnerable'] as $vuln ) {
77 + if ( ! in_array ( $vuln, $prev ) ) {
78 + do_action( 'patchstack_post_dynamic_firewall_rules' );
79 + break;
80 + }
81 + }
82 +
83 + update_site_option( 'patchstack_latest_vulnerable', $results['vulnerable'] );
84 + } else {
85 + update_site_option( 'patchstack_latest_vulnerable', [] );
86 + }
87 +
68 88 return $results;
69 89 }
70 90
71 91 return;
@@ -77,55 +97,82 @@
77 97 * @return void
78 98 */
79 99 public function upload_firewall_logs() {
80 100 global $wpdb;
81 - $lastid = get_option( 'patchstack_firewall_log_lastid', 0 );
82 - $items = $wpdb->get_results( $wpdb->prepare( 'SELECT ip, log_date, request_uri, user_agent, fid, method, post_data FROM ' . $wpdb->prefix . 'patchstack_firewall_log WHERE id > %d ORDER BY id', $lastid ) );
83 101
84 - // No need to synchronize if there are no new logs present.
85 - if ( $wpdb->num_rows == 0 ) {
102 + // Do not process if we are already processing a previous batch.
103 + if ( get_option( 'patchstack_firewall_log_processing', false ) ) {
86 104 return;
87 105 }
88 106
89 - // Construct the array to be uploaded to our API.
90 - $logs = array();
91 - foreach ( $items as $item ) {
107 + update_option( 'patchstack_firewall_log_processing', true );
92 108
93 - // Entries that we don't want to store on the API side.
94 - if ( stripos( $item->request_uri, 'wp-comments-post' ) !== false ) {
95 - continue;
109 + // Attempt to fetch data, if any.
110 + $lastId = get_option( 'patchstack_firewall_log_lastid', 0 );
111 + $successId = $lastId;
112 +
113 + // Do a maximum of 500 log entries per cronjob.
114 + for ($i = 0; $i <= 4; $i++) {
115 + // Pull the data from the database, in batches of 100.
116 + $items = $wpdb->get_results(
117 + $wpdb->prepare(
118 + 'SELECT id, ip, log_date, request_uri, user_agent, fid, method, post_data FROM ' . $wpdb->prefix . 'patchstack_firewall_log WHERE id > %d ORDER BY id LIMIT 0,100',
119 + $lastId
120 + )
121 + );
122 +
123 + // No need to continue if we have no data.
124 + if ( $wpdb->num_rows == 0 ) {
125 + update_option( 'patchstack_firewall_log_lastid', 0 );
126 + break;
96 127 }
128 +
129 + // Construct the array to be uploaded to our API.
130 + $logs = [];
131 + foreach ( $items as $item ) {
132 +
133 + // Entries that we don't want to store on the API side.
134 + if ( stripos( $item->request_uri, 'wp-comments-post' ) !== false ) {
135 + continue;
136 + }
137 +
138 + // Push to entries to be uploaded.
139 + $logs[] = [
140 + 'ip' => $item->ip,
141 + 'fid' => $item->fid,
142 + 'request_uri' => $item->request_uri,
143 + 'user_agent' => $item->user_agent,
144 + 'method' => $item->method,
145 + 'log_date' => $item->log_date,
146 + 'post_data' => $item->post_data,
147 + ];
97 148
98 - // Push to entries to be uploaded.
99 - $logs[] = array(
100 - 'ip' => $item->ip,
101 - 'fid' => $item->fid,
102 - 'request_uri' => $item->request_uri,
103 - 'user_agent' => $item->user_agent,
104 - 'method' => $item->method,
105 - 'log_date' => $item->log_date,
106 - 'post_data' => $item->post_data,
149 + $lastId = $item->id;
150 + }
151 +
152 + // JSON encode the logs and upload.
153 + $logs = json_encode( $logs );
154 + $results = $this->plugin->api->upload_firewall_logs(
155 + [
156 + 'logs' => $logs,
157 + 'type' => 'firewall',
158 + ]
107 159 );
108 - }
109 160
110 - // JSON encode the logs and upload.
111 - $logs = json_encode( $logs );
112 - $results = $this->plugin->api->upload_firewall_logs(
113 - array(
114 - 'logs' => $logs,
115 - 'type' => 'firewall',
116 - )
117 - );
118 - if ( isset( $results['errors'] ) ) {
119 - return;
161 + if ( isset( $results['errors'] ) ) {
162 + update_option( 'patchstack_firewall_log_lastid', $successId );
163 + break;
164 + }
165 +
166 + $successId = $lastId;
167 + update_option( 'patchstack_firewall_log_lastid', $successId );
120 168 }
121 169
122 - // Get the most recent id of the logs.
123 - $lastid = $wpdb->get_var( 'SELECT id FROM ' . $wpdb->prefix . 'patchstack_firewall_log ORDER BY id DESC LIMIT 0, 1' );
124 - update_option( 'patchstack_firewall_log_lastid', $lastid );
170 + // Delete the logs.
171 + $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_firewall_log WHERE id <= ' . (int) $successId );
125 172
126 - // Delete logs that are older than 2 weeks.
127 - $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_firewall_log WHERE log_date < DATE_SUB(NOW(), INTERVAL 14 DAY)' );
173 + // No longer processing.
174 + update_option( 'patchstack_firewall_log_processing', false );
128 175 }
129 176
130 177 /**
131 178 * Synchronize the activity logs with our API.
@@ -134,8 +181,15 @@
134 181 */
135 182 public function upload_activity_logs() {
136 183 global $wpdb;
137 184
185 + // Do not process if we are already processing a previous batch.
186 + if ( get_option( 'patchstack_eventlog_processing', false ) ) {
187 + return;
188 + }
189 +
190 + update_option( 'patchstack_eventlog_processing', true );
191 +
138 192 // Determine if we should upload failed logins to the app.
139 193 $where = " AND action != 'failed login' ";
140 194 if ( $this->get_option( 'patchstack_activity_log_failed_logins_db', 0 ) == 1 ) {
141 195 $where = ' ';
@@ -140,28 +194,49 @@
140 194 if ( $this->get_option( 'patchstack_activity_log_failed_logins_db', 0 ) == 1 ) {
141 195 $where = ' ';
142 196 }
143 197
144 - // Do we have data to upload?
145 - $lastid = get_option( 'patchstack_eventlog_lastid', 0 );
146 - $items = $wpdb->get_results( $wpdb->prepare( 'SELECT author, ip, object, object_id, object_name, action, date FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id > %d' . $where . 'ORDER BY id', array( $lastid ) ) );
147 - if ( $wpdb->num_rows == 0 ) {
148 - return;
149 - }
198 + // Attempt to fetch data, if any.
199 + $lastId = get_option( 'patchstack_eventlog_lastid', 0 );
200 + $successId = $lastId;
150 201
151 - // Send to the API.
152 - $logs = json_encode( $items );
153 - $results = $this->plugin->api->upload_activity_logs( array( 'logs' => $logs ) );
154 - if ( isset( $results['errors'] ) ) {
155 - return;
202 + // Do a maximum of several hundred log entries per cronjob.
203 + for ($i = 0; $i <= 4; $i++) {
204 + // Pull the data from the database, in batches of 100.
205 + $items = $wpdb->get_results(
206 + $wpdb->prepare(
207 + 'SELECT id, author, ip, object, object_id, object_name, action, date FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id > %d' . $where . 'ORDER BY id LIMIT 0,100',
208 + $lastId
209 + ),
210 + ARRAY_A
211 + );
212 +
213 + // No need to continue if we have no data.
214 + if ( $wpdb->num_rows == 0 ) {
215 + update_option( 'patchstack_eventlog_lastid', 0 );
216 + break;
217 + }
218 +
219 + // Get the last ID in the result set.
220 + $lastId = $items[count($items) - 1]['id'];
221 +
222 + // Send to the API.
223 + $logs = json_encode( $items );
224 + $results = $this->plugin->api->upload_activity_logs( [ 'logs' => $logs ] );
225 + if ( isset( $results['errors'] ) ) {
226 + update_option( 'patchstack_eventlog_lastid', $successId );
227 + break;
228 + }
229 +
230 + $successId = $lastId;
231 + update_option( 'patchstack_eventlog_lastid', $successId );
156 232 }
157 233
158 - // Get the most recent id of the logs.
159 - $lastid = $wpdb->get_var( 'SELECT id FROM ' . $wpdb->prefix . 'patchstack_event_log ORDER BY id DESC LIMIT 0, 1' );
160 - update_option( 'patchstack_eventlog_lastid', $lastid );
234 + // Delete the logs.
235 + $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id <= ' . (int) $successId );
161 236
162 - // Delete logs that are older than 2 weeks.
163 - $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE date < DATE_SUB(NOW(), INTERVAL 14 DAY)' );
237 + // No longer processing.
238 + update_option( 'patchstack_eventlog_processing', false );
164 239 }
165 240
166 241 /**
167 242 * Obtain information about the software that the user has installed.
@@ -187,9 +262,9 @@
187 262 // Fetch list of plugins.
188 263 $all_plugin = get_plugins();
189 264 $installed_plugins = array_keys( $all_plugin );
190 265 $updatable_plugins = get_plugin_updates();
191 - $software_list = array();
266 + $software_list = [];
192 267
193 268 foreach ( $installed_plugins as $plugin ) {
194 269 if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin ) ) {
195 270 continue;
@@ -208,9 +283,9 @@
208 283 } else {
209 284 $active = (int) is_plugin_active( $plugin );
210 285 }
211 286
212 - $software_list[] = array(
287 + $software_list[] = [
213 288 'sw_type' => 'plugin',
214 289 'sw_name' => $plugin_name,
215 290 'sw_cur_ver' => $plugin_version,
216 291 'sw_new_ver' => $new_version,
@@ -215,9 +290,9 @@
215 290 'sw_cur_ver' => $plugin_version,
216 291 'sw_new_ver' => $new_version,
217 292 'sw_key' => $plugin,
218 293 'sw_active' => $active
219 - );
294 + ];
220 295 }
221 296 }
222 297
223 298 // Fetch list of themes.
@@ -232,15 +307,15 @@
232 307 $theme_name = $themes_data->get( 'Name' );
233 308 $theme_version = $themes_data->get( 'Version' );
234 309
235 310 if ( ! empty( $theme_name ) && ! empty( $theme_version ) ) {
236 - $software_list[] = array(
311 + $software_list[] = [
237 312 'sw_type' => 'theme',
238 313 'sw_name' => $theme_name,
239 314 'sw_cur_ver' => $theme_version,
240 315 'sw_new_ver' => $theme_new_version,
241 316 'sw_key' => $theme_key,
242 - );
317 + ];
243 318 }
244 319 }
245 320
246 321 // Fetch WordPress version.
@@ -246,22 +321,33 @@
246 321 // Fetch WordPress version.
247 322 global $wp_version;
248 323 $core_updates = get_core_updates();
249 324 $new_wp_version = ( ! empty( $core_updates ) && $core_updates[0]->response == 'upgrade' ) ? $core_updates[0]->version : '';
250 - $software_list[] = array(
325 + $software_list[] = [
251 326 'sw_type' => 'wordpress',
252 327 'sw_name' => 'WordPress',
253 328 'sw_cur_ver' => $wp_version,
254 329 'sw_new_ver' => $new_wp_version,
255 - );
330 + ];
256 331
257 332 // Fetch PHP version.
258 - $software_list[] = array(
333 + $software_list[] = [
259 334 'sw_type' => 'php',
260 335 'sw_name' => 'PHP',
261 - 'sw_cur_ver' => substr( phpversion(), 0, 5 ),
336 + 'sw_cur_ver' => phpversion(),
262 337 'sw_new_ver' => '',
263 - );
338 + ];
339 +
340 + // Fetch database server version.
341 + global $wpdb;
342 + if ( ! is_null( $wpdb ) ) {
343 + $software_list[] = [
344 + 'sw_type' => 'database',
345 + 'sw_name' => 'Database',
346 + 'sw_cur_ver' => $wpdb->get_var( 'SELECT VERSION()' ),
347 + 'sw_new_ver' => ''
348 + ];
349 + }
264 350
265 351 return $software_list;
266 352 }
267 353 }