Skip to content
Merged
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ psalm.xml
/lib/CleantalkSP/Common/FSWatcher/Storage/data/
/lib/CleantalkSP/Common/FSWatcher/logs/
/lib/CleantalkSP/Common/Helpers/composer.json
/tests/.phpunit.result.cache
/tests/.phpunit.result.cache
/skills/
2 changes: 1 addition & 1 deletion inc/spbc-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ function spbc_get_authorized_admins($direct_call = false)

function spbc_action_shuffle_salts()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);

global $spbc;
$salts_array = array(
Expand Down
7 changes: 4 additions & 3 deletions lib/CleantalkSP/SpbctWP/Activator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static function activation($network, $redirect = true)

CDNHeadersChecker::sendCDNCheckerRequest();

if ( current_action() !== 'wp_insert_site' ) {
if ( current_action() !== 'wp_initialize_site' ) {
delete_option('spbc_deactivation_in_process');
}

Expand Down Expand Up @@ -178,7 +178,8 @@ public static function addActionForNetworkBlogLegacy($wp_version)
if ( version_compare($wp_version, '5.1.0', '<') ) {
add_action('wpmu_new_blog', array(static::class, 'actionNewBlogLegacyMode'), 10, 6);
} else {
add_action('wp_insert_site', array(static::class, 'actionNewBlog'), 10, 1);
// Core creates the blog tables on wp_initialize_site with priority 10, so run after it
add_action('wp_initialize_site', array(static::class, 'actionNewBlog'), 11, 1);
}
}

Expand Down Expand Up @@ -215,7 +216,7 @@ public static function actionNewBlogLegacyMode($blog_id, $_user_id, $_domain, $_
* Wrapper for spbc_activation__new_blog()
*
* @param $new_site
* @psalm-suppress UnusedMethod - this method called via hook wp_insert_site/wpmu_new_blog
* @psalm-suppress UnusedMethod - this method called via hook wp_initialize_site/wpmu_new_blog
*/
public static function actionNewBlog($new_site)
{
Expand Down
96 changes: 51 additions & 45 deletions lib/CleantalkSP/SpbctWP/FSWatcher/Analyzer/Analyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use CleantalkSP\SpbctWP\FSWatcher\Controller;
use CleantalkSP\SpbctWP\FSWatcher\Logger;
use CleantalkSP\SpbctWP\FSWatcher\Storage\FileStorage;
use CleantalkSP\SpbctWP\Scanner\Helper;

class Analyzer
{
Expand Down Expand Up @@ -44,82 +44,88 @@ public static function getCompareResult()
*/
public static function getViewFile()
{
$path = isset($_POST['fswatcher_file_path']) ? $_POST['fswatcher_file_path'] : false;
$path = isset($_POST['fswatcher_file_path']) ? wp_unslash($_POST['fswatcher_file_path']) : false;
$first = isset($_POST['fswatcher__first_date']) ? filter_var($_POST['fswatcher__first_date'], FILTER_VALIDATE_INT) : false;
$second = isset($_POST['fswatcher__second_date']) ? filter_var($_POST['fswatcher__second_date'], FILTER_VALIDATE_INT) : false;

$journals_first = isset($_POST['fswatcher__first_date']) ? $_POST['fswatcher__first_date'] : false;
$journals_second = isset($_POST['fswatcher__second_date']) ? $_POST['fswatcher__second_date'] : false;
$journals = array($journals_first, $journals_second);
if (!$path || !is_string($path)) {
throw new \Exception('File path is incorrect.');
}

if (!$path || !is_file($path)) {
$real_path = realpath($path);
if ($real_path === false || !is_file($real_path)) {
throw new \Exception('File path is incorrect.');
}

if (!$journals[0] || !$journals[1]) {
if (self::isWpConfigFile($path, $real_path) && !Helper::canViewWpConfigContents()) {
throw new \Exception(Helper::getWpConfigViewRestrictedMessage());
}

if (!$first || !$second) {
throw new \Exception('Provided journals paths are incorrect.');
}

$path_found_in_journal = false;
$compare_result = self::getCompareResult();

foreach ($journals as $journal_id) {
if (
self::isFileOfFSWJournal($path, $journal_id) &&
self::isFileOfFSWJournalAfterCompareJournals($path)
) {
$path_found_in_journal = true;
break;
}
if (!is_array($compare_result) || !self::isPathInCompareResult($real_path, $compare_result)) {
throw new \Exception('The file is out of FSWatcher journals.');
}

if (!$path_found_in_journal) {
throw new \Exception('The file is out of FSWatcher journals.');
$contents = file_get_contents($real_path);
if ($contents === false) {
throw new \Exception('File path is incorrect.');
}

return esc_html__(file_get_contents($path));
return esc_html__($contents);
}

/**
* @param $path
* @param $journal_id
* Whether the file is the WordPress wp-config.php (site root or one level above), symlinks resolved.
*
* @param string $path Path as requested
* @param string $real_path Resolved path
* @return bool
*/
private static function isFileOfFSWJournal($path, $journal_id)
private static function isWpConfigFile($path, $real_path)
{
$storage = new FileStorage();
$journal_parsed = $storage->getJournal($journal_id);
$analyzer = new Analyzer();
$journal_parsed = $analyzer->uncompress($journal_parsed, true);
if (
strpos($journal_parsed, $path) !== false ||
strpos($journal_parsed, stripslashes($path)) !== false
) {
if (Helper::isWpConfigPath($path) || Helper::isWpConfigPath($real_path)) {
return true;
}

foreach (array(ABSPATH . 'wp-config.php', ABSPATH . '../wp-config.php') as $config_file) {
$real_config_file = realpath($config_file);
if ($real_config_file !== false && $real_config_file === $real_path) {
return true;
}
}

return false;
}

/**
* @param $path
* Exact match of the resolved path against the paths of the journals diff.
*
* @param string $real_path
* @param array $compare_result Result of self::compare()
* @return bool
*/
private static function isFileOfFSWJournalAfterCompareJournals($path)
private static function isPathInCompareResult($real_path, array $compare_result)
{
$journal_result_compare = self::getCompareResult();
$journal_result_string = '';
foreach ($journal_result_compare as $journal_result) {
if (count($journal_result) > 0) {
foreach ($journal_result as $value) {
$journal_result_string .= implode(' , ', $value);
foreach ($compare_result as $entries) {
if (!is_array($entries)) {
continue;
}
foreach ($entries as $entry) {
if (!isset($entry[0])) {
continue;
}
$entry_real_path = realpath((string) $entry[0]);
if ($entry_real_path !== false && $entry_real_path === $real_path) {
return true;
}
}
}

if (
strpos($journal_result_string, $path) !== false ||
strpos($journal_result_string, stripslashes($path)) !== false
) {
return true;
}

return false;
}

Expand Down
6 changes: 3 additions & 3 deletions lib/CleantalkSP/SpbctWP/FSWatcher/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private function checkRateLimit()

public static function ajaxCompare()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);
if (self::$debug) {
Logger::log('run compare file system logs');
}
Expand All @@ -141,7 +141,7 @@ public static function ajaxCompare()

public function ajaxViewFile()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);
if (self::$debug) {
Logger::log('run view file method');
}
Expand All @@ -165,7 +165,7 @@ public function ajaxViewFile()
*/
public function ajaxCreateSnapshot()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);
if (self::$debug) {
Logger::log('run scan file system');
}
Expand Down
20 changes: 20 additions & 0 deletions lib/CleantalkSP/SpbctWP/ListTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,26 @@ public static function ajaxRowActionHandler()
spbc_check_ajax_referer('spbc_secret_nonce', 'security');

$action = Post::getString('add_action');

// Actions that change files, OS crontab or DB triggers shared by the whole network are for Super Admins only on a multisite
$network_wide_actions = array(
'delete',
'delete_quarantine',
'replace',
'quarantine',
'restore',
'enable_oscron_task',
'disable_oscron_task',
'delete-trigger',
);
if ( in_array($action, $network_wide_actions, true) && ! \CleantalkSP\SpbctWP\Scanner\Helper::canCurrentUserDoNetworkWideActions() ) {
wp_send_json(array(
'temp_html' => '<div class="spbc-popup-msg popup--red">'
. esc_html__('Only a Super Admin can perform this action on a multisite network.', 'security-malware-firewall')
. '</div>'
));
}

$is_frontend_malware_action = $action === 'disapprove_page' || $action === 'approve_page';

if ( $action !== 'restore' && ! $is_frontend_malware_action &&
Expand Down
4 changes: 2 additions & 2 deletions lib/CleantalkSP/SpbctWP/Scanner/Cure.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ private static function idsForBulkRequest($allowed_statuses)
*/
public static function cureSelectedAction()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);

$ids = self::idsForBulkRequest(array('critical', 'cure_log'));

Expand Down Expand Up @@ -389,7 +389,7 @@ public static function cureSelectedAction()

public static function restoreSelectedAction()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);

$ids = self::idsForBulkRequest(array('cure_log'));

Expand Down
11 changes: 11 additions & 0 deletions lib/CleantalkSP/SpbctWP/Scanner/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,17 @@ public static function canViewWpConfigContents()
return function_exists('is_super_admin') && is_super_admin();
}

/**
* Whether the current user may act on files shared by the whole network.
* On a multisite the file system is shared by all subsites, so only Super Admins may do it.
*
* @return bool
*/
public static function canCurrentUserDoNetworkWideActions()
{
return ! is_multisite() || self::canViewWpConfigContents();
}

/**
* @return string
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class BackupsActions
*/
protected static function checkAjaxReferer($action = 'spbc_secret_nonce', $query_arg = 'security')
{
spbc_check_ajax_referer($action, $query_arg);
spbc_check_ajax_referer($action, $query_arg, true, true, true);
}

/**
Expand Down
16 changes: 8 additions & 8 deletions lib/CleantalkSP/SpbctWP/Scanner/ScannerAjaxEndpoints.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static function getLastScanInfo()
*/
public static function restoreFileFromBackup()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);

$file_fast_hash = isset($_POST['file_fast_hash']) ? esc_sql($_POST['file_fast_hash']) : null;

Expand All @@ -52,7 +52,7 @@ public static function restoreFileFromBackup()
*/
public static function cureFile()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);

$file_fast_hash = isset($_POST['file_fast_hash']) ? esc_sql($_POST['file_fast_hash']) : null;

Expand Down Expand Up @@ -139,7 +139,7 @@ public static function loadMoreScanLogs()
*/
public static function deleteFile()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);

$time_start = microtime(true);

Expand Down Expand Up @@ -218,7 +218,7 @@ public static function deleteFileFromAnalysisLog($direct_call = false)
public static function restoreFromQuarantine($direct_call = false, $file_id = null)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);
}

$file_id = $direct_call ? $file_id : Post::get('file_id', 'hash');
Expand Down Expand Up @@ -277,7 +277,7 @@ public static function restoreFromQuarantine($direct_call = false, $file_id = nu
public static function doQuarantineFile($direct_call = false, $file_id = null)
{
if ( ! $direct_call ) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);
}

$file_id = $direct_call ? $file_id : Post::get('file_id', 'hash');
Expand All @@ -302,7 +302,7 @@ public static function doQuarantineFile($direct_call = false, $file_id = null)
public static function deleteFromQuarantine($direct_call = false, $file_id = null)
{
if ( ! $direct_call ) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);
}

$file_id = $direct_call ? $file_id : Post::get('file_id', 'hash');
Expand All @@ -327,7 +327,7 @@ public static function deleteFromQuarantine($direct_call = false, $file_id = nul
public static function replaceFileWithOriginal($direct_call = false, $file_id = null)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);
}

$file_id = $direct_call ? $file_id : Post::get('file_id', 'hash');
Expand Down Expand Up @@ -371,7 +371,7 @@ public static function viewPage()
*/
public static function viewFile()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
spbc_check_ajax_referer('spbc_secret_nonce', 'security', true, true, true);

$output = FileSystemActions::viewFile(Post::get('file_id', 'hash'));

Expand Down
5 changes: 4 additions & 1 deletion lib/CleantalkSP/SpbctWP/Settings/SettingsGeneralReact.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,10 @@ private static function orderGroupElements($grouped_settings)
$ordering_ruleset = self::getOrderingRuleset();

// UI-only field (not a saved option); rendered by React ActionShuffleSalts
$grouped_settings['auth_and_logging_in']['action_shuffle_salts'] = true;
// It rewrites the wp-config.php shared by the whole network, so on a multisite it is for Super Admins only
if ( \CleantalkSP\SpbctWP\Scanner\Helper::canCurrentUserDoNetworkWideActions() ) {
$grouped_settings['auth_and_logging_in']['action_shuffle_salts'] = true;
}

foreach ($ordering_ruleset as $group_name => $ruleset) {
$ordered = [];
Expand Down
3 changes: 2 additions & 1 deletion lib/CleantalkSP/SpbctWP/Views/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ public static function page()
"displayDebug" => $display_debug,
"isWPMSMainSite" => is_main_site(),
"isSecFWEnabled" => (bool) $spbc->settings['secfw__enabled'],
"isFsWatcherEnabled" => (bool) $spbc->settings['scanner__fs_watcher'],
"isFsWatcherEnabled" => (bool) $spbc->settings['scanner__fs_watcher']
&& \CleantalkSP\SpbctWP\Scanner\Helper::canCurrentUserDoNetworkWideActions(),
"isVulnerabilityCheckEnabled" => (bool) $spbc->settings['vulnerability_check__enable_cron'],
];
}
Expand Down
Loading
Loading