PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.10
Patchstack – WordPress & Plugins Security v2.2.10
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 +20 -156 trunk2.2.10 View file →
@@ -28,9 +28,8 @@
28 28 // Register the actions.
29 29 add_action( 'patchstack_send_software_data', [ $this, 'upload_software' ] );
30 30 add_action( 'patchstack_send_hacker_logs', [ $this, 'upload_firewall_logs' ] );
31 31 add_action( 'patchstack_send_event_logs', [ $this, 'upload_activity_logs' ] );
32 - add_action( 'patchstack_import_ap_logs', [ $this, 'import_ap_logs' ] );
33 32
34 33 // In case a plugin or upgrade has been performed, re-synchronize with the app.
35 34 add_action( 'activated_plugin', [ $this, 'upload_software' ] );
36 35 add_action( 'deactivated_plugin', [ $this, 'upload_software' ] );
@@ -50,14 +49,14 @@
50 49 $data = $this->get_software_data();
51 50 $hash = sha1( json_encode( $data ) );
52 51
53 52 // Do not sync for no reason.
54 - if ( ! defined( 'DOING_CRON' ) && ! isset( $_POST['patchstack_secret'] ) && get_option( 'patchstack_software_data_hash', false ) === $hash && ! is_admin() && ! defined( 'WP_CLI' ) ) {
53 + if ( ! defined( 'DOING_CRON' ) && ! isset( $_POST['webarx_secret'] ) && get_option( 'patchstack_software_data_hash', false ) === $hash && ! is_admin() && ! defined( 'WP_CLI ' ) ) {
55 54 return;
56 55 }
57 56
58 57 // Make sure to not keep calling this function.
59 - update_option( 'patchstack_software_upload_attempted', 1 );
58 + update_option( 'patchstack_software_upload_attempted', true );
60 59
61 60 // Synchronize the software list with the API.
62 61 $results = $this->plugin->api->upload_software( [ 'software' => json_encode( $data ) ] );
63 62 if ( isset( $results['success'] ) ) {
@@ -74,9 +73,9 @@
74 73 // If we have vulnerable plugins, determine if we had them before and if not, pull latest firewall rules.
75 74 if ( isset( $results['vulnerable'] ) && count( $results['vulnerable'] ) > 0 ) {
76 75 $prev = get_site_option( 'patchstack_latest_vulnerable', [] );
77 76 foreach ( $results['vulnerable'] as $vuln ) {
78 - if ( is_array( $prev ) && ! in_array ( $vuln, $prev ) ) {
77 + if ( ! in_array ( $vuln, $prev ) ) {
79 78 do_action( 'patchstack_post_dynamic_firewall_rules' );
80 79 break;
81 80 }
82 81 }
@@ -85,14 +84,8 @@
85 84 } else {
86 85 update_site_option( 'patchstack_latest_vulnerable', [] );
87 86 }
88 87
89 - // If we received the number of vulnerable count.
90 - if ( isset ( $results['vulnerability_count'], $results['vulnerability_fix_count'] ) ) {
91 - update_option( 'patchstack_vulns_present', $results['vulnerability_count'] );
92 - update_option( 'patchstack_fixes_present', $results['vulnerability_fix_count'] );
93 - }
94 -
95 88 return $results;
96 89 }
97 90
98 91 return;
@@ -105,26 +98,12 @@
105 98 */
106 99 public function upload_firewall_logs() {
107 100 global $wpdb;
108 101
109 - // Do not execute upload action on free sites.
110 - if ( ! $this->license_is_active() || $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
111 - return;
112 - }
113 -
114 - // Do not process if we are already processing a previous batch.
115 - if ( get_option( 'patchstack_firewall_log_processing', false ) ) {
116 - return;
117 - }
118 -
119 - update_option( 'patchstack_firewall_log_processing', 1 );
120 -
121 102 // Attempt to fetch data, if any.
122 103 $lastId = get_option( 'patchstack_firewall_log_lastid', 0 );
123 104 $successId = $lastId;
124 -
125 - // Do a maximum of 100 log entries per cronjob.
126 - for ($i = 0; $i <= 1; $i++) {
105 + while ( true ) {
127 106 // Pull the data from the database, in batches of 100.
128 107 $items = $wpdb->get_results(
129 108 $wpdb->prepare(
130 109 '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',
@@ -133,9 +112,8 @@
133 112 );
134 113
135 114 // No need to continue if we have no data.
136 115 if ( $wpdb->num_rows == 0 ) {
137 - update_option( 'patchstack_firewall_log_lastid', 0 );
138 116 break;
139 117 }
140 118
141 119 // Construct the array to be uploaded to our API.
@@ -140,17 +118,14 @@
140 118
141 119 // Construct the array to be uploaded to our API.
142 120 $logs = [];
143 121 foreach ( $items as $item ) {
144 -
145 - // Always advance the cursor so filtered rows don't stall the batch.
146 - $lastId = $item->id;
147 -
122 +
148 123 // Entries that we don't want to store on the API side.
149 124 if ( stripos( $item->request_uri, 'wp-comments-post' ) !== false ) {
150 125 continue;
151 126 }
152 -
127 +
153 128 // Push to entries to be uploaded.
154 129 $logs[] = [
155 130 'ip' => $item->ip,
156 131 'fid' => $item->fid,
@@ -159,8 +134,10 @@
159 134 'method' => $item->method,
160 135 'log_date' => $item->log_date,
161 136 'post_data' => $item->post_data,
162 137 ];
138 +
139 + $lastId = $item->id;
163 140 }
164 141
165 142 // JSON encode the logs and upload.
166 143 $logs = json_encode( $logs );
@@ -170,24 +147,21 @@
170 147 'type' => 'firewall',
171 148 ]
172 149 );
173 150
174 - // A failed upload returns a status code or null instead of an array;
175 - // bail without advancing so the logs are not deleted before they reach the API.
176 - if ( ! is_array( $results ) || isset( $results['errors'] ) ) {
151 + if ( isset( $results['errors'] ) ) {
177 152 update_option( 'patchstack_firewall_log_lastid', $successId );
178 153 break;
179 154 }
180 155
181 156 $successId = $lastId;
182 - update_option( 'patchstack_firewall_log_lastid', $successId );
183 157 }
184 158
159 + // Set lastid to 0.
160 + update_option( 'patchstack_firewall_log_lastid', 0 );
161 +
185 162 // Delete the logs.
186 - $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_firewall_log WHERE id <= ' . (int) $successId );
187 -
188 - // No longer processing.
189 - update_option( 'patchstack_firewall_log_processing', 0 );
163 + $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_firewall_log' );
190 164 }
191 165
192 166 /**
193 167 * Synchronize the activity logs with our API.
@@ -196,20 +170,8 @@
196 170 */
197 171 public function upload_activity_logs() {
198 172 global $wpdb;
199 173
200 - // Do not execute upload action on free sites.
201 - if ( ! $this->license_is_active() || $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
202 - return;
203 - }
204 -
205 - // Do not process if we are already processing a previous batch.
206 - if ( get_option( 'patchstack_eventlog_processing', false ) ) {
207 - return;
208 - }
209 -
210 - update_option( 'patchstack_eventlog_processing', 1 );
211 -
212 174 // Determine if we should upload failed logins to the app.
213 175 $where = " AND action != 'failed login' ";
214 176 if ( $this->get_option( 'patchstack_activity_log_failed_logins_db', 0 ) == 1 ) {
215 177 $where = ' ';
@@ -217,11 +179,9 @@
217 179
218 180 // Attempt to fetch data, if any.
219 181 $lastId = get_option( 'patchstack_eventlog_lastid', 0 );
220 182 $successId = $lastId;
221 -
222 - // Do a maximum of several hundred log entries per cronjob.
223 - for ($i = 0; $i <= 1; $i++) {
183 + while ( true ) {
224 184 // Pull the data from the database, in batches of 100.
225 185 $items = $wpdb->get_results(
226 186 $wpdb->prepare(
227 187 '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',
@@ -231,9 +191,8 @@
231 191 );
232 192
233 193 // No need to continue if we have no data.
234 194 if ( $wpdb->num_rows == 0 ) {
235 - update_option( 'patchstack_eventlog_lastid', 0 );
236 195 break;
237 196 }
238 197
239 198 // Get the last ID in the result set.
@@ -241,25 +200,21 @@
241 200
242 201 // Send to the API.
243 202 $logs = json_encode( $items );
244 203 $results = $this->plugin->api->upload_activity_logs( [ 'logs' => $logs ] );
245 -
246 - // A failed upload returns a status code or null instead of an array;
247 - // bail without advancing so the logs are not deleted before they reach the API.
248 - if ( ! is_array( $results ) || isset( $results['errors'] ) ) {
204 + if ( isset( $results['errors'] ) ) {
249 205 update_option( 'patchstack_eventlog_lastid', $successId );
250 206 break;
251 207 }
252 208
253 209 $successId = $lastId;
254 - update_option( 'patchstack_eventlog_lastid', $successId );
255 210 }
256 211
212 + // Set lastid to 0.
213 + update_option( 'patchstack_eventlog_lastid', 0 );
214 +
257 215 // Delete the logs.
258 - $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id <= ' . (int) $successId );
259 -
260 - // No longer processing.
261 - update_option( 'patchstack_eventlog_processing', 0 );
216 + $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log' );
262 217 }
263 218
264 219 /**
265 220 * Obtain information about the software that the user has installed.
@@ -275,9 +230,9 @@
275 230 require_once ABSPATH . 'wp-admin/includes/update.php';
276 231 }
277 232
278 233 // Refetch updates data if we are performing a plugin listener related action.
279 - if ( isset( $_POST['patchstack_secret'] ) ) {
234 + if ( isset( $_POST['webarx_secret'] ) ) {
280 235 @require_once ABSPATH . 'wp-includes/update.php';
281 236 @wp_update_themes();
282 237 @wp_update_plugins();
283 238 }
@@ -370,98 +325,7 @@
370 325 'sw_new_ver' => ''
371 326 ];
372 327 }
373 328
374 - $software_list = apply_filters( 'patchstack_get_software_data', $software_list );
375 329 return $software_list;
376 - }
377 -
378 - /**
379 - * Import the logs generated by the auto prepend firewall rules.
380 - *
381 - * @return void
382 - */
383 - public function import_ap_logs()
384 - {
385 - if ( ! get_option( 'patchstack_firewall_ap_enabled', false ) ) {
386 - return;
387 - }
388 -
389 - // Do not process if we are already processing a previous batch.
390 - if ( get_option( 'patchstack_firewall_log_ap_processing', false ) ) {
391 - return;
392 - }
393 -
394 - // Attempt to load config file.
395 - $logs = __DIR__ . '/../../../pslogs/logs.php';
396 - if ( ! file_exists( $logs ) ) {
397 - return;
398 - }
399 -
400 - // Load the extension.
401 - if ( ! file_exists( __DIR__ . '/../lib/patchstack/vendor/autoload.php' ) ) {
402 - return;
403 - }
404 -
405 - // Set the processing lock only once we know there is work to do, otherwise an
406 - // early return above would leave the lock stuck and block all future imports.
407 - update_option( 'patchstack_firewall_log_ap_processing', 1 );
408 -
409 - global $wpdb;
410 -
411 - require_once __DIR__ . '/../lib/patchstack/vendor/autoload.php';
412 - $extension = new Patchstack\Extensions\WordPress\Extension( [], $this );
413 -
414 - // Read the logs file.
415 - $file = new SplFileObject( $logs );
416 -
417 - // Iterate through each line.
418 - while ( ! $file->eof() ) {
419 - $line = $file->fgets();
420 -
421 - // Skip first line.
422 - if ( trim($line) == '' || strpos( $line, '<?php' ) !== false ) {
423 - continue;
424 - }
425 -
426 - // Decode the line to import.
427 - $data = json_decode( base64_decode( $line ), true );
428 - if ( ! $data || ! is_array( $data ) ) {
429 - continue;
430 - }
431 -
432 - // Skip malformed entries that are missing the fields we rely on below.
433 - if ( ! isset( $data['site_id'], $data['ip'], $data['request_uri'], $data['user_agent'], $data['method'], $data['fid'], $data['post_data'] ) ) {
434 - continue;
435 - }
436 -
437 - // Insert into the logs.
438 - $wpdb->insert(
439 - $wpdb->get_blog_prefix( $data['site_id'] ) . 'patchstack_firewall_log',
440 - [
441 - 'ip' => $data['ip'],
442 - 'request_uri' => $data['request_uri'],
443 - 'user_agent' => $data['user_agent'],
444 - 'method' => $data['method'],
445 - 'fid' => $data['fid'],
446 - 'flag' => '',
447 - 'post_data' => $data['post_data'],
448 - 'block_type' => 'BLOCK'
449 - ]
450 - );
451 -
452 - // Update counters.
453 - $hits = (int) $this->get_blog_option( $data['site_id'], 'patchstack_hits_all_time', 0 );
454 - $this->update_blog_option( $data['site_id'], 'patchstack_hits_all_time', $hits + 1 );
455 -
456 - $counters = $this->get_blog_option( $data['site_id'], 'patchstack_hits_last_30', [] );
457 - $counters = $extension->merge_counters( [ date('Y-m-d') => 1 ], $counters );
458 - $this->update_blog_option( $data['site_id'], 'patchstack_hits_last_30', $counters );
459 - }
460 -
461 - $file = null;
462 - file_put_contents( $logs, '<?php exit; ?>' . PHP_EOL );
463 -
464 - // Update processing state.
465 - update_option( 'patchstack_firewall_log_ap_processing', 0 );
466 330 }
467 331 }