PluginProbe
WPVulnerability / 1.2.1
WPVulnerability v1.2.1
5.1.6 5.1.2 5.1.1 5.0.1 5.0.0 trunk 0.1 0.2 1.0 1.0.1 1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 All 57 releases
wpvulnerability / includes / class-plugins-wpvulnerability.php

class-plugins-wpvulnerability.php in WPVulnerability 1.2.1, at includes/class-plugins-wpvulnerability.php

308 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Primary class file for the Health Check plugin.
4 *
5 * @package Health Check
6 */
7
8 // Make sure the file is not directly accessible.
9 if ( ! defined( 'ABSPATH' ) ) {
10 die( 'We\'re sorry, but you can not directly access this file.' );
11 }
12
13 /**
14 * Class HealthCheck
15 */
16 class Plugins_WPVulnerability {
17
18 /**
19 * Notices to show at the head of the admin screen.
20 *
21 * @access public
22 *
23 * @var array
24 */
25 public $admin_notices = array();
26
27 /**
28 * HealthCheck constructor.
29 *
30 * @uses Health_Vulnerability::init()
31 *
32 * @return void
33 */
34 public function __construct() {
35 add_action( 'admin_head', array( $this, 'admin_head' ) );
36
37 register_activation_hook( WPVUL_PLUGIN_FILE, array( $this, 'on_activation' ) );
38 add_action( 'wpvulnerability_pull_db_data_event', array( $this, 'get_installed_plugins' ) );
39
40 register_deactivation_hook( WPVUL_PLUGIN_FILE, array( $this, 'on_deactivation' ) );
41 add_filter( 'plugin_action_links_' . WPVUL_PLUGIN_BASE, array( $this, 'add_settings_link' ) );
42
43 add_action( 'activated_plugin', array( $this, 'get_installed_plugins' ), 10, 2 );
44 add_action( 'upgrader_process_complete', array( $this, 'get_installed_plugins' ), 10, 2 );
45 }
46
47 /**
48 * Admin Head
49 * gets installed plugins cache, adds after row text and notices based on the results for the plugin page
50 *
51 * @return void
52 */
53 public function admin_head() {
54 global $pagenow;
55
56 $plugins = $this->get_installed_plugins_cache();
57
58 // add after plugin row text.
59 foreach ( $plugins as $plugin ) {
60 $path = $plugin['file_path'];
61 $added_notice = false;
62
63 if ( isset( $plugin['vulnerable'] ) && 'true' === $plugin['vulnerable'] ) {
64 add_action( 'after_plugin_row_' . $path, array( $this, 'after_row_text' ), 10, 3 );
65
66 if ( ! $added_notice ) {
67 add_action( 'admin_notices', array( $this, 'vulnerable_admin_notice' ) );
68 $added_notice = true;
69 }
70 }
71 }
72 }
73
74 /**
75 * Get Installed plugins cache
76 *
77 * @return array
78 */
79 public function get_installed_plugins_cache() {
80
81 $plugin_data = json_decode( get_option( 'wpvulnerability-data' ) );
82 if ( ! empty( $plugin_data ) ) {
83
84 if ( ! function_exists( 'get_plugins' ) ) {
85 require_once ABSPATH . 'wp-admin/includes/plugin.php';
86 }
87
88 $plugins = json_decode( get_option( 'wpvulnerability-data' ), true );
89
90 foreach ( $plugins as $key => $plugin ) {
91 $plugin = $this->get_cached_plugin_vulnerabilities( $plugin, $key );
92 $plugins[ $key ] = $plugin;
93 }
94 return $plugins;
95 } else {
96 $this->get_installed_plugins();
97 }
98 }
99
100 /**
101 * On Activation
102 * callback function for when the plugin is activated
103 * add plugin data option if it isn't created already, schedule wp-cron job
104 */
105 public function on_activation() {
106
107 if ( ! get_option( 'wpvulnerability-data' ) ) {
108 add_option( 'wpvulnerability-data', '' );
109 }
110
111 wp_schedule_event( time(), 'twicedaily', 'wpvulnerability_pull_db_data_event' );
112
113 $this->get_installed_plugins( false );
114 }
115
116 /**
117 * On Deactivation
118 * callback function when a plugin is deactivated
119 * delete, option and remove wp-cron job
120 */
121 public function on_deactivation() {
122 delete_option( 'wpvulnerability-data' );
123 wp_clear_scheduled_hook( 'wpvulnerability_pull_db_data_event' );
124 }
125
126 /**
127 * Get Cached Plugin Vulnerabilities
128 * pulls installed plugins, compares version to cached vulnerabilities, adds vulnerable key to plugin.
129 *
130 * @param array $plugin Plugin name.
131 * @param string $file_path plugin file path.
132 * @return array updated plugin array.
133 */
134 public function get_cached_plugin_vulnerabilities( $plugin, $file_path ) {
135
136 global $installed_plugins;
137
138 if ( ! is_array( $installed_plugins ) ) {
139 if ( ! function_exists( 'get_plugins' ) ) {
140 require_once ABSPATH . 'wp-admin/includes/plugin.php';
141 }
142 $installed_plugins = get_plugins();
143 }
144
145 $plugin = $this->set_text_domain( $plugin );
146
147 if ( isset( $installed_plugins[ $file_path ]['Version'] ) ) {
148 $plugin['Version'] = $installed_plugins[ $file_path ]['Version'];
149 $plugin['vulnerable'] = 'false';
150
151 if ( ! empty( $plugin['vulnerabilities'] ) ) {
152 $plugin['vulnerable'] = 'true';
153 }
154 }
155 $plugin['file_path'] = $file_path;
156 return $plugin;
157 }
158
159 /**
160 * Get Fresh Plugin Vulnerabilities
161 * pull vulnerabilities through API, compare version to vulnerabilities, add is_know_vulnerable key
162 *
163 * @param array $plugin_data Plugin data.
164 * @param string $file_path plugin file path.
165 * @return array updated plugin
166 */
167 public function get_fresh_plugin_vulnerabilities( $plugin_data, $file_path ) {
168 $plugin_data['file_path'] = $file_path;
169 $plugin_data = $this->set_text_domain( $plugin_data );
170 $plugin_slug = $plugin_data['TextDomain'];
171 $plugin_data['vulnerable'] = 'false';
172
173 $plugin_vuls = wpvulnerability_get_plugin( $plugin_slug, $plugin_data['Version'] );
174 if ( ! empty( $plugin_vuls ) ) {
175 $plugin_data['vulnerabilities'] = $plugin_vuls;
176 $plugin_data['vulnerable'] = 'true';
177 }
178
179 return $plugin_data;
180 }
181
182 /**
183 * Set Text Domain
184 * sets the text domain to the TextDomain key if it is not set
185 *
186 * @param array $plugin Plugin name.
187 * @return array updated plugin
188 */
189 public function set_text_domain( $plugin ) {
190
191 // get text domain from folder if it isn't listed.
192 if ( isset( $plugin['file_path'] ) ) {
193 $folder_name = explode( '/', $plugin['file_path'] );
194 $plugin['TextDomain'] = $folder_name[0];
195 }
196
197 return $plugin;
198 }
199
200 /**
201 * Get Installed Plugins
202 * gets the installed plugins, checks for vulnerabilities in them, caches the data, sends email if vulnerabilities detected
203 *
204 * @return array
205 */
206 public function get_installed_plugins() {
207
208 if ( ! function_exists( 'get_plugins' ) ) {
209 require_once ABSPATH . 'wp-admin/includes/plugin.php';
210 }
211
212 $plugins = get_plugins();
213 $vuln_plugins = array();
214 foreach ( $plugins as $key => $plugin ) {
215 $plugin = $this->get_fresh_plugin_vulnerabilities( $plugin, $key );
216 $plugins[ $key ] = $plugin;
217
218 if ( isset( $plugin['vulnerable'] ) && 'true' === $plugin['vulnerable'] ) {
219 $vuln_plugins[] = $plugin['Name'];
220 }
221 }
222
223 update_option( 'wpvulnerability-data', wp_json_encode( $plugins ) );
224
225 return $plugins;
226 }
227
228 /**
229 * Add Settings Link
230 *
231 * @param array $links links that appear in the plugin row.
232 */
233 public function add_settings_link( $links ) {
234 // $links[] = '<a href="' . get_admin_url( null, 'options-general.php?page=wpvulnerability-settings' ) . '">' . __( 'Settings', 'wpvulnerability' ) . '</a>';
235 return $links;
236 }
237
238 /**
239 * After Row Text
240 * callback function for adding vulnerability notice under vulnerable plugins
241 *
242 * @param string $plugin_file main plugin folder/file_name.
243 * @param array $plugin_data Plugin data.
244 * @param array $status Status.
245 * @return void
246 */
247 public function after_row_text( $plugin_file, $plugin_data, $status ) {
248
249 global $wpvulnerability_data;
250
251 $tr_class = '';
252 if ( is_plugin_active( $plugin_data['plugin'] ) ) {
253 $tr_class .= 'active';
254 }
255
256 if ( ! is_array( $wpvulnerability_data ) ) {
257 $wpvulnerability_data = json_decode( get_option( 'wpvulnerability-data' ), true );
258 }
259
260 $message = sprintf(
261 /* translators: 1: plugin name */
262 __( '%1$s has a known vulnerability that may be affecting this version.', 'wpvulnerability' ),
263 $plugin_data['Name']
264 );
265
266 $string = '<tr class="wpvulnerability ' . $tr_class . '">';
267 $string .= '<td colspan="4">';
268 $string .= '<p class="text-red"><strong>' . $message . '</strong>';
269 $string .= '</p>';
270 $string .= '<table>';
271
272 $vulnerabilities = $this->get_cached_plugin_vulnerabilities( $wpvulnerability_data[ $plugin_file ], $plugin_file );
273 foreach ( $vulnerabilities['vulnerabilities'] as $vulnerability ) {
274 $source = implode( '<br>', array_column( $vulnerability['source'], 'name' ) );
275 $string .= '<tr>';
276 $string .= '<td><strong>' . esc_html( $vulnerability['name'] ) . '</strong></td>';
277
278 $string .= '<td>';
279 if( $vulnerability['closed'] ) {
280 $string .= '<span class="text-red">' . __( 'This plugin is closed. Please replace it with another.', 'wpvulnerability' ) . '</span><br>';
281 }
282 if( $vulnerability['unfixed'] ) {
283 $string .= '<span class="text-red">' . __( 'This vulnerability appears to be unpatched. Stay tuned for upcoming plugin updates.', 'wpvulnerability' ) . '</span><br>';
284 }
285 $string .= esc_html( $source ) . '</td>';
286 $string .= '</tr>';
287 }
288 $string .= '</table>';
289 $string .= '</td>';
290 $string .= '</tr>';
291
292 echo $string; // phpcs:ignore
293 }
294
295 /**
296 * Vulnerable Admin Notice
297 * prints out error message if plugin(s) is/are vulnerable
298 */
299 public function vulnerable_admin_notice() {
300 sprintf(
301 // translators: Dismissible message.
302 '<div class="notice notice-error is-dismissible"><strong>%s</strong>: %s</div>',
303 'WPVulnerability',
304 __('There are possible vulnerabilities in your installation. Please, check your WordPress, plugins, and themes.', 'wpvulnerability' )
305 );
306 }
307 }
308