PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.13
Patchstack – WordPress & Plugins Security v2.2.13
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
patchstack / includes / upload.php

upload.php in Patchstack – WordPress & Plugins Security 2.2.13, at includes/upload.php

370 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Do not allow the file to be called directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * This class is used to upload the local logs to our API so it can
10 * be shown on the app.
11 */
12 class P_Upload extends P_Core {
13
14 /**
15 * Add the actions required to upload logs to our API.
16 *
17 * @param Patchstack $core
18 * @return void
19 */
20 public function __construct( $core ) {
21 parent::__construct( $core );
22
23 // In case the software has never been synchronized, force it.
24 if ( ! get_option( 'patchstack_software_data_hash', false ) && ! get_option( 'patchstack_software_upload_attempted', false ) ) {
25 $this->upload_software();
26 }
27
28 // Register the actions.
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
33 // In case a plugin or upgrade has been performed, re-synchronize with the app.
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 }
40
41 /**
42 * Synchronize the software data with our API.
43 * This includes plugins, themes, WordPress and PHP version.
44 *
45 * @return void|array
46 */
47 public function upload_software() {
48 // Get the software data and hash.
49 $data = $this->get_software_data();
50 $hash = sha1( json_encode( $data ) );
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 ' ) ) {
54 return;
55 }
56
57 // Make sure to not keep calling this function.
58 update_option( 'patchstack_software_upload_attempted', true );
59
60 // Synchronize the software list with the API.
61 $results = $this->plugin->api->upload_software( [ 'software' => json_encode( $data ) ] );
62 if ( isset( $results['success'] ) ) {
63 update_option( 'patchstack_software_data_hash', $hash );
64
65 // The result will also contain a list of all vulnerable plugins on the site that is returned by the API.
66 // If the auto update setting is enabled for vulnerable plugins, perform the update once the 15 minute
67 // scheduled task "patchstack_update_plugins" is executed.
68 $update = get_site_option( 'patchstack_auto_update', [] );
69 if ( isset( $results['vulnerable'] ) && is_array( $update ) && in_array( 'vulnerable', $update ) ) {
70 update_site_option( 'patchstack_vulnerable_plugins', $results['vulnerable'] );
71 }
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 ( is_array( $prev ) && ! 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
88 // If we received the number of vulnerable count.
89 if ( isset ( $results['vulnerability_count'], $results['vulnerability_fix_count'] ) ) {
90 update_option( 'patchstack_vulns_present', $results['vulnerability_count'] );
91 update_option( 'patchstack_fixes_present', $results['vulnerability_fix_count'] );
92 }
93
94 return $results;
95 }
96
97 return;
98 }
99
100 /**
101 * Synchronize the firewall logs with our API.
102 *
103 * @return void
104 */
105 public function upload_firewall_logs() {
106 global $wpdb;
107
108 // Do not execute upload action on free sites.
109 if ( ! $this->license_is_active() || $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
110 return;
111 }
112
113 // Do not process if we are already processing a previous batch.
114 if ( get_option( 'patchstack_firewall_log_processing', false ) ) {
115 return;
116 }
117
118 update_option( 'patchstack_firewall_log_processing', true );
119
120 // Attempt to fetch data, if any.
121 $lastId = get_option( 'patchstack_firewall_log_lastid', 0 );
122 $successId = $lastId;
123
124 // Do a maximum of 500 log entries per cronjob.
125 for ($i = 0; $i <= 4; $i++) {
126 // Pull the data from the database, in batches of 100.
127 $items = $wpdb->get_results(
128 $wpdb->prepare(
129 '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',
130 $lastId
131 )
132 );
133
134 // No need to continue if we have no data.
135 if ( $wpdb->num_rows == 0 ) {
136 update_option( 'patchstack_firewall_log_lastid', 0 );
137 break;
138 }
139
140 // Construct the array to be uploaded to our API.
141 $logs = [];
142 foreach ( $items as $item ) {
143
144 // Entries that we don't want to store on the API side.
145 if ( stripos( $item->request_uri, 'wp-comments-post' ) !== false ) {
146 continue;
147 }
148
149 // Push to entries to be uploaded.
150 $logs[] = [
151 'ip' => $item->ip,
152 'fid' => $item->fid,
153 'request_uri' => $item->request_uri,
154 'user_agent' => $item->user_agent,
155 'method' => $item->method,
156 'log_date' => $item->log_date,
157 'post_data' => $item->post_data,
158 ];
159
160 $lastId = $item->id;
161 }
162
163 // JSON encode the logs and upload.
164 $logs = json_encode( $logs );
165 $results = $this->plugin->api->upload_firewall_logs(
166 [
167 'logs' => $logs,
168 'type' => 'firewall',
169 ]
170 );
171
172 if ( isset( $results['errors'] ) ) {
173 update_option( 'patchstack_firewall_log_lastid', $successId );
174 break;
175 }
176
177 $successId = $lastId;
178 update_option( 'patchstack_firewall_log_lastid', $successId );
179 }
180
181 // Delete the logs.
182 $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_firewall_log WHERE id <= ' . (int) $successId );
183
184 // No longer processing.
185 update_option( 'patchstack_firewall_log_processing', false );
186 }
187
188 /**
189 * Synchronize the activity logs with our API.
190 *
191 * @return void
192 */
193 public function upload_activity_logs() {
194 global $wpdb;
195
196 // Do not execute upload action on free sites.
197 if ( ! $this->license_is_active() || $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
198 return;
199 }
200
201 // Do not process if we are already processing a previous batch.
202 if ( get_option( 'patchstack_eventlog_processing', false ) ) {
203 return;
204 }
205
206 update_option( 'patchstack_eventlog_processing', true );
207
208 // Determine if we should upload failed logins to the app.
209 $where = " AND action != 'failed login' ";
210 if ( $this->get_option( 'patchstack_activity_log_failed_logins_db', 0 ) == 1 ) {
211 $where = ' ';
212 }
213
214 // Attempt to fetch data, if any.
215 $lastId = get_option( 'patchstack_eventlog_lastid', 0 );
216 $successId = $lastId;
217
218 // Do a maximum of several hundred log entries per cronjob.
219 for ($i = 0; $i <= 4; $i++) {
220 // Pull the data from the database, in batches of 100.
221 $items = $wpdb->get_results(
222 $wpdb->prepare(
223 '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',
224 $lastId
225 ),
226 ARRAY_A
227 );
228
229 // No need to continue if we have no data.
230 if ( $wpdb->num_rows == 0 ) {
231 update_option( 'patchstack_eventlog_lastid', 0 );
232 break;
233 }
234
235 // Get the last ID in the result set.
236 $lastId = $items[count($items) - 1]['id'];
237
238 // Send to the API.
239 $logs = json_encode( $items );
240 $results = $this->plugin->api->upload_activity_logs( [ 'logs' => $logs ] );
241 if ( isset( $results['errors'] ) ) {
242 update_option( 'patchstack_eventlog_lastid', $successId );
243 break;
244 }
245
246 $successId = $lastId;
247 update_option( 'patchstack_eventlog_lastid', $successId );
248 }
249
250 // Delete the logs.
251 $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id <= ' . (int) $successId );
252
253 // No longer processing.
254 update_option( 'patchstack_eventlog_processing', false );
255 }
256
257 /**
258 * Obtain information about the software that the user has installed.
259 * This includes plugins, themes, WordPress and PHP version.
260 *
261 * @return array
262 */
263 public function get_software_data() {
264 if ( ! function_exists( 'get_plugins' ) ) {
265 require_once ABSPATH . 'wp-admin/includes/plugin.php';
266 }
267 if ( ! function_exists( 'get_plugin_updates' ) ) {
268 require_once ABSPATH . 'wp-admin/includes/update.php';
269 }
270
271 // Refetch updates data if we are performing a plugin listener related action.
272 if ( isset( $_POST['webarx_secret'] ) ) {
273 @require_once ABSPATH . 'wp-includes/update.php';
274 @wp_update_themes();
275 @wp_update_plugins();
276 }
277
278 // Fetch list of plugins.
279 $all_plugin = get_plugins();
280 $installed_plugins = array_keys( $all_plugin );
281 $updatable_plugins = get_plugin_updates();
282 $software_list = [];
283
284 foreach ( $installed_plugins as $plugin ) {
285 if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin ) ) {
286 continue;
287 }
288
289 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
290 $new_version = empty( $updatable_plugins[ $plugin ]->update->new_version ) ? '' : $updatable_plugins[ $plugin ]->update->new_version;
291 $plugin_name = empty( $plugin_data['Name'] ) ? '' : $plugin_data['Name'];
292 $plugin_version = empty( $plugin_data['Version'] ) ? '' : $plugin_data['Version'];
293
294 if ( ! empty( $plugin_name ) && ! empty( $plugin_version ) ) {
295
296 // Determine the active state.
297 if ( isset( $_GET['action'], $_GET['plugin'] ) && $_GET['action'] == 'deactivate' && $_GET['plugin'] == $plugin) {
298 $active = 0;
299 } else {
300 $active = (int) is_plugin_active( $plugin );
301 }
302
303 $software_list[] = [
304 'sw_type' => 'plugin',
305 'sw_name' => $plugin_name,
306 'sw_cur_ver' => $plugin_version,
307 'sw_new_ver' => $new_version,
308 'sw_key' => $plugin,
309 'sw_active' => $active
310 ];
311 }
312 }
313
314 // Fetch list of themes.
315 $themes = wp_get_themes();
316 $themes_keys = array_keys( $themes );
317 $updatable_themes = get_theme_updates();
318
319 foreach ( $themes_keys as $theme_key ) {
320 $themes_data = $themes[ $theme_key ];
321 $theme_temporary = empty( $updatable_themes[ $theme_key ] ) ? '' : $updatable_themes[ $theme_key ];
322 $theme_new_version = empty( $updatable_themes[ $theme_key ] ) || ! isset( $theme_temporary->update, $theme_temporary->update['new_version'] ) ? '' : $theme_temporary->update['new_version'];
323 $theme_name = $themes_data->get( 'Name' );
324 $theme_version = $themes_data->get( 'Version' );
325
326 if ( ! empty( $theme_name ) && ! empty( $theme_version ) ) {
327 $software_list[] = [
328 'sw_type' => 'theme',
329 'sw_name' => $theme_name,
330 'sw_cur_ver' => $theme_version,
331 'sw_new_ver' => $theme_new_version,
332 'sw_key' => $theme_key,
333 ];
334 }
335 }
336
337 // Fetch WordPress version.
338 global $wp_version;
339 $core_updates = get_core_updates();
340 $new_wp_version = ( ! empty( $core_updates ) && $core_updates[0]->response == 'upgrade' ) ? $core_updates[0]->version : '';
341 $software_list[] = [
342 'sw_type' => 'wordpress',
343 'sw_name' => 'WordPress',
344 'sw_cur_ver' => $wp_version,
345 'sw_new_ver' => $new_wp_version,
346 ];
347
348 // Fetch PHP version.
349 $software_list[] = [
350 'sw_type' => 'php',
351 'sw_name' => 'PHP',
352 'sw_cur_ver' => phpversion(),
353 'sw_new_ver' => '',
354 ];
355
356 // Fetch database server version.
357 global $wpdb;
358 if ( ! is_null( $wpdb ) ) {
359 $software_list[] = [
360 'sw_type' => 'database',
361 'sw_name' => 'Database',
362 'sw_cur_ver' => $wpdb->get_var( 'SELECT VERSION()' ),
363 'sw_new_ver' => ''
364 ];
365 }
366
367 return $software_list;
368 }
369 }
370