Файловый менеджер - Редактировать - /home/beautybuzzbeyond/public_html/wp-content/plugins/sucuri-scanner/src/api.lib.php
Назад
<?php /** * Code related to the api.lib.php interface. * * PHP version 5 * * @category Library * @package Sucuri * @subpackage SucuriScanner * @author Daniel Cid <dcid@sucuri.net> * @copyright 2010-2018 Sucuri Inc. * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL2 * @link https://wordpress.org/plugins/sucuri-scanner */ if (!defined('SUCURISCAN_INIT') || SUCURISCAN_INIT !== true) { if (!headers_sent()) { /* Report invalid access if possible. */ header('HTTP/1.1 403 Forbidden'); } exit(1); } /** * Plugin API library. * * When used in the context of web development, an API is typically defined as a * set of Hypertext Transfer Protocol (HTTP) request messages, along with a * definition of the structure of response messages, which is usually in an * Extensible Markup Language (XML) or JavaScript Object Notation (JSON) format. * While "web API" historically has been virtually synonymous for web service, * the recent trend (so-called Web 2.0) has been moving away from Simple Object * Access Protocol (SOAP) based web services and service-oriented architecture * (SOA) towards more direct representational state transfer (REST) style web * resources and resource-oriented architecture (ROA). Part of this trend is * related to the Semantic Web movement toward Resource Description Framework * (RDF), a concept to promote web-based ontology engineering technologies. Web * APIs allow the combination of multiple APIs into new applications known as * mashups. * * @category Library * @package Sucuri * @subpackage SucuriScanner * @author Daniel Cid <dcid@sucuri.net> * @copyright 2010-2018 Sucuri Inc. * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL2 * @link https://wordpress.org/plugins/sucuri-scanner */ class SucuriScanAPI extends SucuriScanOption { /** * Alternative to the built-in PHP method http_build_query. * * Some PHP installations with different encoding or with different language * (German for example) might produce an unwanted behavior when building an * URL, because of this we decided to write our own URL query builder to * keep control of the output. * * @param array $params May be an array or object containing properties. * @return string Returns a URL-encoded string. */ private static function buildQuery($params = array()) { $trail = ''; foreach ($params as $param => $value) { $value = urlencode($value); $trail .= sprintf('&%s=%s', $param, $value); } return substr($trail, 1); } /** * Sends a HTTP request via WordPress WP_HTTP class. * * @suppress PhanNonClassMethodCall * @see https://secure.php.net/manual/en/book.curl.php * @see https://developer.wordpress.org/reference/classes/wp_http/request/ * * @param string $url The target URL where the request will be sent. * @param string $method HTTP method that will be used to send the request. * @param array $params Parameters for the request defined in an associative array. * @param array $args Request arguments like the timeout, headers, cookies, etc. * @return mixed HTTP response, JSON-decoded array, or false on failure. */ public static function apiCall($url = '', $method = 'GET', $params = array(), $args = array()) { if (!$url) { return self::throwException(__('URL is invalid', 'sucuri-scanner')); } if ($method !== 'GET' && $method !== 'POST') { return self::throwException(__('Only GET and POST methods allowed', 'sucuri-scanner')); } $res = null; $timeout = SUCURISCAN_MAX_REQUEST_TIMEOUT; $args = is_array($args) ? $args : array(); if (isset($args['timeout'])) { $timeout = (int)$args['timeout']; } /* include request arguments */ $args['method'] = $method; $args['timeout'] = $timeout; $args['redirection'] = 5; $args['httpversion'] = '1.1'; $args['blocking'] = true; $args['sslverify'] = true; /* separate hardcoded query parameters */ if (empty($params) && strpos($url, '?')) { $parts = @parse_url($url); if (array_key_exists('query', $parts)) { $portions = explode('&', $parts['query']); $url = str_replace('?' . $parts['query'], '', $url); foreach ($portions as $portion) { $bits = explode('=', $portion, 2); $params[$bits[0]] = $bits[1]; } } } /* include current timestamp for trackability */ if (!array_key_exists('time', $params)) { $params['time'] = time(); } /* support HTTP GET requests */ if ($method === 'GET') { $args['body'] = null; $url .= '?' . self::buildQuery($params); $res = wp_remote_get($url, $args); } /* support HTTP POST requests */ if ($method === 'POST') { if (array_key_exists('a', $params)) { /* include action to increase visibility */ $url .= '?a=' . $params['a']; } $args['body'] = $params; $res = wp_remote_post($url, $args); } if (is_wp_error($res)) { return self::throwException($res->get_error_message()); } /* try to return a JSON-encode object */ $data = @json_decode($res['body'], true); return $data ? $data : $res['body']; } /** * Check whether the plugin API key is valid or not. * * @param string $api_key An unique string to identify this installation. * @return bool True if the API key is valid, false otherwise. */ private static function isValidKey($api_key = '') { return (bool)@preg_match('/^[a-z0-9]{32}$/', $api_key); } /** * Store the API key locally. * * @param string $api_key An unique string of characters to identify this installation. * @param bool $validate Whether the format of the key should be validated before store it. * @return bool Either true or false if the key was saved successfully or not respectively. */ public static function setPluginKey($api_key = '', $validate = false) { if ($validate && !self::isValidKey($api_key)) { return SucuriScanInterface::error(__('Invalid API key format', 'sucuri-scanner')); } if (!empty($api_key)) { SucuriScanEvent::notifyEvent( 'plugin_change', sprintf(__('API key was successfully set: %s', 'sucuri-scanner'), $api_key) ); } return self::updateOption(':api_key', $api_key); } /** * Retrieve the API key from the local storage. * * @return string|bool The API key or false if it does not exists. */ public static function getPluginKey() { $api_key = self::getOption(':api_key'); if (is_string($api_key) && self::isValidKey($api_key)) { return $api_key; } return false; } /** * Call an action from the remote API interface of our WordPress service. * * @param string $method HTTP method that will be used to send the request. * @param array $params Parameters for the request defined in an associative array of key-value. * @param bool $send_api_key Whether the API key should be added to the request parameters or not. * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc. * @return array|bool Response object after the HTTP request is executed. */ public static function apiCallWordpress($method = 'GET', $params = array(), $send_api_key = true, $args = array()) { if (!SucuriScan::issetScanApiUrl()) { return false; } $params[SUCURISCAN_API_VERSION] = 1; $params['p'] = 'wordpress'; if ($send_api_key) { $api_key = self::getPluginKey(); if (!$api_key) { return false; } $params['k'] = $api_key; } return self::apiCall(SUCURISCAN_API_URL, $method, $params, $args); } /** * Determine whether an API response was successful or not by checking the * expected generic variables and types, in case of an error a notification * will appears in the administrator panel explaining the result of the * operation. * * For failures in the HTTP response: * * Log file not found: means that the API key used to execute the request is * not associated to the website, this may indicate that either the key was * invalidated by an administrator of the service or that the API key was * custom generated with invalid data. * * Wrong API key: means that the TLD of the origin of the request is not the * domain used to generate the API key in the first place, or that the email * address of the site administrator was changed so the data is not valid * anymore. * * @param array $res HTTP response after API endpoint execution. * @return bool False if the API call failed, true otherwise. */ public static function handleResponse($res = array()) { if (!$res || getenv('SUCURISCAN_NO_API_HANDLE')) { return false; } if (is_array($res) && array_key_exists('status', $res) && intval($res['status']) === 1 ) { return true; } if (is_string($res) && !empty($res)) { return SucuriScanInterface::error($res); } if (!is_array($res) || !isset($res['messages']) || empty($res['messages']) ) { return SucuriScanInterface::error(__('Unknown error, there is no information', 'sucuri-scanner')); } $msg = implode(".\x20", $res['messages']); $raw = $msg; /* Keep a copy of the original message. */ // Special response for invalid API keys. if (stripos($raw, 'log file not found') !== false) { $key = SucuriScanOption::getOption(':api_key'); $msg .= '; this generally happens when you use an invalid API key,' . ' or when the connection with the API service suddently closes.'; SucuriScanEvent::reportCriticalEvent($msg); } // Special response for invalid firewall API keys. if (stripos($raw, 'wrong api key') !== false) { $key = SucuriScanOption::getOption(':cloudproxy_apikey'); $key = SucuriScan::escape($key); $msg .= sprintf('; invalid firewall API key: %s', $key); SucuriScanOption::setRevProxy('disable', true); SucuriScanOption::setAddrHeader('REMOTE_ADDR', true); return SucuriScanInterface::error($msg); } // Stop SSL peer verification on connection failures. if (stripos($raw, 'no alternative certificate') || stripos($raw, 'error setting certificate') || stripos($raw, 'SSL connect error') ) { $msg .= '. The website seems to be using an old version of the Ope' . 'nSSL library or the CURL extension was compiled without support' . ' for the algorithm used in the certificate installed in the API' . ' service. Contact your hosting provider to fix this issue.'; } // Check if the MX records as missing for API registration. if (strpos($raw, 'Invalid email') !== false) { $msg = __('Invalid email format or the host is missing MX records.', 'sucuri-scanner'); } return SucuriScanInterface::error($msg); } /** * Send a request to the API to register this site. * * @param string $email Optional email address for the registration. * @return bool True if the API key was generated, false otherwise. */ public static function registerSite($email = '') { if (!is_string($email) || empty($email)) { $email = self::getSiteEmail(); } $res = self::apiCallWordpress( 'POST', array( 'e' => $email, 's' => self::getDomain(), 'a' => 'register_site', ), false ); if (!self::handleResponse($res)) { return false; } self::setPluginKey($res['output']['api_key']); SucuriScanEvent::installScheduledTask(); SucuriScanEvent::notifyEvent('plugin_change', __('API key was generated and set', 'sucuri-scanner')); return SucuriScanInterface::info(__('API key successfully generated and saved.', 'sucuri-scanner')); } /** * Send a request to recover a previously registered API key. * * @return bool True if the API key was sent to the admin email, false otherwise. */ public static function recoverKey() { $domain = self::getDomain(); $res = self::apiCallWordpress( 'GET', array( 'e' => self::getSiteEmail(), 's' => $domain, 'a' => 'recover_key', ), false ); if (!self::handleResponse($res)) { return false; } SucuriScanEvent::notifyEvent( 'plugin_change', sprintf(__('API key recovery for domain: %s', 'sucuri-scanner'), $domain) ); return SucuriScanInterface::info($res['output']['message']); } /** * Retrieve the event logs registered by the API service. * * @param int $lines Maximum number of logs to return. * @param array $filters Filters to apply to the logs. * @return array|bool The data structure with the logs. */ public static function getAuditLogs($lines = 50, $filters = array()) { if (SucuriScanOption::isDisabled(':api_service') || SucuriScan::issetScanApiUrl()) { return self::parseAuditLogs(array()); } $res = self::apiCallWordpress( 'GET', array( 'a' => 'get_logs', 'l' => $lines, ) ); if (!self::handleResponse($res)) { return false; } return self::parseAuditLogs($res, $filters); } /** * Returns the security logs from the system queue. * In case the logs comes from the queue, set key "from_queue" to true, * as the parse function later will need to prevent timezone conflicts. * * @return array The data structure with the logs. */ public static function getAuditLogsFromQueue($filters = array()) { $auditlogs = array(); $cache = new SucuriScanCache('auditqueue'); $events = $cache->getAll(); if (is_array($events) && !empty($events)) { $events = array_reverse($events); foreach ($events as $micro => $message) { if (!is_string($message)) { /* incompatible JSON data */ continue; } $offset = strpos($micro, '_'); $time = substr($micro, 0, $offset); $auditlogs[] = sprintf( '%s %s : %s', SucuriScan::datetime($time, 'Y-m-d H:i:s'), SucuriScan::getSiteEmail(), $message ); } } $res = array( 'status' => 1, 'action' => 'get_logs', 'request_time' => time(), 'verbose' => 0, 'output' => array_reverse($auditlogs), 'total_entries' => count($auditlogs), 'from_queue' => '1', ); return self::parseAuditLogs($res, $filters); } /** * Retrieves the available filters for the audit logs. * * @return array An associative array containing the filters for the auditlogs. */ public static function getFilters() { $format = 'Y-m-d'; $today = strtotime('today'); $yesterday = strtotime('yesterday'); $thisWeek = strtotime('monday this week'); $last7Days = strtotime('-7 days'); $lastWeek = strtotime('monday last week'); $last14Days = strtotime('-14 days'); $thisMonth = strtotime('first day of this month'); $last30Days = strtotime('-30 days'); $lastMonth = strtotime('first day of last month'); $thisYear = strtotime('first day of January this year'); $lastYear = strtotime('first day of January last year'); return array( 'time' => array( 'all time' => array( 'label' => __('All Time', 'sucuri-scanner'), 'date' => null, ), 'today' => array( 'label' => __('Today', 'sucuri-scanner'), 'date' => SucuriScan::datetime($today, $format), ), 'yesterday' => array( 'label' => __('Yesterday', 'sucuri-scanner'), 'date' => SucuriScan::datetime($yesterday, $format), ), 'this week' => array( 'label' => __('This Week', 'sucuri-scanner'), 'date' => SucuriScan::datetime($thisWeek, $format), ), 'last 7 days' => array( 'label' => __('Last 7 Days', 'sucuri-scanner'), 'date' => SucuriScan::datetime($last7Days, $format), ), 'last week' => array( 'label' => __('Last Week', 'sucuri-scanner'), 'date' => SucuriScan::datetime($lastWeek, $format), ), 'last 14 days' => array( 'label' => __('Last 14 Days', 'sucuri-scanner'), 'date' => SucuriScan::datetime($last14Days, $format), ), 'this month' => array( 'label' => __('This Month', 'sucuri-scanner'), 'date' => SucuriScan::datetime($thisMonth, $format), ), 'last 30 days' => array( 'label' => __('Last 30 Days', 'sucuri-scanner'), 'date' => SucuriScan::datetime($last30Days, $format), ), 'last month' => array( 'label' => __('Last Month', 'sucuri-scanner'), 'date' => SucuriScan::datetime($lastMonth, $format), ), 'this year' => array( 'label' => __('This Year', 'sucuri-scanner'), 'date' => SucuriScan::datetime($thisYear, $format), ), 'last year' => array( 'label' => __('Last Year', 'sucuri-scanner'), 'date' => SucuriScan::datetime($lastYear, $format), ), 'custom' => array( 'label' => __('Custom', 'sucuri-scanner'), 'date' => null, ), ), 'startDate' => '', 'endDate' => '', 'posts' => array( 'all posts' => array( 'label' => __('All Posts', 'sucuri-scanner'), 'value' => null, ), 'created' => array( 'label' => __('Created', 'sucuri-scanner'), 'value' => 'Post was created', ), 'updated' => array( 'label' => __('Updated', 'sucuri-scanner'), 'value' => 'Post was updated', ), 'deleted' => array( 'label' => __('Deleted', 'sucuri-scanner'), 'value' => 'Post moved to trash', ), ), 'logins' => array( 'all logins' => array( 'label' => __('All Logins', 'sucuri-scanner'), 'value' => '', ), 'failed' => array( 'label' => __('Failed', 'sucuri-scanner'), 'value' => 'authentication failed', ), 'succeeded' => array( 'label' => __('Succeeded', 'sucuri-scanner'), 'value' => 'authentication succeeded', ), ), 'users' => array( 'all users' => array( 'label' => __('All Users', 'sucuri-scanner'), 'value' => '', ), 'created' => array( 'label' => __('Created', 'sucuri-scanner'), 'value' => 'User account created', ), 'edited' => array( 'label' => __('Edited', 'sucuri-scanner'), 'value' => 'User account edited', ), 'deleted' => array( 'label' => __('Deleted', 'sucuri-scanner'), 'value' => 'User account deleted', ), ), 'plugins' => array( 'all plugins' => array( 'label' => __('All Plugins', 'sucuri-scanner'), 'value' => '', ), 'installed' => array( 'label' => __('Installed', 'sucuri-scanner'), 'value' => 'Plugin installed', ), 'activated' => array( 'label' => __('Activated', 'sucuri-scanner'), 'value' => 'Plugin activated', ), 'deactivated' => array( 'label' => __('Deactivated', 'sucuri-scanner'), 'value' => 'Plugin deactivated', ), ), ); } /** * Filters a log entry based on the frontend filters provided. * Returns true if the log matches the time filter (if applied) and any of the other filters. * * @param array $log The log entry to be filtered. * @param array $frontend_filters The filters applied from the frontend. * * @return bool True if the log passes the filters, false otherwise. */ private static function filterAuditLog($log, $frontend_filters) { $filters = self::getFilters(); // Check the time filter first if (isset($frontend_filters['time']) && $frontend_filters['time'] !== 'all time') { if (!self::filterByTime($log['date'], $frontend_filters)) { return false; } } $other_filters = $frontend_filters; unset($other_filters['time'], $other_filters['startDate'], $other_filters['endDate']); if (empty($other_filters)) { return true; } // Check if the log matches any of the other filters foreach ($other_filters as $active_filter => $value_filter) { if (isset($filters[$active_filter][$value_filter])) { $search_term = $filters[$active_filter][$value_filter]['value']; if (strpos($log['message'], $search_term) !== false) { return true; // Log matches one of the filters } } } return false; } /** * Checks if a log entry matches the specified time filter. * * @param array $log_date The date of the log. * @param array $frontend_filters The filters applied from the frontend. * * @return bool True if the log matches the time filter, false otherwise. */ private static function filterByTime($log_date, $frontend_filters) { $filters = self::getFilters(); $today = strtotime('today'); $time_option = $frontend_filters['time']; $filter_date = SucuriScan::datetime($today, 'Y-m-d'); if (isset($filters['time'][$time_option]['date'])) { $filter_date = $filters['time'][$time_option]['date']; } switch ($time_option) { case 'today': case 'yesterday': return $log_date === $filter_date; case 'last week': $endDate = strtotime('sunday last week'); $endDate = SucuriScan::datetime($endDate, 'Y-m-d'); return $log_date >= $filter_date && $log_date <= $endDate; case 'last month': $endDate = strtotime('last day of last month'); $endDate = SucuriScan::datetime($endDate, 'Y-m-d'); return $log_date >= $filter_date && $log_date <= $endDate; case 'last year': $endDate = strtotime('last day of December last year'); $endDate = SucuriScan::datetime($endDate, 'Y-m-d'); return $log_date >= $filter_date && $log_date <= $endDate; case 'this week': case 'last 7 days': case 'last 14 days': case 'this month': case 'last 30 days': case 'this year': return $log_date >= $filter_date; case 'custom': $startDate = strtotime($frontend_filters['startDate']); $startDate = SucuriScan::datetime($startDate, 'Y-m-d'); $endDate = strtotime($frontend_filters['endDate']); $endDate = SucuriScan::datetime($endDate, 'Y-m-d'); return $log_date >= $startDate && $log_date <= $endDate; default: // Unrecognized time option; don't consider it a match. return false; } } /** * Reads, parses and extracts relevant data from the security logs. * * @param array $res JSON-decoded logs. * @return array|null Full data extracted from the logs. */ private static function parseAuditLogs($res, $filters = array()) { if (!is_array($res) || !isset($res['output'])) { return null; } $res['output_data'] = array(); foreach ((array)@$res['output'] as $log) { /* YYYY-MM-dd HH:ii:ss EMAIL : MESSAGE: (multiple entries): a,b,c */ if (strpos($log, "\x20:\x20") === false) { continue; /* ignore; invalid format */ } $log_data = array( 'event' => 'notice', 'date' => '', 'time' => '', 'datetime' => '', 'timestamp' => 0, 'account' => '', 'username' => 'system', 'remote_addr' => '127.0.0.1', 'message' => '', 'file_list' => false, 'file_list_count' => 0, ); list($left, $right) = explode("\x20:\x20", $log, 2); $dateAndEmail = explode("\x20", $left, 3); /* set basic information */ $log_data['message'] = $right; $log_data['account'] = $dateAndEmail[2]; /** * When the audit logs comes from the queue, it's necessary to convert * the logs using the correct timezone before parsing to avoid issues. * First, use timezone override feature if set on the plugin settings, * convert it properly as the syntax must be compatible with php strtotime, * otherwise use WordPress timezone or offset with a quick fix only for UTC * as by default it would be set as "0" instead of "UTC". */ $tz_override = SucuriScanOption::getOption(':timezone'); if (empty($tz_override)) { $wpTimezone = get_option('timezone_string'); if (empty($wpTimezone)) { $wpTimezone = get_option('gmt_offset'); } /* set wpTimezone to UTC if was previously unset */ if ($wpTimezone == "0") { $wpTimezone = "UTC"; } } else { $tz_override_replace_from = array(".", "UTC"); $tz_override_replace_to = array(":", ""); $wpTimezone = str_replace($tz_override_replace_from, $tz_override_replace_to, $tz_override); } /** * When the audit logs comes from the audit logs server, it will * be using EDT timezone, however due to the seasonal nature of the * EDT timzeone, here we will be using America/New_York when and only * when the audit logs comes from the audit logs server, cause when * it comes from the queue, wpTimezone var will be used. */ if (array_key_exists('from_queue', $res)) { $datetime = sprintf('%s %s %s', $dateAndEmail[0], $dateAndEmail[1], $wpTimezone); } else { $datetime = sprintf('%s %s America/New_York', $dateAndEmail[0], $dateAndEmail[1]); } $log_data['timestamp'] = strtotime($datetime); $log_data['datetime'] = SucuriScan::datetime($log_data['timestamp'], 'Y-m-d H:i:s'); $log_data['date'] = SucuriScan::datetime($log_data['timestamp'], 'Y-m-d'); $log_data['time'] = SucuriScan::datetime($log_data['timestamp'], 'H:i:s'); /* extract more information from the generic audit logs */ $log_data['message'] = str_replace('<br>', ";\x20", $log_data['message']); $eventTypes = self::getAuditEventTypes(); $eventTypes = array_keys($eventTypes); /* LEVEL: USERNAME, IP; MESSAGE */ if (strpos($log_data['message'], ":\x20") && strpos($log_data['message'], ";\x20")) { $offset = strpos($log_data['message'], ":\x20"); $level = substr($log_data['message'], 0, $offset); $log_data['event'] = strtolower($level); /* ignore; invalid event type */ if (!in_array($log_data['event'], $eventTypes)) { continue; } /* extract the IP address */ $log_data['message'] = substr($log_data['message'], $offset + 2); $offset = strpos($log_data['message'], ";\x20"); $log_data['remote_addr'] = substr($log_data['message'], 0, $offset); /* extract the username */ if (strpos($log_data['remote_addr'], ",\x20")) { $index = strpos($log_data['remote_addr'], ",\x20"); $log_data['username'] = substr($log_data['remote_addr'], 0, $index); $log_data['remote_addr'] = substr($log_data['remote_addr'], $index + 2); } /* fix old user authentication logs for backward compatibility */ $log_data['message'] = substr($log_data['message'], $offset + 2); $log_data['message'] = str_replace( 'logged in', 'authentication succeeded', $log_data['message'] ); /* extract the username of a successful/failed login */ if (strpos($log_data['message'], "User authentication\x20") === 0) { $offset = strpos($log_data['message'], ":\x20"); $username = substr($log_data['message'], $offset + 2); if (strpos($username, ';') !== false) { $username = substr($username, 0, strpos($username, ';')); } $log_data['username'] = $username; } } /* extract more information from the special formatted logs */ if (strpos($log_data['message'], "(multiple entries):\x20")) { $offset = strpos($log_data['message'], "(multiple entries):\x20"); $message = substr($log_data['message'], 0, $offset + 19); $entries = substr($log_data['message'], $offset + 20); $log_data['message'] = $message; $entries = str_replace(', new size', '; new size', $entries); $entries = str_replace(",\x20", ";\x20", $entries); $log_data['file_list'] = explode(',', $entries); $log_data['file_list_count'] = count($log_data['file_list']); } /* extract additional details from the message */ if (strpos($log_data['message'], '; details:')) { $idx = strpos($log_data['message'], '; details:'); $message = substr($log_data['message'], 0, $idx); $details = substr($log_data['message'], $idx + 11); $log_data['message'] = $message . ' (details):'; $log_data['file_list'] = explode(',', $details); $log_data['file_list_count'] = count($log_data['file_list']); } $log_data = self::getLogsHotfix($log_data); // Based on filters, evaluate if should skip. if (self::filterAuditLog($log_data, $filters) === false) { continue; } if ($log_data) { $res['output_data'][] = $log_data; } } return $res; } /** * Modifies some of the security logs to detail the information. * * @param array $data Valid security log data structure. * @return array|bool Modified security log. */ private static function getLogsHotfix($data) { /** * PHP Compatibility Checker * * The WP Engine PHP Compatibility Checker can be used by any WordPress * website on any web host to check PHP version compatibility. This * plugin will lint theme and plugin code inside your WordPress file * system and give you back a report of compatibility issues for you to * fix. * * @see https://wordpress.org/plugins/php-compatibility-checker/ */ if (isset($data['message']) && strpos($data['message'], 'Wpephpcompat_jobs') === 0) { $offset = strpos($data['message'], "ID:\x20"); $id = substr($data['message'], $offset + 4); $id = substr($id, 0, strpos($id, ';')); $offset = strpos($data['message'], "name:\x20"); $name = substr($data['message'], $offset + 6); $data['message'] = sprintf( __('WP Engine PHP Compatibility Checker: %s (created post #%d as cache)', 'sucuri-scanner'), $name, /* plugin or theme name */ $id /* unique post or page identifier */ ); } return $data; } /** * Get a list of valid audit event types with their respective colors. * * @return array Valid audit event types with their colors. */ public static function getAuditEventTypes() { return array( 'critical' => '#000000', 'debug' => '#c690ec', 'error' => '#f27d7d', 'info' => '#5bc0de', 'notice' => '#428bca', 'warning' => '#f0ad4e', ); } /** * Parse the event logs with multiple entries. * * @param string $event_log Event log that will be processed. * @return string|array List of parts of the event log. */ public static function parseMultipleEntries($event_log = '') { $pattern = "\x20(multiple entries):\x20"; if (strpos($event_log, $pattern)) { return explode(',', str_replace($pattern, ',', $event_log)); } return $event_log; } /** * Send a request to the API to store and analyze the file's hashes of the site. * This will be the core of the monitoring tools and will enhance the * information of the audit logs alerting the administrator of suspicious * changes in the system. * * @param string $hashes The information gathered after the scanning of the site's files. * @return bool True if the hashes were stored, false otherwise. */ public static function sendHashes($hashes = '') { if (empty($hashes)) { return false; } $params = array('a' => 'send_hashes', 'h' => $hashes); $res = self::apiCallWordpress('POST', $params); return self::handleResponse($res); } /** * Generates a new set of WordPress security keys. * * @return array New set of WordPress security keys. */ public static function getNewSecretKeys() { $new_keys = array(); $pattern = self::secretKeyPattern(); $res = self::apiCall('https://api.wordpress.org/secret-key/1.1/salt/', 'GET'); if ($res && @preg_match_all($pattern, $res, $match)) { foreach ($match[1] as $key => $value) { $new_keys[$value] = $match[3][$key]; } } return $new_keys; } /** * Returns the URL for the WordPress checksums API service. * * @return string URL for the WordPress checksums API. */ public static function checksumAPI() { $url = 'https://api.wordpress.org/core/checksums/1.0/?version={version}&locale={locale}'; $custom = SucuriScanOption::getOption(':checksum_api'); if ($custom) { $url = sprintf( 'https://api.github.com/repos/%s/git/trees/master?recursive=1', $custom /* expect: username/repository */ ); } $url = str_replace('{version}', SucuriScan::siteVersion(), $url); $url = str_replace('{locale}', get_locale(), $url); return $url; } /** * Returns the name of the hash to use in the integrity tool * * By default, the plugin will use MD5 to hash the content of the specified * file, however, if the core integrity tool is using a custom URL, and this * URL is pointing to GitHub API, then we will assume that the checksum that * comes from this service is using SHA1. * * @return string Hash to use in the integrity tool. */ public static function checksumAlgorithm() { return strpos(self::checksumAPI(), '//api.github.com') ? 'sha1' : 'md5'; } /** * Calculates the md5/sha1 hash of a given file. * * When the user decides to configure the integrity tool to use the checksum * from a GitHub repository the plugin will have to use the SHA1 algorithm * instead of MD5 (which is what WordPress uses in their API). For this, we * will have to calculate the GIT hash object of the file which is basically * the merge of the text "blob" a single white space, the length of the text * a null byte and then the text in itself (content of the file). * * Example: * * - Input: "hello world\n" * - GIT (object): "blob 16\u0000hello world\n" * - GIT (shaobj): "3b18e512dba79e4c8300dd08aeb37f8e728b8dad" * * @see https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#_object_storage * * @param string $algorithm Either md5 or sha1. * @param string $filename Absolute path to the given file. * @return string Hash of the given file. */ public static function checksum($algorithm, $filename) { if ($algorithm === 'sha1') { $content = SucuriScanFileInfo::fileContent($filename); return @sha1("blob\x20" . strlen($content) . "\x00" . $content); } return @md5_file($filename); } /** * Returns the checksum of all the files of the current WordPress version. * * The webmaster can change this URL using an option form the settings page. * This allows them to control which repository will be used to check the * integrity of the installation. * * For example, projectnami.org offers an option to use Microsoft SQL Server * instead of MySQL has a different set of files and even with the same * filenames many of them have been modified to support the new database * engine, since the checksums are different than the official ones the * number of false positives will increase. This option allows the webmaster * to point the plugin to a different URL where the new checksums for this * project will be retrieved. * * If the custom API is part of GitHub infrastructure, the plugin will try * to build the expected JSON object from the output, if it fails it will * pass the unmodified response to the rest of the code and try to analyze * the integrity of the installation with that information. * * @see Release Archive https://wordpress.org/download/release-archive/ * @see https://api.github.com/repos/user/repo/git/trees/master?recursive=1 * * @return array|bool Checksums of the WordPress installation. */ public static function getOfficialChecksums() { $url = self::checksumAPI(); $version = SucuriScan::siteVersion(); $res = self::apiCall($url, 'GET', array()); if (is_array($res) && array_key_exists('sha', $res) && array_key_exists('url', $res) && array_key_exists('tree', $res) && strpos($url, '//api.github.com') ) { $checksums = array(); foreach ($res['tree'] as $meta) { $checksums[$meta['path']] = $meta['sha']; } $res = array('checksums' => array($version => $checksums)); } if (!isset($res['checksums'])) { return false; } /* checksums for a specific version */ if (isset($res['checksums'][$version])) { return $res['checksums'][$version]; } return $res['checksums']; } /** * Returns the metadata of all the installed plugins. * * @see https://developer.wordpress.org/reference/functions/is_plugin_active/ * * @return array List of plugins with associated metadata. */ public static function getPlugins() { $cache = new SucuriScanCache('plugindata'); $cached_data = $cache->get('plugins', SUCURISCAN_GET_PLUGINS_LIFETIME, 'array'); /* use cache data instead of API */ if ($cached_data) { return $cached_data; } // Get the plugin's basic information from WordPress transient data. $plugins = get_plugins(); $wp_market = 'https://wordpress.org/plugins/%s/'; $pattern = '/^http(s)?:\/\/wordpress\.org\/plugins\/(.*)\/$/'; // Loop through each plugin data and complement its information with more attributes. foreach ($plugins as $path => $plugin_data) { // Default values for the plugin extra attributes. $repository = ''; $repository_name = ''; $is_free_plugin = false; /** * Extract the information of the plugin which includes the repository name, * repository URL, and if the source code of the plugin is publicly released or * not, in this last case if the source code of the plugin is not hosted in the * official WordPress server it means that it is premium and is being * distributed by an independent developer. */ if (isset($plugin_data['PluginURI']) && strpos($plugin_data['PluginURI'], '.org/plugins/') && strpos($plugin_data['PluginURI'], '://wordpress.org/') ) { $is_free_plugin = true; $repository = $plugin_data['PluginURI']; $offset = strpos($plugin_data['PluginURI'], '/plugins/'); $repository_name = substr($plugin_data['PluginURI'], $offset + 9); if (strpos($repository_name, '/') !== false) { $offset = strpos($repository_name, '/'); $repository_name = substr($repository_name, 0, $offset); } } else { $delimiter = strpos($path, '/') ? '/' : '.'; $parts = explode($delimiter, $path, 2); $possible_repository = sprintf($wp_market, $parts[0]); $resp = wp_remote_head($possible_repository); if (!is_wp_error($resp) && $resp['response']['code'] == 200) { $repository = $possible_repository; $repository_name = $parts[0]; $is_free_plugin = true; } } // Complement the plugin's information with these attributes. $plugins[$path]['Repository'] = $repository; $plugins[$path]['RepositoryName'] = $repository_name; $plugins[$path]['InstallationPath'] = sprintf('%s/%s', WP_PLUGIN_DIR, $repository_name); $plugins[$path]['PluginType'] = ($is_free_plugin ? 'free' : 'premium'); $plugins[$path]['IsPluginInstalled'] = is_dir($plugins[$path]['InstallationPath']); $plugins[$path]['IsPluginActive'] = is_plugin_active($path); $plugins[$path]['IsFreePlugin'] = $is_free_plugin; } /* cache data for future usage */ $cache->add('plugins', $plugins); return $plugins; } /** * Retrieve plugin installer pages from WordPress Plugins API. * * It is possible for a plugin to override the Plugin API result with three * filters. Assume this is for plugins, which can extend on the Plugin Info to * offer more choices. This is very powerful and must be used with care, when * overriding the filters. * * The first filter, 'plugins_api_args', is for the args and gives the action as * the second parameter. The hook for 'plugins_api_args' must ensure that an * object is returned. * * The second filter, 'plugins_api', is the result that would be returned. * * @param string $plugin Frienly name of the plugin. * @return array|bool Object on success, WP_Error on failure. */ public static function getRemotePluginData($plugin = '') { $resp = self::apiCall('https://api.wordpress.org/plugins/info/1.0/' . $plugin . '.json', 'GET'); return ($resp === 'null') ? false : $resp; } /** * Retrieve a specific file from the official WordPress subversion repository, * the content of the file is determined by the tags defined using the site * version specified. Only official core files are allowed to fetch. * * @see https://core.svn.wordpress.org/ * @see https://i18n.svn.wordpress.org/ * @see https://core.svn.wordpress.org/tags/VERSION_NUMBER/ * * @param string $filename Relative path of a core file. * @return string|bool Original code for the core file, false otherwise. */ public static function getOriginalCoreFile($filename) { $version = self::siteVersion(); $url = 'https://core.svn.wordpress.org/tags/{version}/{filename}'; $custom = SucuriScanOption::getOption(':checksum_api'); if ($custom) { $url = sprintf( 'https://raw.githubusercontent.com/%s/master/{filename}', $custom /* expect: username/repository */ ); } $url = str_replace('{version}', $version, $url); $url = str_replace('{filename}', $filename, $url); $resp = self::apiCall($url, 'GET'); if (strpos($resp, '404 Not Found') !== false) { /* not found comes from the official WordPress API */ return self::throwException(__('WordPress version is not supported anymore', 'sucuri-scanner')); } if (strpos($resp, '400: Invalid request') !== false) { /* invalid request comes from the unofficial GitHub API */ return self::throwException(__('WordPress version is not supported anymore', 'sucuri-scanner')); } return $resp ? $resp : false; } }
| ver. 1.4 |
Github
|
.
| PHP 8.0.30 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка