mainwp-child/class/class-mainwp-child-vulnerability-checker.php
2020-06-16 20:15:03 +07:00

307 lines
9.3 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;
/**
* 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/';
/**
* Whether or not to use wpvulndb_token. Default: false.
*
* @var bool true|false
*/
private $wpvulndb_token = false;
/**
* Method instance()
*
* Create a public static instance.
*
* @return mixed Class instance.
*/
public static function instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Method __construct()
*
* Run any time MainWP_Child is called.
*/
public function __construct() {
$this->wpvulndb_token = get_option( 'mainwp_child_wpvulndb_token', '' );
}
/**
* MainWP Child Vulnerability Checker actions.
*/
public function action() {
$information = array();
if ( ! empty( $this->wpvulndb_token ) ) {
if ( isset( $_POST['mwp_action'] ) ) {
switch ( $_POST['mwp_action'] ) {
case 'vulner_recheck':
$information = $this->vulner_recheck();
break;
}
}
}
MainWP_Helper::write( $information );
}
/**
* Check for vulnerabilities.
*
* @return array Return an array $information[] containing $results[] array.
*/
public function vulner_recheck() {
$result = array();
$force = ( isset( $_POST['force'] ) && ! empty( $_POST['force'] ) ) ? true : false;
$result['plugin'] = $this->check_plugins( $force );
$result['wp'] = $this->check_wp( $force );
$result['theme'] = $this->check_themes( $force );
$information = array(
'result' => $result,
'ok' => 1,
);
return $information;
}
/**
* Check for plugin vulnerabilities.
*
* @param bool $force Whether or not to force check. Default: false.
* @return array Return $result array.
*/
public function check_plugins( $force = false ) {
$result = array();
$active_plugins = get_option( 'active_plugins' );
if ( ! empty( $active_plugins ) ) {
foreach ( $active_plugins as $plug ) {
$plugin_file = WP_CONTENT_DIR . '/plugins/' . $plug;
$plugin_info = get_plugin_data( $plugin_file );
$plugin_version = isset( $plugin_info['Version'] ) ? $plugin_info['Version'] : '';
$string = explode( '/', $plug );
$plug_vuln = get_transient( 'mainwp_vulnche_trans_plug_' . $string[0] );
if ( false === $plug_vuln || $force ) {
$plug_vuln = $this->vulnche_get_content( $this->wpvulndb_api . 'plugins/' . $string[0] );
set_transient( 'mainwp_vulnche_trans_plug_' . $string[0], $plug_vuln, 1 * DAY_IN_SECONDS );
}
if ( $plug_vuln ) {
$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 ( 0 == count( $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 ( 0 == count( $plug_vuln_filter ) ) {
continue;
}
$plug_vuln = wp_json_encode( $plug_vuln_filter );
} else {
continue;
}
$result[ $plug ] = $plug_vuln;
}
}
return $result;
}
/**
* Check for WP vulnerabilities.
*
* @param bool $force Whether or not to force check. Default: false.
* @return bool|string|null
*/
public function check_wp( $force = false ) {
$wp_vuln = get_transient( 'mainwp_vulnche_trans_wp_json' );
$wp_version = str_replace( '.', '', get_bloginfo( 'version' ) );
if ( false === $wp_vuln || $force ) {
$wp_vuln = $this->vulnche_get_content( $this->wpvulndb_api . 'wordpresses/' . $wp_version );
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.
* @return array Return $result array.
*/
public function check_themes( $force = false ) {
require_once ABSPATH . 'wp-admin/includes/misc.php';
require_once ABSPATH . 'wp-admin/includes/theme.php';
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 ) {
$th_vuln = $this->vulnche_get_content( $this->wpvulndb_api . 'themes/' . $th['id'] );
set_transient( 'mainwp_vulnche_trans_theme_' . $th['id'], $th_vuln, 1 * DAY_IN_SECONDS );
}
if ( $th_vuln ) {
$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 ( 0 == count( $th_vulner_data ) ) {
unset( $th_vuln_filter[ $slug ] );
} else {
$th_vuln_filter[ $slug ]['vulnerabilities'] = $th_vulner_data;
}
} else {
unset( $th_vuln_filter[ $slug ] );
}
}
if ( 0 == count( $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 string $url URL to check.
*
* @return bool|string|null
*/
public function vulnche_get_content( $url ) {
// phpcs:disable WordPress.WP.AlternativeFunctions -- Required to achieve desired results, pull request solutions appreciated.
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_HEADER, 0 );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Authorization: Token token=' . $this->wpvulndb_token ) );
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 );
curl_close( $ch );
// phpcs:enable
if ( false === $output || 200 != $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 ];
}
}