PluginProbe
WPVulnerability / 5.1.2
WPVulnerability v5.1.2
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-core.php

wpvulnerability-core.php in WPVulnerability 5.1.2, at wpvulnerability-core.php

287 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Core functions
4 *
5 * @package WPVulnerability
6 *
7 * @version 2.0.0
8 */
9
10 defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
11
12 /**
13 * Adds a vulnerability notice under vulnerable core.
14 *
15 * @since 2.0.0
16 *
17 * @return void
18 */
19 function wpvulnerability_core_info_after() {
20
21 // Retrieve the vulnerabilities for core from the options table and decode the JSON.
22 if ( is_multisite() ) {
23 $raw_core = get_site_option( 'wpvulnerability-core' );
24 $core_vulnerabilities = json_decode( is_string( $raw_core ) ? $raw_core : '', true );
25 } else {
26 $raw_core = get_option( 'wpvulnerability-core' );
27 $core_vulnerabilities = json_decode( is_string( $raw_core ) ? $raw_core : '', true );
28 }
29
30 // Generate the vulnerability notice message.
31 $message = sprintf(
32 /* translators: 1: core version */
33 __( 'WordPress %1$s has a known vulnerability that may be affecting your installed version.', 'wpvulnerability' ),
34 get_bloginfo( 'version' )
35 );
36
37 $information = '<p class="text-red"><img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon.svg" style="height: 16px; vertical-align: text-top; width: 16px;" alt="" title="WPVulnerability"> <strong>' . esc_html( $message ) . '</strong></p>';
38 $information .= '<table class="widefat wpvulnerability">';
39
40 // Loop through all vulnerabilities for the current version.
41 $core_vuln_array = is_array( $core_vulnerabilities ) ? $core_vulnerabilities : array();
42 foreach ( $core_vuln_array as $vulnerability ) {
43 if ( ! is_array( $vulnerability ) ) {
44 continue;
45 }
46 $vuln_impact_raw = $vulnerability['impact'] ?? null;
47 $vuln_impact = is_array( $vuln_impact_raw ) ? $vuln_impact_raw : array();
48 $vuln_cvss_raw = $vuln_impact['cvss'] ?? null;
49 $vuln_cvss = is_array( $vuln_cvss_raw ) ? $vuln_cvss_raw : array();
50 $vuln_cvss2_raw = $vuln_impact['cvss2'] ?? null;
51 $vuln_cvss2 = is_array( $vuln_cvss2_raw ) ? $vuln_cvss2_raw : array();
52 $vuln_cvss3_raw = $vuln_impact['cvss3'] ?? null;
53 $vuln_cvss3 = is_array( $vuln_cvss3_raw ) ? $vuln_cvss3_raw : array();
54 $vuln_cvss4_raw = $vuln_impact['cvss4'] ?? null;
55 $vuln_cvss4 = is_array( $vuln_cvss4_raw ) ? $vuln_cvss4_raw : array();
56 $vuln_ssvc_raw = $vuln_impact['ssvc'] ?? null;
57 $vuln_ssvc = is_array( $vuln_ssvc_raw ) ? $vuln_ssvc_raw : array();
58 $vuln_cwe_raw = $vuln_impact['cwe'] ?? null;
59 $vuln_cwe = is_array( $vuln_cwe_raw ) ? $vuln_cwe_raw : array();
60 $vuln_src_raw = $vulnerability['source'] ?? null;
61 $vuln_sources = is_array( $vuln_src_raw ) ? $vuln_src_raw : array();
62
63 $kev = ( isset( $vuln_ssvc['kev'] ) && true === $vuln_ssvc['kev'] );
64 $exploitation = isset( $vuln_ssvc['exploitation'] ) && is_string( $vuln_ssvc['exploitation'] ) ? $vuln_ssvc['exploitation'] : '';
65 $automatable = isset( $vuln_ssvc['automatable'] ) && is_string( $vuln_ssvc['automatable'] ) ? $vuln_ssvc['automatable'] : '';
66 $kev_date_raw = $vuln_ssvc['kev_date'] ?? null;
67 $kev_date = is_string( $kev_date_raw ) && '' !== $kev_date_raw ? $kev_date_raw : null;
68 $epss_raw = $vuln_impact['epss'] ?? null;
69 $epss = is_numeric( $epss_raw ) ? (float) $epss_raw : null;
70 $description = wpvulnerability_get_source_description( $vuln_sources );
71
72 $what = array();
73 foreach ( $vuln_cwe as $vulnerability_cwe ) {
74 if ( ! is_array( $vulnerability_cwe ) ) {
75 continue;
76 }
77 $cwe_name = is_scalar( $vulnerability_cwe['name'] ?? '' ) ? (string) ( $vulnerability_cwe['name'] ?? '' ) : '';
78 $cwe_desc = is_scalar( $vulnerability_cwe['description'] ?? '' ) ? (string) ( $vulnerability_cwe['description'] ?? '' ) : '';
79 $what[] = '<div><b>' . wp_kses( $cwe_name, 'strip' ) . '</b></div><div><i>' . esc_html( $cwe_desc ) . '</i></div>';
80 }
81
82 $source_pills = wpvulnerability_render_source_pills( $vuln_sources );
83
84 // Best available CVSS score and severity: cvss4 > cvss3 > cvss2 > legacy cvss.
85 $score = null;
86 $sev_raw = null;
87 foreach ( array( $vuln_cvss4, $vuln_cvss3, $vuln_cvss2, $vuln_cvss ) as $cvss_c ) {
88 if ( empty( $cvss_c ) ) {
89 continue;
90 }
91 $s_raw = $cvss_c['score'] ?? null;
92 $v_raw = $cvss_c['severity'] ?? null;
93 $s = is_numeric( $s_raw ) ? number_format( (float) $s_raw, 1, '.', '' ) : null;
94 $v = is_string( $v_raw ) && '' !== $v_raw ? $v_raw : null;
95 if ( null !== $s || null !== $v ) {
96 $score = $s;
97 $sev_raw = $v;
98 break;
99 }
100 }
101
102 $vuln_name = is_scalar( $vulnerability['name'] ?? '' ) ? (string) ( $vulnerability['name'] ?? '' ) : '';
103 $score_badge = wpvulnerability_render_score_badge( $score, $sev_raw, $epss );
104 $show_active = $kev || 'active' === $exploitation;
105 $show_poc = 'poc' === $exploitation;
106 $show_auto = 'yes' === $automatable;
107
108 $information .= '<tr>';
109 $information .= '<td style="max-width: 256px; min-width: 96px; vertical-align: top; padding-top: 6px;">WordPress <b>' . wp_kses( $vuln_name, 'strip' ) . '</b></td>';
110 $information .= '<td>';
111
112 if ( $show_active || $show_poc || $show_auto || '' !== $score_badge ) {
113 $information .= '<div style="display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-bottom:5px;">';
114 if ( $show_active ) {
115 $information .= '<span class="wpvuln-kev-label">&#9888; ' . esc_html__( 'Actively exploited', 'wpvulnerability' );
116 if ( $kev && null !== $kev_date ) {
117 $information .= ' &middot; ' . esc_html( $kev_date );
118 }
119 $information .= '</span>';
120 }
121 if ( $show_poc ) {
122 $information .= '<span class="wpvuln-poc-label">&#9889; ' . esc_html__( 'Public exploit', 'wpvulnerability' ) . '</span>';
123 }
124 if ( $show_auto ) {
125 $information .= '<span class="wpvuln-auto-label">&#9881; ' . esc_html__( 'Automatable', 'wpvulnerability' ) . '</span>';
126 }
127 if ( '' !== $score_badge ) {
128 $information .= $score_badge;
129 }
130 $information .= '</div>';
131 }
132 if ( null !== $description ) {
133 $information .= '<div style="padding-bottom: 5px;">' . esc_html( $description ) . '</div>';
134 }
135 if ( count( $what ) ) {
136 $information .= '<div style="padding-bottom: 5px;">' . implode( '', $what ) . '</div>';
137 }
138 if ( '' !== $source_pills ) {
139 $information .= '<div class="wpvuln-refs-row"><span class="wpvuln-refs-label">' . esc_html__( 'References:', 'wpvulnerability' ) . '</span>';
140 $information .= $source_pills;
141 $information .= '</div>';
142 }
143
144 $information .= '</td>';
145 $information .= '</tr>';
146 }
147
148 $information .= '</table>';
149
150 echo $information; // phpcs:ignore
151 }
152
153 /**
154 * Retrieves vulnerabilities for a given WordPress core version and updates its data.
155 *
156 * @since 2.0.0
157 *
158 * @return array<int, array<string, mixed>>|false The updated core data array or false if no vulnerabilities are found.
159 */
160 function wpvulnerability_get_fresh_core_vulnerabilities() {
161
162 // Get the core version and sanitize it.
163 $version = wpvulnerability_sanitize_version( get_bloginfo( 'version' ) );
164
165 // Retrieve vulnerabilities for the core version.
166 $response = wpvulnerability_get_core( $version, 0 );
167
168 $core_data = array();
169
170 // If no vulnerabilities are found, return false.
171 if ( empty( $response ) ) {
172 return false;
173 }
174
175 // If vulnerabilities are found, update the core data.
176 foreach ( $response as $v ) {
177 if ( isset( $v['name'], $v['source'], $v['impact'] ) ) { // Ensure expected keys exist.
178 $core_data[] = array(
179 'name' => wp_kses( is_scalar( $v['name'] ) ? (string) $v['name'] : '', 'strip' ),
180 'source' => $v['source'],
181 'impact' => $v['impact'],
182 'uuid' => is_scalar( $v['uuid'] ?? '' ) ? (string) ( $v['uuid'] ?? '' ) : '',
183 );
184 }
185 }
186
187 return ! empty( $core_data ) ? $core_data : false; // Return false if core_data is empty.
188 }
189
190 /**
191 * Get Vulnerabilities
192 *
193 * Retrieves and caches the vulnerabilities for the installed WordPress core version.
194 *
195 * @since 2.0.0
196 *
197 * @return string JSON-encoded array of core data with vulnerabilities and vulnerable status.
198 */
199 function wpvulnerability_core_get_installed() {
200
201 $wpvulnerability_core_vulnerable = 0;
202 $current_version = wpvulnerability_sanitize_version( get_bloginfo( 'version' ) );
203
204 // Get fresh core vulnerabilities.
205 $core = wpvulnerability_get_fresh_core_vulnerabilities();
206
207 // Check if vulnerabilities were found and count them.
208 if ( is_array( $core ) && count( $core ) > 0 ) {
209 $wpvulnerability_core_vulnerable = count( $core );
210 }
211
212 // Cache the vulnerability data and the timestamp for cache expiration.
213 if ( is_multisite() ) {
214 update_site_option( 'wpvulnerability-core', wp_json_encode( $core ) );
215 update_site_option( 'wpvulnerability-core-vulnerable', wp_json_encode( number_format( $wpvulnerability_core_vulnerable, 0, '.', '' ) ) );
216 update_site_option( 'wpvulnerability-core-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
217 update_site_option( 'wpvulnerability-core-version', wp_json_encode( $current_version ) );
218 } else {
219 update_option( 'wpvulnerability-core', wp_json_encode( $core ), false );
220 update_option( 'wpvulnerability-core-vulnerable', wp_json_encode( number_format( $wpvulnerability_core_vulnerable, 0, '.', '' ) ), false );
221 update_option( 'wpvulnerability-core-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ), false );
222 update_option( 'wpvulnerability-core-version', wp_json_encode( $current_version ), false );
223 }
224
225 // Return the JSON-encoded array of core vulnerabilities.
226 $encoded = wp_json_encode( $core );
227 return false !== $encoded ? $encoded : '';
228 }
229
230 /**
231 * Get cached core vulnerabilities without refreshing external data.
232 *
233 * @since 2.0.0
234 * @since 4.1.2 Refreshes when the stored WordPress core version differs from the running version.
235 *
236 * @return array<mixed> Array of core with their vulnerabilities.
237 */
238 function wpvulnerability_core_get_vulnerabilities() {
239
240 if ( is_multisite() ) {
241 $raw = get_site_option( 'wpvulnerability-core' );
242 $core_data = json_decode( is_string( $raw ) ? $raw : '', true );
243 } else {
244 $raw = get_option( 'wpvulnerability-core' );
245 $core_data = json_decode( is_string( $raw ) ? $raw : '', true );
246 }
247
248 return is_array( $core_data ) ? $core_data : array();
249 }
250
251 /**
252 * Update the core cache and remove any old cache data.
253 *
254 * @since 2.0.0
255 *
256 * @return void
257 */
258 function wpvulnerability_core_get_vulnerabilities_clean() {
259 wpvulnerability_clear_cache( 'core' );
260 wpvulnerability_core_get_installed();
261 }
262
263 /**
264 * Adds vulnerability information after the core version and notices on the update-core.php page.
265 *
266 * @since 2.0.0
267 *
268 * @return void
269 */
270 function wpvulnerability_core_page() {
271
272 // Check if the current page is the update-core.php page.
273 global $pagenow;
274 if ( wpvulnerability_analyze_filter( 'core' ) && 'update-core.php' === $pagenow && wpvulnerability_capabilities() ) {
275
276 // Get the vulnerabilities for the core.
277 $core = wpvulnerability_core_get_vulnerabilities();
278
279 // If there are vulnerabilities, add an action to display them after the core auto updates settings.
280 if ( ! empty( $core ) ) {
281 add_action( 'after_core_auto_updates_settings', 'wpvulnerability_core_info_after' );
282 }
283 }
284 }
285 // Add notices for vulnerable core on the core page.
286 add_action( 'admin_head', 'wpvulnerability_core_page' );
287