mirror of
https://gh.wpcy.net/https://github.com/mainwp/mainwp-child.git
synced 2026-04-23 10:32:22 +08:00
Populate cvss_score and cvss_rating in the vulnerability info by extracting CVSS data from the incoming CVE payload. The code now checks for cvssMetricV31, then cvssMetricV30, and finally cvssMetricV2 (using baseSeverity if available) and sets baseScore/baseSeverity accordingly before continuing with existing processing.
877 lines
40 KiB
PHP
877 lines
40 KiB
PHP
<?php
|
|
/**
|
|
* MainWP Child Vulnerability Checker
|
|
*
|
|
* MainWP Vulnerability Checker Extension handler.
|
|
*
|
|
* @link https://mainwp.com/extension/vulnerability-checker/
|
|
*
|
|
* @package MainWP\Child
|
|
*
|
|
* Credits
|
|
*
|
|
* Plugin-Name: Vulnerability Alerts
|
|
* Plugin URI: http://wordpress.org/plugins/vulnerability-alerts/
|
|
* Author: Edir Pedro
|
|
* Author URI: http://edirpedro.com.br
|
|
* License: GPL2
|
|
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
|
*/
|
|
|
|
namespace MainWP\Child;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class MainWP_Child_Vulnerability_Checker
|
|
*
|
|
* MainWP Vulnerability Checker Extension handler.
|
|
*/
|
|
class MainWP_Child_Vulnerability_Checker {
|
|
|
|
/**
|
|
* Public static variable to hold the single instance of the class.
|
|
*
|
|
* @var mixed Default null
|
|
*/
|
|
public static $instance = null;
|
|
|
|
/**
|
|
* WPVulnDB API URL
|
|
*
|
|
* @var string
|
|
*/
|
|
private $wpvulndb_api = 'https://wpvulndb.com/api/v3/';
|
|
|
|
/**
|
|
* Nvd.nist.gov API URL
|
|
*
|
|
* @var string
|
|
*/
|
|
private $wpvulndb_nvd_api = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
|
|
|
|
/**
|
|
* Wordfence API URL
|
|
*
|
|
* @var string
|
|
*/
|
|
private $wordfence_api_url = 'https://www.wordfence.com/api/intelligence/v2/vulnerabilities/';
|
|
|
|
/**
|
|
* Method instance()
|
|
*
|
|
* Create a public static instance.
|
|
*
|
|
* @return mixed Class instance.
|
|
*/
|
|
public static function instance() {
|
|
if ( null === static::$instance ) {
|
|
static::$instance = new self();
|
|
}
|
|
return static::$instance;
|
|
}
|
|
|
|
/**
|
|
* MainWP_Child_Vulnerability_Checker constructor.
|
|
*
|
|
* Run any time class is called.
|
|
*/
|
|
public function __construct() {
|
|
$wpvulndb_token = get_option( 'mainwp_child_wpvulndb_token', '' );
|
|
if ( ! empty( $wpvulndb_token ) ) {
|
|
delete_option( 'mainwp_child_wpvulndb_token' ); // old value.
|
|
}
|
|
add_action( 'mainwp_child_cron_plugin_vuln_nvd_continue_check', array( $this, 'cron_nvd_continue_check_plugins' ) );
|
|
add_action( 'mainwp_child_cron_plugin_vuln_nvd_continue_force_check', array( $this, 'cron_nvd_continue_force_check_plugins' ) );
|
|
}
|
|
|
|
/**
|
|
* MainWP Child Vulnerability Checker actions.
|
|
*
|
|
* @uses \MainWP\Child\MainWP_Helper::write()
|
|
*/
|
|
public function action() {
|
|
$information = array();
|
|
$mwp_action = MainWP_System::instance()->validate_params( 'mwp_action' );
|
|
|
|
if ( 'vulner_recheck' === $mwp_action ) {
|
|
$information = $this->vulner_recheck();
|
|
}
|
|
|
|
MainWP_Helper::write( $information );
|
|
}
|
|
|
|
/**
|
|
* Check for vulnerabilities.
|
|
*
|
|
* @return array Return an array $information[] containing $results[] array.
|
|
*/
|
|
public function vulner_recheck() {
|
|
$result = array();
|
|
// phpcs:disable WordPress.Security.NonceVerification
|
|
$force = ( isset( $_POST['force'] ) && ! empty( $_POST['force'] ) ) ? true : false;
|
|
|
|
$service = '';
|
|
if ( isset( $_POST['service'] ) && 'nvd_nist' === $_POST['service'] ) {
|
|
$service = 'nvd_nist';
|
|
} elseif ( isset( $_POST['service'] ) && 'wordfence' === $_POST['service'] ) {
|
|
$service = 'wordfence';
|
|
} else {
|
|
$service = 'wpvulndb';
|
|
}
|
|
|
|
if ( 'wpvulndb' === $service ) {
|
|
if ( empty( $_POST['wpvulndb_tk'] ) ) {
|
|
return array( 'error' => 'Error: empty WPScan API token.' );
|
|
}
|
|
$encrypted = MainWP_Child_Keys_Manager::instance()->encrypt_string( sanitize_text_field( wp_unslash( $_POST['wpvulndb_tk'] ) ) );
|
|
set_transient( 'mainwp_child_trans_wpvulndb_tk', $encrypted, 30 * MINUTE_IN_SECONDS );
|
|
} elseif ( ! empty( $_POST['nvd_nist_tk'] ) ) {
|
|
$encrypted = MainWP_Child_Keys_Manager::instance()->encrypt_string( sanitize_text_field( wp_unslash( $_POST['nvd_nist_tk'] ) ) );
|
|
set_transient( 'mainwp_child_trans_nvd_nist_tk', $encrypted, 30 * MINUTE_IN_SECONDS );
|
|
} else {
|
|
delete_transient( 'mainwp_child_trans_nvd_nist_tk' );
|
|
}
|
|
// phpcs:enable
|
|
|
|
$result['plugin'] = $this->check_plugins( $force, $service );
|
|
$result['wp'] = $this->check_wp( $force, $service );
|
|
$result['theme'] = $this->check_themes( $force, $service );
|
|
return array(
|
|
'result' => $result,
|
|
'ok' => 1,
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Method is_fixed_version.
|
|
*
|
|
* @param mixed $version version.
|
|
* @param mixed $data data.
|
|
* @return bool
|
|
*/
|
|
public function is_fixed_version( $version, $data ) {
|
|
if ( ! is_array( $data ) || ! isset( $data['from_version'] ) || ! isset( $data['to_version'] ) || ( '*' === $data['from_version'] && '*' === $data['to_version'] ) ) {
|
|
return false;
|
|
}
|
|
if ( '*' === $data['from_version'] ) {
|
|
$data['from_version'] = '0';
|
|
}
|
|
$fromOperator = $data['from_inclusive'] ? '>=' : '>';
|
|
$toOperator = $data['to_inclusive'] ? '<=' : '<';
|
|
return version_compare( $version, $data['from_version'], $fromOperator ) && version_compare( $version, $data['to_version'], $toOperator ) ? false : true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get vuln wordfence info.
|
|
*
|
|
* @param mixed $data data.
|
|
* @param mixed $slug slug.
|
|
* @param mixed $version version.
|
|
* @return array
|
|
*/
|
|
public function get_vuln_wordfence_info( $data, $slug, $version ) { //phpcs:ignore -- NOSONAR - complexity.
|
|
|
|
$data = json_decode( $data, true );
|
|
$filtered_data = array();
|
|
|
|
if ( is_array( $data ) && isset( $data['result'] ) && isset( $data['result']['CVE_Items'] ) ) {
|
|
$vulns = array();
|
|
$version_missed = true;
|
|
$fixed = false;
|
|
|
|
foreach ( $data['result']['CVE_Items'] as $item ) {
|
|
$info = array();
|
|
$remediation = '';
|
|
foreach ( $item['software'] as $software ) {
|
|
foreach ( $software['affected_versions'] as $affected_version ) {
|
|
$fixed = $this->is_fixed_version( $version, $affected_version );
|
|
$version_missed = false;
|
|
if ( $fixed ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
$remediation .= ' [' . ucfirst( $software['type'] ) . '->' . $software['name'] . '] ' . $software['remediation'];
|
|
}
|
|
|
|
if ( ! $fixed ) {
|
|
if ( isset( $item['published'] ) ) {
|
|
$info['date'] = $item['published'];
|
|
}
|
|
|
|
if ( isset( $item['description'] ) ) {
|
|
$info['detail'] = $item['description'] . $remediation;
|
|
}
|
|
|
|
$customCveId = explode( '-', $item['id'] );
|
|
$info['cve_id'] = $item['cve'] ?? end( $customCveId );
|
|
$info['slug'] = $slug;
|
|
|
|
if ( isset( $item['title'] ) ) {
|
|
$info['title'] = $item['title'];
|
|
}
|
|
if ( isset( $item['cvss'] ) ) {
|
|
$info['cvss_score'] = $item['cvss']['score'];
|
|
$info['cvss_rating'] = $item['cvss']['rating'];
|
|
}
|
|
if ( isset( $item['software'][0]['patched'] ) ) {
|
|
$info['patched'] = $item['software'][0]['patched'];
|
|
}
|
|
if ( isset( $item['software'][0]['name'] ) ) {
|
|
$info['software_name'] = $item['software'][0]['name'];
|
|
}
|
|
}
|
|
if ( $version_missed ) {
|
|
$info['missed_version'] = 1;
|
|
}
|
|
if ( ! empty( $info ) ) {
|
|
$vulns[] = $info;
|
|
}
|
|
}
|
|
$filtered_data[ $slug ]['vulnerabilities'] = $vulns;
|
|
}
|
|
|
|
return $filtered_data;
|
|
}
|
|
|
|
/**
|
|
* Handle continue check for plugin vulnerabilities.
|
|
*/
|
|
public function cron_nvd_continue_check_plugins() {
|
|
$this->check_plugins( false, 'nvd_nist' );
|
|
}
|
|
|
|
/**
|
|
* Handle continue force check for plugin vulnerabilities.
|
|
*/
|
|
public function cron_nvd_continue_force_check_plugins() {
|
|
$this->check_plugins( true, 'nvd_nist' );
|
|
}
|
|
|
|
/**
|
|
* Check for plugin vulnerabilities.
|
|
*
|
|
* @param bool $force Whether or not to force check. Default: false.
|
|
* @param bool $service Selected service.
|
|
*
|
|
* @return array Return $result array.
|
|
*/
|
|
public function check_plugins( $force = false, $service = '' ) { //phpcs:ignore -- NOSONAR - ignore complex.
|
|
$result = array();
|
|
|
|
if ( ! function_exists( '\get_plugins' ) ) {
|
|
require_once ABSPATH . 'wp-admin/includes/plugin.php'; // NOSONAR - ok.
|
|
}
|
|
$all_plugins = \get_plugins();
|
|
$limit_request = 0;
|
|
$max_request = 5;
|
|
$nvd_nist_token = '';
|
|
|
|
$nvd_continue_checks = array();
|
|
|
|
if ( 'nvd_nist' === $service ) {
|
|
|
|
$encrypted = get_transient( 'mainwp_child_trans_nvd_nist_tk' );
|
|
$nvd_nist_token = MainWP_Child_Keys_Manager::instance()->decrypt_string( $encrypted );
|
|
|
|
if ( ! empty( $nvd_nist_token ) ) {
|
|
$max_request = 50;
|
|
} else {
|
|
$nvd_continue_checks = get_option( 'mainwp_child_vuln_nvd_checking_list', '' );
|
|
if ( empty( $nvd_continue_checks ) ) {
|
|
$nvd_continue_checks = array();
|
|
foreach ( $all_plugins as $plug => $plugin_info ) {
|
|
$nvd_continue_checks[ $plug ] = 0; // not checked.
|
|
}
|
|
update_option( 'mainwp_child_vuln_nvd_checking_list', wp_json_encode( $nvd_continue_checks ) );
|
|
} else {
|
|
$nvd_continue_checks = json_decode( $nvd_continue_checks, true );
|
|
}
|
|
|
|
if ( ! is_array( $nvd_continue_checks ) ) {
|
|
$nvd_continue_checks = array();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! empty( $all_plugins ) ) {
|
|
foreach ( $all_plugins as $plug => $plugin_info ) {
|
|
|
|
$plugin_version = isset( $plugin_info['Version'] ) ? $plugin_info['Version'] : '';
|
|
$plugin_name = isset( $plugin_info['Name'] ) ? strtolower( $plugin_info['Name'] ) : '';
|
|
|
|
if ( empty( $plugin_name ) ) {
|
|
continue;
|
|
}
|
|
|
|
$slug = str_replace( array( '-', ' ' ), '_', $plugin_name );
|
|
$string = explode( '/', $plug );
|
|
$plug_vuln = get_transient( 'mainwp_vulnche_trans_plug_' . $string[0] );
|
|
|
|
if ( false === $plug_vuln || $force ) {
|
|
|
|
if ( 'nvd_nist' === $service ) {
|
|
if ( 'wordfence' === strtolower( $string[0] ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ! empty( $nvd_continue_checks[ $plug ] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$nvd_continue_checks[ $plug ] = 1;
|
|
|
|
++$limit_request;
|
|
|
|
if ( $limit_request >= $max_request ) {
|
|
if ( ! empty( $nvd_nist_token ) ) {
|
|
sleep( 30 );
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// URL Syntax example: https://services.nvd.nist.gov/rest/json/cves/2.0?virtualMatchString=cpe:2.3:a:automattic:akismet:* .
|
|
$url = $this->wpvulndb_nvd_api . '?virtualMatchString=cpe:2.3:a:*:' . $slug . ':*:*:*:*:*:wordpress:*:*'; //phpcs:ignore --Misspelled.
|
|
} elseif ( 'wordfence' === $service ) {
|
|
$url = $this->wordfence_api_url . 'production?keyword=' . $string[0];
|
|
} else {
|
|
$url = $this->wpvulndb_api . 'plugins/' . $string[0];
|
|
}
|
|
$plug_vuln = $this->vulnche_get_content( $url, $service );
|
|
set_transient( 'mainwp_vulnche_trans_plug_' . $string[0], $plug_vuln, 1 * DAY_IN_SECONDS );
|
|
} elseif ( 'nvd_nist' === $service ) {
|
|
$nvd_continue_checks[ $plug ] = 1;
|
|
}
|
|
|
|
if ( $plug_vuln ) {
|
|
$plug_vuln_filter = array();
|
|
if ( 'nvd_nist' === $service ) {
|
|
$plug_vuln_filter = $this->get_vuln_nvd_nist_info( $plug_vuln, $string[0], $plugin_version );
|
|
} elseif ( 'wordfence' === $service ) {
|
|
$plug_vuln_filter = $this->get_vuln_wordfence_info( $plug_vuln, $string[0], $plugin_version );
|
|
} else {
|
|
$plug_vuln = json_decode( $plug_vuln, true );
|
|
$plug_vuln_filter = $plug_vuln;
|
|
foreach ( $plug_vuln as $slug => $pl_data ) {
|
|
if ( isset( $pl_data['vulnerabilities'] ) && count( $pl_data['vulnerabilities'] ) > 0 ) {
|
|
$plug_vulner_data = array();
|
|
foreach ( $pl_data['vulnerabilities'] as $vuln_data ) {
|
|
if ( isset( $vuln_data['fixed_in'] ) && version_compare( $plugin_version, $vuln_data['fixed_in'] ) >= 0 ) {
|
|
continue;
|
|
}
|
|
$plug_vulner_data[] = $vuln_data;
|
|
}
|
|
|
|
if ( empty( $plug_vulner_data ) ) {
|
|
unset( $plug_vuln_filter[ $slug ] );
|
|
} else {
|
|
$plug_vuln_filter[ $slug ]['vulnerabilities'] = $plug_vulner_data;
|
|
$plug_vuln_filter[ $slug ]['detected_version'] = $plugin_version;
|
|
$plug_vuln_filter[ $slug ]['plugin_slug'] = $plug;
|
|
}
|
|
} else {
|
|
unset( $plug_vuln_filter[ $slug ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( empty( $plug_vuln_filter ) ) {
|
|
continue;
|
|
}
|
|
$plug_vuln = wp_json_encode( $plug_vuln_filter );
|
|
} else {
|
|
continue;
|
|
}
|
|
$result[ $plug ] = $plug_vuln;
|
|
}
|
|
|
|
if ( 'nvd_nist' === $service && empty( $nvd_nist_token ) ) {
|
|
if ( $limit_request < $max_request ) {
|
|
// finished bulk checks.
|
|
update_option( 'mainwp_child_vuln_nvd_checking_list', '' ); // clear list.
|
|
} else {
|
|
update_option( 'mainwp_child_vuln_nvd_checking_list', wp_json_encode( $nvd_continue_checks ) );
|
|
if ( $force ) {
|
|
wp_schedule_single_event( time() + 30, 'mainwp_child_cron_plugin_vuln_nvd_continue_force_check' );
|
|
} else {
|
|
wp_schedule_single_event( time() + 30, 'mainwp_child_cron_plugin_vuln_nvd_continue_check' );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Check for WP vulnerabilities.
|
|
*
|
|
* @param bool $force Whether to force check. Default: false.
|
|
* @param bool $service Selected service.
|
|
* @return bool|string|null
|
|
*/
|
|
public function check_wp( $force = false, $service = '' ) {
|
|
$wp_vuln = get_transient( 'mainwp_vulnche_trans_wp_json' );
|
|
// Get WordPress version via the shared helper method for consistency.
|
|
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Using static access for centralized version retrieval
|
|
$wp_ver = MainWP_Child_Server_Information_Base::get_wordpress_version();
|
|
$number_version = str_replace( '.', '', $wp_ver );
|
|
if ( false === $wp_vuln || $force ) {
|
|
if ( 'nvd_nist' === $service ) {
|
|
$url = $this->wpvulndb_nvd_api . '?virtualMatchString=cpe:2.3:a:wordpress:wordpress:' . $wp_ver; //phpcs:ignore -- Misspelled.
|
|
} elseif ( 'wordfence' === $service ) {
|
|
$url = $this->wordfence_api_url . 'production?keyword=wordpress';//phpcs:ignore -- Misspelled.
|
|
} else {
|
|
$url = $this->wpvulndb_api . 'wordpresses/' . $number_version;
|
|
}
|
|
$wp_vuln = $this->vulnche_get_content( $url, $service );
|
|
|
|
if ( 'nvd_nist' === $service ) {
|
|
$wp_vuln = $this->get_vuln_nvd_nist_info( $wp_vuln, 'WordPress', $wp_ver ); //phpcs:ignore -- wordpress.
|
|
$wp_vuln = wp_json_encode( $wp_vuln );
|
|
}
|
|
|
|
if ( 'wordfence' === $service ) {
|
|
$wp_vuln = $this->get_vuln_wordfence_info( $wp_vuln, 'WordPress', $wp_ver );
|
|
$wp_vuln = wp_json_encode( $wp_vuln );
|
|
}
|
|
|
|
set_transient( 'mainwp_vulnche_trans_wp_json', $wp_vuln, 1 * DAY_IN_SECONDS );
|
|
}
|
|
return $wp_vuln;
|
|
}
|
|
|
|
/**
|
|
* Check if themes have vunerabilities.
|
|
*
|
|
* @param bool $force Whether or not to force check. Default: false.
|
|
* @param bool $service Selected service.
|
|
*
|
|
* @return array Return $result array.
|
|
*/
|
|
public function check_themes( $force = false, $service = '' ) { // phpcs:ignore -- NOSONAR - ignore complex method notice.
|
|
|
|
include_once ABSPATH . '/wp-admin/includes/update.php'; // NOSONAR -- WP compatible.
|
|
require_once ABSPATH . 'wp-admin/includes/misc.php'; // NOSONAR -- WP compatible.
|
|
require_once ABSPATH . 'wp-admin/includes/theme.php'; // NOSONAR -- WP compatible.
|
|
|
|
if ( current_user_can( 'switch_themes' ) ) {
|
|
$themes = wp_prepare_themes_for_js();
|
|
} else {
|
|
$themes = wp_prepare_themes_for_js( array( wp_get_theme() ) );
|
|
}
|
|
wp_reset_vars( array( 'theme', 'search' ) );
|
|
$result = array();
|
|
if ( ! empty( $themes ) ) {
|
|
foreach ( $themes as $th ) {
|
|
if ( empty( $th['parent'] ) ) {
|
|
$th_vuln = get_transient( 'mainwp_vulnche_trans_theme_' . $th['id'] );
|
|
if ( false === $th_vuln || $force ) {
|
|
|
|
if ( 'nvd_nist' === $service ) {
|
|
$url = $this->wpvulndb_nvd_api . '?keywordSearch=' . $th['id'] . '&keywordExactMatch';
|
|
} elseif ( 'wordfence' === $service ) {
|
|
$url = $this->wordfence_api_url . 'production?keyword=' . $th['id'];
|
|
} else {
|
|
$url = $this->wpvulndb_api . 'themes/' . $th['id'];
|
|
}
|
|
|
|
$th_vuln = $this->vulnche_get_content( $url, $service );
|
|
set_transient( 'mainwp_vulnche_trans_theme_' . $th['id'], $th_vuln, 1 * DAY_IN_SECONDS );
|
|
}
|
|
|
|
if ( $th_vuln ) {
|
|
if ( 'nvd_nist' === $service ) {
|
|
$th_vuln_filter = $this->get_vuln_nvd_nist_info( $th_vuln, $th['id'], $th['version'] );
|
|
} elseif ( 'wordfence' === $service ) {
|
|
$th_vuln_filter = $this->get_vuln_wordfence_info( $th_vuln, $th['id'], $th['version'] );
|
|
} else {
|
|
$th_vuln = json_decode( $th_vuln, true );
|
|
$th_vuln_filter = $th_vuln;
|
|
foreach ( $th_vuln as $slug => $th_data ) {
|
|
if ( isset( $th_data['vulnerabilities'] ) && count( $th_data['vulnerabilities'] ) > 0 ) {
|
|
$th_vulner_data = array();
|
|
foreach ( $th_data['vulnerabilities'] as $vuln_data ) {
|
|
if ( empty( $vuln_data ) ) {
|
|
continue;
|
|
}
|
|
if ( isset( $vuln_data['fixed_in'] ) && version_compare( $th['version'], $vuln_data['fixed_in'] ) >= 0 ) {
|
|
continue;
|
|
}
|
|
$th_vulner_data[] = $vuln_data;
|
|
}
|
|
if ( empty( $th_vulner_data ) ) {
|
|
unset( $th_vuln_filter[ $slug ] );
|
|
} else {
|
|
$th_vuln_filter[ $slug ]['vulnerabilities'] = $th_vulner_data;
|
|
}
|
|
} else {
|
|
unset( $th_vuln_filter[ $slug ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( empty( $th_vuln_filter ) ) {
|
|
continue;
|
|
}
|
|
$th_vuln = wp_json_encode( $th_vuln_filter );
|
|
|
|
} else {
|
|
continue;
|
|
}
|
|
$result[ $th['id'] ]['vulner_data'] = $th_vuln;
|
|
$result[ $th['id'] ]['name'] = $th['name'];
|
|
$result[ $th['id'] ]['author'] = $th['author'];
|
|
$result[ $th['id'] ]['detected_version'] = $th['version'];
|
|
}
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Check if content is vulnerable.
|
|
*
|
|
* @param mixed $data data of check.
|
|
* @param string $slug slug.
|
|
* @param string $version Current version.
|
|
*
|
|
* @return bool|string|null
|
|
*/
|
|
public function get_vuln_nvd_nist_info( $data, $slug, $version ) { // phpcs:ignore -- NOSONAR - ignore complex method notice.
|
|
$data = ! empty( $data ) ? json_decode( $data, true ) : array();
|
|
|
|
$filtered_data = array();
|
|
if ( is_array( $data ) && isset( $data['vulnerabilities'] ) ) {
|
|
$vulns = array();
|
|
|
|
foreach ( $data['vulnerabilities'] as $item ) {
|
|
|
|
$info = array();
|
|
|
|
if ( isset( $item['cve']['published'] ) ) {
|
|
$info['date'] = $item['cve']['published'];
|
|
}
|
|
|
|
if ( isset( $item['cve']['descriptions'][0]['value'] ) ) {
|
|
$info['detail'] = $item['cve']['descriptions'][0]['value'];
|
|
}
|
|
|
|
$version_missed = true;
|
|
if ( ! empty( $info ) ) {
|
|
$info['cve_id'] = $item['cve']['id'];
|
|
$info['slug'] = $slug;
|
|
|
|
if ( isset( $item['cve']['metrics']['cvssMetricV31'][0]['cvssData'] ) ) {
|
|
$cvss = $item['cve']['metrics']['cvssMetricV31'][0]['cvssData'];
|
|
$info['cvss_score'] = $cvss['baseScore'];
|
|
$info['cvss_rating'] = $cvss['baseSeverity'];
|
|
} elseif ( isset( $item['cve']['metrics']['cvssMetricV30'][0]['cvssData'] ) ) {
|
|
$cvss = $item['cve']['metrics']['cvssMetricV30'][0]['cvssData'];
|
|
$info['cvss_score'] = $cvss['baseScore'];
|
|
$info['cvss_rating'] = $cvss['baseSeverity'];
|
|
} elseif ( isset( $item['cve']['metrics']['cvssMetricV2'][0]['cvssData'] ) ) {
|
|
$cvss = $item['cve']['metrics']['cvssMetricV2'][0]['cvssData'];
|
|
$info['cvss_score'] = $cvss['baseScore'];
|
|
$info['cvss_rating'] = isset( $item['cve']['metrics']['cvssMetricV2'][0]['baseSeverity'] ) ? $item['cve']['metrics']['cvssMetricV2'][0]['baseSeverity'] : '';
|
|
}
|
|
|
|
$fixed = false;
|
|
if ( 'wordpress' === $slug ) { //phpcs:ignore -- wordpress.
|
|
$founded = false;
|
|
if ( isset( $item['configurations'] ) && isset( $item['configurations'][0]['nodes'] ) ) {
|
|
foreach ( $item['configurations'][0]['nodes'] as $conf_node ) {
|
|
if ( isset( $conf_node['cpe_match'] ) ) {
|
|
foreach ( $conf_node['cpe_match'] as $cpe_match ) {
|
|
if ( ! isset( $cpe_match['cpe23Uri'] ) || false === stripos( $cpe_match['cpe23Uri'], 'cpe:2.3:a:wordpress:wordpress' ) ) { //phpcs:ignore --Misspelled.
|
|
continue;
|
|
}
|
|
$founded = true;
|
|
if ( isset( $cpe_match['versionEndExcluding'] ) ) {
|
|
$version_end_excluding = $cpe_match['versionEndExcluding'];
|
|
if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
} elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
|
|
$version_end_including = $cpe_match['versionEndIncluding'];
|
|
if ( version_compare( $version, $version_end_including ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
}
|
|
if ( $fixed ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! $fixed && isset( $conf_node['children'] ) ) {
|
|
foreach ( $conf_node['children'] as $child_node ) {
|
|
if ( isset( $child_node['cpe_match'] ) ) {
|
|
foreach ( $child_node['cpe_match'] as $cpe_match ) {
|
|
if ( ! isset( $cpe_match['cpe23Uri'] ) || false === stripos( $cpe_match['cpe23Uri'], 'cpe:2.3:a:wordpress:wordpress' ) ) { //phpcs:ignore --Misspelled.
|
|
continue;
|
|
}
|
|
$founded = true;
|
|
if ( isset( $cpe_match['versionEndExcluding'] ) ) {
|
|
$version_end_excluding = $cpe_match['versionEndExcluding'];
|
|
if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
} elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
|
|
$version_end_including = $cpe_match['versionEndIncluding'];
|
|
if ( version_compare( $version, $version_end_including ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
}
|
|
if ( $fixed ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( ! $founded ) {
|
|
continue;
|
|
}
|
|
} elseif ( isset( $item['configurations'] ) && isset( $item['configurations'][0]['nodes'] ) ) {
|
|
foreach ( $item['configurations'][0]['nodes'] as $conf_node ) {
|
|
if ( isset( $conf_node['cpe_match'] ) ) {
|
|
foreach ( $conf_node['cpe_match'] as $cpe_match ) {
|
|
|
|
if ( isset( $cpe_match['versionEndExcluding'] ) ) {
|
|
$version_end_excluding = $cpe_match['versionEndExcluding'];
|
|
if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
} elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
|
|
$version_end_including = $cpe_match['versionEndIncluding'];
|
|
if ( version_compare( $version, $version_end_including ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
}
|
|
if ( $fixed ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! $fixed && isset( $conf_node['children'] ) ) {
|
|
foreach ( $conf_node['children'] as $child_node ) {
|
|
if ( isset( $child_node['cpe_match'] ) ) {
|
|
foreach ( $child_node['cpe_match'] as $cpe_match ) {
|
|
if ( isset( $cpe_match['versionEndExcluding'] ) ) {
|
|
$version_end_excluding = $cpe_match['versionEndExcluding'];
|
|
if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
} elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
|
|
$version_end_including = $cpe_match['versionEndIncluding'];
|
|
if ( version_compare( $version, $version_end_including ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
}
|
|
if ( $fixed ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} elseif ( isset( $item['cve']['configurations'] ) && isset( $item['cve']['configurations'][0]['nodes'] ) ) {
|
|
// nvd_nist plugins & themes filter.
|
|
foreach ( $item['cve']['configurations'][0]['nodes'] as $conf_node ) {
|
|
if ( isset( $conf_node['cpeMatch'] ) ) {
|
|
foreach ( $conf_node['cpeMatch'] as $cpe_match ) {
|
|
|
|
if ( isset( $cpe_match['versionEndExcluding'] ) ) {
|
|
$version_end_excluding = $cpe_match['versionEndExcluding'];
|
|
if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
} elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
|
|
$version_end_including = $cpe_match['versionEndIncluding'];
|
|
if ( version_compare( $version, $version_end_including ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
}
|
|
if ( $fixed ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! $fixed && isset( $conf_node['children'] ) ) {
|
|
foreach ( $conf_node['children'] as $child_node ) {
|
|
if ( isset( $child_node['cpeMatch'] ) ) {
|
|
foreach ( $child_node['cpeMatch'] as $cpe_match ) {
|
|
if ( isset( $cpe_match['versionEndExcluding'] ) ) {
|
|
$version_end_excluding = $cpe_match['versionEndExcluding'];
|
|
if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
} elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
|
|
$version_end_including = $cpe_match['versionEndIncluding'];
|
|
if ( version_compare( $version, $version_end_including ) >= 0 ) {
|
|
$fixed = true; // to continue.
|
|
}
|
|
$version_missed = false;
|
|
}
|
|
if ( $fixed ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Plugin was found to be past the vulnerability date.
|
|
if ( $fixed ) {
|
|
continue;
|
|
}
|
|
if ( $version_missed ) {
|
|
$info['missed_version'] = 1;
|
|
}
|
|
$vulns[] = $info;
|
|
}
|
|
}
|
|
$filtered_data[ $slug ]['vulnerabilities'] = $vulns;
|
|
}
|
|
|
|
return $filtered_data;
|
|
}
|
|
|
|
/**
|
|
* Check if content is vulnerable.
|
|
*
|
|
* @param string $url URL to check.
|
|
* @param string $service service to check.
|
|
*
|
|
* @return bool|string|null
|
|
*/
|
|
public function vulnche_get_content( $url, $service = '' ) { //phpcs:ignore -- NOSONAR - complexity.
|
|
|
|
//phpcs:disable WordPress.WP.AlternativeFunctions
|
|
if ( 'wordfence' === $service ) {
|
|
|
|
$typeName = explode( '=', $url );
|
|
if ( empty( $this->wordfenceJson ) ) {
|
|
$ch = curl_init();
|
|
curl_setopt( $ch, CURLOPT_URL, $url );
|
|
curl_setopt( $ch, CURLOPT_HEADER, 0 );
|
|
curl_setopt( $ch, CURLOPT_USERAGENT, $this->get_random_user_agent() );
|
|
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
|
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
|
|
$response = curl_exec( $ch );
|
|
$this->wordfenceJson = json_decode( $response, true );
|
|
}
|
|
$output = array();
|
|
if ( ! empty( $this->wordfenceJson ) && is_array( $this->wordfenceJson ) ) {
|
|
foreach ( $this->wordfenceJson as $feed ) {
|
|
if ( ! empty( $typeName[1] ) && ! empty( $feed['software'][0]['slug'] ) && $typeName[1] === $feed['software'][0]['slug'] ) {
|
|
$output[] = $feed;
|
|
}
|
|
}
|
|
}
|
|
|
|
$data = array(
|
|
'resultsPerPage' => count( $output ),
|
|
'startIndex' => 0,
|
|
'totalResults' => count( $output ),
|
|
'result' => array(
|
|
'CVE_Items' => $output,
|
|
),
|
|
);
|
|
|
|
return json_encode( $data );
|
|
} else {
|
|
$ch = curl_init();
|
|
curl_setopt( $ch, CURLOPT_URL, $url );
|
|
curl_setopt( $ch, CURLOPT_HEADER, 0 );
|
|
if ( 'nvd_nist' !== $service ) {
|
|
$encrypted = get_transient( 'mainwp_child_trans_wpvulndb_tk' );
|
|
$wpvulndb_token = MainWP_Child_Keys_Manager::instance()->decrypt_string( $encrypted );
|
|
if ( empty( $wpvulndb_token ) ) {
|
|
if ( 'resource' === gettype( $ch ) ) {
|
|
curl_close( $ch );
|
|
}
|
|
return false;
|
|
}
|
|
curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Authorization: Token token=' . $wpvulndb_token ) );
|
|
} else {
|
|
$encrypted = get_transient( 'mainwp_child_trans_nvd_nist_tk' );
|
|
$nvd_nist_token = MainWP_Child_Keys_Manager::instance()->decrypt_string( $encrypted );
|
|
|
|
if ( ! empty( $nvd_nist_token ) ) {
|
|
curl_setopt(
|
|
$ch,
|
|
CURLOPT_HTTPHEADER,
|
|
array(
|
|
"apiKey: $nvd_nist_token",
|
|
'Content-Type: application/json',
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
curl_setopt( $ch, CURLOPT_USERAGENT, $this->get_random_user_agent() );
|
|
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
|
$output = curl_exec( $ch );
|
|
$info = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
|
|
|
if ( 'resource' === gettype( $ch ) ) {
|
|
curl_close( $ch );
|
|
}
|
|
// phpcs:enable WordPress.WP.AlternativeFunctions
|
|
|
|
if ( false === $output || 200 !== (int) $info ) {
|
|
$output = null;
|
|
}
|
|
return $output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get random useragent.
|
|
*
|
|
* @return string User agent string.
|
|
*/
|
|
public function get_random_user_agent() {
|
|
$someUA = array(
|
|
'Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.1b1) Gecko/20081007 Firefox/3.1b1',
|
|
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.0',
|
|
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.18 Safari/525.19',
|
|
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13',
|
|
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)',
|
|
'Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.40607)',
|
|
'Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322)',
|
|
'Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.0.3705; Media Center PC 3.1; Alexa Toolbar; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
|
|
'Mozilla/45.0 (compatible; MSIE 6.0; Windows NT 5.1)',
|
|
'Mozilla/4.08 (compatible; MSIE 6.0; Windows NT 5.1)',
|
|
'Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)',
|
|
);
|
|
$i = wp_rand( 0, count( $someUA ) - 1 );
|
|
return $someUA[ $i ];
|
|
}
|
|
}
|