wpvulnerability/wpvulnerability-software.php
2026-07-09 06:20:06 +00:00

277 lines
8.9 KiB
PHP

<?php
/**
* Software functions
*
* @package WPVulnerability
*
* @version 3.5.0
*/
defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
/**
* Retrieves the specified software version.
*
* This function returns the version of PHP, Apache, Nginx, MySQL, or MariaDB
* after performing necessary validations. It ensures that the returned value
* is clean and sanitized.
*
* @since 3.5.0
* @since 4.3.0 Updated to handle hybrid detection format from ImageMagick, Redis, Memcached, and SQLite.
*
* @param string $software The name of the software ('php', 'apache', 'nginx', 'mysql', 'mariadb').
* @return string|null The sanitized version of the software, or null if not found.
*/
function wpvulnerability_get_software_version( $software ) {
switch ( $software ) {
case 'php':
$php_version = wpvulnerability_detect_php();
if ( null !== $php_version && '' !== $php_version ) {
return wp_kses( (string) $php_version, 'strip' );
}
break;
case 'apache':
case 'nginx':
$webserver = wpvulnerability_detect_webserver();
if ( isset( $webserver['id'] ) && $webserver['id'] === $software && ! empty( $webserver['version'] ) ) {
return wp_kses( (string) $webserver['version'], 'strip' );
}
break;
case 'mysql':
case 'mariadb':
$sqlserver = wpvulnerability_detect_sqlserver();
if ( isset( $sqlserver['id'] ) && $sqlserver['id'] === $software && ! empty( $sqlserver['version'] ) ) {
return wp_kses( (string) $sqlserver['version'], 'strip' );
}
break;
case 'imagemagick':
$detection = wpvulnerability_detect_imagemagick();
if ( isset( $detection['version'] ) && 'unknown' !== $detection['version'] ) {
return wp_kses( (string) $detection['version'], 'strip' );
}
break;
case 'curl':
$curl_version = wpvulnerability_detect_curl();
if ( null !== $curl_version && '' !== $curl_version ) {
return wp_kses( (string) $curl_version, 'strip' );
}
break;
case 'memcached':
$detection = wpvulnerability_detect_memcached();
if ( isset( $detection['version'] ) && 'unknown' !== $detection['version'] ) {
return wp_kses( (string) $detection['version'], 'strip' );
}
break;
case 'redis':
$detection = wpvulnerability_detect_redis();
if ( isset( $detection['version'] ) && 'unknown' !== $detection['version'] ) {
return wp_kses( (string) $detection['version'], 'strip' );
}
break;
case 'sqlite':
$detection = wpvulnerability_detect_sqlite();
if ( isset( $detection['version'] ) && 'unknown' !== $detection['version'] ) {
return wp_kses( (string) $detection['version'], 'strip' );
}
break;
default:
break;
}
return null;
}
/**
* Retrieves vulnerabilities for a given software version and updates its data.
*
* This function detects the installed software version, checks for vulnerabilities using an external API,
* and updates the data array with the vulnerabilities found.
*
* @since 3.5.0
*
* @param string $software The software name (e.g., 'php', 'apache', 'nginx', 'mysql', 'mariadb').
*
* @return array<string, mixed> The updated data array containing vulnerability information.
*/
function wpvulnerability_get_fresh_vulnerabilities( $software ) {
$version = null;
$data = array(
'vulnerabilities' => null,
'vulnerable' => 0,
'lifecycle' => array(),
);
switch ( $software ) {
case 'php':
case 'apache':
case 'nginx':
case 'mysql':
case 'mariadb':
case 'imagemagick':
case 'curl':
case 'memcached':
case 'redis':
case 'sqlite':
$version = wpvulnerability_get_software_version( $software );
break;
default:
return $data;
}
if ( $version ) {
$transient_key = 'wpvulnerability_' . $software;
// Delete the transient so the next call fetches fresh data and repopulates it.
if ( is_multisite() ) {
delete_site_transient( $transient_key );
} else {
delete_transient( $transient_key );
}
switch ( $software ) {
case 'php':
case 'apache':
case 'nginx':
case 'mysql':
case 'mariadb':
case 'imagemagick':
case 'curl':
case 'memcached':
case 'redis':
case 'sqlite':
// cache=1: transient was just cleared, so a fresh API call is made and result cached.
$api_response = wpvulnerability_get_vulnerabilities( $software, $version, 1 );
break;
}
if ( ! empty( $api_response ) ) {
$data['vulnerabilities'] = $api_response;
$data['vulnerable'] = 1;
}
// Read back the transient to extract lifecycle fields from the full API response.
$raw_body = is_multisite() ? get_site_transient( $transient_key ) : get_transient( $transient_key );
$raw_response = json_decode( is_string( $raw_body ) ? $raw_body : '', true );
if ( is_array( $raw_response ) && isset( $raw_response['data'] ) && is_array( $raw_response['data'] ) ) {
$resp_data = $raw_response['data'];
$data['lifecycle'] = array(
'name' => is_scalar( $resp_data['name'] ?? '' ) ? (string) ( $resp_data['name'] ?? '' ) : '',
'status' => is_scalar( $resp_data['status'] ?? '' ) ? (string) ( $resp_data['status'] ?? '' ) : '',
'date_start' => is_scalar( $resp_data['date_start'] ?? '' ) ? (string) ( $resp_data['date_start'] ?? '' ) : '',
'date_end' => is_scalar( $resp_data['date_end'] ?? '' ) ? (string) ( $resp_data['date_end'] ?? '' ) : '',
);
}
}
return $data;
}
/**
* Get Installed Software
*
* Retrieves the list of installed software versions, checks for vulnerabilities,
* caches the data, and sends an email notification if vulnerabilities are detected.
*
* @since 3.5.0
*
* @param string $software The software name (e.g., 'php', 'apache').
*
* @return string JSON-encoded array of software data with vulnerabilities and vulnerable status.
*/
function wpvulnerability_get_installed( $software ) {
$wpvulnerability_software_vulnerable = 0;
// Retrieve fresh vulnerabilities for the installed software version.
$data = wpvulnerability_get_fresh_vulnerabilities( $software );
// Check if the software version is vulnerable and count the vulnerabilities.
if ( isset( $data['vulnerable'] ) && is_numeric( $data['vulnerable'] ) && (int) $data['vulnerable'] ) {
$vulns = isset( $data['vulnerabilities'] ) && is_array( $data['vulnerabilities'] ) ? $data['vulnerabilities'] : array();
$wpvulnerability_software_vulnerable = count( $vulns );
}
// Cache the vulnerability data and the timestamp for cache expiration.
if ( is_multisite() ) {
update_site_option( 'wpvulnerability-' . $software, wp_json_encode( $data ) );
update_site_option( 'wpvulnerability-' . $software . '-vulnerable', wp_json_encode( number_format( $wpvulnerability_software_vulnerable, 0, '.', '' ) ) );
update_site_option( 'wpvulnerability-' . $software . '-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
} else {
update_option( 'wpvulnerability-' . $software, wp_json_encode( $data ), false );
update_option( 'wpvulnerability-' . $software . '-vulnerable', wp_json_encode( number_format( $wpvulnerability_software_vulnerable, 0, '.', '' ) ), false );
update_option( 'wpvulnerability-' . $software . '-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ), false );
}
// Return the JSON-encoded array of software data.
$encoded = wp_json_encode( $data );
return false !== $encoded ? $encoded : '';
}
/**
* Get cached software vulnerabilities without triggering remote calls.
*
* @since 3.5.0
*
* @param string $software The software name (e.g., 'php', 'apache').
*
* @return array<string, mixed>|null Array of software data with vulnerabilities, or null if software is invalid.
*/
function wpvulnerability_software_get_vulnerabilities( $software ) {
$valid_software = array( 'php', 'apache', 'mariadb', 'mysql', 'nginx', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
// Use strict comparison for in_array.
if ( in_array( $software, $valid_software, true ) ) {
if ( is_multisite() ) {
$raw = get_site_option( 'wpvulnerability-' . $software );
$data = json_decode( is_string( $raw ) ? $raw : '', true );
} else {
$raw = get_option( 'wpvulnerability-' . $software );
$data = json_decode( is_string( $raw ) ? $raw : '', true );
}
return is_array( $data ) ? $data : array();
} else {
return null;
}
}
/**
* Update the software cache and remove any old cache data.
*
* @since 3.0.0
*
* @param string $software The software name (e.g., 'php', 'apache').
*
* @return void
*/
function wpvulnerability_get_vulnerabilities_clean( $software ) {
// Skip detection for components the administrator has hidden (either via the
// analysis settings or a WPVULNERABILITY_HIDE_* wp-config constant). This
// prevents shell_exec from running for components that are meant to be
// deactivated, matching the behaviour of the core/plugins/themes clean functions.
if ( ! wpvulnerability_analyze_filter( $software ) ) {
return;
}
// Update the installed software cache.
wpvulnerability_get_installed( $software );
}