Sucuri Security – Auditing, Malware Scanner and Security Hardening - Version 1.7.12

Version Description

  • Improved hardening options
  • Added more logging events
  • Various bugfixes and improvements
Download this release

Release Info

Developer akresic
Plugin Icon 128x128 Sucuri Security – Auditing, Malware Scanner and Security Hardening
Version 1.7.12
Comparing to
See all releases

Code changes from version 1.7.11 to 1.7.12

inc/css/sucuriscan-default-css.css CHANGED
@@ -230,7 +230,10 @@ td.sucuriscan-corefiles-warning > div{background:#f2dede;color:#a94442;border-co
230
.sucuriscan-maincontent td.sucuriscan-corefiles-warning div{padding:10px;border-width:1px;border-style:solid}
231
.sucuriscan-maincontent td.sucuriscan-corefiles-warning code{font-size:12px;padding:0 5px}
232
.sucuriscan-maincontent .sucuriscan-integrity-message{position:relative}
233
- .sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-mark{position:absolute;top:1px;right:1px;background:#7ad03a;font-weight:bold;color:#fff;line-height:35px;padding:0 10px;border-left:1px solid #ddd}
234
.sucuriscan-maincontent .sucuriscan-ignoredfiles{margin-top:0}
235
.sucuriscan-ignore-file form{padding:10px;padding-top:0;border-bottom:1px solid #ddd;border-right:1px solid #ddd}
236
.sucuriscan-ignore-file p{border-bottom:none}
230
.sucuriscan-maincontent td.sucuriscan-corefiles-warning div{padding:10px;border-width:1px;border-style:solid}
231
.sucuriscan-maincontent td.sucuriscan-corefiles-warning code{font-size:12px;padding:0 5px}
232
.sucuriscan-maincontent .sucuriscan-integrity-message{position:relative}
233
+ .sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-mark,
234
+ .sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-failure{position:absolute;top:1px;right:1px;background:#ddd;font-weight:bold;color:#fff;line-height:35px;padding:0 10px;border-left:1px solid #ddd}
235
+ .sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-mark{background:#7ad03a}
236
+ .sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-failure{background:#dd3d36;border-left:0}
237
.sucuriscan-maincontent .sucuriscan-ignoredfiles{margin-top:0}
238
.sucuriscan-ignore-file form{padding:10px;padding-top:0;border-bottom:1px solid #ddd;border-right:1px solid #ddd}
239
.sucuriscan-ignore-file p{border-bottom:none}
inc/tpl/integrity-corefiles.html.tpl CHANGED
@@ -8,6 +8,22 @@
8
</div>
9
</div>
10
11
<form action="%%SUCURI.URL.Home%%" method="post">
12
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
13
8
</div>
9
</div>
10
11
+ <div class="postbox sucuriscan-border sucuriscan-border-bad sucuriscan-integrity-message sucuriscan-%%SUCURI.CoreFiles.FailureVisibility%%">
12
+ <span class="sucuriscan-integrity-failure">FAILURE</span>
13
+ <h3>Core integrity</h3>
14
+
15
+ <div class="inside">
16
+ <p>
17
+ Error retrieving the WordPress core hashes. The information used by the plugin
18
+ to determine the integrity of the core files is retrieved and controlled by
19
+ WordPress. Any error message related with this tool is likely related with a
20
+ modification in their API service that is not supported yet. It is also possible
21
+ that your website is not able to communicate with this server due to a missing
22
+ HTTP transport tool.
23
+ </p>
24
+ </div>
25
+ </div>
26
+
27
<form action="%%SUCURI.URL.Home%%" method="post">
28
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
29
inc/tpl/notification-corefiles.html.tpl ADDED
@@ -0,0 +1,47 @@
1
+
2
+ <p>
3
+ Changes in the integrity of your core files were detected, you may want to check
4
+ each file to determine if they were infected with malicious code. The WordPress
5
+ core directories <code>/&lt;root&gt;</code>, <code>/wp-admin</code> and <code>
6
+ /wp-includes</code> are the only ones being scanned; the content, uploads, and
7
+ custom directories are not part of the official archives so you have to check
8
+ them manually.
9
+ </p>
10
+
11
+ <table border="1" cellspacing="1" cellpadding="5">
12
+ <thead>
13
+ <tr>
14
+ <th colspan="5">
15
+ Core integrity (%%SUCURI.CoreFiles.ListCount%% files)
16
+ </th>
17
+ </tr>
18
+
19
+ <tr>
20
+ <th>&nbsp;</th>
21
+ <th width="80">Status</th>
22
+ <th width="100">File Size</th>
23
+ <th width="170">Modified At</th>
24
+ <th>File Path</th>
25
+ </tr>
26
+ </thead>
27
+
28
+ <tbody>
29
+ %%SUCURI.CoreFiles.List%%
30
+ </tbody>
31
+
32
+ <tfoot>
33
+ <tr>
34
+ <td colspan="5">
35
+ <p>
36
+ <strong>Note.</strong> This is not a malware scanner but an integrity checker
37
+ which is a completely different thing, if you want to check if your site is
38
+ generating malicious code then use the <a href="%%SUCURI.URL.Scanner%%">malware
39
+ scan</a> tool. If you see the text <em>"must be fixed manually"</em> in any of
40
+ these files that means that they do not have write permissions so you can not
41
+ fix them using this tool. Access the <a href="%%SUCURI.URL.Home%%">admin area
42
+ </a> of your website to fix these files.
43
+ </p>
44
+ </td>
45
+ </tr>
46
+ </tfoot>
47
+ </table>
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: dd@sucuri.net
3
Donate Link: http://sucuri.net/
4
Tags: malware, security, firewall, scan, spam, virus, sucuri, protection,WordPress Security, Login Security,Security Auditing,File Integrity,htaccess,phishing,backdoors,SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
5
Requires at least:3.2
6
- Stable tag:1.7.11
7
- Tested up to: 4.2.2
8
9
The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
10
@@ -352,6 +352,11 @@ service from the WordPress dashboard.
352
353
== Changelog ==
354
355
= 1.7.11 =
356
* Reverted change for CloudProxy detection to protect legacy users
357
3
Donate Link: http://sucuri.net/
4
Tags: malware, security, firewall, scan, spam, virus, sucuri, protection,WordPress Security, Login Security,Security Auditing,File Integrity,htaccess,phishing,backdoors,SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
5
Requires at least:3.2
6
+ Stable tag:1.7.12
7
+ Tested up to: 4.2.3
8
9
The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
10
352
353
== Changelog ==
354
355
+ = 1.7.12 =
356
+ * Improved hardening options
357
+ * Added more logging events
358
+ * Various bugfixes and improvements
359
+
360
= 1.7.11 =
361
* Reverted change for CloudProxy detection to protect legacy users
362
sucuri.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Sucuri Security - Auditing, Malware Scanner and Hardening
4
Plugin URI: http://wordpress.sucuri.net/
5
Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
6
Author: Sucuri, INC
7
- Version: 1.7.11
8
Author URI: http://sucuri.net
9
*/
10
@@ -66,7 +66,7 @@ define( 'SUCURISCAN', 'sucuriscan' );
66
/**
67
* Current version of the plugin's code.
68
*/
69
- define( 'SUCURISCAN_VERSION', '1.7.11' );
70
71
/**
72
* The name of the Sucuri plugin main file.
@@ -189,6 +189,7 @@ if ( defined( 'SUCURISCAN' ) ) {
189
'sucuriscan_notify_plugin_change' => 'Receive email alerts for <strong>Sucuri</strong> plugin changes',
190
'sucuriscan_prettify_mails' => 'Receive email alerts in HTML <em>(there may be issues with some mail services)</em>',
191
'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
192
'sucuriscan_notify_user_registration' => 'user:Receive email alerts for new user registration',
193
'sucuriscan_notify_success_login' => 'user:Receive email alerts for successful login attempts',
194
'sucuriscan_notify_failed_login' => 'user:Receive email alerts for failed login attempts',
@@ -253,6 +254,8 @@ if ( defined( 'SUCURISCAN' ) ) {
253
$sucuriscan_email_subjects = array(
254
'Sucuri Alert, :domain, :event',
255
'Sucuri Alert, :domain, :event, :remoteaddr',
256
'Sucuri Alert, :event, :remoteaddr',
257
'Sucuri Alert, :event',
258
);
@@ -268,7 +271,7 @@ if ( defined( 'SUCURISCAN' ) ) {
268
* information to the Sucuri API service where a security and integrity scan
269
* will be performed against the hashes provided and the official versions.
270
*/
271
- add_action( 'sucuriscan_scheduled_scan', 'SucuriScanEvent::filesystem_scan' );
272
273
/**
274
* Initialize the execute of the main plugin's functions.
@@ -305,29 +308,30 @@ if ( defined( 'SUCURISCAN' ) ) {
305
*/
306
if ( class_exists( 'SucuriScanHook' ) ) {
307
$sucuriscan_hooks = array(
308
- // Passes.
309
'add_attachment',
310
'add_link',
311
'create_category',
312
'delete_post',
313
'delete_user',
314
'login_form_resetpass',
315
'private_to_published',
316
'publish_page',
317
- 'publish_post',
318
'publish_phone',
319
- 'xmlrpc_publish_post',
320
'retrieve_password',
321
'switch_theme',
322
'user_register',
323
'wp_login',
324
'wp_login_failed',
325
'wp_trash_post',
326
);
327
328
foreach ( $sucuriscan_hooks as $hook_name ) {
329
$hook_func = 'SucuriScanHook::hook_' . $hook_name;
330
- add_action( $hook_name, $hook_func, 50 );
331
}
332
333
add_action( 'admin_init', 'SucuriScanHook::hook_undefined_actions' );
@@ -618,6 +622,16 @@ class SucuriScan {
618
return '/define\(\s*\'([A-Z_]+)\',(\s*)\'(.+)\'\s*\);/';
619
}
620
621
/**
622
* Retrieve the real ip address of the user in the current request.
623
*
@@ -729,16 +743,26 @@ class SucuriScan {
729
}
730
731
/**
732
- * Check whether the site is behing the Sucuri CloudProxy network.
733
*
734
* @param boolean $verbose Return an array with the hostname, address, and status, or not.
735
* @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
736
*/
737
public static function is_behind_cloudproxy( $verbose = false ){
738
$http_host = self::get_top_level_domain();
739
- $host_by_addr = @gethostbyname( $http_host );
740
- $host_by_name = @gethostbyaddr( $host_by_addr );
741
- $status = (bool) preg_match( '/^cloudproxy[0-9]+\.sucuri\.net#x2F;', $host_by_name );
742
743
/*
744
* If the DNS reversion failed but the CloudProxy API key is set, then consider
@@ -2533,6 +2557,7 @@ class SucuriScanOption extends SucuriScanRequest {
2533
'sucuriscan_notify_plugin_installed' => 'disabled',
2534
'sucuriscan_notify_plugin_updated' => 'disabled',
2535
'sucuriscan_notify_post_publication' => 'enabled',
2536
'sucuriscan_notify_settings_updated' => 'disabled',
2537
'sucuriscan_notify_success_login' => 'enabled',
2538
'sucuriscan_notify_theme_activated' => 'disabled',
@@ -3087,9 +3112,10 @@ class SucuriScanEvent extends SucuriScan {
3087
* @param integer $severity Importance of the event that will be reported, values from one to five.
3088
* @param string $location In which part of the system was the event triggered.
3089
* @param string $message The explanation of the event.
3090
* @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
3091
*/
3092
- private static function report_event( $severity = 0, $location = '', $message = '' ){
3093
$user = wp_get_current_user();
3094
$username = false;
3095
$current_time = date( 'Y-m-d H:i:s' );
@@ -3122,6 +3148,11 @@ class SucuriScanEvent extends SucuriScan {
3122
default: $severity_name = 'Info'; break;
3123
}
3124
3125
// Clear event message.
3126
$message = strip_tags( $message );
3127
$message = str_replace( "\r", '', $message );
@@ -3142,71 +3173,78 @@ class SucuriScanEvent extends SucuriScan {
3142
/**
3143
* Reports a debug event on the website.
3144
*
3145
- * @param string $message Text witht the explanation of the event or action performed.
3146
- * @return boolean Either true or false depending on the success of the operation.
3147
*/
3148
- public static function report_debug_event( $message = '' ){
3149
- return self::report_event( 0, 'core', $message );
3150
}
3151
3152
/**
3153
* Reports a notice event on the website.
3154
*
3155
- * @param string $message Text witht the explanation of the event or action performed.
3156
- * @return boolean Either true or false depending on the success of the operation.
3157
*/
3158
- public static function report_notice_event( $message = '' ){
3159
- return self::report_event( 1, 'core', $message );
3160
}
3161
3162
/**
3163
* Reports a info event on the website.
3164
*
3165
- * @param string $message Text witht the explanation of the event or action performed.
3166
- * @return boolean Either true or false depending on the success of the operation.
3167
*/
3168
- public static function report_info_event( $message = '' ){
3169
- return self::report_event( 2, 'core', $message );
3170
}
3171
3172
/**
3173
* Reports a warning event on the website.
3174
*
3175
- * @param string $message Text witht the explanation of the event or action performed.
3176
- * @return boolean Either true or false depending on the success of the operation.
3177
*/
3178
- public static function report_warning_event( $message = '' ){
3179
- return self::report_event( 3, 'core', $message );
3180
}
3181
3182
/**
3183
* Reports a error event on the website.
3184
*
3185
- * @param string $message Text witht the explanation of the event or action performed.
3186
- * @return boolean Either true or false depending on the success of the operation.
3187
*/
3188
- public static function report_error_event( $message = '' ){
3189
- return self::report_event( 4, 'core', $message );
3190
}
3191
3192
/**
3193
* Reports a critical event on the website.
3194
*
3195
- * @param string $message Text witht the explanation of the event or action performed.
3196
- * @return boolean Either true or false depending on the success of the operation.
3197
*/
3198
- public static function report_critical_event( $message = '' ){
3199
- return self::report_event( 5, 'core', $message );
3200
}
3201
3202
/**
3203
* Reports a notice or error event for enable and disable actions.
3204
*
3205
- * @param string $message Text witht the explanation of the event or action performed.
3206
* @param string $action An optional text, hopefully either enabled or disabled.
3207
- * @return boolean Either true or false depending on the success of the operation.
3208
*/
3209
- public static function report_auto_event( $message = '', $action = '' ){
3210
$message = strip_tags( $message );
3211
3212
// Auto-detect the action performed, either enabled or disabled.
@@ -3216,11 +3254,11 @@ class SucuriScanEvent extends SucuriScan {
3216
3217
// Report the correct event for the action performed.
3218
if ( $action == 'enabled' ) {
3219
- return self::report_notice_event( $message );
3220
} elseif ( $action == 'disabled' ) {
3221
- return self::report_error_event( $message );
3222
} else {
3223
- return self::report_info_event( $message );
3224
}
3225
}
3226
@@ -3295,6 +3333,9 @@ class SucuriScanEvent extends SucuriScan {
3295
} elseif ( $event == 'bruteforce_attack' ) {
3296
// Send a notification even if the limit of emails per hour was reached.
3297
$email_params['Force'] = true;
3298
}
3299
3300
$title = str_replace( '_', chr( 32 ), $event );
@@ -3823,8 +3864,72 @@ class SucuriScanHook extends SucuriScanEvent {
3823
}
3824
}
3825
3826
// TODO: Detect auto updates in core, themes, and plugin files.
3827
3828
/**
3829
* Send a notifications to the administrator of some specific events that are
3830
* not triggered through an hooked action, but through a simple request in the
@@ -4841,7 +4946,7 @@ class SucuriScanAPI extends SucuriScanOption {
4841
$response['body']->output_data = array();
4842
$log_pattern = '/^([0-9\-]+) ([0-9:]+) (\S+) : (.+)/';
4843
$extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
4844
- $generic_pattern = '/^([A-Z][a-z]{3,7}): ([^:;]+; )?(.+)/';
4845
$auth_pattern = '/^User authentication (succeeded|failed): ([^<;]+)/';
4846
4847
foreach ( $response['body']->output as $log ) {
@@ -5569,28 +5674,50 @@ class SucuriScanMail extends SucuriScanOption {
5569
* @return string A text with the subject for the email alert.
5570
*/
5571
private static function get_email_subject( $event = '' ){
5572
- $email_subject = self::get_option( ':email_subject' );
5573
5574
/**
5575
* Probably a bad value in the options table. Delete the entry from the database
5576
* and call this function to try again, it will probably fall in an infinite
5577
* loop, but this is the easiest way to control this procedure.
5578
*/
5579
- if ( ! $email_subject ) {
5580
self::delete_option( ':email_subject' );
5581
5582
return self::get_email_subject( $event );
5583
}
5584
5585
- $domain_name = self::get_domain();
5586
- $remote_addr = self::get_remote_addr();
5587
- $email_subject = str_replace(
5588
- array( ':domain', ':event', ':remoteaddr' ),
5589
- array( $domain_name, $event, $remote_addr ),
5590
- strip_tags( $email_subject )
5591
- );
5592
5593
- return $email_subject;
5594
}
5595
5596
/**
@@ -7760,6 +7887,160 @@ function sucuriscan_monitoring_dates( $type = '', $date = '', $in_html = true ){
7760
return $options;
7761
}
7762
7763
/**
7764
* Sucuri one-click hardening page.
7765
*
@@ -7968,59 +8249,36 @@ function sucuriscan_harden_nginx_phpfpm(){
7968
* @return void
7969
*/
7970
function sucuriscan_harden_upload(){
7971
- $cp = 1;
7972
- $datastore_path = SucuriScan::datastore_folder_path();
7973
- $htaccess_upload = dirname( $datastore_path ) . '/.htaccess';
7974
-
7975
- if ( ! is_readable( $htaccess_upload ) ) {
7976
- $cp = 0;
7977
- } else {
7978
- $cp = 0;
7979
- $fcontent = SucuriScanFileInfo::file_lines( $htaccess_upload );
7980
-
7981
- foreach ( $fcontent as $fline ) {
7982
- if ( stripos( $fline, 'deny from all' ) !== false ) {
7983
- $cp = 1;
7984
- break;
7985
- }
7986
- }
7987
- }
7988
7989
if ( SucuriScanRequest::post( ':run_hardening' ) ) {
7990
- if ( SucuriScanRequest::post( ':harden_upload' ) && $cp == 0 ) {
7991
- if ( @file_put_contents( $htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>" ) === false ) {
7992
- SucuriScanInterface::error( 'Unable to create <code>.htaccess</code> file, folder destination is not writable.' );
7993
- } else {
7994
- $cp = 1;
7995
$message = 'Hardening applied to the uploads directory';
7996
SucuriScanEvent::report_notice_event( $message );
7997
SucuriScanInterface::info( $message );
7998
}
7999
} elseif ( SucuriScanRequest::post( ':harden_upload_unharden' ) ) {
8000
- $htaccess_upload_writable = ( file_exists( $htaccess_upload ) && is_writable( $htaccess_upload ) ) ? true : false;
8001
- $htaccess_content = $htaccess_upload_writable ? @file_get_contents( $htaccess_upload ) : '';
8002
-
8003
- if ( $htaccess_upload_writable ) {
8004
- $cp = 0;
8005
-
8006
- if ( preg_match( '/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match ) ) {
8007
- $htaccess_content = str_replace( "<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content );
8008
- @file_put_contents( $htaccess_upload, $htaccess_content, LOCK_EX );
8009
- }
8010
8011
$message = 'Hardening reverted in the uploads directory';
8012
SucuriScanEvent::report_error_event( $message );
8013
SucuriScanInterface::info( $message );
8014
} else {
8015
- SucuriScanInterface::error(
8016
- 'File <code>/wp-content/uploads/.htaccess</code> does not exists or
8017
- is not writable, you will need to remove the following code (manually):
8018
- <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
8019
- );
8020
}
8021
}
8022
}
8023
8024
$description = 'It checks if the uploads directory of this site allows the direct execution'
8025
. ' of PHP files. It is recommendable to prevent this because someone may try to exploit'
8026
. ' a vulnerability of a plugin, theme, and/or other PHP-based code located in this'
@@ -8051,71 +8309,40 @@ function sucuriscan_harden_upload(){
8051
* @return void
8052
*/
8053
function sucuriscan_harden_wpcontent(){
8054
- $cp = 1;
8055
- $htaccess_upload = WP_CONTENT_DIR . '/.htaccess';
8056
-
8057
- if ( ! is_readable( $htaccess_upload ) ) {
8058
- $cp = 0;
8059
- } else {
8060
- $cp = 0;
8061
- $fcontent = SucuriScanFileInfo::file_lines( $htaccess_upload );
8062
-
8063
- foreach ( $fcontent as $fline ) {
8064
- if ( stripos( $fline, 'deny from all' ) !== false ) {
8065
- $cp = 1;
8066
- break;
8067
- }
8068
- }
8069
- }
8070
-
8071
if ( SucuriScanRequest::post( ':run_hardening' ) ) {
8072
- if ( SucuriScanRequest::post( ':harden_wpcontent' ) && $cp == 0 ) {
8073
- if ( @file_put_contents( $htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>" ) === false ) {
8074
- SucuriScanInterface::error( 'Unable to create <code>.htaccess</code> file, folder destination is not writable.' );
8075
- } else {
8076
- $cp = 1;
8077
$message = 'Hardening applied to the content directory';
8078
SucuriScanEvent::report_notice_event( $message );
8079
SucuriScanInterface::info( $message );
8080
}
8081
} elseif ( SucuriScanRequest::post( ':harden_wpcontent_unharden' ) ) {
8082
- $htaccess_upload_writable = ( file_exists( $htaccess_upload ) && is_writable( $htaccess_upload ) ) ? true : false;
8083
- $htaccess_content = $htaccess_upload_writable ? @file_get_contents( $htaccess_upload ) : '';
8084
-
8085
- if ( $htaccess_upload_writable ) {
8086
- $cp = 0;
8087
-
8088
- if ( preg_match( '/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match ) ) {
8089
- $htaccess_content = str_replace( "<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content );
8090
- @file_put_contents( $htaccess_upload, $htaccess_content, LOCK_EX );
8091
- }
8092
8093
$message = 'Hardening reverted in the content directory';
8094
SucuriScanEvent::report_error_event( $message );
8095
SucuriScanInterface::info( $message );
8096
} else {
8097
- SucuriScanInterface::info(
8098
- 'File <code>' . WP_CONTENT_DIR . '/.htaccess</code> does not exists or is not
8099
- writable, you will need to remove the following code manually from there:
8100
- <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
8101
- );
8102
}
8103
}
8104
}
8105
8106
$description = 'This option blocks direct access to any PHP file located under the content'
8107
. ' directory of this site. The note under the <em>"Protect uploads directory"</em>'
8108
. ' section also applies to this option so you may want to read that part too. If you'
8109
. ' experience any kind of issues in your site after you apply this hardening go to the'
8110
. ' content directory using a FTP client or a file manager <em>(generally available in'
8111
- . ' your hosting panel)</em> and rename a file named <code>.htaccess</code>.</p><p><b>'
8112
- . 'Note:</b> Apache/2.4 introduced new directives to configure the access level of'
8113
- . ' certain resources in the server, for instance the rules applied to harden these'
8114
- . ' directories will not work and will probably cause issues. We will not fix this'
8115
- . ' because there is no accurate way to determine the exact version number of Apache'
8116
- . ' installed in this server considering some security measures applied to its'
8117
- . ' configuration that will prevent the version number to be readable by PHP, proceed'
8118
- . ' with caution.';
8119
8120
return sucuriscan_harden_status(
8121
'Restrict wp-content access',
@@ -8139,72 +8366,36 @@ function sucuriscan_harden_wpcontent(){
8139
* @return void
8140
*/
8141
function sucuriscan_harden_wpincludes(){
8142
- $cp = 1;
8143
- $htaccess_upload = ABSPATH . '/wp-includes/.htaccess';
8144
-
8145
- if ( ! is_readable( $htaccess_upload ) ) {
8146
- $cp = 0;
8147
- } else {
8148
- $cp = 0;
8149
- $fcontent = SucuriScanFileInfo::file_lines( $htaccess_upload );
8150
-
8151
- foreach ( $fcontent as $fline ) {
8152
- if ( stripos( $fline, 'deny from all' ) !== false ) {
8153
- $cp = 1;
8154
- break;
8155
- }
8156
- }
8157
- }
8158
8159
if ( SucuriScanRequest::post( ':run_hardening' ) ) {
8160
- if ( SucuriScanRequest::post( ':harden_wpincludes' ) && $cp == 0 ) {
8161
- $file_rules = "\n<Files *.php>"
8162
- . "\ndeny from all"
8163
- . "\n</Files>"
8164
- . "\n<Files wp-tinymce.php>"
8165
- . "\nallow from all"
8166
- . "\n</Files>"
8167
- . "\n<Files ms-files.php>"
8168
- . "\nallow from all"
8169
- . "\n</Files>"
8170
- . "\n";
8171
-
8172
- if ( @file_put_contents( $htaccess_upload, $file_rules ) === false ) {
8173
- SucuriScanInterface::error( 'Unable to create <code>.htaccess</code> file, folder destination is not writable.' );
8174
- } else {
8175
- $cp = 1;
8176
$message = 'Hardening applied to the library directory';
8177
SucuriScanEvent::report_notice_event( $message );
8178
SucuriScanInterface::info( $message );
8179
}
8180
} elseif ( SucuriScanRequest::post( ':harden_wpincludes_unharden' ) ) {
8181
- $htaccess_upload_writable = ( file_exists( $htaccess_upload ) && is_writable( $htaccess_upload ) ) ? true : false;
8182
- $htaccess_content = $htaccess_upload_writable ? @file_get_contents( $htaccess_upload ) : '';
8183
-
8184
- if ( $htaccess_upload_writable ) {
8185
- $cp = 0;
8186
-
8187
- if ( preg_match_all( '/<Files (\*|wp-tinymce|ms-files)\.php>\n(deny|allow) from all\n<\/Files>/', $htaccess_content, $match ) ) {
8188
- foreach ( $match[0] as $restriction ) {
8189
- $htaccess_content = str_replace( $restriction, '', $htaccess_content );
8190
- }
8191
-
8192
- @file_put_contents( $htaccess_upload, $htaccess_content, LOCK_EX );
8193
- }
8194
8195
$message = 'Hardening reverted in the library directory';
8196
SucuriScanEvent::report_error_event( $message );
8197
SucuriScanInterface::info( $message );
8198
} else {
8199
- SucuriScanInterface::error(
8200
- 'File <code>wp-includes/.htaccess</code> does not exists or is not
8201
- writable, you will need to remove the following code manually from
8202
- there: <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
8203
- );
8204
}
8205
}
8206
}
8207
8208
return sucuriscan_harden_status(
8209
'Restrict wp-includes access',
8210
$cp,
@@ -8913,18 +9104,23 @@ function sucuriscan_wordpress_outdated(){
8913
/**
8914
* Compare the md5sum of the core files in the current site with the hashes hosted
8915
* remotely in Sucuri servers. These hashes are updated every time a new version
8916
- * of WordPress is released.
8917
*
8918
- * @return void
8919
*/
8920
- function sucuriscan_core_files(){
8921
$site_version = SucuriScan::site_version();
8922
8923
$template_variables = array(
8924
'CoreFiles.List' => '',
8925
'CoreFiles.ListCount' => 0,
8926
'CoreFiles.GoodVisibility' => 'visible',
8927
'CoreFiles.BadVisibility' => 'hidden',
8928
);
8929
8930
if ( $site_version && SucuriScanOption::get_option( ':scan_checksums' ) == 'enabled' ) {
@@ -8984,6 +9180,7 @@ function sucuriscan_core_files(){
8984
'CoreFiles.IsNotFixable' => $is_fixable_text,
8985
));
8986
$counter += 1;
8987
}
8988
}
8989
@@ -8993,10 +9190,24 @@ function sucuriscan_core_files(){
8993
$template_variables['CoreFiles.BadVisibility'] = 'visible';
8994
}
8995
} else {
8996
- SucuriScanInterface::error( 'Error retrieving the WordPress core hashes, try again.' );
8997
}
8998
}
8999
9000
return SucuriScanTemplate::get_section( 'integrity-corefiles', $template_variables );
9001
}
9002
@@ -9997,7 +10208,11 @@ if ( ! function_exists( 'sucuri_login_redirect' ) ) {
9997
function sucuriscan_login_redirect( $redirect_to = '', $request = null, $user = false ){
9998
$login_url = ! empty($redirect_to) ? $redirect_to : admin_url();
9999
10000
- if ( $user instanceof WP_User && $user->ID ) {
10001
$login_url = add_query_arg( 'sucuriscan_lastlogin', 1, $login_url );
10002
}
10003
@@ -10965,6 +11180,10 @@ function sucuriscan_settings_form_submissions( $page_nonce = null ){
10965
if ( SucuriScanRequest::post( ':save_notification_settings' ) !== false ) {
10966
$options_updated_counter = 0;
10967
10968
foreach ( $sucuriscan_notify_options as $alert_type => $alert_label ) {
10969
$option_value = SucuriScanRequest::post( $alert_type, '(1|0)' );
10970
4
Plugin URI: http://wordpress.sucuri.net/
5
Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
6
Author: Sucuri, INC
7
+ Version: 1.7.12
8
Author URI: http://sucuri.net
9
*/
10
66
/**
67
* Current version of the plugin's code.
68
*/
69
+ define( 'SUCURISCAN_VERSION', '1.7.12' );
70
71
/**
72
* The name of the Sucuri plugin main file.
189
'sucuriscan_notify_plugin_change' => 'Receive email alerts for <strong>Sucuri</strong> plugin changes',
190
'sucuriscan_prettify_mails' => 'Receive email alerts in HTML <em>(there may be issues with some mail services)</em>',
191
'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
192
+ 'sucuriscan_notify_scan_checksums' => 'Receive email alerts for core integrity checks',
193
'sucuriscan_notify_user_registration' => 'user:Receive email alerts for new user registration',
194
'sucuriscan_notify_success_login' => 'user:Receive email alerts for successful login attempts',
195
'sucuriscan_notify_failed_login' => 'user:Receive email alerts for failed login attempts',
254
$sucuriscan_email_subjects = array(
255
'Sucuri Alert, :domain, :event',
256
'Sucuri Alert, :domain, :event, :remoteaddr',
257
+ 'Sucuri Alert, :domain, :event, :username',
258
+ 'Sucuri Alert, :domain, :event, :email',
259
'Sucuri Alert, :event, :remoteaddr',
260
'Sucuri Alert, :event',
261
);
271
* information to the Sucuri API service where a security and integrity scan
272
* will be performed against the hashes provided and the official versions.
273
*/
274
+ add_action( 'sucuriscan_scheduled_scan', 'SucuriScan::run_scheduled_task' );
275
276
/**
277
* Initialize the execute of the main plugin's functions.
308
*/
309
if ( class_exists( 'SucuriScanHook' ) ) {
310
$sucuriscan_hooks = array(
311
'add_attachment',
312
'add_link',
313
+ 'all',
314
'create_category',
315
'delete_post',
316
'delete_user',
317
'login_form_resetpass',
318
'private_to_published',
319
'publish_page',
320
'publish_phone',
321
+ 'publish_post',
322
'retrieve_password',
323
'switch_theme',
324
'user_register',
325
+ 'wp_insert_comment',
326
'wp_login',
327
'wp_login_failed',
328
'wp_trash_post',
329
+ 'xmlrpc_publish_post',
330
);
331
332
foreach ( $sucuriscan_hooks as $hook_name ) {
333
$hook_func = 'SucuriScanHook::hook_' . $hook_name;
334
+ add_action( $hook_name, $hook_func, 50, 5 );
335
}
336
337
add_action( 'admin_init', 'SucuriScanHook::hook_undefined_actions' );
622
return '/define\(\s*\'([A-Z_]+)\',(\s*)\'(.+)\'\s*\);/';
623
}
624
625
+ /**
626
+ * Execute the plugin' scheduled tasks.
627
+ *
628
+ * @return void
629
+ */
630
+ public static function run_scheduled_task(){
631
+ SucuriScanEvent::filesystem_scan();
632
+ sucuriscan_core_files( true );
633
+ }
634
+
635
/**
636
* Retrieve the real ip address of the user in the current request.
637
*
743
}
744
745
/**
746
+ * Check whether the site is behind the Sucuri CloudProxy network.
747
*
748
* @param boolean $verbose Return an array with the hostname, address, and status, or not.
749
* @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
750
*/
751
public static function is_behind_cloudproxy( $verbose = false ){
752
$http_host = self::get_top_level_domain();
753
+
754
+ if (
755
+ defined( 'NOT_USING_CLOUDPROXY' )
756
+ && NOT_USING_CLOUDPROXY === true
757
+ ) {
758
+ $status = false;
759
+ $host_by_addr = '::1';
760
+ $host_by_name = 'localhost';
761
+ } else {
762
+ $host_by_addr = @gethostbyname( $http_host );
763
+ $host_by_name = @gethostbyaddr( $host_by_addr );
764
+ $status = (bool) preg_match( '/^cloudproxy[0-9]+\.sucuri\.net#x2F;', $host_by_name );
765
+ }
766
767
/*
768
* If the DNS reversion failed but the CloudProxy API key is set, then consider
2557
'sucuriscan_notify_plugin_installed' => 'disabled',
2558
'sucuriscan_notify_plugin_updated' => 'disabled',
2559
'sucuriscan_notify_post_publication' => 'enabled',
2560
+ 'sucuriscan_notify_scan_checksums' => 'disabled',
2561
'sucuriscan_notify_settings_updated' => 'disabled',
2562
'sucuriscan_notify_success_login' => 'enabled',
2563
'sucuriscan_notify_theme_activated' => 'disabled',
3112
* @param integer $severity Importance of the event that will be reported, values from one to five.
3113
* @param string $location In which part of the system was the event triggered.
3114
* @param string $message The explanation of the event.
3115
+ * @param boolean $internal Whether the event will be publicly visible or not.
3116
* @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
3117
*/
3118
+ private static function report_event( $severity = 0, $location = '', $message = '', $internal = false ){
3119
$user = wp_get_current_user();
3120
$username = false;
3121
$current_time = date( 'Y-m-d H:i:s' );
3148
default: $severity_name = 'Info'; break;
3149
}
3150
3151
+ // Mark the event as internal if necessary.
3152
+ if ( $internal === true ) {
3153
+ $severity_name = '@' . $severity_name;
3154
+ }
3155
+
3156
// Clear event message.
3157
$message = strip_tags( $message );
3158
$message = str_replace( "\r", '', $message );
3173
/**
3174
* Reports a debug event on the website.
3175
*
3176
+ * @param string $message Text witht the explanation of the event or action performed.
3177
+ * @param boolean $internal Whether the event will be publicly visible or not.
3178
+ * @return boolean Either true or false depending on the success of the operation.
3179
*/
3180
+ public static function report_debug_event( $message = '', $internal = false ){
3181
+ return self::report_event( 0, 'core', $message, $internal );
3182
}
3183
3184
/**
3185
* Reports a notice event on the website.
3186
*
3187
+ * @param string $message Text witht the explanation of the event or action performed.
3188
+ * @param boolean $internal Whether the event will be publicly visible or not.
3189
+ * @return boolean Either true or false depending on the success of the operation.
3190
*/
3191
+ public static function report_notice_event( $message = '', $internal = false ){
3192
+ return self::report_event( 1, 'core', $message, $internal );
3193
}
3194
3195
/**
3196
* Reports a info event on the website.
3197
*
3198
+ * @param string $message Text witht the explanation of the event or action performed.
3199
+ * @param boolean $internal Whether the event will be publicly visible or not.
3200
+ * @return boolean Either true or false depending on the success of the operation.
3201
*/
3202
+ public static function report_info_event( $message = '', $internal = false ){
3203
+ return self::report_event( 2, 'core', $message, $internal );
3204
}
3205
3206
/**
3207
* Reports a warning event on the website.
3208
*
3209
+ * @param string $message Text witht the explanation of the event or action performed.
3210
+ * @param boolean $internal Whether the event will be publicly visible or not.
3211
+ * @return boolean Either true or false depending on the success of the operation.
3212
*/
3213
+ public static function report_warning_event( $message = '', $internal = false ){
3214
+ return self::report_event( 3, 'core', $message, $internal );
3215
}
3216
3217
/**
3218
* Reports a error event on the website.
3219
*
3220
+ * @param string $message Text witht the explanation of the event or action performed.
3221
+ * @param boolean $internal Whether the event will be publicly visible or not.
3222
+ * @return boolean Either true or false depending on the success of the operation.
3223
*/
3224
+ public static function report_error_event( $message = '', $internal = false ){
3225
+ return self::report_event( 4, 'core', $message, $internal );
3226
}
3227
3228
/**
3229
* Reports a critical event on the website.
3230
*
3231
+ * @param string $message Text witht the explanation of the event or action performed.
3232
+ * @param boolean $internal Whether the event will be publicly visible or not.
3233
+ * @return boolean Either true or false depending on the success of the operation.
3234
*/
3235
+ public static function report_critical_event( $message = '', $internal = false ){
3236
+ return self::report_event( 5, 'core', $message, $internal );
3237
}
3238
3239
/**
3240
* Reports a notice or error event for enable and disable actions.
3241
*
3242
+ * @param string $message Text witht the explanation of the event or action performed.
3243
* @param string $action An optional text, hopefully either enabled or disabled.
3244
+ * @param boolean $internal Whether the event will be publicly visible or not.
3245
+ * @return boolean Either true or false depending on the success of the operation.
3246
*/
3247
+ public static function report_auto_event( $message = '', $action = '', $internal = false ){
3248
$message = strip_tags( $message );
3249
3250
// Auto-detect the action performed, either enabled or disabled.
3254
3255
// Report the correct event for the action performed.
3256
if ( $action == 'enabled' ) {
3257
+ return self::report_notice_event( $message, $internal );
3258
} elseif ( $action == 'disabled' ) {
3259
+ return self::report_error_event( $message, $internal );
3260
} else {
3261
+ return self::report_info_event( $message, $internal );
3262
}
3263
}
3264
3333
} elseif ( $event == 'bruteforce_attack' ) {
3334
// Send a notification even if the limit of emails per hour was reached.
3335
$email_params['Force'] = true;
3336
+ } elseif ( $event == 'scan_checksums' ) {
3337
+ $event = 'core_integrity_checks';
3338
+ $email_params['Force'] = true;
3339
}
3340
3341
$title = str_replace( '_', chr( 32 ), $event );
3864
}
3865
}
3866
3867
+ /**
3868
+ * Fires immediately after a comment is inserted into the database.
3869
+ *
3870
+ * The action comment-post can also be used to track the insertion of data in
3871
+ * the comments table, but this only returns the identifier of the new entry in
3872
+ * the database and the status (approved, not approved, spam). The WP-Insert-
3873
+ * Comment action returns the same identifier and additionally the full data set
3874
+ * with the comment information.
3875
+ *
3876
+ * @see https://codex.wordpress.org/Plugin_API/Action_Reference/wp_insert_comment
3877
+ * @see https://codex.wordpress.org/Plugin_API/Action_Reference/comment_post
3878
+ *
3879
+ * @param integer $id The comment identifier.
3880
+ * @param object $comment The comment object.
3881
+ * @return void
3882
+ */
3883
+ public static function hook_wp_insert_comment( $id = 0, $comment = false ){
3884
+ if (
3885
+ $comment instanceof stdClass
3886
+ && property_exists( $comment, 'comment_ID' )
3887
+ && property_exists( $comment, 'comment_agent' )
3888
+ && property_exists( $comment, 'comment_author_IP' )
3889
+ ) {
3890
+ $data_set = array(
3891
+ 'id' => $comment->comment_ID,
3892
+ 'post_id' => $comment->comment_post_ID,
3893
+ 'user_id' => $comment->user_id,
3894
+ 'parent' => $comment->comment_parent,
3895
+ 'approved' => $comment->comment_approved,
3896
+ 'remote_addr' => $comment->comment_author_IP,
3897
+ 'author_email' => $comment->comment_author_email,
3898
+ 'date' => $comment->comment_date,
3899
+ 'content' => $comment->comment_content,
3900
+ 'user_agent' => $comment->comment_agent,
3901
+ );
3902
+ $message = base64_encode( json_encode( $data_set ) );
3903
+ self::report_notice_event( 'Base64:' . $message, true );
3904
+ }
3905
+ }
3906
+
3907
+ // TODO: Log when the comment status is modified: wp_set_comment_status
3908
+ // TODO: Log when the comment data is modified: edit_comment
3909
+ // TODO: Log when the comment is going to be deleted: delete_comment, trash_comment
3910
+ // TODO: Log when the comment is finally deleted: deleted_comment, trashed_comment
3911
+ // TODO: Log when the comment is closed: comment_closed
3912
// TODO: Detect auto updates in core, themes, and plugin files.
3913
3914
+ /**
3915
+ * Placeholder for arbitrary actions.
3916
+ *
3917
+ * @return void
3918
+ */
3919
+ public static function hook_all( $action = null, $data = false ){
3920
+ global $wp_filter;
3921
+
3922
+ if (
3923
+ is_array( $wp_filter )
3924
+ && ! empty( $wp_filter )
3925
+ && ! array_key_exists( $action, $wp_filter )
3926
+ && preg_match( '/^(admin_post|wp_ajax)_.+/', $action )
3927
+ ) {
3928
+ $message = sprintf( 'Undefined XHR action %s', $action );
3929
+ self::report_error_event( $message );
3930
+ }
3931
+ }
3932
+
3933
/**
3934
* Send a notifications to the administrator of some specific events that are
3935
* not triggered through an hooked action, but through a simple request in the
4946
$response['body']->output_data = array();
4947
$log_pattern = '/^([0-9\-]+) ([0-9:]+) (\S+) : (.+)/';
4948
$extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
4949
+ $generic_pattern = '/^@?([A-Z][a-z]{3,7}): ([^:;]+; )?(.+)/';
4950
$auth_pattern = '/^User authentication (succeeded|failed): ([^<;]+)/';
4951
4952
foreach ( $response['body']->output as $log ) {
5674
* @return string A text with the subject for the email alert.
5675
*/
5676
private static function get_email_subject( $event = '' ){
5677
+ $subject = self::get_option( ':email_subject' );
5678
5679
/**
5680
* Probably a bad value in the options table. Delete the entry from the database
5681
* and call this function to try again, it will probably fall in an infinite
5682
* loop, but this is the easiest way to control this procedure.
5683
*/
5684
+ if ( ! $subject ) {
5685
self::delete_option( ':email_subject' );
5686
5687
return self::get_email_subject( $event );
5688
}
5689
5690
+ $subject = strip_tags( $subject );
5691
+ $subject = str_replace( ':event', $event, $subject );
5692
+ $subject = str_replace( ':domain', self::get_domain(), $subject );
5693
+ $subject = str_replace( ':remoteaddr', self::get_remote_addr(), $subject );
5694
+
5695
+ /**
5696
+ * Extract user data from the current session.
5697
+ *
5698
+ * Get the data of the user in the current session only if the pseudo-tags for
5699
+ * the username and/or email address are necessary to build the email subject,
5700
+ * otherwise this operation may delay the sending of the alerts.
5701
+ */
5702
+ if ( preg_match( '/:(username|email)/', $subject ) ) {
5703
+ $user = wp_get_current_user();
5704
+ $username = 'unknown';
5705
+ $eaddress = 'unknown';
5706
+
5707
+ if (
5708
+ $user instanceof WP_User
5709
+ && isset( $user->user_login )
5710
+ && isset( $user->user_email )
5711
+ ) {
5712
+ $username = $user->user_login;
5713
+ $eaddress = $user->user_email;
5714
+ }
5715
+
5716
+ $subject = str_replace( ':username', $user->user_login, $subject );
5717
+ $subject = str_replace( ':email', $user->user_email, $subject );
5718
+ }
5719
5720
+ return $subject;
5721
}
5722
5723
/**
7887
return $options;
7888
}
7889
7890
+ /**
7891
+ * Project hardening library.
7892
+ *
7893
+ * In computing, hardening is usually the process of securing a system by
7894
+ * reducing its surface of vulnerability. A system has a larger vulnerability
7895
+ * surface the more functions it fulfills; in principle a single-function system
7896
+ * is more secure than a multipurpose one. Reducing available vectors of attack
7897
+ * typically includes the removal of unnecessary software, unnecessary usernames
7898
+ * or logins and the disabling or removal of unnecessary services.
7899
+ *
7900
+ * There are various methods of hardening Unix and Linux systems. This may
7901
+ * involve, among other measures, applying a patch to the kernel such as Exec
7902
+ * Shield or PaX; closing open network ports; and setting up intrusion-detection
7903
+ * systems, firewalls and intrusion-prevention systems. There are also hardening
7904
+ * scripts and tools like Bastille Linux, JASS for Solaris systems and
7905
+ * Apache/PHP Hardener that can, for example, deactivate unneeded features in
7906
+ * configuration files or perform various other protective measures.
7907
+ */
7908
+ class SucuriScanHardening extends SucuriScan {
7909
+
7910
+ /**
7911
+ * Returns a list of access control rules for the Apache web server that can be
7912
+ * used to deny and allow certain files to be accessed by certain network nodes.
7913
+ * Currently supports Apache 2.2 and 2.4 and denies access to all PHP files with
7914
+ * any mixed extension case.
7915
+ *
7916
+ * @param string $directory Valid directory path.
7917
+ * @return array List of access control rules.
7918
+ */
7919
+ private static function get_rules( $directory = '' ){
7920
+ $directory = basename( $directory );
7921
+ $rules = array(
7922
+ '<FilesMatch "\.(?i:php)quot;>',
7923
+ ' <IfModule !mod_authz_core.c>',
7924
+ ' Order allow,deny',
7925
+ ' Deny from all',
7926
+ ' </IfModule>',
7927
+ ' <IfModule mod_authz_core.c>',
7928
+ ' Require all denied',
7929
+ ' </IfModule>',
7930
+ '</FilesMatch>',
7931
+ );
7932
+
7933
+ if ( $directory == 'wp-includes' ) {
7934
+ $rules[] = '<Files wp-tinymce.php>';
7935
+ $rules[] = ' Allow from all';
7936
+ $rules[] = '</Files>';
7937
+ $rules[] = '<Files ms-files.php>';
7938
+ $rules[] = ' Allow from all';
7939
+ $rules[] = '</Files>';
7940
+ }
7941
+
7942
+ return $rules;
7943
+ }
7944
+
7945
+ /**
7946
+ * Adds some rules to an existing access control file (or creates it if does not
7947
+ * exists) to deny access to all files with certain extension in any mixed case.
7948
+ * The permissions to modify the file are checked before anything else, this
7949
+ * function is self-contained.
7950
+ *
7951
+ * @param string $directory Valid directory path where to place the access rules.
7952
+ * @return boolean True if the rules are successfully added, false otherwise.
7953
+ */
7954
+ public static function harden_directory( $directory = '' ){
7955
+ if (
7956
+ file_exists( $directory)
7957
+ && is_writable( $directory )
7958
+ && is_dir( $directory )
7959
+ ) {
7960
+ $fhandle = false;
7961
+ $target = $directory . '/.htaccess';
7962
+ $deny_rules = self::get_rules( $directory );
7963
+
7964
+ if ( file_exists( $target ) ) {
7965
+ $fhandle = @fopen( $target, 'a' );
7966
+ } else {
7967
+ $fhandle = @fopen( $target, 'w' );
7968
+ }
7969
+
7970
+ if ( $fhandle ) {
7971
+ $rules_str = implode( "\n", $deny_rules );
7972
+ $written = fwrite( $fhandle, $rules_str );
7973
+ fclose( $fhandle );
7974
+
7975
+ return (bool) ( $written !== false );
7976
+ }
7977
+ }
7978
+
7979
+ return false;
7980
+ }
7981
+
7982
+ /**
7983
+ * Deletes some rules from an existing access control file to allow access to
7984
+ * all files with certain extension in any mixed case. The file is truncated if
7985
+ * after the operation its size is equals to zero.
7986
+ *
7987
+ * @param string $directory Valid directory path where to access rules are.
7988
+ * @return boolean True if the rules are successfully deleted, false otherwise.
7989
+ */
7990
+ public static function unharden_directory( $directory = '' ){
7991
+ if ( self::is_hardened( $directory ) ) {
7992
+ $deny_rules = self::get_rules( $directory );
7993
+ $fpath = $directory . '/.htaccess';
7994
+ $content = @file_get_contents( $fpath );
7995
+
7996
+ if ( $content ) {
7997
+ $rules_str = implode( "\n", $deny_rules );
7998
+ $content = str_replace( $rules_str, '', $content );
7999
+ $written = @file_put_contents( $fpath, $content );
8000
+
8001
+ if ( filesize( $fpath ) === 0 ) {
8002
+ @unlink( $fpath );
8003
+ }
8004
+
8005
+ return (bool) ( $written !== false );
8006
+ }
8007
+ }
8008
+
8009
+ return false;
8010
+ }
8011
+
8012
+ /**
8013
+ * Check whether a directory is hardened or not.
8014
+ *
8015
+ * @param string $directory Valid directory path.
8016
+ * @return boolean True if the directory is hardened, false otherwise.
8017
+ */
8018
+ public static function is_hardened( $directory = '' ){
8019
+ if (
8020
+ file_exists( $directory )
8021
+ && is_dir( $directory )
8022
+ ) {
8023
+ $fpath = $directory . '/.htaccess';
8024
+
8025
+ if (
8026
+ file_exists( $fpath )
8027
+ && is_readable( $fpath )
8028
+ ) {
8029
+ $rules = self::get_rules( $directory );
8030
+ $rules_str = implode( "\n", $rules );
8031
+ $content = @file_get_contents( $fpath );
8032
+
8033
+ if ( strpos( $content, $rules_str ) !== false ) {
8034
+ return true;
8035
+ }
8036
+ }
8037
+ }
8038
+
8039
+ return false;
8040
+ }
8041
+
8042
+ }
8043
+
8044
/**
8045
* Sucuri one-click hardening page.
8046
*
8249
* @return void
8250
*/
8251
function sucuriscan_harden_upload(){
8252
+ $dpath = WP_CONTENT_DIR . '/uploads';
8253
8254
if ( SucuriScanRequest::post( ':run_hardening' ) ) {
8255
+ if ( SucuriScanRequest::post( ':harden_upload' ) ) {
8256
+ $result = SucuriScanHardening::harden_directory( $dpath );
8257
+
8258
+ if ( $result === true ) {
8259
$message = 'Hardening applied to the uploads directory';
8260
SucuriScanEvent::report_notice_event( $message );
8261
SucuriScanInterface::info( $message );
8262
+ } else {
8263
+ SucuriScanInterface::error( 'Error hardening directory, check the permissions.' );
8264
}
8265
} elseif ( SucuriScanRequest::post( ':harden_upload_unharden' ) ) {
8266
+ $result = SucuriScanHardening::unharden_directory( $dpath );
8267
8268
+ if ( $result === true ) {
8269
$message = 'Hardening reverted in the uploads directory';
8270
SucuriScanEvent::report_error_event( $message );
8271
SucuriScanInterface::info( $message );
8272
} else {
8273
+ SucuriScanInterface::info( 'Access file is not writable, check the permissions.' );
8274
}
8275
}
8276
}
8277
8278
+ // Check whether the directory is already hardened or not.
8279
+ $is_hardened = SucuriScanHardening::is_hardened( $dpath );
8280
+ $cp = ( $is_hardened === true ) ? 1 : 0;
8281
+
8282
$description = 'It checks if the uploads directory of this site allows the direct execution'
8283
. ' of PHP files. It is recommendable to prevent this because someone may try to exploit'
8284
. ' a vulnerability of a plugin, theme, and/or other PHP-based code located in this'
8309
* @return void
8310
*/
8311
function sucuriscan_harden_wpcontent(){
8312
if ( SucuriScanRequest::post( ':run_hardening' ) ) {
8313
+ if ( SucuriScanRequest::post( ':harden_wpcontent' ) ) {
8314
+ $result = SucuriScanHardening::harden_directory( WP_CONTENT_DIR );
8315
+
8316
+ if ( $result === true ) {
8317
$message = 'Hardening applied to the content directory';
8318
SucuriScanEvent::report_notice_event( $message );
8319
SucuriScanInterface::info( $message );
8320
+ } else {
8321
+ SucuriScanInterface::error( 'Error hardening directory, check the permissions.' );
8322
}
8323
} elseif ( SucuriScanRequest::post( ':harden_wpcontent_unharden' ) ) {
8324
+ $result = SucuriScanHardening::unharden_directory( WP_CONTENT_DIR );
8325
8326
+ if ( $result === true ) {
8327
$message = 'Hardening reverted in the content directory';
8328
SucuriScanEvent::report_error_event( $message );
8329
SucuriScanInterface::info( $message );
8330
} else {
8331
+ SucuriScanInterface::info( 'Access file is not writable, check the permissions.' );
8332
}
8333
}
8334
}
8335
8336
+ // Check whether the directory is already hardened or not.
8337
+ $is_hardened = SucuriScanHardening::is_hardened( WP_CONTENT_DIR );
8338
+ $cp = ( $is_hardened === true ) ? 1 : 0;
8339
+
8340
$description = 'This option blocks direct access to any PHP file located under the content'
8341
. ' directory of this site. The note under the <em>"Protect uploads directory"</em>'
8342
. ' section also applies to this option so you may want to read that part too. If you'
8343
. ' experience any kind of issues in your site after you apply this hardening go to the'
8344
. ' content directory using a FTP client or a file manager <em>(generally available in'
8345
+ . ' your hosting panel)</em> and rename a file named <code>.htaccess</code>.';
8346
8347
return sucuriscan_harden_status(
8348
'Restrict wp-content access',
8366
* @return void
8367
*/
8368
function sucuriscan_harden_wpincludes(){
8369
+ $dpath = ABSPATH . '/wp-includes';
8370
8371
if ( SucuriScanRequest::post( ':run_hardening' ) ) {
8372
+ if ( SucuriScanRequest::post( ':harden_wpincludes' ) ) {
8373
+ $result = SucuriScanHardening::harden_directory( $dpath );
8374
+
8375
+ if ( $result === true ) {
8376
$message = 'Hardening applied to the library directory';
8377
SucuriScanEvent::report_notice_event( $message );
8378
SucuriScanInterface::info( $message );
8379
+ } else {
8380
+ SucuriScanInterface::error( 'Error hardening directory, check the permissions.' );
8381
}
8382
} elseif ( SucuriScanRequest::post( ':harden_wpincludes_unharden' ) ) {
8383
+ $result = SucuriScanHardening::unharden_directory( $dpath );
8384
8385
+ if ( $result === true ) {
8386
$message = 'Hardening reverted in the library directory';
8387
SucuriScanEvent::report_error_event( $message );
8388
SucuriScanInterface::info( $message );
8389
} else {
8390
+ SucuriScanInterface::info( 'Access file is not writable, check the permissions.' );
8391
}
8392
}
8393
}
8394
8395
+ // Check whether the directory is already hardened or not.
8396
+ $is_hardened = SucuriScanHardening::is_hardened( $dpath );
8397
+ $cp = ( $is_hardened === true ) ? 1 : 0;
8398
+
8399
return sucuriscan_harden_status(
8400
'Restrict wp-includes access',
8401
$cp,
9104
/**
9105
* Compare the md5sum of the core files in the current site with the hashes hosted
9106
* remotely in Sucuri servers. These hashes are updated every time a new version
9107
+ * of WordPress is released. If the "Send Email" parameter is set the function will
9108
+ * send a notification to the administrator with a list of files that were added,
9109
+ * modified and/or deleted so far.
9110
*
9111
+ * @param boolean $send_email If the HTML code returned will be sent via email.
9112
+ * @return string HTML code with a list of files that were affected.
9113
*/
9114
+ function sucuriscan_core_files( $send_email = false ){
9115
$site_version = SucuriScan::site_version();
9116
+ $affected_files = 0;
9117
9118
$template_variables = array(
9119
'CoreFiles.List' => '',
9120
'CoreFiles.ListCount' => 0,
9121
'CoreFiles.GoodVisibility' => 'visible',
9122
'CoreFiles.BadVisibility' => 'hidden',
9123
+ 'CoreFiles.FailureVisibility' => 'hidden',
9124
);
9125
9126
if ( $site_version && SucuriScanOption::get_option( ':scan_checksums' ) == 'enabled' ) {
9180
'CoreFiles.IsNotFixable' => $is_fixable_text,
9181
));
9182
$counter += 1;
9183
+ $affected_files += 1;
9184
}
9185
}
9186
9190
$template_variables['CoreFiles.BadVisibility'] = 'visible';
9191
}
9192
} else {
9193
+ $template_variables['CoreFiles.GoodVisibility'] = 'hidden';
9194
+ $template_variables['CoreFiles.BadVisibility'] = 'hidden';
9195
+ $template_variables['CoreFiles.FailureVisibility'] = 'visible';
9196
}
9197
}
9198
9199
+ // Send an email notification with the affected files.
9200
+ if ( $send_email === true ) {
9201
+ if ( $affected_files > 0 ) {
9202
+ $content = SucuriScanTemplate::get_section( 'notification-corefiles', $template_variables );
9203
+ $sent = SucuriScanEvent::notify_event( 'scan_checksums', $content );
9204
+
9205
+ return $sent;
9206
+ }
9207
+
9208
+ return false;
9209
+ }
9210
+
9211
return SucuriScanTemplate::get_section( 'integrity-corefiles', $template_variables );
9212
}
9213
10208
function sucuriscan_login_redirect( $redirect_to = '', $request = null, $user = false ){
10209
$login_url = ! empty($redirect_to) ? $redirect_to : admin_url();
10210
10211
+ if (
10212
+ $user instanceof WP_User
10213
+ && in_array( 'administrator', $user->roles )
10214
+ && SucuriScanOption::get_option( ':lastlogin_redirection' ) === 'enabled'
10215
+ ) {
10216
$login_url = add_query_arg( 'sucuriscan_lastlogin', 1, $login_url );
10217
}
10218
11180
if ( SucuriScanRequest::post( ':save_notification_settings' ) !== false ) {
11181
$options_updated_counter = 0;
11182
11183
+ if ( SucuriScanRequest::post( ':notify_scan_checksums', '1' ) ) {
11184
+ $_POST['sucuriscan_prettify_mails'] = '1';
11185
+ }
11186
+
11187
foreach ( $sucuriscan_notify_options as $alert_type => $alert_label ) {
11188
$option_value = SucuriScanRequest::post( $alert_type, '(1|0)' );
11189