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$/', $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$/', $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)$">',
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