PluginProbe
WPVulnerability / 2.0.0
WPVulnerability v2.0.0
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 / wpvulnerability-plugins.php

wpvulnerability-plugins.php in WPVulnerability 2.0.0, at wpvulnerability-plugins.php

279 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin functions
4 *
5 * @package WPVulnerability
6 *
7 * @version 2.0.0
8 */
9 defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
10
11 /**
12 * Adds a vulnerability notice under vulnerable plugins.
13 *
14 * @since 2.0.0
15 *
16 * @param string $plugin_file Main plugin folder/file name.
17 * @param array $plugin_data Plugin data.
18 * @param array $status Status.
19 *
20 * @return string $information HTML.
21 */
22 function wpvulnerability_plugin_info_after( $plugin_file, $plugin_data, $status ) {
23
24 // Retrieve the vulnerabilities for all plugins from the options table and decode the JSON.
25 $plugin_vulnerabilities = json_decode( get_option( 'wpvulnerability-plugins' ), true );
26
27 // Determine whether the plugin is active and add an appropriate CSS class to the table row.
28 $tr_class = '';
29 if ( is_plugin_active( $plugin_file ) ) {
30 $tr_class .= 'active';
31 }
32
33 // Generate the vulnerability notice message with the plugin name.
34 $message = sprintf(
35 /* translators: 1: Plugin name */
36 __( '%1$s has a known vulnerability that may be affecting this version.', 'wpvulnerability' ),
37 wp_kses( $plugin_data['Name'], 'strip' )
38 );
39
40 // Begin generating the table row HTML markup with appropriate CSS classes and the vulnerability notice message.
41 $information = '<tr class="wpvulnerability ' . $tr_class . '">';
42 $information .= '<td colspan="4">';
43 $information .= '<p class="text-red"><img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/logo16.png" style="height: 16px; vertical-align: text-top; width: 16px;" alt="" title="WPVulnerability"> <strong>' . $message . '</strong>';
44 $information .= '</p>';
45 $information .= '<table>';
46
47 // Loop through all vulnerabilities for the current plugin and add their details to the table row HTML markup.
48 $vulnerabilities = $plugin_vulnerabilities[ $plugin_file ]['vulnerabilities'];
49
50 foreach ( $vulnerabilities as $vulnerability ) {
51
52 $what = array();
53 if( isset( $vulnerability['impact']['cwe'] ) ) {
54 foreach( $vulnerability['impact']['cwe'] as $vulnerability_cwe ) {
55 $what[] = '<div><b>' . wp_kses( $vulnerability_cwe['name'], 'strip' ) . '</b></div><div><i>' . wp_kses_post( $vulnerability_cwe['description'] ) . '</i></div>';
56 }
57 }
58
59 $sources = array();
60 if( isset( $vulnerability['source'] ) ) {
61 foreach( $vulnerability['source'] as $vulnerability_source ) {
62 $sources[] = '<a href="' . esc_url_raw( $vulnerability_source['link'], 'strip') . '" target="_blank" rel="external nofollow noopener noreferrer">[+]</a>&nbsp;' . wp_kses( $vulnerability_source['name'], 'strip' );
63 }
64 }
65 if( count( $sources ) ) {
66 $source = '<div style="padding-bottom: 5px;">' . implode( '<br>', $sources ) . '</div>';
67 }
68
69 $score = null;
70 if( isset( $vulnerability['impact']['cvss']['score'] ) ) {
71 $score = number_format( (float) $vulnerability['impact']['cvss']['score'], 1, '.', '' );
72 }
73 $severity = null;
74 if( isset( $vulnerability['impact']['cvss']['severity'] ) ) {
75 $severity = wpvulnerability_severity( $vulnerability['impact']['cvss']['severity'] );
76 }
77 $exploitable = null;
78 if( isset( $vulnerability['impact']['cvss']['exploitable'] ) ) {
79 $exploitable = number_format( (float) $vulnerability['impact']['cvss']['exploitable'], 1, '.', '' );
80 }
81
82 $information .= '<tr>';
83 $information .= '<td style="max-width: 256px; min-width: 96px;"><b>' . wp_kses( $vulnerability['versions'], 'strip' ) . '</b></td>';
84 $information .= '<td>';
85 if( (int)$vulnerability['closed'] || (int)$vulnerability['unfixed'] ) {
86 $information .= '<div style="padding-bottom: 5px;">';
87 if( (int)$vulnerability['closed'] ) {
88 $information .= '<div class="text-red">' . __( 'This plugin is closed. Please replace it with another.', 'wpvulnerability' ) . '</div>';
89 }
90 if( (int)$vulnerability['unfixed'] ) {
91 $information .= '<div class="text-red">' . __( 'This vulnerability appears to be unpatched. Stay tuned for upcoming plugin updates.', 'wpvulnerability' ) . '</div>';
92 }
93 $information .= '</div>';
94 }
95 if( count( $what ) ) {
96 $information .= '<div style="padding-bottom: 5px;">';
97 foreach( $what as $w ) {
98 $information .= $w;
99 }
100 $information .= '</div>';
101 }
102 if( !is_null( $score ) || !is_null( $severity ) || !is_null( $exploitable ) ) {
103 $information .= '<div style="padding-bottom: 5px;">';
104 if( !is_null( $score ) ) {
105 $information .= '<div>' . __( 'Global score: ', 'wpvulnerability' ) . $score . ' / 10</div>';
106 }
107 if( !is_null( $severity ) ) {
108 $information .= '<div>' . __( 'Severity: ', 'wpvulnerability' ) . $severity . '</div>';
109 }
110 if( !is_null( $exploitable ) ) {
111 $information .= '<div>' . __( 'Exploitability: ', 'wpvulnerability' ) . $exploitable . ' / 10</div>';
112 }
113 $information .= '</div>';
114 }
115 $information .= wp_kses( $source, 'post' );
116 $information .= '</td>';
117 $information .= '</tr>';
118
119 }
120
121 $information .= '</table>';
122 $information .= '</td>';
123 $information .= '</tr>';
124
125 echo $information; // phpcs:ignore
126 }
127
128 /**
129 * Retrieves vulnerabilities for a given plugin and updates its data.
130 *
131 * @since 2.0.0
132 *
133 * @param array $plugin_data The plugin data array.
134 * @param string $file_path The path to the plugin file.
135 *
136 * @return array The updated plugin data array.
137 *
138 */
139 function get_fresh_plugin_vulnerabilities( $plugin_data, $file_path ) {
140
141 // If the TextDomain key is empty, extract it from the file path.
142 if( empty( $plugin_data['TextDomain'] ) ) {
143
144 $folder_name = explode( '/', $file_path );
145
146 if( isset( $folder_name[0] ) ) {
147 $plugin_data['TextDomain'] = wp_kses( $folder_name[0], 'strip' );
148 }
149
150 }
151
152 // Get the plugin slug and version from the plugin data.
153 $plugin_slug = wp_kses( $plugin_data['TextDomain'], 'strip' );
154 $plugin_version = wp_kses( $plugin_data['Version'], 'strip' );
155
156 // Initialize vulnerability related fields.
157 $plugin_data['vulnerabilities'] = null;
158 $plugin_data['vulnerable'] = 0;
159
160 // Retrieve vulnerabilities for the plugin using its slug and version.
161 if( $plugin_slug ) {
162
163 $plugin_api_response = wpvulnerability_get_plugin( $plugin_slug, $plugin_version );
164
165 // If vulnerabilities are found, update the plugin data accordingly.
166 if ( !empty( $plugin_api_response ) ) {
167
168 $plugin_data['vulnerabilities'] = $plugin_api_response;
169 $plugin_data['vulnerable'] = 1;
170
171 }
172
173 }
174
175 return $plugin_data;
176 }
177
178 /**
179 * Get Installed Plugins
180 * Retrieves the list of installed plugins, checks for vulnerabilities in each of them, caches the data, and sends an email notification if vulnerabilities are detected.
181 *
182 * @since 2.0.0
183 *
184 * @return string JSON-encoded array of plugin data with vulnerabilities and vulnerable status
185 */
186 function wpvulnerability_plugin_get_installed() {
187
188 if ( ! function_exists( 'get_plugins' ) ) {
189 require_once ABSPATH . 'wp-admin/includes/plugin.php';
190 }
191
192 $plugins = get_plugins();
193
194 foreach ( $plugins as $file_path => $plugin_data ) {
195
196 $plugins[$file_path] = get_fresh_plugin_vulnerabilities( $plugin_data, $file_path );
197
198 }
199
200 update_option( 'wpvulnerability-plugins', wp_json_encode( $plugins ) );
201
202 return wp_json_encode( $plugins );
203 }
204
205 /**
206 * Get the cached plugin vulnerabilities or update the cache if it's stale or missing.
207 *
208 * @since 2.0.0
209 *
210 * @return array Array of installed plugins with their vulnerabilities.
211 */
212 function wpvulnerability_plugin_get_vulnerabilities( ) {
213
214 // Get the cached plugin data and decode it.
215 $plugin_data_cache = json_decode( get_option( 'wpvulnerability-plugins-cache' ) );
216
217 // Get the installed plugin data and decode it.
218 $plugin_data = json_decode( get_option( 'wpvulnerability-plugins' ), true );
219
220 // If the cache is stale or the plugin data is empty, update the cache.
221 if( $plugin_data_cache < time() || empty( $plugin_data ) ) {
222
223 // Get the installed plugin data and update the cache.
224 $plugin_data = json_decode( wpvulnerability_plugin_get_installed( ), true );
225 update_option( 'wpvulnerability-plugins-cache', wp_json_encode( number_format( time() + ( 3600 * WPVULNERABILITY_CACHE_HOURS ), 0, '.', '' ) ) );
226
227 }
228
229 return $plugin_data;
230
231 }
232
233 /**
234 * Update the installed plugins cache and remove any old cache data
235 *
236 * @since 2.0.0
237 *
238 * @return void
239 */
240 function wpvulnerability_plugin_get_vulnerabilities_clean( ) {
241
242 // Update the installed plugins cache
243 wpvulnerability_plugin_get_installed( );
244
245 }
246
247 /**
248 * Admin Head
249 * Adds vulnerability information after the plugin row and notices on the plugin page based on the installed plugins cache
250 *
251 * @since 2.0.0
252 *
253 * @return void
254 */
255 function wpvulnerability_plugin_page() {
256
257 // Check if the current page is the plugins page
258 global $pagenow;
259 if( 'plugins.php' == $pagenow ) {
260
261 // Get the vulnerabilities for the installed plugins
262 $plugins = wpvulnerability_plugin_get_vulnerabilities();
263
264 // Loop through the plugins and add vulnerability information after the plugin row for vulnerable plugins
265 foreach ( $plugins as $file_path => $plugin_data ) {
266
267 if ( isset( $plugin_data['vulnerable'] ) && 1 === $plugin_data['vulnerable'] ) {
268
269 add_action( 'after_plugin_row_' . $file_path, 'wpvulnerability_plugin_info_after', 10, 3 );
270
271 }
272
273 }
274
275 }
276 }
277 // Add notices for vulnerable plugins on the plugin page
278 add_action( 'admin_head', 'wpvulnerability_plugin_page' );
279