wpvulnerability/wpvulnerability-core.php
2026-08-23 07:08:42 +00:00

286 lines
11 KiB
PHP

<?php
/**
* Core functions
*
* @package WPVulnerability
*
* @version 2.0.0
*/
defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
/**
* Adds a vulnerability notice under vulnerable core.
*
* @since 2.0.0
*
* @return void
*/
function wpvulnerability_core_info_after() {
// Retrieve the vulnerabilities for core from the options table and decode the JSON.
if ( is_multisite() ) {
$raw_core = get_site_option( 'wpvulnerability-core' );
$core_vulnerabilities = json_decode( is_string( $raw_core ) ? $raw_core : '', true );
} else {
$raw_core = get_option( 'wpvulnerability-core' );
$core_vulnerabilities = json_decode( is_string( $raw_core ) ? $raw_core : '', true );
}
// Generate the vulnerability notice message.
$message = sprintf(
/* translators: 1: core version */
__( 'WordPress %1$s has a known vulnerability that may be affecting your installed version.', 'wpvulnerability' ),
get_bloginfo( 'version' )
);
$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>';
$information .= '<table class="widefat wpvulnerability">';
// Loop through all vulnerabilities for the current version.
$core_vuln_array = is_array( $core_vulnerabilities ) ? $core_vulnerabilities : array();
foreach ( $core_vuln_array as $vulnerability ) {
if ( ! is_array( $vulnerability ) ) {
continue;
}
$vuln_impact_raw = $vulnerability['impact'] ?? null;
$vuln_impact = is_array( $vuln_impact_raw ) ? $vuln_impact_raw : array();
$vuln_cvss_raw = $vuln_impact['cvss'] ?? null;
$vuln_cvss = is_array( $vuln_cvss_raw ) ? $vuln_cvss_raw : array();
$vuln_cvss2_raw = $vuln_impact['cvss2'] ?? null;
$vuln_cvss2 = is_array( $vuln_cvss2_raw ) ? $vuln_cvss2_raw : array();
$vuln_cvss3_raw = $vuln_impact['cvss3'] ?? null;
$vuln_cvss3 = is_array( $vuln_cvss3_raw ) ? $vuln_cvss3_raw : array();
$vuln_cvss4_raw = $vuln_impact['cvss4'] ?? null;
$vuln_cvss4 = is_array( $vuln_cvss4_raw ) ? $vuln_cvss4_raw : array();
$vuln_ssvc_raw = $vuln_impact['ssvc'] ?? null;
$vuln_ssvc = is_array( $vuln_ssvc_raw ) ? $vuln_ssvc_raw : array();
$vuln_cwe_raw = $vuln_impact['cwe'] ?? null;
$vuln_cwe = is_array( $vuln_cwe_raw ) ? $vuln_cwe_raw : array();
$vuln_src_raw = $vulnerability['source'] ?? null;
$vuln_sources = is_array( $vuln_src_raw ) ? $vuln_src_raw : array();
$kev = ( isset( $vuln_ssvc['kev'] ) && true === $vuln_ssvc['kev'] );
$exploitation = isset( $vuln_ssvc['exploitation'] ) && is_string( $vuln_ssvc['exploitation'] ) ? $vuln_ssvc['exploitation'] : '';
$automatable = isset( $vuln_ssvc['automatable'] ) && is_string( $vuln_ssvc['automatable'] ) ? $vuln_ssvc['automatable'] : '';
$kev_date_raw = $vuln_ssvc['kev_date'] ?? null;
$kev_date = is_string( $kev_date_raw ) && '' !== $kev_date_raw ? $kev_date_raw : null;
$epss_raw = $vuln_impact['epss'] ?? null;
$epss = is_numeric( $epss_raw ) ? (float) $epss_raw : null;
$description = wpvulnerability_get_source_description( $vuln_sources );
$what = array();
foreach ( $vuln_cwe as $vulnerability_cwe ) {
if ( ! is_array( $vulnerability_cwe ) ) {
continue;
}
$cwe_name = is_scalar( $vulnerability_cwe['name'] ?? '' ) ? (string) ( $vulnerability_cwe['name'] ?? '' ) : '';
$cwe_desc = is_scalar( $vulnerability_cwe['description'] ?? '' ) ? (string) ( $vulnerability_cwe['description'] ?? '' ) : '';
$what[] = '<div><b>' . wp_kses( $cwe_name, 'strip' ) . '</b></div><div><i>' . esc_html( $cwe_desc ) . '</i></div>';
}
$source_pills = wpvulnerability_render_source_pills( $vuln_sources );
// Best available CVSS score and severity: cvss4 > cvss3 > cvss2 > legacy cvss.
$score = null;
$sev_raw = null;
foreach ( array( $vuln_cvss4, $vuln_cvss3, $vuln_cvss2, $vuln_cvss ) as $cvss_c ) {
if ( empty( $cvss_c ) ) {
continue;
}
$s_raw = $cvss_c['score'] ?? null;
$v_raw = $cvss_c['severity'] ?? null;
$s = is_numeric( $s_raw ) ? number_format( (float) $s_raw, 1, '.', '' ) : null;
$v = is_string( $v_raw ) && '' !== $v_raw ? $v_raw : null;
if ( null !== $s || null !== $v ) {
$score = $s;
$sev_raw = $v;
break;
}
}
$vuln_name = is_scalar( $vulnerability['name'] ?? '' ) ? (string) ( $vulnerability['name'] ?? '' ) : '';
$score_badge = wpvulnerability_render_score_badge( $score, $sev_raw, $epss );
$show_active = $kev || 'active' === $exploitation;
$show_poc = 'poc' === $exploitation;
$show_auto = 'yes' === $automatable;
$information .= '<tr>';
$information .= '<td style="max-width: 256px; min-width: 96px; vertical-align: top; padding-top: 6px;">WordPress <b>' . wp_kses( $vuln_name, 'strip' ) . '</b></td>';
$information .= '<td>';
if ( $show_active || $show_poc || $show_auto || '' !== $score_badge ) {
$information .= '<div style="display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-bottom:5px;">';
if ( $show_active ) {
$information .= '<span class="wpvuln-kev-label">&#9888; ' . esc_html__( 'Actively exploited', 'wpvulnerability' );
if ( $kev && null !== $kev_date ) {
$information .= ' &middot; ' . esc_html( $kev_date );
}
$information .= '</span>';
}
if ( $show_poc ) {
$information .= '<span class="wpvuln-poc-label">&#9889; ' . esc_html__( 'Public exploit', 'wpvulnerability' ) . '</span>';
}
if ( $show_auto ) {
$information .= '<span class="wpvuln-auto-label">&#9881; ' . esc_html__( 'Automatable', 'wpvulnerability' ) . '</span>';
}
if ( '' !== $score_badge ) {
$information .= $score_badge;
}
$information .= '</div>';
}
if ( null !== $description ) {
$information .= '<div style="padding-bottom: 5px;">' . esc_html( $description ) . '</div>';
}
if ( count( $what ) ) {
$information .= '<div style="padding-bottom: 5px;">' . implode( '', $what ) . '</div>';
}
if ( '' !== $source_pills ) {
$information .= '<div class="wpvuln-refs-row"><span class="wpvuln-refs-label">' . esc_html__( 'References:', 'wpvulnerability' ) . '</span>';
$information .= $source_pills;
$information .= '</div>';
}
$information .= '</td>';
$information .= '</tr>';
}
$information .= '</table>';
echo $information; // phpcs:ignore
}
/**
* Retrieves vulnerabilities for a given WordPress core version and updates its data.
*
* @since 2.0.0
*
* @return array<int, array<string, mixed>>|false The updated core data array or false if no vulnerabilities are found.
*/
function wpvulnerability_get_fresh_core_vulnerabilities() {
// Get the core version and sanitize it.
$version = wpvulnerability_sanitize_version( get_bloginfo( 'version' ) );
// Retrieve vulnerabilities for the core version.
$response = wpvulnerability_get_core( $version, 0 );
$core_data = array();
// If no vulnerabilities are found, return false.
if ( empty( $response ) ) {
return false;
}
// If vulnerabilities are found, update the core data.
foreach ( $response as $v ) {
if ( isset( $v['name'], $v['source'], $v['impact'] ) ) { // Ensure expected keys exist.
$core_data[] = array(
'name' => wp_kses( is_scalar( $v['name'] ) ? (string) $v['name'] : '', 'strip' ),
'source' => $v['source'],
'impact' => $v['impact'],
'uuid' => is_scalar( $v['uuid'] ?? '' ) ? (string) ( $v['uuid'] ?? '' ) : '',
);
}
}
return ! empty( $core_data ) ? $core_data : false; // Return false if core_data is empty.
}
/**
* Get Vulnerabilities
*
* Retrieves and caches the vulnerabilities for the installed WordPress core version.
*
* @since 2.0.0
*
* @return string JSON-encoded array of core data with vulnerabilities and vulnerable status.
*/
function wpvulnerability_core_get_installed() {
$wpvulnerability_core_vulnerable = 0;
$current_version = wpvulnerability_sanitize_version( get_bloginfo( 'version' ) );
// Get fresh core vulnerabilities.
$core = wpvulnerability_get_fresh_core_vulnerabilities();
// Check if vulnerabilities were found and count them.
if ( is_array( $core ) && count( $core ) > 0 ) {
$wpvulnerability_core_vulnerable = count( $core );
}
// Cache the vulnerability data and the timestamp for cache expiration.
if ( is_multisite() ) {
update_site_option( 'wpvulnerability-core', wp_json_encode( $core ) );
update_site_option( 'wpvulnerability-core-vulnerable', wp_json_encode( number_format( $wpvulnerability_core_vulnerable, 0, '.', '' ) ) );
update_site_option( 'wpvulnerability-core-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
update_site_option( 'wpvulnerability-core-version', wp_json_encode( $current_version ) );
} else {
update_option( 'wpvulnerability-core', wp_json_encode( $core ), false );
update_option( 'wpvulnerability-core-vulnerable', wp_json_encode( number_format( $wpvulnerability_core_vulnerable, 0, '.', '' ) ), false );
update_option( 'wpvulnerability-core-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ), false );
update_option( 'wpvulnerability-core-version', wp_json_encode( $current_version ), false );
}
// Return the JSON-encoded array of core vulnerabilities.
$encoded = wp_json_encode( $core );
return false !== $encoded ? $encoded : '[]';
}
/**
* Get cached core vulnerabilities without refreshing external data.
*
* @since 2.0.0
* @since 4.1.2 Refreshes when the stored WordPress core version differs from the running version.
*
* @return array<mixed> Array of core with their vulnerabilities.
*/
function wpvulnerability_core_get_vulnerabilities() {
if ( is_multisite() ) {
$raw = get_site_option( 'wpvulnerability-core' );
$core_data = json_decode( is_string( $raw ) ? $raw : '', true );
} else {
$raw = get_option( 'wpvulnerability-core' );
$core_data = json_decode( is_string( $raw ) ? $raw : '', true );
}
return is_array( $core_data ) ? $core_data : array();
}
/**
* Update the core cache and remove any old cache data.
*
* @since 2.0.0
*
* @return void
*/
function wpvulnerability_core_get_vulnerabilities_clean() {
wpvulnerability_clear_cache( 'core' );
wpvulnerability_core_get_installed();
}
/**
* Adds vulnerability information after the core version and notices on the update-core.php page.
*
* @since 2.0.0
*
* @return void
*/
function wpvulnerability_core_page() {
// Check if the current page is the update-core.php page.
global $pagenow;
if ( wpvulnerability_analyze_filter( 'core' ) && 'update-core.php' === $pagenow && wpvulnerability_capabilities() ) {
// Get the vulnerabilities for the core.
$core = wpvulnerability_core_get_vulnerabilities();
// If there are vulnerabilities, add an action to display them after the core auto updates settings.
if ( ! empty( $core ) ) {
add_action( 'after_core_auto_updates_settings', 'wpvulnerability_core_info_after' );
}
}
}
// Add notices for vulnerable core on the core page.
add_action( 'admin_head', 'wpvulnerability_core_page' );