/wp-content/plugins/sucuri-scanner/sucuri.php
https://github.com/CaffeinatedJim/catsinmyyard · PHP · 11683 lines · 7249 code · 1669 blank · 2765 comment · 1250 complexity · 3f9a5bf111a2bd57ffe47be637e3db9a MD5 · raw file
Large files are truncated click here to view the full file
- <?php
- /*
- Plugin Name: Sucuri Security - Auditing, Malware Scanner and Hardening
- Plugin URI: http://wordpress.sucuri.net/
- Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
- Author: Sucuri, INC
- Version: 1.7.7
- Author URI: http://sucuri.net
- */
- /**
- * Main file to control the plugin.
- *
- * @package Sucuri Security
- * @author Yorman Arias <yorman.arias@sucuri.net>
- * @author Daniel Cid <dcid@sucuri.net>
- * @copyright Since 2010-2015 Sucuri Inc.
- * @license Released under the GPL - see LICENSE file for details.
- * @link https://wordpress.sucuri.net/
- * @since File available since Release 0.1
- */
- /**
- * Plugin dependencies.
- *
- * List of required functions for the execution of this plugin, we are assuming
- * that this site was built on top of the WordPress project, and that it is
- * being loaded through a pluggable system, these functions most be defined
- * before to continue.
- *
- * @var array
- */
- $sucuriscan_dependencies = array(
- 'wp',
- 'wp_die',
- 'add_action',
- 'remove_action',
- 'wp_remote_get',
- 'wp_remote_post',
- );
- // Terminate execution if any of the functions mentioned above is not defined.
- foreach ( $sucuriscan_dependencies as $dependency ) {
- if ( ! function_exists( $dependency ) ) {
- exit(0);
- }
- }
- /**
- * Plugin's constants.
- *
- * These constants will hold the basic information of the plugin, file/folder
- * paths, version numbers, read-only variables that will affect the functioning
- * of the rest of the code. The conditional will act as a container helping in
- * the readability of the code considering the total number of lines that this
- * file will have.
- */
- /**
- * Unique name of the plugin through out all the code.
- */
- define( 'SUCURISCAN', 'sucuriscan' );
- /**
- * Current version of the plugin's code.
- */
- define( 'SUCURISCAN_VERSION', '1.7.7' );
- /**
- * The name of the Sucuri plugin main file.
- */
- define( 'SUCURISCAN_PLUGIN_FILE', 'sucuri.php' );
- /**
- * The name of the folder where the plugin's files will be located.
- */
- define( 'SUCURISCAN_PLUGIN_FOLDER', 'sucuri-scanner' );
- /**
- * The fullpath where the plugin's files will be located.
- */
- define( 'SUCURISCAN_PLUGIN_PATH', WP_PLUGIN_DIR.'/'.SUCURISCAN_PLUGIN_FOLDER );
- /**
- * The fullpath of the main plugin file.
- */
- define( 'SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE );
- /**
- * The local URL where the plugin's files and assets are served.
- */
- define( 'SUCURISCAN_URL', rtrim( plugin_dir_url( SUCURISCAN_PLUGIN_FILEPATH ), '/' ) );
- /**
- * Checksum of this file to check the integrity of the plugin.
- */
- define( 'SUCURISCAN_PLUGIN_CHECKSUM', @md5_file( SUCURISCAN_PLUGIN_FILEPATH ) );
- /**
- * Remote URL where the public Sucuri API service is running.
- */
- define( 'SUCURISCAN_API', 'https://wordpress.sucuri.net/api/' );
- /**
- * Latest version of the public Sucuri API.
- */
- define( 'SUCURISCAN_API_VERSION', 'v1' );
- /**
- * Remote URL where the CloudProxy API service is running.
- */
- define( 'SUCURISCAN_CLOUDPROXY_API', 'https://waf.sucuri.net/api' );
- /**
- * Latest version of the CloudProxy API.
- */
- define( 'SUCURISCAN_CLOUDPROXY_API_VERSION', 'v2' );
- /**
- * The maximum quantity of entries that will be displayed in the last login page.
- */
- define( 'SUCURISCAN_LASTLOGINS_USERSLIMIT', 25 );
- /**
- * The maximum quantity of entries that will be displayed in the audit logs page.
- */
- define( 'SUCURISCAN_AUDITLOGS_PER_PAGE', 50 );
- /**
- * The maximum quantity of buttons in the paginations.
- */
- define( 'SUCURISCAN_MAX_PAGINATION_BUTTONS', 20 );
- /**
- * The minimum quantity of seconds to wait before each filesystem scan.
- */
- define( 'SUCURISCAN_MINIMUM_RUNTIME', 10800 );
- /**
- * The life time of the cache for the results of the SiteCheck scans.
- */
- define( 'SUCURISCAN_SITECHECK_LIFETIME', 1200 );
- /**
- * The life time of the cache for the results of the get_plugins function.
- */
- define( 'SUCURISCAN_GET_PLUGINS_LIFETIME', 1800 );
- /**
- * Plugin's global variables.
- *
- * These variables will be defined globally to allow the inclusion in multiple
- * functions and classes defined in the libraries loaded by this plugin. The
- * conditional will act as a container helping in the readability of the code
- * considering the total number of lines that this file will have.
- */
- if ( defined( 'SUCURISCAN' ) ){
- /**
- * List an associative array with the sub-pages of this plugin.
- *
- * @return array
- */
- $sucuriscan_pages = array(
- 'sucuriscan' => 'Dashboard',
- 'sucuriscan_scanner' => 'Malware Scan',
- 'sucuriscan_monitoring' => 'Firewall (WAF)',
- 'sucuriscan_hardening' => 'Hardening',
- 'sucuriscan_posthack' => 'Post-Hack',
- 'sucuriscan_lastlogins' => 'Last Logins',
- 'sucuriscan_settings' => 'Settings',
- 'sucuriscan_infosys' => 'Site Info',
- );
- /**
- * Settings options.
- *
- * The following global variables are mostly associative arrays where the key is
- * linked to an option that will be stored in the database, and their
- * correspondent values are the description of the option. These variables will
- * be used in the settings page to offer the user a way to configure the
- * behaviour of the plugin.
- *
- * @var array
- */
- $sucuriscan_notify_options = array(
- 'sucuriscan_notify_plugin_change' => 'Receive email alerts for <strong>Sucuri</strong> plugin changes',
- 'sucuriscan_prettify_mails' => 'Receive email alerts in HTML <em>(there may be issues with some mail services)</em>',
- 'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
- 'sucuriscan_notify_user_registration' => 'user:Receive email alerts for new user registration',
- 'sucuriscan_notify_success_login' => 'user:Receive email alerts for successful login attempts',
- 'sucuriscan_notify_failed_login' => 'user:Receive email alerts for failed login attempts',
- 'sucuriscan_notify_bruteforce_attack' => 'user:Receive email alerts for password guessing brute force attacks',
- 'sucuriscan_notify_post_publication' => 'Receive email alerts for new content <em>(posts, attachments, forms, etc)</em>',
- 'sucuriscan_notify_website_updated' => 'Receive email alerts when the WordPress version is updated',
- 'sucuriscan_notify_settings_updated' => 'Receive email alerts when your website settings are updated',
- 'sucuriscan_notify_theme_editor' => 'Receive email alerts when a file is modified with theme/plugin editor',
- 'sucuriscan_notify_plugin_installed' => 'plugin:Receive email alerts when a plugin is installed',
- 'sucuriscan_notify_plugin_activated' => 'plugin:Receive email alerts when a plugin is activated',
- 'sucuriscan_notify_plugin_deactivated' => 'plugin:Receive email alerts when a plugin is deactivated',
- 'sucuriscan_notify_plugin_updated' => 'plugin:Receive email alerts when a plugin is updated',
- 'sucuriscan_notify_plugin_deleted' => 'plugin:Receive email alerts when a plugin is deleted',
- 'sucuriscan_notify_widget_added' => 'widget:Receive email alerts when a widget is added to a sidebar',
- 'sucuriscan_notify_widget_deleted' => 'widget:Receive email alerts when a widget is deleted from a sidebar',
- 'sucuriscan_notify_theme_installed' => 'theme:Receive email alerts when a theme is installed',
- 'sucuriscan_notify_theme_activated' => 'theme:Receive email alerts when a theme is activated',
- 'sucuriscan_notify_theme_updated' => 'theme:Receive email alerts when a theme is updated',
- 'sucuriscan_notify_theme_deleted' => 'theme:Receive email alerts when a theme is deleted',
- );
- $sucuriscan_schedule_allowed = array(
- 'hourly' => 'Every three hours (3 hours)',
- 'twicedaily' => 'Twice daily (12 hours)',
- 'daily' => 'Once daily (24 hours)',
- '_oneoff' => 'Never',
- );
- $sucuriscan_interface_allowed = array(
- 'spl' => 'SPL (high performance)',
- 'opendir' => 'OpenDir (medium)',
- 'glob' => 'Glob (low)',
- );
- $sucuriscan_emails_per_hour = array(
- '5' => 'Maximum 5 per hour',
- '10' => 'Maximum 10 per hour',
- '20' => 'Maximum 20 per hour',
- '40' => 'Maximum 40 per hour',
- '80' => 'Maximum 80 per hour',
- '160' => 'Maximum 160 per hour',
- 'unlimited' => 'Unlimited',
- );
- $sucuriscan_maximum_failed_logins = array(
- '30' => '30 failed logins per hour',
- '60' => '60 failed logins per hour',
- '120' => '120 failed logins per hour',
- '240' => '240 failed logins per hour',
- '480' => '480 failed logins per hour',
- );
- $sucuriscan_verify_ssl_cert = array(
- 'true' => 'Verify peer\'s cert',
- 'false' => 'Stop peer\'s cert verification',
- );
- $sucuriscan_no_notices_in = array(
- /* Value of the page parameter to ignore. */
- );
- $sucuriscan_email_subjects = array(
- 'Sucuri Alert, :domain, :event',
- 'Sucuri Alert, :domain, :event, :remoteaddr',
- 'Sucuri Alert, :event, :remoteaddr',
- 'Sucuri Alert, :event',
- );
- /**
- * Remove the WordPress generator meta-tag from the source code.
- */
- remove_action( 'wp_head', 'wp_generator' );
- /**
- * Run a specific function defined in the plugin's code to locate every
- * directory and file, collect their checksum and file size, and send this
- * information to the Sucuri API service where a security and integrity scan
- * will be performed against the hashes provided and the official versions.
- */
- add_action( 'sucuriscan_scheduled_scan', 'SucuriScanEvent::filesystem_scan' );
- /**
- * Initialize the execute of the main plugin's functions.
- *
- * This will load the menu options in the WordPress administrator panel, and
- * execute the bootstrap function of the plugin.
- */
- add_action( 'init', 'SucuriScanInterface::initialize', 1 );
- add_action( 'admin_init', 'SucuriScanInterface::create_datastore_folder' );
- add_action( 'admin_init', 'SucuriScanInterface::handle_old_plugins' );
- add_action( 'admin_enqueue_scripts', 'SucuriScanInterface::enqueue_scripts', 1 );
- add_action( 'admin_menu', 'SucuriScanInterface::add_interface_menu' );
- /**
- * Function call interceptors.
- *
- * Define the names for the hooks that will intercept specific function calls in
- * the admin interface and parts of the external site, an event report will be
- * sent to the API service and an email notification to the administrator of the
- * site.
- *
- * @see Class SucuriScanHook
- */
- if ( class_exists( 'SucuriScanHook' ) ){
- $sucuriscan_hooks = array(
- // Passes.
- 'add_attachment',
- 'add_link',
- 'create_category',
- 'delete_post',
- 'delete_user',
- 'login_form_resetpass',
- 'private_to_published',
- 'publish_page',
- 'publish_post',
- 'publish_phone',
- 'xmlrpc_publish_post',
- 'retrieve_password',
- 'switch_theme',
- 'user_register',
- 'wp_login',
- 'wp_login_failed',
- 'wp_trash_post',
- );
- foreach ( $sucuriscan_hooks as $hook_name ){
- $hook_func = 'SucuriScanHook::hook_' . $hook_name;
- add_action( $hook_name, $hook_func, 50 );
- }
- add_action( 'admin_init', 'SucuriScanHook::hook_undefined_actions' );
- add_action( 'login_form', 'SucuriScanHook::hook_undefined_actions' );
- } else {
- SucuriScanInterface::error( 'Function call interceptors are not working properly.' );
- }
- /**
- * Display a message if the plugin is not activated.
- *
- * Display a message at the top of the administration panel with a button that
- * once clicked will send the site's email and domain name to the Sucuri API
- * service where an API key will be generated for the site, this key will allow
- * the plugin to execute the filesystem scans, the project integrity, and the
- * email notifications.
- */
- add_action( 'admin_notices', 'SucuriScanInterface::setup_notice' );
- /**
- * Heartbeat API
- *
- * Update the settings of the Heartbeat API according to the values set by an
- * administrator. This tool may cause an increase in the CPU usage, a bad
- * configuration may cause low account to run out of resources, but in better
- * cases it may improve the performance of the site by reducing the quantity of
- * requests sent to the server per session.
- */
- add_filter( 'init', 'SucuriScanHeartbeat::register_script', 1 );
- add_filter( 'heartbeat_settings', 'SucuriScanHeartbeat::update_settings' );
- add_filter( 'heartbeat_send', 'SucuriScanHeartbeat::respond_to_send', 10, 3 );
- add_filter( 'heartbeat_received', 'SucuriScanHeartbeat::respond_to_received', 10, 3 );
- add_filter( 'heartbeat_nopriv_send', 'SucuriScanHeartbeat::respond_to_send', 10, 3 );
- add_filter( 'heartbeat_nopriv_received', 'SucuriScanHeartbeat::respond_to_received', 10, 3 );
- }
- /**
- * Miscellaneous library.
- *
- * Multiple and generic functions that will be used through out the code of
- * other libraries extending from this and functions defined in other files, be
- * aware of the hierarchy and check the other libraries for duplicated methods.
- */
- class SucuriScan {
- /**
- * Class constructor.
- */
- public function __construct(){
- }
- /**
- * Return name of a variable with the plugin's prefix (if needed).
- *
- * To facilitate the development, you can prefix the name of the key in the
- * request (when accessing it) with a single colon, this function will
- * automatically replace that character with the unique identifier of the
- * plugin.
- *
- * @param string $var_name Name of a variable with an optional colon at the beginning.
- * @return string Full name of the variable with the extra characters (if needed).
- */
- public static function variable_prefix( $var_name = '' ){
- if ( preg_match( '/^:(.*)/', $var_name, $match ) ){
- $var_name = sprintf( '%s_%s', SUCURISCAN, $match[1] );
- }
- return $var_name;
- }
- /**
- * Gets the value of a configuration option.
- *
- * @param string $property The configuration option name.
- * @return string Value of the configuration option as a string on success.
- */
- public static function ini_get( $property = '' ){
- $ini_value = ini_get( $property );
- if ( empty($ini_value) || is_null( $ini_value ) ){
- switch ( $property ){
- case 'error_log': $ini_value = 'error_log'; break;
- case 'safe_mode': $ini_value = 'Off'; break;
- case 'allow_url_fopen': $ini_value = '1'; break;
- case 'memory_limit': $ini_value = '128M'; break;
- case 'upload_max_filesize': $ini_value = '2M'; break;
- case 'post_max_size': $ini_value = '8M'; break;
- case 'max_execution_time': $ini_value = '30'; break;
- case 'max_input_time': $ini_value = '-1'; break;
- }
- }
- if ( $property == 'error_log' ) {
- $ini_value = basename( $ini_value );
- }
- return $ini_value;
- }
- /**
- * Encodes the less-than, greater-than, ampersand, double quote and single quote
- * characters, will never double encode entities.
- *
- * @param string $text The text which is to be encoded.
- * @return string The encoded text with HTML entities.
- */
- public static function escape( $text = '' ){
- // Escape the value of the variable using a built-in function if possible.
- if ( function_exists( 'esc_attr' ) ){
- $text = esc_attr( $text );
- } else {
- $text = htmlspecialchars( $text );
- }
- return $text;
- }
- /**
- * Generates a lowercase random string with an specific length.
- *
- * @param integer $length Length of the string that will be generated.
- * @return string The random string generated.
- */
- public static function random_char( $length = 4 ){
- $string = '';
- $chars = range( 'a','z' );
- for ( $i = 0; $i < $length; $i++ ){
- $string .= $chars[ rand( 0, count( $chars ) -1 ) ];
- }
- return $string;
- }
- /**
- * Translate a given number in bytes to a human readable file size using the
- * a approximate value in Kylo, Mega, Giga, etc.
- *
- * @link http://www.php.net/manual/en/function.filesize.php#106569
- * @param integer $bytes An integer representing a file size in bytes.
- * @param integer $decimals How many decimals should be returned after the translation.
- * @return string Human readable representation of the given number in Kylo, Mega, Giga, etc.
- */
- public static function human_filesize( $bytes = 0, $decimals = 2 ){
- $sz = 'BKMGTP';
- $factor = floor( (strlen( $bytes ) - 1) / 3 );
- return sprintf( "%.{$decimals}f", $bytes / pow( 1024, $factor ) ) . @$sz[ $factor ];
- }
- /**
- * Returns the system filepath to the relevant user uploads directory for this
- * site. This is a multisite capable function.
- *
- * @param string $path The relative path that needs to be completed to get the absolute path.
- * @return string The full filesystem path including the directory specified.
- */
- public static function datastore_folder_path( $path = '' ){
- $datastore_path = SucuriScanOption::get_option( ':datastore_path' );
- $datastore_dirname = 'sucuri';
- // Use the uploads folder by default.
- if ( empty($datastore_path) ) {
- $uploads_path = false;
- // Multisite installations may have different paths.
- if ( function_exists( 'wp_upload_dir' ) ) {
- $upload_dir = wp_upload_dir();
- if ( isset($upload_dir['basedir']) ) {
- $uploads_path = rtrim( $upload_dir['basedir'], '/' );
- }
- }
- if ( $uploads_path === false ) {
- if ( defined( 'WP_CONTENT_DIR' ) ) {
- $uploads_path = rtrim( WP_CONTENT_DIR, '/' ) . '/uploads';
- } else {
- $uploads_path = rtrim( ABSPATH, '/' ) . '/wp-content/uploads';
- }
- }
- $datastore_path = $uploads_path . '/' . $datastore_dirname;
- SucuriScanOption::update_option( ':datastore_path', $datastore_path );
- }
- $wp_filepath = rtrim( $datastore_path, '/' ) . '/' . $path;
- return $wp_filepath;
- }
- /**
- * Check whether the current site is working as a multi-site instance.
- *
- * @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
- */
- public static function is_multisite(){
- if (
- function_exists( 'is_multisite' )
- && is_multisite()
- ){
- return true;
- }
- return false;
- }
- /**
- * Find and retrieve the current version of Wordpress installed.
- *
- * @return string The version number of Wordpress installed.
- */
- public static function site_version(){
- global $wp_version;
- if ( $wp_version === null ) {
- $wp_version_path = ABSPATH . WPINC . '/version.php';
- if ( file_exists( $wp_version_path ) ) {
- include($wp_version_path);
- $wp_version = isset($wp_version) ? $wp_version : '0.0';
- }
- else {
- $option_version = get_option( 'version' );
- $wp_version = $option_version ? $option_version : '0.0';
- }
- }
- $wp_version = self::escape( $wp_version );
- return $wp_version;
- }
- /**
- * Find and retrieve the absolute path of the WordPress configuration file.
- *
- * @return string Absolute path of the WordPress configuration file.
- */
- public static function get_wpconfig_path(){
- if ( defined( 'ABSPATH' ) ){
- $file_path = ABSPATH . '/wp-config.php';
- // if wp-config.php doesn't exist, or is not readable check one directory up.
- if ( ! file_exists( $file_path ) ){
- $file_path = ABSPATH . '/../wp-config.php';
- }
- // Remove duplicated double slashes.
- $file_path = @realpath( $file_path );
- if ( $file_path ){
- return $file_path;
- }
- }
- return false;
- }
- /**
- * Find and retrieve the absolute path of the main WordPress htaccess file.
- *
- * @return string Absolute path of the main WordPress htaccess file.
- */
- public static function get_htaccess_path(){
- if ( defined( 'ABSPATH' ) ){
- $base_dirs = array(
- rtrim( ABSPATH, '/' ),
- dirname( ABSPATH ),
- dirname( dirname( ABSPATH ) ),
- );
- foreach ( $base_dirs as $base_dir ){
- $htaccess_path = sprintf( '%s/.htaccess', $base_dir );
- if ( file_exists( $htaccess_path ) ){
- return $htaccess_path;
- }
- }
- }
- return false;
- }
- /**
- * Get the pattern of the definition related with a WordPress secret key.
- *
- * @return string Secret key definition pattern.
- */
- public static function secret_key_pattern(){
- return '/define\((\s+)?\'([A-Z_]+)\',(\s+)?\'(.*)\'(\s+)?\);/';
- }
- /**
- * Retrieve the real ip address of the user in the current request.
- *
- * @param boolean $return_header Whether the header name where the address was found must be returned.
- * @return string The real ip address of the user in the current request.
- */
- public static function get_remote_addr( $return_header = false ){
- $remote_addr = '';
- $header_used = 'unknown';
- if (
- self::support_reverse_proxy()
- || self::is_behind_cloudproxy()
- ) {
- $alternatives = array(
- 'HTTP_X_SUCURI_CLIENTIP',
- 'HTTP_X_REAL_IP',
- 'HTTP_CLIENT_IP',
- 'HTTP_X_FORWARDED_FOR',
- 'HTTP_X_FORWARDED',
- 'HTTP_FORWARDED_FOR',
- 'HTTP_FORWARDED',
- 'SUCURI_RIP',
- 'REMOTE_ADDR',
- );
- foreach ( $alternatives as $alternative ){
- if (
- isset($_SERVER[ $alternative ])
- && self::is_valid_ip( $_SERVER[ $alternative ] )
- ){
- $remote_addr = $_SERVER[ $alternative ];
- $header_used = $alternative;
- break;
- }
- }
- }
- elseif ( isset($_SERVER['REMOTE_ADDR']) ) {
- $remote_addr = $_SERVER['REMOTE_ADDR'];
- $header_used = 'REMOTE_ADDR';
- }
- if ( $remote_addr == '::1' ){
- $remote_addr = '127.0.0.1';
- }
- if ( $return_header ){
- return $header_used;
- }
- return $remote_addr;
- }
- /**
- * Return the HTTP header used to retrieve the remote address.
- *
- * @return string The HTTP header used to retrieve the remote address.
- */
- public static function get_remote_addr_header(){
- return self::get_remote_addr( true );
- }
- /**
- * Retrieve the user-agent from the current request.
- *
- * @return string The user-agent from the current request.
- */
- public static function get_user_agent(){
- if ( isset($_SERVER['HTTP_USER_AGENT']) ){
- return self::escape( $_SERVER['HTTP_USER_AGENT'] );
- }
- return false;
- }
- /**
- * Get the clean version of the current domain.
- *
- * @return string The domain of the current site.
- */
- public static function get_domain( $return_tld = false ){
- if ( function_exists( 'get_site_url' ) ) {
- $site_url = get_site_url();
- $pattern = '/([fhtps]+:\/\/)?([^:\/]+)(:[0-9:]+)?(\/.*)?/';
- $replacement = ( $return_tld === true ) ? '$2' : '$2$3$4';
- $domain_name = @preg_replace( $pattern, $replacement, $site_url );
- return $domain_name;
- }
- return false;
- }
- /**
- * Get top-level domain (TLD) of the website.
- *
- * @return string Top-level domain (TLD) of the website.
- */
- public static function get_top_level_domain(){
- return self::get_domain( true );
- }
- /**
- * Check whether reverse proxy servers must be supported.
- *
- * @return boolean TRUE if reverse proxies must be supported, FALSE otherwise.
- */
- public static function support_reverse_proxy(){
- return (bool) ( SucuriScanOption::get_option( ':revproxy' ) === 'enabled' );
- }
- /**
- * Check whether the site is behing the Sucuri CloudProxy network.
- *
- * @param boolean $verbose Return an array with the hostname, address, and status, or not.
- * @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
- */
- public static function is_behind_cloudproxy( $verbose = false ){
- $http_host = self::get_top_level_domain();
- $host_by_addr = @gethostbyname( $http_host );
- $host_by_name = @gethostbyaddr( $host_by_addr );
- $status = (bool) preg_match( '/^cloudproxy[0-9]+\.sucuri\.net$/', $host_by_name );
- /*
- * If the DNS reversion failed but the CloudProxy API key is set, then consider
- * the site as protected by a firewall. A fake key can be used to bypass the DNS
- * checking, but that is not something that will affect us, only the client.
- */
- if (
- $status === false
- && SucuriScanAPI::get_cloudproxy_key()
- ) {
- $status = true;
- }
- if ( $verbose ){
- return array(
- 'http_host' => $http_host,
- 'host_name' => $host_by_name,
- 'host_addr' => $host_by_addr,
- 'status' => $status,
- );
- }
- return $status;
- }
- /**
- * Get the email address set by the administrator to receive the notifications
- * sent by the plugin, if the email is missing the WordPress email address is
- * chosen by default.
- *
- * @return string The administrator email address.
- */
- public static function get_site_email(){
- $email = get_option( 'admin_email' );
- if ( self::is_valid_email( $email ) ){
- return $email;
- }
- return false;
- }
- /**
- * Returns the current time measured in the number of seconds since the Unix Epoch.
- *
- * @return integer Return current Unix timestamp.
- */
- public static function local_time(){
- if ( function_exists( 'current_time' ) ){
- return current_time( 'timestamp' );
- } else {
- return time();
- }
- }
- /**
- * Retrieve the date in localized format, based on timestamp.
- *
- * If the locale specifies the locale month and weekday, then the locale will
- * take over the format for the date. If it isn't, then the date format string
- * will be used instead.
- *
- * @param integer $timestamp Unix timestamp.
- * @return string The date, translated if locale specifies it.
- */
- public static function datetime( $timestamp = 0 ){
- if ( is_numeric( $timestamp ) && $timestamp > 0 ){
- $date_format = get_option( 'date_format' );
- $time_format = get_option( 'time_format' );
- $timezone_format = sprintf( '%s %s', $date_format, $time_format );
- return date_i18n( $timezone_format, $timestamp );
- }
- return null;
- }
- /**
- * Retrieve the date in localized format based on the current time.
- *
- * @return string The date, translated if locale specifies it.
- */
- public static function current_datetime(){
- $local_time = self::local_time();
- return self::datetime( $local_time );
- }
- /**
- * Return the time passed since the specified timestamp until now.
- *
- * @param integer $timestamp The Unix time number of the date/time before now.
- * @return string The time passed since the timestamp specified.
- */
- public static function time_ago( $timestamp = 0 ){
- if ( ! is_numeric( $timestamp ) ){
- $timestamp = strtotime( $timestamp );
- }
- $local_time = self::local_time();
- $diff = abs( $local_time - intval( $timestamp ) );
- if ( $diff == 0 ){ return 'just now'; }
- $intervals = array(
- 1 => array( 'year', 31556926, ),
- $diff < 31556926 => array( 'month', 2592000, ),
- $diff < 2592000 => array( 'week', 604800, ),
- $diff < 604800 => array( 'day', 86400, ),
- $diff < 86400 => array( 'hour', 3600, ),
- $diff < 3600 => array( 'minute', 60, ),
- $diff < 60 => array( 'second', 1, ),
- );
- $value = floor( $diff / $intervals[1][1] );
- $time_ago = sprintf(
- '%s %s%s ago',
- $value,
- $intervals[1][0],
- ( $value > 1 ? 's' : '' )
- );
- return $time_ago;
- }
- /**
- * Convert an string of characters into a valid variable name.
- *
- * @see http://www.php.net/manual/en/language.variables.basics.php
- *
- * @param string $text A text containing alpha-numeric and special characters.
- * @return string A valid variable name.
- */
- public static function human2var( $text = '' ){
- $text = strtolower( $text );
- $pattern = '/[^a-z0-9_]/';
- $var_name = preg_replace( $pattern, '_', $text );
- return $var_name;
- }
- /**
- * Check whether a variable contains a serialized data or not.
- *
- * @param string $data The data that will be checked.
- * @return boolean TRUE if the data was serialized, FALSE otherwise.
- */
- public static function is_serialized( $data = '' ){
- return ( is_string( $data ) && preg_match( '/^(a|O):[0-9]+:.+/', $data ) );
- }
- /**
- * Check whether an IP address has a valid format or not.
- *
- * @param string $remote_addr The host IP address.
- * @return boolean Whether the IP address specified is valid or not.
- */
- public static function is_valid_ip( $remote_addr = '' ){
- // Check for IPv4 and IPv6.
- if ( function_exists( 'filter_var' ) ){
- return (bool) filter_var( $remote_addr, FILTER_VALIDATE_IP );
- }
- // Assuming older version of PHP and server, so only will check for IPv4.
- elseif ( strlen( $remote_addr ) >= 7 ) {
- $pattern = '/^([0-9]{1,3}\.){3}[0-9]{1,3}$/';
- if ( preg_match( $pattern, $remote_addr, $match ) ){
- for ( $i = 0; $i < 4; $i++ ){
- if ( $match[ $i ] > 255 ){ return false; }
- }
- return true;
- }
- }
- return false;
- }
- /**
- * Check whether an IP address is formatted as CIDR or not.
- *
- * @param string $remote_addr The supposed ip address that will be checked.
- * @return boolean Either TRUE or FALSE if the ip address specified is valid or not.
- */
- public static function is_valid_cidr( $remote_addr = '' ){
- if ( preg_match( '/^([0-9\.]{7,15})\/(8|16|24)$/', $remote_addr, $match ) ) {
- if ( self::is_valid_ip( $match[1] ) ) {
- return true;
- }
- }
- return false;
- }
- /**
- * Separate the parts of an IP address.
- *
- * @param string $remote_addr The supposed ip address that will be formatted.
- * @return array Clean address, CIDR range, and CIDR format; FALSE otherwise.
- */
- public static function get_ip_info( $remote_addr = '' ){
- if ( $remote_addr ) {
- $ip_parts = explode( '/', $remote_addr );
- if (
- array_key_exists( 0, $ip_parts )
- && self::is_valid_ip( $ip_parts[0] )
- ) {
- $addr_info = array();
- $addr_info['remote_addr'] = $ip_parts[0];
- $addr_info['cidr_range'] = isset($ip_parts[1]) ? $ip_parts[1] : '32';
- $addr_info['cidr_format'] = $addr_info['remote_addr'] . '/' . $addr_info['cidr_range'];
- return $addr_info;
- }
- }
- return false;
- }
- /**
- * Validate email address.
- *
- * This use the native PHP function filter_var which is available in PHP >=
- * 5.2.0 if it is not found in the interpreter this function will sue regular
- * expressions to check whether the email address passed is valid or not.
- *
- * @see http://www.php.net/manual/en/function.filter-var.php
- *
- * @param string $email The string that will be validated as an email address.
- * @return boolean TRUE if the email address passed to the function is valid, FALSE if not.
- */
- public static function is_valid_email( $email = '' ){
- if ( function_exists( 'filter_var' ) ){
- return (bool) filter_var( $email, FILTER_VALIDATE_EMAIL );
- } else {
- $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix';
- return (bool) preg_match( $pattern, $email );
- }
- }
- /**
- * Return a string with all the valid email addresses.
- *
- * @param string $email The string that will be validated as an email address.
- * @param boolean $as_array TRUE to return the list of valid email addresses as an array.
- * @return string All the valid email addresses separated by a comma.
- */
- public static function get_valid_email( $email = '', $as_array = false ){
- $valid_emails = array();
- $is_valid_string = (bool) ( is_string( $email ) && ! empty($email) );
- if (
- $is_valid_string === true
- && strpos( $email, ',' ) !== false
- ) {
- $addresses = explode( ',', $email );
- foreach ( $addresses as $address ){
- $address = trim( $address );
- if ( self::is_valid_email( $address ) ){
- $valid_emails[] = $address;
- }
- }
- }
- elseif (
- $is_valid_string === true
- && self::is_valid_email( $email )
- ) {
- $valid_emails[] = $email;
- }
- if ( ! empty($valid_emails) ) {
- $valid_emails = array_unique( $valid_emails );
- if ( $as_array === true ) {
- return $valid_emails;
- }
- return self::implode( ', ', $valid_emails );
- }
- return false;
- }
- /**
- * Cut a long text to the length specified, and append suspensive points at the end.
- *
- * @param string $text String of characters that will be cut.
- * @param integer $length Maximum length of the returned string, default is 10.
- * @return string Short version of the text specified.
- */
- public static function excerpt( $text = '', $length = 10 ){
- $text_length = strlen( $text );
- if ( $text_length > $length ){
- return substr( $text, 0, $length ) . '...';
- }
- return $text;
- }
- /**
- * Same as the excerpt method but with the string reversed.
- *
- * @param string $text String of characters that will be cut.
- * @param integer $length Maximum length of the returned string, default is 10.
- * @return string Short version of the text specified.
- */
- public static function excerpt_rev( $text = '', $length = 10 ){
- $str_reversed = strrev( $text );
- $str_excerpt = self::excerpt( $str_reversed, $length );
- $text_transformed = strrev( $str_excerpt );
- return $text_transformed;
- }
- /**
- * Check whether an list is a multidimensional array or not.
- *
- * @param array $list An array or multidimensional array of different values.
- * @return boolean TRUE if the list is multidimensional, FALSE otherwise.
- */
- public static function is_multi_list( $list = array() ){
- if ( ! empty($list) ){
- foreach ( (array) $list as $item ) {
- if ( is_array( $item ) ) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * Join array elements with a string no matter if it is multidimensional.
- *
- * @param string $separator Character that will act as a separator, default to an empty string.
- * @param array $list The array of strings to implode.
- * @return string String of all the items in the list, with the separator between them.
- */
- public static function implode( $separator = '', $list = array() ){
- if ( self::is_multi_list( $list ) ){
- $pieces = array();
- foreach ( $list as $items ){
- $pieces[] = @implode( $separator, $items );
- }
- $joined_pieces = '(' . implode( '), (', $pieces ) . ')';
- return $joined_pieces;
- } else {
- return implode( $separator, $list );
- }
- }
- /**
- * Determine if the plugin notices can be displayed in the current page.
- *
- * @param string $current_page Identifier of the current page.
- * @return boolean TRUE if the current page must not have noticies.
- */
- public static function no_notices_here( $current_page = false ){
- global $sucuriscan_no_notices_in;
- if ( $current_page === false ) {
- $current_page = SucuriScanRequest::get( 'page' );
- }
- if (
- isset($sucuriscan_no_notices_in)
- && is_array( $sucuriscan_no_notices_in )
- && ! empty($sucuriscan_no_notices_in)
- ) {
- return (bool) in_array( $current_page, $sucuriscan_no_notices_in );
- }
- return false;
- }
- /**
- * Check whether the site is running over the Nginx web server.
- *
- * @return boolean TRUE if the site is running over Nginx, FALSE otherwise.
- */
- public static function is_nginx_server(){
- return (bool) preg_match( '/^nginx(\/[0-9\.]+)?$/', @$_SERVER['SERVER_SOFTWARE'] );
- }
- /**
- * Check whether the site is running over the Nginx web server.
- *
- * @return boolean TRUE if the site is running over Nginx, FALSE otherwise.
- */
- public static function is_iis_server(){
- return (bool) preg_match( '/Microsoft-IIS/i', @$_SERVER['SERVER_SOFTWARE'] );
- }
- }
- /**
- * HTTP request handler.
- *
- * Function definitions to retrieve, validate, and clean the parameters during a
- * HTTP request, generally after a form submission or while loading a URL. Use
- * these methods at most instead of accessing an index in the global PHP
- * variables _POST, _GET, _REQUEST since they may come with insecure data.
- */
- class SucuriScanRequest extends SucuriScan {
- /**
- * Returns the value stored in a specific index in the global _GET, _POST or
- * _REQUEST variables, you can specify a pattern as the second argument to
- * match allowed values.
- *
- * @param array $list The array where the specified key will be searched.
- * @param string $key Name of the index where the requested variable is supposed to be.
- * @param string $pattern Optional pattern to match allowed values in the requested key.
- * @return string The value stored in the specified key inside the global _GET variable.
- */
- public static function request( $list = array(), $key = '', $pattern = '' ){
- $key = self::variable_prefix( $key );
- if (
- is_array( $list )
- && is_string( $key )
- && isset($list[ $key ])
- ){
- // Select the key from the list and escape its content.
- $key_value = $list[ $key ];
- // Define regular expressions for specific value types.
- if ( $pattern === '' ){
- $pattern = '/.*/';
- } else {
- switch ( $pattern ){
- case '_nonce': $pattern = '/^[a-z0-9]{10}$/'; break;
- case '_page': $pattern = '/^[a-z_]+$/'; break;
- case '_array': $pattern = '_array'; break;
- case '_yyyymmdd': $pattern = '/^[0-9]{4}(\-[0-9]{2}){2}$/'; break;
- default: $pattern = '/^'.$pattern.'$/'; break;
- }
- }
- // If the request data is an array, then only cast the value.
- if ( $pattern == '_array' && is_array( $key_value ) ){
- return (array) $key_value;
- }
- // Check the format of the request data with a regex defined above.
- if ( @preg_match( $pattern, $key_value ) ){
- return self::escape( $key_value );
- }
- }
- return false;
- }
- /**
- * Returns the value stored in a specific index in the global _GET variable,
- * you can specify a pattern as the second argument to match allowed values.
- *
- * @param string $key Name of the index where the requested variable is supposed to be.
- * @param string $pattern Optional pattern to match allowed values in the requested key.
- * @return string The value stored in the specified key inside the global _GET variable.
- */
- public static function get( $key = '', $pattern = '' ){
- return self::request( $_GET, $key, $pattern );
- }
- /**
- * Returns the value stored in a specific index in the global _POST variable,
- * you can specify a pattern as the second argument to match allowed values.
- *
- * @param string $key Name of the index where the requested variable is supposed to be.
- * @param string $pattern Optional pattern to match allowed values in the requested key.
- * @return string The value stored in the specified key inside the global _POST variable.
- */
- public static function post( $key = '', $pattern = '' ){
- return self::request( $_POST, $key, $pattern );
- }
- /**
- * Returns the value stored in a specific index in the global _REQUEST variable,
- * you can specify a pattern as the second argument to match allowed values.
- *
- * @param string $key Name of the index where the requested variable is supposed to be.
- * @param string $pattern Optional pattern to match allowed values in the requested key.
- * @return string The value stored in the specified key inside the global _POST variable.
- */
- public static function get_or_post( $key = '', $pattern = '' ){
- return self::request( $_REQUEST, $key, $pattern );
- }
- }
- /**
- * Class to process files and folders.
- *
- * Here are implemented the functions needed to open, scan, read, create files
- * and folders using the built-in PHP class SplFileInfo. The SplFileInfo class
- * offers a high-level object oriented interface to information for an individual
- * file.
- */
- class SucuriScanFileInfo extends SucuriScan {
- /**
- * Define the interface that will be used to execute the file system scans, the
- * available options are SPL, OpenDir, and Glob (all in lowercase). This can be
- * configured from the settings page.
- *
- * @var string
- */
- public $scan_interface = 'spl';
- /**
- * Whether the list of files that can be ignored from the filesystem scan will
- * be used to return the directory tree, this should be disabled when scanning a
- * directory without the need to filter the items in the list.
- *
- * @var boolean
- */
- public $ignore_files = true;
- /**
- * Whether the list of folders that can be ignored from the filesystem scan will
- * be used to return the directory tree, this should be disabled when scanning a
- * path without the need to filter the items in the list.
- *
- * @var boolean
- */
- public $ignore_directories = true;
- /**
- * A list of ignored directory paths, these folders will be skipped during the
- * execution of the file system scans, and any sub-directory or files inside
- * these paths will be ignored too.
- *
- * @see SucuriScanFSScanner.get_ignored_directories()
- * @var array
- */
- private $ignored_directories = array();
- /**
- * Whether the filesystem scanner should run recursively or not.
- *
- * @var boolean
- */
- public $run_recursively = true;
- /**
- * Class constructor.
- */
- public function __construct(){
- }
- /**
- * Retrieve a long text string with signatures of all the files contained
- * in the main and subdirectories of the folder specified, also the filesize
- * and md5sum of that file. Some folders and files will be ignored depending
- * on some rules defined by the developer.
- *
- * @param string $directory Parent directory where the filesystem scan will start.
- * @param boolean $as_array Whether the result of the operation will be returned as an array or string.
- * @return array List of files in the main and subdirectories of the folder specified.
- */
- public function get_directory_tree_md5( $directory = '', $as_array = false ){
- $project_signatures = '';
- $abs_path = rtrim( ABSPATH, DIRECTORY_SEPARATOR );
- $files = $this->get_directory_tree( $directory );
- if ( $as_array ){
- $project_signatures = array();
- }
- if ( $files ){
- sort( $files );
- foreach ( $files as $filepath ){
- $file_checksum = @md5_file( $filepath );
- $filesize = @filesize( $filepath );
- if ( $as_array ){
- $basename = str_replace( $abs_path . DIRECTORY_SEPARATOR, '', $filepath );
- $project_signatures[ $basename ] = array(
- 'filepath' => $filepath,
- 'checksum' => $file_checksum,
- 'filesize' => $filesize,
- 'created_at' => @filectime( $filepath ),
- 'modified_at' => @filemtime( $filepath ),
- );
- } else {
- $filepath = str_replace( $abs_path, $abs_path . DIRECTORY_SEPARATOR, $filepath );
- $project_signatures .= sprintf(
- "%s%s%s%s\n",
- $file_checksum,
- $filesize,
- chr( 32 ),
- $filepath
- );
- }
- }
- }
- return $project_signatures;
- }
- /**
- * Retrieve a list with all the files contained in the main and subdirectories
- * of the folder specified. Some folders and files will be ignored depending
- * on some rules defined by the developer.
- *
- * @param string $directory Parent directory where the filesystem scan will start.
- * @return array List of files in the main and subdirectories of the folder specified.
- */
- public function get_directory_tree( $directory = '' ){
- if ( file_exists( $directory ) && is_dir( $directory ) ){
- $tree = array();
- // Check whether the ignore scanning feature is enabled or not.
- if ( SucuriScanFSScanner::will_ignore_scanning() ){
- $this->ignored_directories = SucuriScanFSScanner::get_ignored_directories();
- }
- switch ( $this->scan_interface ){
- case 'spl':
- if ( $this->is_spl_available() ){
- $tree = $this->get_directory_tree_with_spl( $directory );
- } else {
- $this->scan_interface = 'opendir';
- $tree = $this->get_directory_tree( $directory );
- }
- break;
- case 'glob':
- $tree = $this->get_directory_tree_with_glob( $directory );
- break;
- case 'opendir':
- $tree = $this->get_directory_tree_with_opendir( $directory );
- break;
- default:
- $this->scan_interface = 'spl';
- $tree = $this->get_directory_tree( $directory );
- break;
- }
- return $tree;
- }
- return false;
- }
- /**
- * Find a file under the directory tree specified.
- *
- * @param string $filename Name of the folder or file being scanned at the moment.
- * @param string $directory Directory where the scanner is located at the moment.
- * @return array List of file paths where the file was found.
- */
- public function find_file( $filename = '', $directory = null ){
- $file_paths = array();…