<?php
/**
* Code related to the settings-general.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);
}
/**
* Renders a page with information about the reset options feature.
*
* @param bool $nonce True if the CSRF protection worked.
* @return string Page with information about the reset options.
*/
function sucuriscan_settings_general_resetoptions($nonce)
{
// Reset all the plugin's options.
if ($nonce && SucuriScanRequest::post(':reset_options') !== false) {
$process = SucuriScanRequest::post(':process_form');
if (intval($process) === 1) {
$message = __('Local security logs, hardening and settings were deleted', 'sucuri-scanner');
sucuriscanResetAndDeactivate(); /* simulate plugin deactivation */
SucuriScanEvent::reportCriticalEvent($message);
SucuriScanEvent::notifyEvent('plugin_change', $message);
SucuriScanInterface::info(__('Local security logs, hardening and settings were deleted', 'sucuri-scanner'));
} else {
SucuriScanInterface::error(__('You need to confirm that you understand the risk of this operation.', 'sucuri-scanner'));
}
}
return SucuriScanTemplate::getSection('settings-general-resetoptions');
}
/**
* Renders a page with information about the data storage feature.
*
* @param bool $nonce True if the CSRF protection worked.
* @return string Page with information about the data storage.
*/
function sucuriscan_settings_general_datastorage($nonce)
{
$params = array();
$files = array(
'<root>' => __('Directory used to store the plugin settings, cache and system logs', 'sucuri-scanner'),
'auditlogs' => sprintf(__('Cache to store the system logs obtained from the API service; expires after %s seconds.', 'sucuri-scanner'), SUCURISCAN_AUDITLOGS_LIFETIME),
'auditqueue' => __('Local queue to store the most recent logs before they are sent to the remote API service.', 'sucuri-scanner'),
'blockedusers' => __('Deprecated on 1.8.12; it was used to store a list of blocked user names.', 'sucuri-scanner'), /* TODO: deprecated on 1.8.12 */
'failedlogins' => __('Stores the data for every failed login attempt. The data is moved to "oldfailedlogins" every hour during a brute force password attack.', 'sucuri-scanner'),
'hookdata' => __('Temporarily stores data to complement the logs during destructive operations like deleting a post, page, comment, etc.', 'sucuri-scanner'),
'ignorescanning' => __('Stores a list of files and folders chosen by the user to be ignored by the file system scanner.', 'sucuri-scanner'),
'integrity' => __('Stores a list of files marked as fixed by the user via the WordPress Integrity tool.', 'sucuri-scanner'),
'lastlogins' => __('Stores the data associated to every successful user login. The data never expires; manually delete if the file is too large.', 'sucuri-scanner'),
'oldfailedlogins' => __('Stores the data for every failed login attempt after the plugin sends a report about a brute force password attack via email.', 'sucuri-scanner'),
'plugindata' => sprintf(__('Cache to store the data associated to the installed plugins listed in the Post-Hack page. Expires after %s seconds.', 'sucuri-scanner'), SUCURISCAN_GET_PLUGINS_LIFETIME),
'settings' => __('Stores all the options used to configure the functionality and behavior of the plugin.', 'sucuri-scanner'),
'sitecheck' => sprintf(__('Cache to store the result of the malware scanner. Expires after %s seconds, reset at any time to force a re-scan.', 'sucuri-scanner'), SUCURISCAN_SITECHECK_LIFETIME),
'trustip' => __('Stores a list of IP addresses trusted by the plugin, events triggered by one of these IPs will not be reported to the remote monitoring API service.', 'sucuri-scanner'),
);
$params['Storage.Files'] = '';
$params['Storage.Path'] = SucuriScan::dataStorePath();
if ($nonce) {
$filenames = SucuriScanRequest::post(':filename', '_array');
if ($filenames) {
$deleted = 0;
foreach ($filenames as $filename) {
$short = substr($filename, 7); /* drop directroy path */
$short = substr($short, 0, -4); /* drop file extension */
if (!$short || empty($short) || !array_key_exists($short, $files)) {
continue; /* prevent path traversal */
}
$filepath = SucuriScan::dataStorePath($filename);
if (!file_exists($filepath) || is_dir($filepath)) {
continue; /* there is nothing to reset */
}
/* ignore write permissions */
if (@unlink($filepath)) {
$deleted++;
}
}
// Register on audit logs and return result.
SucuriScanEvent::reportInfoEvent(
sprintf(
__('%s were deleted.', 'sucuri-scanner'),
implode(', ', $filenames)
)
);
SucuriScanInterface::info(
sprintf(
__('%d out of %d files have been deleted.', 'sucuri-scanner'),
$deleted,
count($filenames)
)
);
}
}
foreach ($files as $name => $desc) {
if ($name === '<root>') {
/* convert to folder */
$name = '';
}
$fsize = 0;
$fname = ($name ? sprintf('sucuri-%s.php', $name) : '');
$fpath = SucuriScan::dataStorePath($fname);
$disabled = 'disabled="disabled"';
$iswritable = __('Not Writable', 'sucuri-scanner');
$exists = __('Does Not Exist', 'sucuri-scanner');
$labelExistence = 'danger';
$labelWritability = 'default';
if (file_exists($fpath)) {
$fsize = @filesize($fpath);
$exists = __('Exists', 'sucuri-scanner');
$labelExistence = 'success';
$labelWritability = 'danger';
if (is_writable($fpath)) {
$disabled = ''; /* Allow file deletion */
$iswritable = __('Writable', 'sucuri-scanner');
$labelWritability = 'success';
}
}
$params['Storage.Filename'] = $fname;
$params['Storage.Filepath'] = str_replace(ABSPATH, '', $fpath);
$params['Storage.Filesize'] = SucuriScan::humanFileSize($fsize);
$params['Storage.Exists'] = $exists;
$params['Storage.IsWritable'] = $iswritable;
$params['Storage.DisabledInput'] = $disabled;
$params['Storage.Existence'] = $labelExistence;
$params['Storage.Writability'] = $labelWritability;
$params['Storage.Description'] = $desc;
if (is_dir($fpath)) {
$params['Storage.Filesize'] = '';
$params['Storage.DisabledInput'] = 'disabled="disabled"';
}
$params['Storage.Files'] .= SucuriScanTemplate::getSnippet('settings-general-datastorage', $params);
}
return SucuriScanTemplate::getSection('settings-general-datastorage', $params);
}
/**
* Returns the path to the local event monitoring file.
*
* The website owner can configure the plugin to send a copy of the security
* events to a local file that can be integrated with other monitoring systems
* like OSSEC, OpenVAS, NewRelic and similar.
*
* @return string|bool Path to the log file, false if disabled.
*/
function sucuriscan_selfhosting_fpath()
{
$monitor = SucuriScanOption::getOption(':selfhosting_monitor');
$monitor_fpath = SucuriScanOption::getOption(':selfhosting_fpath');
$folder = dirname($monitor_fpath);
if ($monitor === 'enabled'
&& !empty($monitor_fpath)
&& is_writable($folder)
) {
return $monitor_fpath;
}
return false;
}
/**
* Renders a page with information about the self-hosting feature.
*
* @param bool $nonce True if the CSRF protection worked.
* @return string Page with information about the self-hosting.
*/
function sucuriscan_settings_general_selfhosting($nonce)
{
$params = array();
$params['SelfHosting.DisabledVisibility'] = 'visible';
$params['SelfHosting.Status'] = __('Enabled', 'sucuri-scanner');
$params['SelfHosting.SwitchText'] = __('Disable', 'sucuri-scanner');
$params['SelfHosting.SwitchValue'] = 'disable';
$params['SelfHosting.FpathVisibility'] = 'hidden';
$params['SelfHosting.Fpath'] = '';
if ($nonce) {
// Set a file path for the self-hosted event monitor.
$monitor_fpath = SucuriScanRequest::post(':selfhosting_fpath');
if ($monitor_fpath !== false) {
if (empty($monitor_fpath)) {
$message = __('Log exporter was disabled', 'sucuri-scanner');
SucuriScanEvent::reportInfoEvent($message);
SucuriScanOption::deleteOption(':selfhosting_fpath');
SucuriScanOption::updateOption(':selfhosting_monitor', 'disabled');
SucuriScanEvent::notifyEvent('plugin_change', $message);
SucuriScanInterface::info(__('The log exporter feature has been disabled', 'sucuri-scanner'));
} elseif (strpos($monitor_fpath, $_SERVER['DOCUMENT_ROOT']) !== false) {
SucuriScanInterface::error(__('File should not be publicly accessible.', 'sucuri-scanner'));
} elseif (file_exists($monitor_fpath)) {
SucuriScanInterface::error(__('File already exists and will not be overwritten.', 'sucuri-scanner'));
} elseif (!is_writable(dirname($monitor_fpath))) {
SucuriScanInterface::error(__('File parent directory is not writable.', 'sucuri-scanner'));
} else {
@file_put_contents($monitor_fpath, '', LOCK_EX);
$message = __('Log exporter file path was correctly set', 'sucuri-scanner');
SucuriScanEvent::reportInfoEvent($message);
SucuriScanOption::updateOption(':selfhosting_monitor', 'enabled');
SucuriScanOption::updateOption(':selfhosting_fpath', $monitor_fpath);
SucuriScanEvent::notifyEvent('plugin_change', $message);
SucuriScanInterface::info(__('The log exporter feature has been enabled and the data file was successfully set.', 'sucuri-scanner'));
}
}
}
$monitor = SucuriScanOption::getOption(':selfhosting_monitor');
$monitor_fpath = SucuriScanOption::getOption(':selfhosting_fpath');
if ($monitor === 'disabled') {
$params['SelfHosting.Status'] = __('Disabled', 'sucuri-scanner');
$params['SelfHosting.SwitchText'] = __('Enable', 'sucuri-scanner');
$params['SelfHosting.SwitchValue'] = 'enable';
}
if ($monitor === 'enabled' && $monitor_fpath) {
$params['SelfHosting.DisabledVisibility'] = 'hidden';
$params['SelfHosting.FpathVisibility'] = 'visible';
$params['SelfHosting.Fpath'] = SucuriScan::escape($monitor_fpath);
}
return SucuriScanTemplate::getSection('settings-general-selfhosting', $params);
}
/**
* Renders a page with information about the reverse proxy feature.
*
* @param bool $nonce True if the CSRF protection worked.
* @return string Page with information about the reverse proxy.
*/
function sucuriscan_settings_general_reverseproxy($nonce)
{
$params = array(
'ReverseProxyStatus' => __('Enabled', 'sucuri-scanner'),
'ReverseProxySwitchText' => __('Disable', 'sucuri-scanner'),
'ReverseProxySwitchValue' => 'disable',
);
// Enable or disable the reverse proxy support.
if ($nonce) {
$revproxy = SucuriScanRequest::post(':revproxy', '(en|dis)able');
if ($revproxy) {
if ($revproxy === 'enable') {
SucuriScanOption::setRevProxy('enable');
SucuriScanOption::setAddrHeader('HTTP_X_SUCURI_CLIENTIP');
} else {
SucuriScanOption::setRevProxy('disable');
SucuriScanOption::setAddrHeader('REMOTE_ADDR');
}
}
}
if (SucuriScanOption::isDisabled(':revproxy')) {
$params['ReverseProxyStatus'] = __('Disabled', 'sucuri-scanner');
$params['ReverseProxySwitchText'] = __('Enable', 'sucuri-scanner');
$params['ReverseProxySwitchValue'] = 'enable';
}
return SucuriScanTemplate::getSection('settings-general-reverseproxy', $params);
}
/**
* Renders a page with information about the IP discoverer feature.
*
* @param bool $nonce True if the CSRF protection worked.
* @return string Page with information about the IP discoverer.
*/
function sucuriscan_settings_general_ipdiscoverer($nonce)
{
$params = array(
'TopLevelDomain' => __('unknown', 'sucuri-scanner'),
'WebsiteHostName' => __('unknown', 'sucuri-scanner'),
'WebsiteHostAddress' => __('unknown', 'sucuri-scanner'),
'IsUsingFirewall' => __('unknown', 'sucuri-scanner'),
'WebsiteURL' => __('unknown', 'sucuri-scanner'),
'RemoteAddress' => '127.0.0.1',
'RemoteAddressHeader' => __('INVALID', 'sucuri-scanner'),
'AddrHeaderOptions' => '',
/* Switch form information. */
'DnsLookupsStatus' => __('Enabled', 'sucuri-scanner'),
'DnsLookupsSwitchText' => __('Disable', 'sucuri-scanner'),
'DnsLookupsSwitchValue' => 'disable',
);
// Get main HTTP header for IP retrieval.
$allowed_headers = SucuriScan::allowedHttpHeaders(true);
// Configure the DNS lookups option for reverse proxy detection.
if ($nonce) {
$dns_lookups = SucuriScanRequest::post(':dns_lookups', '(en|dis)able');
$addr_header = SucuriScanRequest::post(':addr_header');
if ($dns_lookups) {
$action_d = $dns_lookups . 'd';
$message = sprintf(__('DNS lookups for reverse proxy detection <code>%s</code>', 'sucuri-scanner'), $action_d);
SucuriScanOption::updateOption(':dns_lookups', $action_d);
SucuriScanEvent::reportInfoEvent($message);
SucuriScanEvent::notifyEvent('plugin_change', $message);
SucuriScanInterface::info(__('The status of the DNS lookups for the reverse proxy detection has been changed', 'sucuri-scanner'));
}
if ($addr_header) {
if ($addr_header === 'REMOTE_ADDR') {
SucuriScanOption::setAddrHeader('REMOTE_ADDR');
SucuriScanOption::setRevProxy('disable');
} else {
SucuriScanOption::setAddrHeader($addr_header);
SucuriScanOption::setRevProxy('enable');
}
}
}
if (SucuriScanOption::isDisabled(':dns_lookups')) {
$params['DnsLookupsStatus'] = __('Disabled', 'sucuri-scanner');
$params['DnsLookupsSwitchText'] = __('Enable', 'sucuri-scanner');
$params['DnsLookupsSwitchValue'] = 'enable';
}
$proxy_info = SucuriScan::isBehindFirewall(true);
$base_domain = SucuriScan::getDomain(true);
$params['TopLevelDomain'] = $proxy_info['http_host'];
$params['WebsiteHostName'] = $proxy_info['host_name'];
$params['WebsiteHostAddress'] = $proxy_info['host_addr'];
$params['RemoteAddressHeader'] = SucuriScan::getRemoteAddrHeader();
$params['RemoteAddress'] = SucuriScan::getRemoteAddr();
$params['WebsiteURL'] = SucuriScan::getDomain();
$params['AddrHeaderOptions'] = SucuriScanTemplate::selectOptions(
$allowed_headers, /* list is limited to a few options */
SucuriScanOption::getOption(':addr_header')
);
$params['IsUsingFirewall'] = $proxy_info['status'] ? 'active' : 'not active';
if ($base_domain !== $proxy_info['http_host']) {
$params['TopLevelDomain'] = sprintf('%s (%s)', $params['TopLevelDomain'], $base_domain);
}
return SucuriScanTemplate::getSection('settings-general-ipdiscoverer', $params);
}
/**
* Renders a page with information about the import export feature.
*
* @param bool $nonce True if the CSRF protection worked.
* @return string Page with information about the import export.
*/
function sucuriscan_settings_general_importexport($nonce)
{
$settings = array();
$params = array();
$allowed = array(
':addr_header',
':api_protocol',
':api_service',
':cloudproxy_apikey',
':diff_utility',
':dns_lookups',
':email_subject',
':emails_per_hour',
':ignored_events',
':lastlogin_redirection',
':maximum_failed_logins',
':notify_available_updates',
':notify_bruteforce_attack',
':notify_failed_login',
':notify_plugin_activated',
':notify_plugin_change',
':notify_plugin_deactivated',
':notify_plugin_deleted',
':notify_plugin_installed',
':notify_plugin_updated',
':notify_post_publication',
':notify_scan_checksums',
':notify_settings_updated',
':notify_success_login',
':notify_theme_activated',
':notify_theme_deleted',
':notify_theme_editor',
':notify_theme_installed',
':notify_theme_updated',
':notify_to',
':notify_user_registration',
':notify_website_updated',
':notify_widget_added',
':notify_widget_deleted',
':prettify_mails',
':revproxy',
':selfhosting_fpath',
':selfhosting_monitor',
':use_wpmail',
);
if ($nonce && SucuriScanRequest::post(':import') !== false) {
$process = SucuriScanRequest::post(':process_form');
if (intval($process) === 1) {
$json = SucuriScanRequest::post(':settings');
$json = str_replace('\"', '"', $json);
$data = @json_decode($json, true);
if ($data) {
$count = 0;
$total = count($data);
/* minimum length for option name */
$minLength = strlen(SUCURISCAN . '_');
foreach ($data as $option => $value) {
if (strlen($option) <= $minLength) {
continue;
}
$option_name = ':' . substr($option, $minLength);
/* check if the option can be imported */
if (!in_array($option_name, $allowed)) {
continue;
}
SucuriScanOption::updateOption($option_name, $value);
$count++;
}
/* import trusted ip addresses */
if (array_key_exists('trusted_ips', $data) && is_array($data)) {
$cache = new SucuriScanCache('trustip');
foreach ($data['trusted_ips'] as $trustedIP) {
$trustedIP = str_replace('\/', '/', $trustedIP);
$trustedIP = str_replace('/32', '', $trustedIP);
if (SucuriScan::isValidIP($trustedIP) || SucuriScan::isValidCIDR($trustedIP)) {
$ipInfo = SucuriScan::getIPInfo($trustedIP);
$cacheKey = md5($ipInfo['remote_addr']);
$ipInfo['added_at'] = time();
if (!$cache->exists($cacheKey)) {
$cache->add($cacheKey, $ipInfo);
}
}
}
}
SucuriScanInterface::info(
sprintf(
__('%d out of %d option have been successfully imported', 'sucuri-scanner'),
$count,
$total
)
);
} else {
SucuriScanInterface::error(__('Data is incorrectly encoded', 'sucuri-scanner'));
}
} else {
SucuriScanInterface::error(__('You need to confirm that you understand the risk of this operation.', 'sucuri-scanner'));
}
}
foreach ($allowed as $option) {
$option_name = SucuriScan::varPrefix($option);
$settings[$option_name] = SucuriScanOption::getOption($option);
}
/* include the trusted IP address list */
$settings['trusted_ips'] = array();
$cache = new SucuriScanCache('trustip');
$trusted = $cache->getAll();
foreach ($trusted as $trustedIP) {
$settings['trusted_ips'][] = $trustedIP->cidr_format;
}
$params['Export'] = @json_encode($settings);
return SucuriScanTemplate::getSection('settings-general-importexport', $params);
}
/**
* Renders a page with the option to configure the timezone.
*
* @param bool $nonce True if the CSRF protection worked.
* @return string Page to configure the timezone.
*/
function sucuriscan_settings_general_timezone($nonce)
{
$params = array();
$current = time();
$options = array();
$offsets = array(
-12.0, -11.5, -11.0, -10.5, -10.0, -9.50, -9.00, -8.50, -8.00, -7.50,
-7.00, -6.50, -6.00, -5.50, -5.00, -4.50, -4.00, -3.50, -3.00, -2.50,
-2.00, -1.50, -1.00, -0.50, +0.00, +0.50, +1.00, +1.50, +2.00, +2.50,
+3.00, +3.50, +4.00, +4.50, +5.00, +5.50, +5.75, +6.00, +6.50, +7.00,
+7.50, +8.00, +8.50, +8.75, +9.00, +9.50, 10.00, 10.50, 11.00, 11.50,
12.00, 12.75, 13.00, 13.75, 14.00
);
foreach ($offsets as $hour) {
$sign = ($hour < 0) ? '-' : '+';
$fill = (abs($hour) < 10) ? '0' : '';
$keyname = sprintf('UTC%s%s%.2f', $sign, $fill, abs($hour));
$label = date('d M, Y H:i:s', $current + ($hour * 3600));
$options[$keyname] = $keyname . ' (' . $label . ')';
}
if ($nonce) {
$pattern = 'UTC[\-\+][0-9]{2}\.[0-9]{2}';
$timezone = SucuriScanRequest::post(':timezone', $pattern);
if ($timezone) {
$message = sprintf(__('Timezone override will use %s', 'sucuri-scanner'), $timezone);
SucuriScanOption::updateOption(':timezone', $timezone);
SucuriScanEvent::reportInfoEvent($message);
SucuriScanEvent::notifyEvent('plugin_change', $message);
SucuriScanInterface::info(__('The timezone for the date and time in the audit logs has been changed', 'sucuri-scanner'));
}
}
$val = SucuriScanOption::getOption(':timezone');
$params['Timezone.Dropdown'] = SucuriScanTemplate::selectOptions($options, $val);
$params['Timezone.Example'] = SucuriScan::datetime();
return SucuriScanTemplate::getSection('settings-general-timezone', $params);
}