File "firewall.lib.php"
Full Path: /home/rrterraplen/public_html/wp-content-20241221212636/plugins/sucuri-scanner/src/firewall.lib.php
File size: 25.74 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Code related to the firewall.lib.php interface.
*
* PHP version 5
*
* @category Library
* @package Sucuri
* @subpackage SucuriScanner
* @author Daniel Cid <[email protected]>
* @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);
}
/**
* Defines methods to interact with Sucuri Firewall's API service.
*
* @category Library
* @package Sucuri
* @subpackage SucuriScanner
* @author Daniel Cid <[email protected]>
* @copyright 2010-2018 Sucuri Inc.
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL2
* @link https://wordpress.org/plugins/sucuri-scanner
*/
class SucuriScanFirewall extends SucuriScanAPI
{
/**
* Check whether the firewall API key is valid or not.
*
* @param string $api_key The firewall API key.
* @param bool $return_match Whether the parts of the API key must be returned or not.
* @return array|bool True if the API key specified is valid, false otherwise.
*/
public static function isValidKey($api_key = '', $return_match = false)
{
$pattern = '/^([a-z0-9]{32})\/([a-z0-9]{32})$/';
if ($api_key && preg_match($pattern, $api_key, $match)) {
return $return_match ? $match : true;
}
return false;
}
/**
* Check and return the API key for the plugin.
*
* In this plugin the key is a pair of two strings concatenated by a single
* slash, the first part of it is in fact the key and the second part is the
* unique identifier of the site in the remote server.
*
* @return array|bool false if the key is invalid or not present, an array otherwise.
*/
public static function getKey()
{
$option_name = ':cloudproxy_apikey';
$api_key = self::getOption($option_name);
// Check the validity of the API key.
$match = self::isValidKey($api_key, true);
if ($match) {
return array(
'string' => $match[1].'/'.$match[2],
'k' => $match[1],
's' => $match[2],
);
}
return false;
}
/**
* Call an action from the remote API interface of our firewall service.
*
* @param string $method HTTP method that will be used to send the request.
* @param array $params HTTP request parameters (key-value array).
* @return array|bool HTTP response object.
*/
public static function apiCallFirewall($method = 'GET', $params = array())
{
$send_request = (bool) (isset($params['k']) && isset($params['s']));
if (!$send_request) {
$api_key = self::getKey();
if ($api_key) {
$send_request = true;
$params['k'] = $api_key['k'];
$params['s'] = $api_key['s'];
}
}
if ($send_request) {
unset($params['string']);
$params[SUCURISCAN_CLOUDPROXY_API_VERSION] = 1;
return self::apiCall(SUCURISCAN_CLOUDPROXY_API, $method, $params);
}
return false;
}
/**
* Retrieve the public settings of the account associated with the API keys
* registered by the administrator of the site. This method will send a HTTP
* request to the remote API service and process its response, when successful
* it will return an array/object containing the public attributes of the site.
*
* @param array|bool $api_key The firewall API key.
* @return array|bool A hash with the settings of a firewall account.
*/
public static function settings($api_key = false)
{
$params = array('a' => 'show_settings');
if ($api_key) {
$params = array_merge($params, $api_key);
}
$response = self::apiCallFirewall('GET', $params);
return self::handleResponse($response) ? $response['output'] : false;
}
/**
* Generate the HTML code for the firewall settings panel.
*
* @codeCoverageIgnore
*
* @return string The parsed-content of the firewall settings panel.
*/
public static function settingsPage()
{
$params = array(
'Firewall.APIKey' => '',
'Firewall.APIKeyVisibility' => 'hidden',
'Firewall.APIKeyFormVisibility' => 'visible',
);
if (SucuriScanInterface::checkNonce()) {
// Add and/or Update the Sucuri WAF API Key (do it before anything else).
$option_name = ':cloudproxy_apikey';
$api_key = SucuriScanRequest::post($option_name);
if ($api_key !== false) {
$api_key = trim($api_key);
if (self::isValidKey($api_key)) {
SucuriScanOption::updateOption($option_name, $api_key);
SucuriScanInterface::info(__('Firewall API key was successfully saved', 'sucuri-scanner'));
SucuriScanOption::setRevProxy('enable');
SucuriScanOption::setAddrHeader('HTTP_X_SUCURI_CLIENTIP');
} else {
SucuriScanInterface::error('Invalid firewall API key');
}
}
// Delete the firewall API key from the plugin.
if (SucuriScanRequest::post(':delete_wafkey') !== false) {
SucuriScanOption::deleteOption($option_name);
SucuriScanInterface::info(__('Firewall API key was successfully removed', 'sucuri-scanner'));
SucuriScanOption::setRevProxy('disable');
SucuriScanOption::setAddrHeader('REMOTE_ADDR');
}
}
$api_key = self::getKey();
if ($api_key && array_key_exists('string', $api_key)) {
$params['Firewall.APIKeyVisibility'] = 'visible';
$params['Firewall.APIKeyFormVisibility'] = 'hidden';
$params['Firewall.APIKey'] = $api_key['string'];
}
return SucuriScanTemplate::getSection('firewall-settings', $params);
}
/**
* Converts the value of some of the firewall settings into a human-readable
* text, for example changing numbers or variable names into a more explicit
* text so the administrator can understand the meaning of these settings.
*
* @param array $settings A hash with the settings of a firewall account.
* @return array The explained version of the firewall settings.
*/
public static function settingsExplanation($settings = array())
{
if (!is_array($settings)) {
return array();
}
$cache_modes = array(
'docache' => __('enabled (recommended)', 'sucuri-scanner'),
'sitecache' => __('site caching (using your site headers)', 'sucuri-scanner'),
'nocache' => __('minimal (only for a few minutes)', 'sucuri-scanner'),
'nocacheatall' => __('caching disabled (use with caution)', 'sucuri-scanner'),
);
foreach ($settings as $keyname => $value) {
if ($keyname == 'proxy_active') {
$settings[$keyname] = ($value === 1) ? 'active' : 'not active';
continue;
}
if ($keyname == 'cache_mode') {
if (array_key_exists($value, $cache_modes)) {
$settings[$keyname] = $cache_modes[$value];
} else {
$settings[$keyname] = 'unknown';
}
continue;
}
}
return $settings;
}
/**
* Returns the public firewall settings.
*
* @codeCoverageIgnore
*
* @return void
*/
public static function getSettingsAjax()
{
if (SucuriScanRequest::post('form_action') !== 'firewall_settings') {
return;
}
$response = array();
$response['ok'] = false;
$api_key = self::getKey();
ob_start();
$settings = self::settings($api_key);
$error = ob_get_clean();
if (!$settings) {
if (empty($error)) {
ob_start();
SucuriScanInterface::error(__('Firewall API key was not found.', 'sucuri-scanner'));
$response['error'] = ob_get_clean();
} else {
$response['error'] = $error;
}
wp_send_json($response, 200);
}
$response['ok'] = true;
$response['settings'] = self::settingsExplanation($settings);
unset($response['settings']['whitelist_list']);
unset($response['settings']['blacklist_list']);
wp_send_json($response, 200);
}
/**
* Retrieve the audit logs of the account associated with the API keys
* registered b the administrator of the site. This method will send a HTTP
* request to the remote API service and process its response, when successful
* it will return an array/object containing a list of requests blocked by our
* firewall.
*
* By default the logs that will be retrieved are from today, if you need to see
* the logs of previous days you will need to add a new parameter to the request
* URL named "date" with format yyyy-mm-dd.
*
* @param array|string $api_key The firewall API key.
* @param string $date Retrieve the data from this date.
* @param string $query Filter the data to match this query.
* @param int $limit Retrieve this maximum of data.
* @param int $offset Retrieve the data from this point.
* @return array|bool Objects with details of each blocked request.
*/
public static function auditlogs($api_key, $date = '', $query = '', $limit = 10, $offset = 0)
{
$params = array(
'a' => 'audit_trails',
'date' => $date,
'query' => $query,
'limit' => $limit,
'offset' => $offset,
);
if (is_array($api_key)) {
$params = array_merge($params, $api_key);
}
$response = self::apiCallFirewall('GET', $params);
return self::handleResponse($response) ? $response['output'] : false;
}
/**
* Generate the HTML code for the firewall logs panel.
*
* @return string The parsed-content of the firewall logs panel.
*/
public static function auditlogsPage()
{
$params = array();
/* logs are available after 24 hours */
$date = SucuriScan::datetime(strtotime('-1 day'), 'Y-m-d');
$params['AuditLogs.DateYears'] = self::dates('years', $date);
$params['AuditLogs.DateMonths'] = self::dates('months', $date);
$params['AuditLogs.DateDays'] = self::dates('days', $date);
return SucuriScanTemplate::getSection('firewall-auditlogs', $params);
}
/**
* Returns the security logs from the Firewall API.
*
* The API allows to filter the logs by day and by user input. This operation
* depends on the availability of the Firewall API key, if the website owner has
* not signed up for the Firewall service then they will not have access to this
* feature. The plugin will display a warning in this case.
*
* @codeCoverageIgnore
*
* @return void
*/
public static function auditlogsAjax()
{
if (SucuriScanRequest::post('form_action') !== 'get_firewall_logs') {
return;
}
$response = '';
$api_key = self::getKey();
if (!$api_key) {
ob_start();
SucuriScanInterface::error(__('Firewall API key was not found.', 'sucuri-scanner'));
$response = ob_get_clean();
wp_send_json($response, 200);
}
$query = SucuriScanRequest::post(':query');
$month = SucuriScanRequest::post(':month');
$year = SucuriScanRequest::post(':year');
$day = SucuriScanRequest::post(':day');
$limit = 50;
$offset = 1;
if ($year && $month && $day) {
$date = sprintf('%s-%s-%s', $year, $month, $day);
} else {
$date = SucuriScan::datetime(null, 'Y-m-d');
}
ob_start();
$auditlogs = self::auditlogs(
$api_key,
$date, /* Retrieve the data from this date. */
$query, /* Filter the data to match this query. */
$limit, /* Retrieve this maximum of data. */
$offset /* Retrieve the data from this point. */
);
$error = ob_get_clean();
if (!$auditlogs && !empty($error)) {
wp_send_json($error, 200);
}
if ($auditlogs && array_key_exists('total_lines', $auditlogs)) {
$response = self::auditlogsEntries($auditlogs['access_logs']);
if (empty($response)) {
$response = '<tr><td>' . __('no data available.', 'sucuri-scanner') . '</td></tr>';
}
}
wp_send_json($response, 200);
}
/**
* Returns the security logs from the firewall in HTML.
*
* @param array $entries Security logs retrieved from the Firewall API.
* @return string HTML with the information from the logs.
*/
public static function auditlogsEntries($entries = array())
{
if (!is_array($entries) || empty($entries)) {
return ''; /* empty response */
}
$output = '';
$attributes = array(
'remote_addr',
'request_date',
'request_time',
'request_timezone',
'request_method',
'resource_path',
'http_protocol',
'http_status',
'http_status_title',
'http_referer',
'http_user_agent',
'sucuri_block_code',
'sucuri_block_reason',
'request_country_name',
'request_country_code',
);
foreach ($entries as $entry) {
if (array_key_exists('is_usable', $entry) && $entry['is_usable']) {
$data_set = array();
foreach ($attributes as $attr) {
/* generate variable name for the template pseudo-tags */
$keyname = str_replace('_', "\x20", $attr);
$keyname = ucwords($keyname);
$keyname = str_replace("\x20", '', $keyname);
$keyname = 'AccessLog.' . $keyname;
/* assign and escape variable value before rendering */
$data_set[$keyname] = isset($entry[$attr]) ? $entry[$attr] : '';
/* special cases to convert value to readable data */
if ($attr == 'resource_path' && $data_set[$keyname] == '/') {
$data_set[$keyname] = '/ (root of the website)';
} elseif ($attr == 'http_referer' && $data_set[$keyname] == '-') {
$data_set[$keyname] = '- (no referer)';
} elseif ($attr == 'request_country_name' && $data_set[$keyname] == '') {
$data_set[$keyname] = __('Anonymous', 'sucuri-scanner');
}
}
$output .= SucuriScanTemplate::getSnippet('firewall-auditlogs', $data_set);
}
}
return $output;
}
/**
* Get a list of years, months or days depending of the type specified.
*
* @param string $type Either years, months or days.
* @param string $date Year, month and day selected from the request.
* @param bool $in_html Whether the list should be converted to a HTML select options or not.
* @return array|string Either an array with the expected values, or a HTML code.
*/
public static function dates($type = '', $date = '', $in_html = true)
{
$options = array();
$selected = '';
$pattern = '/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/';
$s_year = '';
$s_month = '';
$s_day = '';
if (@preg_match($pattern, $date, $date_m)) {
$s_year = $date_m[1];
$s_month = $date_m[2];
$s_day = $date_m[3];
}
switch ($type) {
case 'years':
$selected = $s_year;
$current_year = (int) SucuriScan::datetime(null, 'Y');
$max_years = 5; /* Maximum number of years to keep the logs. */
$options = range(($current_year - $max_years), $current_year);
break;
case 'months':
$selected = $s_month;
$options = array(
'01' => __('January', 'sucuri-scanner'),
'02' => __('February', 'sucuri-scanner'),
'03' => __('March', 'sucuri-scanner'),
'04' => __('April', 'sucuri-scanner'),
'05' => __('May', 'sucuri-scanner'),
'06' => __('June', 'sucuri-scanner'),
'07' => __('July', 'sucuri-scanner'),
'08' => __('August', 'sucuri-scanner'),
'09' => __('September', 'sucuri-scanner'),
'10' => __('October', 'sucuri-scanner'),
'11' => __('November', 'sucuri-scanner'),
'12' => __('December', 'sucuri-scanner'),
);
break;
case 'days':
$options = range(1, 31);
$selected = $s_day;
break;
}
if ($in_html) {
$html_options = '';
foreach ($options as $key => $value) {
if (is_numeric($value)) {
$value = str_pad($value, 2, '0', STR_PAD_LEFT);
}
if ($type != 'months') {
$key = $value;
}
$selected_tag = ( $key == $selected ) ? 'selected="selected"' : '';
$html_options .= sprintf('<option value="%s" %s>%s</option>', $key, $selected_tag, $value);
}
return $html_options;
}
return $options;
}
/**
* Generate the HTML code for the firewall IP access panel.
*
* @return string The parsed-content of the firewall IP access panel.
*/
public static function ipAccessPage()
{
$params = array();
return SucuriScanTemplate::getSection('firewall-ipaccess', $params);
}
/**
* Returns the IP addresses in the blocklist and allowlist.
*
* @codeCoverageIgnore
*
* @return void
*/
public static function ipAccessAjax()
{
if (SucuriScanRequest::post('form_action') !== 'firewall_ipaccess') {
return;
}
$response = array();
$response['ok'] = false;
$api_key = self::getKey();
ob_start();
$settings = self::settings($api_key);
$error = ob_get_clean();
if (!$settings) {
if (empty($error)) {
ob_start();
SucuriScanInterface::error(__('Firewall API key was not found.', 'sucuri-scanner'));
$response['error'] = ob_get_clean();
} else {
$response['error'] = $error;
}
wp_send_json($response, 200);
}
$response['ok'] = true;
$response['allowlist'] = $settings['whitelist_list'];
$response['blocklist'] = $settings['blacklist_list'];
wp_send_json($response, 200);
}
/**
* Blocklists an IP address.
*
* @codeCoverageIgnore
*
* @return void
*/
public static function blocklistAjax()
{
if (SucuriScanRequest::post('form_action') !== 'firewall_blocklist') {
return;
}
$response = array();
$response['ok'] = false;
$params = self::getKey();
if (!$params) {
ob_start();
SucuriScanInterface::error(__('Firewall API key was not found.', 'sucuri-scanner'));
$response['msg'] = ob_get_clean();
wp_send_json($response, 200);
}
$params['a'] = 'blacklist_ip';
$params['ip'] = SucuriScanRequest::post('ip');
$out = self::apiCallFirewall('POST', $params);
$response['msg'] = __('Failure connecting to the API service; try again.', 'sucuri-scanner');
if ($out && !empty($out['messages'])) {
$response['ok'] = (bool) ($out['status'] == 1);
$response['msg'] = implode(";\x20", $out['messages']);
if ($out['status'] == 1) {
SucuriScanEvent::reportInfoEvent(sprintf(__('IP has been added to the blocklist: %s', 'sucuri-scanner'), $params['ip']));
}
}
wp_send_json($response, 200);
}
/**
* Deletes an IP address from the blocklist.
*
* @codeCoverageIgnore
*
* @return void
*/
public static function deblocklistAjax()
{
if (SucuriScanRequest::post('form_action') !== 'firewall_deblocklist') {
return;
}
$response = array();
$params = self::getKey();
if (!$params) {
ob_start();
$response['ok'] = false;
SucuriScanInterface::error(__('Firewall API key was not found.', 'sucuri-scanner'));
$response['error'] = ob_get_clean();
wp_send_json($response, 200);
}
$params['a'] = 'delete_blacklist_ip';
$params['ip'] = SucuriScanRequest::post('ip');
$out = self::apiCallFirewall('POST', $params);
$response['ok'] = (bool) ($out['status'] == 1);
$response['msg'] = implode(";\x20", $out['messages']);
if ($out['status'] == 1) {
SucuriScanEvent::reportInfoEvent(sprintf(__('IP has been removed from the blocklist: %s', 'sucuri-scanner'), $params['ip']));
}
wp_send_json($response, 200);
}
/**
* Flush the cache of the site(s) associated with the API key.
*
* @param array|bool $api_key The firewall API key.
* @return string|bool Message explaining the result of the operation.
*/
public static function clearCache($api_key = false, $path = '')
{
$params = array('a' => 'clear_cache');
$path = ltrim(trim($path), '/');
if ($path) {
$params['file'] = $path;
}
if (is_array($api_key)) {
$params = array_merge($params, $api_key);
}
$response = self::apiCallFirewall('GET', $params);
return self::handleResponse($response) ? $response : false;
}
/**
* Generate the HTML code for the firewall clear cache panel.
*
* @codeCoverageIgnore
*
* @return string The parsed-content of the firewall clear cache panel.
*/
public static function clearCachePage()
{
$params = array();
$params['FirewallAutoClearCache'] = 'data-status="disabled"';
if (self::shouldAutoClearCache()) {
$params['FirewallAutoClearCache'] = 'checked="checked"';
}
return SucuriScanTemplate::getSection('firewall-clearcache', $params);
}
/**
* Clear the firewall cache if necessary.
*
* Every time a page or post is modified and saved into the database the
* plugin will send a HTTP request to the firewall API service and except
* that, if the API key is valid, the cache is reset. Notice that the cache
* of certain files is going to stay as it is due to the configuration on the
* edge of the servers.
*
* @return void
*/
public static function clearCacheHook()
{
if (self::shouldAutoClearCache()) {
ob_start();
self::clearCache();
$error = ob_get_clean();
}
}
/**
* Requests a cache flush to the firewall service.
*
* @codeCoverageIgnore
*
* @return void
*/
public static function clearCacheAjax()
{
if (SucuriScanRequest::post('form_action') !== 'firewall_clear_cache') {
return;
}
ob_start();
SucuriScanInterface::error(__('Firewall API key was not found.', 'sucuri-scanner'));
$response = ob_get_clean();
$api_key = self::getKey();
if ($api_key) {
$path = SucuriScanRequest::post('path');
$res = self::clearCache($api_key, $path);
if (is_array($res) && isset($res['messages'])) {
$response = sprintf(
'<div class="sucuriscan-inline-alert-%s"><p>%s</p></div>',
($res['status'] == 1) ? 'success' : 'error',
implode('<br>', $res['messages'])
);
}
}
wp_send_json($response, 200);
}
/**
* Configures the status of the automatic cache flush.
*
* @codeCoverageIgnore
*
* @return void
*/
public static function clearAutoCacheAjax()
{
if (SucuriScanRequest::post('form_action') !== 'firewall_auto_clear_cache') {
return;
}
$response = array();
if (SucuriScanRequest::post('auto_clear_cache') === 'enable') {
$response['ok'] = SucuriScanOption::updateOption(':auto_clear_cache', 'enabled');
$response['status'] = 'enabled';
} else {
$response['ok'] = SucuriScanOption::deleteOption(':auto_clear_cache');
$response['status'] = 'disabled';
}
wp_send_json($response, 200);
}
/**
* Returns true if the plugin should flush the firewall cache.
*
* @return bool True if the plugin should flush the firewall cache.
*/
private static function shouldAutoClearCache()
{
return (bool) (
defined('SUCURI_CLEAR_CACHE_ON_PUBLISH')
|| SucuriScanOption::isEnabled(':auto_clear_cache')
);
}
}