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
patchstack / includes / upload.php

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

354 lines 11.3 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 ( ! 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 return $results;
89 }
90
91 return;
92 }
93
94 /**
95 * Synchronize the firewall logs with our API.
96 *
97 * @return void
98 */
99 public function upload_firewall_logs() {
100 global $wpdb;
101
102 // Do not process if we are already processing a previous batch.
103 if ( get_option( 'patchstack_firewall_log_processing', false ) ) {
104 return;
105 }
106
107 update_option( 'patchstack_firewall_log_processing', true );
108
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;
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 ];
148
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 ]
159 );
160
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 );
168 }
169
170 // Delete the logs.
171 $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_firewall_log WHERE id <= ' . (int) $successId );
172
173 // No longer processing.
174 update_option( 'patchstack_firewall_log_processing', false );
175 }
176
177 /**
178 * Synchronize the activity logs with our API.
179 *
180 * @return void
181 */
182 public function upload_activity_logs() {
183 global $wpdb;
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
192 // Determine if we should upload failed logins to the app.
193 $where = " AND action != 'failed login' ";
194 if ( $this->get_option( 'patchstack_activity_log_failed_logins_db', 0 ) == 1 ) {
195 $where = ' ';
196 }
197
198 // Attempt to fetch data, if any.
199 $lastId = get_option( 'patchstack_eventlog_lastid', 0 );
200 $successId = $lastId;
201
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 );
232 }
233
234 // Delete the logs.
235 $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id <= ' . (int) $successId );
236
237 // No longer processing.
238 update_option( 'patchstack_eventlog_processing', false );
239 }
240
241 /**
242 * Obtain information about the software that the user has installed.
243 * This includes plugins, themes, WordPress and PHP version.
244 *
245 * @return array
246 */
247 public function get_software_data() {
248 if ( ! function_exists( 'get_plugins' ) ) {
249 require_once ABSPATH . 'wp-admin/includes/plugin.php';
250 }
251 if ( ! function_exists( 'get_plugin_updates' ) ) {
252 require_once ABSPATH . 'wp-admin/includes/update.php';
253 }
254
255 // Refetch updates data if we are performing a plugin listener related action.
256 if ( isset( $_POST['webarx_secret'] ) ) {
257 @require_once ABSPATH . 'wp-includes/update.php';
258 @wp_update_themes();
259 @wp_update_plugins();
260 }
261
262 // Fetch list of plugins.
263 $all_plugin = get_plugins();
264 $installed_plugins = array_keys( $all_plugin );
265 $updatable_plugins = get_plugin_updates();
266 $software_list = [];
267
268 foreach ( $installed_plugins as $plugin ) {
269 if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin ) ) {
270 continue;
271 }
272
273 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
274 $new_version = empty( $updatable_plugins[ $plugin ]->update->new_version ) ? '' : $updatable_plugins[ $plugin ]->update->new_version;
275 $plugin_name = empty( $plugin_data['Name'] ) ? '' : $plugin_data['Name'];
276 $plugin_version = empty( $plugin_data['Version'] ) ? '' : $plugin_data['Version'];
277
278 if ( ! empty( $plugin_name ) && ! empty( $plugin_version ) ) {
279
280 // Determine the active state.
281 if ( isset( $_GET['action'], $_GET['plugin'] ) && $_GET['action'] == 'deactivate' && $_GET['plugin'] == $plugin) {
282 $active = 0;
283 } else {
284 $active = (int) is_plugin_active( $plugin );
285 }
286
287 $software_list[] = [
288 'sw_type' => 'plugin',
289 'sw_name' => $plugin_name,
290 'sw_cur_ver' => $plugin_version,
291 'sw_new_ver' => $new_version,
292 'sw_key' => $plugin,
293 'sw_active' => $active
294 ];
295 }
296 }
297
298 // Fetch list of themes.
299 $themes = wp_get_themes();
300 $themes_keys = array_keys( $themes );
301 $updatable_themes = get_theme_updates();
302
303 foreach ( $themes_keys as $theme_key ) {
304 $themes_data = $themes[ $theme_key ];
305 $theme_temporary = empty( $updatable_themes[ $theme_key ] ) ? '' : $updatable_themes[ $theme_key ];
306 $theme_new_version = empty( $updatable_themes[ $theme_key ] ) || ! isset( $theme_temporary->update, $theme_temporary->update['new_version'] ) ? '' : $theme_temporary->update['new_version'];
307 $theme_name = $themes_data->get( 'Name' );
308 $theme_version = $themes_data->get( 'Version' );
309
310 if ( ! empty( $theme_name ) && ! empty( $theme_version ) ) {
311 $software_list[] = [
312 'sw_type' => 'theme',
313 'sw_name' => $theme_name,
314 'sw_cur_ver' => $theme_version,
315 'sw_new_ver' => $theme_new_version,
316 'sw_key' => $theme_key,
317 ];
318 }
319 }
320
321 // Fetch WordPress version.
322 global $wp_version;
323 $core_updates = get_core_updates();
324 $new_wp_version = ( ! empty( $core_updates ) && $core_updates[0]->response == 'upgrade' ) ? $core_updates[0]->version : '';
325 $software_list[] = [
326 'sw_type' => 'wordpress',
327 'sw_name' => 'WordPress',
328 'sw_cur_ver' => $wp_version,
329 'sw_new_ver' => $new_wp_version,
330 ];
331
332 // Fetch PHP version.
333 $software_list[] = [
334 'sw_type' => 'php',
335 'sw_name' => 'PHP',
336 'sw_cur_ver' => phpversion(),
337 'sw_new_ver' => '',
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 }
350
351 return $software_list;
352 }
353 }
354