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

Version Description

  • Added better checks for SSL issues
  • Fix for audit log timezones
  • 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.10
Comparing to
See all releases

Code changes from version 1.7.9 to 1.7.10

inc/css/sucuriscan-default-css.css CHANGED
@@ -269,6 +269,14 @@ td.sucuriscan-corefiles-warning > div{background:#f2dede;color:#a94442;border-co
269
  .sucuriscan-admins-lastlogins .sucuriscan-ellipsis{width:170px}
270
  .sucuriscan-admins-lastlogins td{padding:4px 8px}
271
  .sucuriscan-lastlogins-failed{}
 
 
 
 
 
 
 
 
272
  /* About Page */
273
  .sucuriscan-about ul{margin-left:20px}
274
  .sucuriscan-about ul li{list-style:initial}
269
  .sucuriscan-admins-lastlogins .sucuriscan-ellipsis{width:170px}
270
  .sucuriscan-admins-lastlogins td{padding:4px 8px}
271
  .sucuriscan-lastlogins-failed{}
272
+ /* Pattern Search Styles */
273
+ .sucuriscan-pattern-search {}
274
+ .sucuriscan-pattern-search-inputbox {margin-top:12px}
275
+ .sucuriscan-pattern-search-inputbox .input-text {width:84.7777%;line-height:30px;margin:0;margin-right:6px}
276
+ .sucuriscan-pattern-search-inputbox .input-button {width:14%;height:initial;line-height:35px}
277
+ .sucuriscan-pattern-search .sucuriscan-cleanup-btn {margin-top:12px}
278
+ .sucuriscan-pattern-search table label {color:#999}
279
+ .sucuriscan-pattern-search .sucuriscan-grep-text em{color:#ea3838}
280
  /* About Page */
281
  .sucuriscan-about ul{margin-left:20px}
282
  .sucuriscan-about ul li{list-style:initial}
inc/images/main-logo.png ADDED
Binary file
inc/tpl/lastlogins-all.html.tpl CHANGED
@@ -13,7 +13,7 @@
13
  </th>
14
  </tr>
15
  <tr>
16
- <th class="manage-column">No.</th>
17
  <th class="manage-column">User</th>
18
  <th class="manage-column">IP Address</th>
19
  <th class="manage-column">Hostname</th>
13
  </th>
14
  </tr>
15
  <tr>
16
+ <th class="manage-column">&nbsp;</th>
17
  <th class="manage-column">User</th>
18
  <th class="manage-column">IP Address</th>
19
  <th class="manage-column">Hostname</th>
inc/tpl/lastlogins-failedlogins.html.tpl CHANGED
@@ -44,16 +44,17 @@
44
  <table class="wp-list-table widefat sucuriscan-table sucuriscan-lastlogins-failed sucuriscan-%%SUCURI.IgnoreRules.TableVisibility%%">
45
  <thead>
46
  <tr>
47
- <th width="20">No.</th>
48
- <th>User</th>
49
- <th>Password</th>
50
- <th>IP Address</th>
51
- <th>Date/Time</th>
52
- <th width="300">User-Agent</th>
53
  </tr>
54
  </thead>
55
 
56
  <tbody>
 
57
  %%SUCURI.FailedLogins.List%%
58
 
59
  <tr class="sucuriscan-%%SUCURI.FailedLogins.NoItemsVisibility%%">
@@ -61,5 +62,14 @@
61
  <em>No logs so far.</em>
62
  </td>
63
  </tr>
 
 
 
 
 
 
 
 
 
64
  </tbody>
65
  </table>
44
  <table class="wp-list-table widefat sucuriscan-table sucuriscan-lastlogins-failed sucuriscan-%%SUCURI.IgnoreRules.TableVisibility%%">
45
  <thead>
46
  <tr>
47
+ <th class="manage-column">&nbsp;</th>
48
+ <th class="manage-column">User</th>
49
+ <th class="manage-column">Password</th>
50
+ <th class="manage-column">IP Address</th>
51
+ <th class="manage-column">Date/Time</th>
52
+ <th class="manage-column" width="300">User-Agent</th>
53
  </tr>
54
  </thead>
55
 
56
  <tbody>
57
+
58
  %%SUCURI.FailedLogins.List%%
59
 
60
  <tr class="sucuriscan-%%SUCURI.FailedLogins.NoItemsVisibility%%">
62
  <em>No logs so far.</em>
63
  </td>
64
  </tr>
65
+
66
+ <tr class="sucuriscan-%%SUCURI.FailedLogins.PaginationVisibility%%">
67
+ <td colspan="6">
68
+ <ul class="sucuriscan-pagination">
69
+ %%SUCURI.FailedLogins.PaginationLinks%%
70
+ </ul>
71
+ </td>
72
+ </tr>
73
+
74
  </tbody>
75
  </table>
inc/tpl/malwarescan.html.tpl CHANGED
@@ -7,9 +7,19 @@
7
  <form action="%%SUCURI.URL.Scanner%%" method="post" class="sucuriscan-sitecheck-form">
8
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
9
  <input type="hidden" name="sucuriscan_malware_scan" value="1" />
10
- <button type="submit" class="button button-hero button-primary">Scan Website</button>
11
  </form>
12
 
 
 
 
 
 
 
 
 
 
 
13
  <div class="sucuriscan-sitelogo">&nbsp;</div>
14
 
15
  <div class="sucuriscan-sitecheck-disclaimer">
7
  <form action="%%SUCURI.URL.Scanner%%" method="post" class="sucuriscan-sitecheck-form">
8
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
9
  <input type="hidden" name="sucuriscan_malware_scan" value="1" />
10
+ <input type="submit" value="Scan Website" class="button button-hero button-primary input-button" />
11
  </form>
12
 
13
+ <script type="text/javascript">
14
+ jQuery(function($){
15
+ $('.sucuriscan-sitecheck-form .input-button').click(function(){
16
+ $('.sucuriscan-sitecheck-form .input-button')
17
+ .val('Scanning Website...')
18
+ .addClass('disabled');
19
+ });
20
+ });
21
+ </script>
22
+
23
  <div class="sucuriscan-sitelogo">&nbsp;</div>
24
 
25
  <div class="sucuriscan-sitecheck-disclaimer">
inc/tpl/notification-pretty.html.tpl CHANGED
@@ -4,7 +4,7 @@
4
  <tr style="background-color:#4b4b4b;background-image:-moz-linear-gradient(top, #555555, #3b3b3b);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#3b3b3b));background-image:-webkit-linear-gradient(top, #555555, #3b3b3b);background-image:-o-linear-gradient(top, #555555, #3b3b3b);background-image:linear-gradient(to bottom, #555555, #3b3b3b);background-repeat:repeat-x">
5
  <td sytle="font-size:20px;font-weight:normal;color:#ffffff;padding:10px;border-right:1px solid #2f2f2f;border-left:1px solid #6f6f6f;-webkit-box-shadow:inset 0 1px 0 #888888;-moz-box-shadow:inset 0 1px 0 #888888;box-shadow:inset 0 1px 0 #888888;text-shadow:1px 1px 2px rgba(0, 0, 0, 0.5)">
6
  <a href="http://sucuri.net/" style="text-decoration:none;display:inline-block;margin:8px 0 5px 20px">
7
- <img src="http://sucuri.net/wp-content/themes/sucuri-two/images/main-logo.png" alt="Sucuri, Inc." style="border:none" />
8
  </a>
9
  <span style="display:inline-block;line-height:46px;margin:0 20px 0 0;float:right;color:#ffffff">%%SUCURI.TemplateTitle%%</span>
10
  </td>
4
  <tr style="background-color:#4b4b4b;background-image:-moz-linear-gradient(top, #555555, #3b3b3b);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#3b3b3b));background-image:-webkit-linear-gradient(top, #555555, #3b3b3b);background-image:-o-linear-gradient(top, #555555, #3b3b3b);background-image:linear-gradient(to bottom, #555555, #3b3b3b);background-repeat:repeat-x">
5
  <td sytle="font-size:20px;font-weight:normal;color:#ffffff;padding:10px;border-right:1px solid #2f2f2f;border-left:1px solid #6f6f6f;-webkit-box-shadow:inset 0 1px 0 #888888;-moz-box-shadow:inset 0 1px 0 #888888;box-shadow:inset 0 1px 0 #888888;text-shadow:1px 1px 2px rgba(0, 0, 0, 0.5)">
6
  <a href="http://sucuri.net/" style="text-decoration:none;display:inline-block;margin:8px 0 5px 20px">
7
+ <img src="%%SUCURI.SucuriURL%%/inc/images/main-logo.png" alt="Sucuri, Inc." style="border:none" />
8
  </a>
9
  <span style="display:inline-block;line-height:46px;margin:0 20px 0 0;float:right;color:#ffffff">%%SUCURI.TemplateTitle%%</span>
10
  </td>
inc/tpl/notification-resetpwd.html.tpl ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Your password for <strong>%%SUCURI.ResetPassword.UserName%%</strong>
3
+ <em>(%%SUCURI.ResetPassword.DisplayName%%)</em> at <a target="_blank"
4
+ href="http://%%SUCURI.ResetPassword.Website%%">%%SUCURI.ResetPassword.Website%%</a>
5
+ has been reset for security reasons.<br>
6
+ You can use this temporary password to log in:
7
+ <span style="display:inline-block;background:#f5f5f5;padding:2px 6px;
8
+ font-family:Menlo, Monaco, monospace, serif;border:1px solid #ddd">
9
+ %%SUCURI.ResetPassword.Password%%</span><br>
10
+ Please change your password after you log in.
inc/tpl/posthack-resetplugins.html.tpl CHANGED
@@ -1,8 +1,12 @@
1
 
2
  <div id="poststuff" class="sucuriscan-reset-plugins">
 
3
  <div class="postbox">
 
4
  <div class="inside">
 
5
  <form action="%%SUCURI.URL.Posthack%%#reset-plugins" method="post">
 
6
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
7
  <input type="hidden" name="sucuriscan_reset_plugins" value="1" />
8
 
@@ -24,7 +28,7 @@
24
  </p>
25
  </div>
26
 
27
- <table class="wp-list-table widefat sucuriscan-table">
28
  <thead>
29
  <tr>
30
  <th class="manage-column column-cb check-column">
@@ -39,7 +43,11 @@
39
  </thead>
40
 
41
  <tbody>
42
- %%SUCURI.ResetPlugin.PluginList%%
 
 
 
 
43
  </tbody>
44
  </table>
45
 
@@ -52,7 +60,23 @@
52
  </p>
53
 
54
  <input type="submit" value="Process selected items" class="button button-primary" />
 
55
  </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  </div>
 
57
  </div>
 
58
  </div>
1
 
2
  <div id="poststuff" class="sucuriscan-reset-plugins">
3
+
4
  <div class="postbox">
5
+
6
  <div class="inside">
7
+
8
  <form action="%%SUCURI.URL.Posthack%%#reset-plugins" method="post">
9
+
10
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
11
  <input type="hidden" name="sucuriscan_reset_plugins" value="1" />
12
 
28
  </p>
29
  </div>
30
 
31
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-reset-plugins-table">
32
  <thead>
33
  <tr>
34
  <th class="manage-column column-cb check-column">
43
  </thead>
44
 
45
  <tbody>
46
+ <tr>
47
+ <td colspan="5">
48
+ <span>Loading <em>(may take several seconds)</em>...</span>
49
+ </td>
50
+ </tr>
51
  </tbody>
52
  </table>
53
 
60
  </p>
61
 
62
  <input type="submit" value="Process selected items" class="button button-primary" />
63
+
64
  </form>
65
+
66
+ <script type="text/javascript">
67
+ jQuery(function($){
68
+ $.post( '%%SUCURI.AjaxURL.Posthack%%', {
69
+ action: 'sucuriscan_posthack_ajax',
70
+ sucuriscan_page_nonce: '%%SUCURI.PageNonce%%',
71
+ form_action: 'get_plugins_data',
72
+ }, function(data){
73
+ $('.sucuriscan-reset-plugins-table tbody').html( data );
74
+ });
75
+ });
76
+ </script>
77
+
78
  </div>
79
+
80
  </div>
81
+
82
  </div>
inc/tpl/settings-general.html.tpl CHANGED
@@ -152,27 +152,34 @@
152
  </tr>
153
 
154
  <tr>
155
- <td>API proxy host:port</td>
156
  <td><span class="sucuriscan-monospace">%%SUCURI.APIProxy.Host%%:%%SUCURI.APIProxy.Port%%</span></td>
157
  <td>&nbsp;</td>
158
  </tr>
159
 
160
  <tr class="alternate">
161
- <td>API proxy username</td>
162
- <td><span class="sucuriscan-monospace">%%SUCURI.APIProxy.Username%%</span></td>
163
- <td>&nbsp;</td>
164
- </tr>
165
-
166
- <tr>
167
- <td>API proxy password</td>
168
  <td>
 
169
  <span class="sucuriscan-label-%%SUCURI.APIProxy.PasswordType%%">
170
- %%SUCURI.APIProxy.PasswordText%%
171
  </span>
172
  </td>
173
  <td>&nbsp;</td>
174
  </tr>
175
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  <tr class="alternate">
177
  <td>Support reverse proxy</td>
178
  <td>%%SUCURI.ReverseProxyStatus%%</td>
152
  </tr>
153
 
154
  <tr>
155
+ <td>API proxy <em>(host:port)</em></td>
156
  <td><span class="sucuriscan-monospace">%%SUCURI.APIProxy.Host%%:%%SUCURI.APIProxy.Port%%</span></td>
157
  <td>&nbsp;</td>
158
  </tr>
159
 
160
  <tr class="alternate">
161
+ <td>API proxy <em>(auth)</em></td>
 
 
 
 
 
 
162
  <td>
163
+ <span class="sucuriscan-monospace">%%SUCURI.APIProxy.Username%%</span>
164
  <span class="sucuriscan-label-%%SUCURI.APIProxy.PasswordType%%">
165
+ <em>password is %%SUCURI.APIProxy.PasswordText%%</em>
166
  </span>
167
  </td>
168
  <td>&nbsp;</td>
169
  </tr>
170
 
171
+ <tr>
172
+ <td>API test request</td>
173
+ <td><em>(Test ability to send HTTP requests)</em></td>
174
+ <td class="td-with-button">
175
+ <form action="%%SUCURI.URL.Settings%%" method="post">
176
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
177
+ <input type="hidden" name="sucuriscan_debug_request" value="1" />
178
+ <button type="submit" class="button-primary">Proceed</button>
179
+ </form>
180
+ </td>
181
+ </tr>
182
+
183
  <tr class="alternate">
184
  <td>Support reverse proxy</td>
185
  <td>%%SUCURI.ReverseProxyStatus%%</td>
readme.txt CHANGED
@@ -3,7 +3,7 @@ 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.9
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.
@@ -352,6 +352,11 @@ service from the WordPress dashboard.
352
 
353
  == Changelog ==
354
 
 
 
 
 
 
355
  = 1.7.9 =
356
  * Improved reinstallation process
357
  * Updated sidebar banners
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.10
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.
352
 
353
  == Changelog ==
354
 
355
+ = 1.7.10 =
356
+ * Added better checks for SSL issues
357
+ * Fix for audit log timezones
358
+ * Various bugfixes and improvements
359
+
360
  = 1.7.9 =
361
  * Improved reinstallation process
362
  * Updated sidebar banners
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.9
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.9' );
70
 
71
  /**
72
  * The name of the Sucuri plugin main file.
@@ -197,17 +197,17 @@ if ( defined( 'SUCURISCAN' ) ) {
197
  'sucuriscan_notify_website_updated' => 'Receive email alerts when the WordPress version is updated',
198
  'sucuriscan_notify_settings_updated' => 'Receive email alerts when your website settings are updated',
199
  'sucuriscan_notify_theme_editor' => 'Receive email alerts when a file is modified with theme/plugin editor',
200
- 'sucuriscan_notify_plugin_installed' => 'plugin:Receive email alerts when a plugin is installed',
201
- 'sucuriscan_notify_plugin_activated' => 'plugin:Receive email alerts when a plugin is activated',
202
- 'sucuriscan_notify_plugin_deactivated' => 'plugin:Receive email alerts when a plugin is deactivated',
203
- 'sucuriscan_notify_plugin_updated' => 'plugin:Receive email alerts when a plugin is updated',
204
- 'sucuriscan_notify_plugin_deleted' => 'plugin:Receive email alerts when a plugin is deleted',
205
- 'sucuriscan_notify_widget_added' => 'widget:Receive email alerts when a widget is added to a sidebar',
206
- 'sucuriscan_notify_widget_deleted' => 'widget:Receive email alerts when a widget is deleted from a sidebar',
207
- 'sucuriscan_notify_theme_installed' => 'theme:Receive email alerts when a theme is installed',
208
- 'sucuriscan_notify_theme_activated' => 'theme:Receive email alerts when a theme is activated',
209
- 'sucuriscan_notify_theme_updated' => 'theme:Receive email alerts when a theme is updated',
210
- 'sucuriscan_notify_theme_deleted' => 'theme:Receive email alerts when a theme is deleted',
211
  );
212
 
213
  $sucuriscan_schedule_allowed = array(
@@ -282,6 +282,17 @@ if ( defined( 'SUCURISCAN' ) ) {
282
  add_action( 'admin_enqueue_scripts', 'SucuriScanInterface::enqueue_scripts', 1 );
283
  add_action( 'admin_menu', 'SucuriScanInterface::add_interface_menu' );
284
 
 
 
 
 
 
 
 
 
 
 
 
285
  /**
286
  * Function call interceptors.
287
  *
@@ -795,11 +806,16 @@ class SucuriScan {
795
  */
796
  public static function datetime( $timestamp = 0 ){
797
  if ( is_numeric( $timestamp ) && $timestamp > 0 ) {
 
798
  $date_format = get_option( 'date_format' );
799
  $time_format = get_option( 'time_format' );
800
- $timezone_format = sprintf( '%s %s', $date_format, $time_format );
801
 
802
- return date_i18n( $timezone_format, $timestamp );
 
 
 
 
803
  }
804
 
805
  return null;
@@ -1631,9 +1647,9 @@ class SucuriScanFileInfo extends SucuriScan {
1631
  /**
1632
  * Skip some specific directories and file paths from the filesystem scan.
1633
  *
1634
- * @param string $directory Directory where the scanner is located at the moment.
1635
- * @param string $filename Name of the folder or file being scanned at the moment.
1636
- * @return boolean Either TRUE or FALSE representing that the scan should ignore this folder or not.
1637
  */
1638
  private function ignore_folderpath( $directory = '', $filename = '' ){
1639
  // Ignoring current and parent folders.
@@ -1682,6 +1698,15 @@ class SucuriScanFileInfo extends SucuriScan {
1682
  return true;
1683
  }
1684
 
 
 
 
 
 
 
 
 
 
1685
  // Any file maching one of these rules WILL NOT be ignored.
1686
  if (
1687
  ( strpos( $filename, '.php' ) !== false) ||
@@ -1725,6 +1750,53 @@ class SucuriScanFileInfo extends SucuriScan {
1725
  return $dirs;
1726
  }
1727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1728
  /**
1729
  * Remove a directory recursively.
1730
  *
@@ -2424,7 +2496,7 @@ class SucuriScanOption extends SucuriScanRequest {
2424
  'sucuriscan_site_version' => '0.0',
2425
  'sucuriscan_sitecheck_counter' => 0,
2426
  'sucuriscan_sitecheck_scanner' => 'enabled',
2427
- 'sucuriscan_verify_ssl_cert' => 'true',
2428
  );
2429
 
2430
  return $defaults;
@@ -2926,9 +2998,9 @@ class SucuriScanEvent extends SucuriScan {
2926
  ) {
2927
  self::report_site_version();
2928
 
2929
- $sucuri_fileinfo = new SucuriScanFileInfo();
2930
- $sucuri_fileinfo->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
2931
- $signatures = $sucuri_fileinfo->get_directory_tree_md5( ABSPATH );
2932
 
2933
  if ( $signatures ) {
2934
  $hashes_sent = SucuriScanAPI::send_hashes( $signatures );
@@ -3237,13 +3309,17 @@ class SucuriScanEvent extends SucuriScan {
3237
  $user = get_userdata( $user_id );
3238
 
3239
  if ( $user instanceof WP_User ) {
 
 
 
3240
  $new_password = wp_generate_password( 15, true, false );
3241
 
3242
- $message = 'The password for your user account <strong>"'. $user->display_name .'"</strong> '
3243
- . 'in the website specified above was changed, this is the new password generated automatically '
3244
- . 'by the system, please update as soon as possible.<br><div style="display:inline-block;'
3245
- . 'background:#ddd;font-family:monaco,monospace,courier;font-size:30px;margin:0;padding:15px;'
3246
- . 'border:1px solid #999">'. $new_password .'</div>';
 
3247
 
3248
  $data_set = array( 'Force' => true ); // Skip limit for emails per hour.
3249
  SucuriScanMail::send_mail( $user->user_email, 'Password changed', $message, $data_set );
@@ -4150,7 +4226,7 @@ class SucuriScanAPI extends SucuriScanOption {
4150
  * @param string $method HTTP method that will be used to send the request.
4151
  * @param array $params Parameters for the request defined in an associative array of key-value.
4152
  * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
4153
- * @return array Array of results including HTTP headers or WP_Error if the request failed.
4154
  */
4155
  private static function api_call( $url = '', $method = 'GET', $params = array(), $args = array() ){
4156
  if ( ! $url ) {
@@ -4192,38 +4268,39 @@ class SucuriScanAPI extends SucuriScanOption {
4192
  } elseif ( $method == 'POST' ) {
4193
  $req_args['body'] = $params;
4194
  $response = wp_remote_post( $url, $req_args );
 
 
 
4195
  }
4196
 
4197
- if ( isset($response) ) {
4198
- if ( is_wp_error( $response ) ) {
4199
- SucuriScanInterface::error(sprintf(
4200
- 'Something went wrong with an API call (%s action): %s',
4201
- ( isset($params['a']) ? self::escape( $params['a'] ) : 'unknown' ),
4202
- $response->get_error_message()
4203
- ));
4204
- } else {
4205
- $response['body_raw'] = $response['body'];
4206
 
4207
- // Check if the response data is JSON-encoded, then decode it.
4208
- if (
4209
- isset($response['headers']['content-type'])
4210
- && $response['headers']['content-type'] == 'application/json'
4211
- ) {
4212
- $assoc = ( isset($args['assoc']) && $args['assoc'] === true ) ? true : false;
4213
- $response['body'] = @json_decode( $response['body_raw'], $assoc );
4214
- } elseif ( self::is_serialized( $response['body'] ) ) {
4215
- // Check if the response data is serialized (which we will consider as insecure).
4216
- $response['body_raw'] = null;
4217
- $response['body'] = 'ERROR:Serialized data is not supported.';
4218
- }
 
4219
 
4220
- return $response;
4221
- }
4222
- } else {
4223
- SucuriScanInterface::error( 'HTTP method not allowed: ' . $method );
 
 
 
 
4224
  }
4225
 
4226
- return false;
4227
  }
4228
 
4229
  /**
@@ -4328,7 +4405,7 @@ class SucuriScanAPI extends SucuriScanOption {
4328
  * @param array $params Parameters for the request defined in an associative array of key-value.
4329
  * @param boolean $send_api_key Whether the API key should be added to the request parameters or not.
4330
  * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
4331
- * @return array Array of results including HTTP headers or WP_Error if the request failed.
4332
  */
4333
  public static function api_call_wordpress( $method = 'GET', $params = array(), $send_api_key = true, $args = array() ){
4334
  $url = SUCURISCAN_API;
@@ -4355,7 +4432,7 @@ class SucuriScanAPI extends SucuriScanOption {
4355
  *
4356
  * @param string $method HTTP method that will be used to send the request.
4357
  * @param array $params Parameters for the request defined in an associative array of key-value.
4358
- * @return array Array of results including HTTP headers or WP_Error if the request failed.
4359
  */
4360
  public static function api_call_cloudproxy( $method = 'GET', $params = array() ){
4361
  $send_request = false;
@@ -4385,12 +4462,90 @@ class SucuriScanAPI extends SucuriScanOption {
4385
  return false;
4386
  }
4387
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4388
  /**
4389
  * Determine whether an API response was successful or not checking the expected
4390
  * generic variables and types, in case of an error a notification will appears
4391
  * in the administrator panel explaining the result of the operation.
4392
  *
4393
- * @param array $response Array of results including HTTP headers or WP_Error if the request failed.
4394
  * @return boolean Either true or false in case of success or failure of the API response (respectively).
4395
  */
4396
  private static function handle_response( $response = array() ){
@@ -4404,11 +4559,14 @@ class SucuriScanAPI extends SucuriScanOption {
4404
 
4405
  // Check whether the message list is empty or not.
4406
  if ( isset($response['body']->messages[0]) ) {
4407
- $action_message = $response['body']->messages[0];
4408
  }
4409
 
 
 
 
4410
  // Special response for invalid API keys.
4411
- if ( strpos( $action_message, 'Log file not found' ) !== false ) {
4412
  SucuriScanOption::delete_option( ':api_key' );
4413
 
4414
  $action_message .= ' This generally happens when you add an invalid API key, the'
@@ -4417,7 +4575,50 @@ class SucuriScanAPI extends SucuriScanOption {
4417
  . ' key to your email address.';
4418
  }
4419
 
4420
- SucuriScanInterface::error( ucwords( $response['body']->action ) . ': ' . $action_message );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4421
  }
4422
  } else {
4423
  SucuriScanInterface::error( 'Could not determine the status of an API call.' );
@@ -4560,8 +4761,8 @@ class SucuriScanAPI extends SucuriScanOption {
4560
  if ( preg_match( $log_pattern, $log, $log_match ) ) {
4561
  $log_data = array(
4562
  'event' => 'notice',
4563
- 'date' => $log_match[1],
4564
- 'time' => $log_match[2],
4565
  'datetime' => '',
4566
  'timestamp' => 0,
4567
  'account' => $log_match[3],
@@ -4572,11 +4773,16 @@ class SucuriScanAPI extends SucuriScanOption {
4572
  'file_list_count' => 0,
4573
  );
4574
 
4575
- $log_data['datetime'] = sprintf( '%s %s', $log_match[1], $log_match[2] );
4576
- $log_data['timestamp'] = strtotime( $log_data['datetime'] );
4577
- $log_data['message'] = str_replace( '<br>', '; ', $log_data['message'] );
 
 
 
4578
 
4579
  // Extract more information from the generic audit logs.
 
 
4580
  if ( preg_match( $generic_pattern, $log_data['message'], $log_extra ) ) {
4581
  $log_data['event'] = strtolower( $log_extra[1] );
4582
  $log_data['message'] = trim( $log_extra[3] );
@@ -4615,6 +4821,7 @@ class SucuriScanAPI extends SucuriScanOption {
4615
  if ( preg_match( $extra_pattern, $log_data['message'], $log_extra ) ) {
4616
  $log_data['message'] = $log_extra[1];
4617
  $log_extra[2] = str_replace( ', new size', '; new size', $log_extra[2] );
 
4618
  $log_data['file_list'] = explode( ',', $log_extra[2] );
4619
  $log_data['file_list_count'] = count( $log_data['file_list'] );
4620
  }
@@ -5488,6 +5695,23 @@ class SucuriScanTemplate extends SucuriScanRequest {
5488
  return $url_path;
5489
  }
5490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5491
  /**
5492
  * Complement the list of pseudo-variables that will be used in the base
5493
  * template files, this will also generate the navigation bar and detect which
@@ -5529,6 +5753,10 @@ class SucuriScanTemplate extends SucuriScanRequest {
5529
 
5530
  $params[ $pseudo_var ] = self::get_url( $unique_name );
5531
 
 
 
 
 
5532
  $navbar_item_css_class = 'nav-tab';
5533
 
5534
  if ( $params['CurrentPageFunc'] == $sub_page_func ) {
@@ -5939,11 +6167,11 @@ class SucuriScanFSScanner extends SucuriScan {
5939
  }
5940
 
5941
  // Scan the project and file all directories.
5942
- $sucuri_fileinfo = new SucuriScanFileInfo();
5943
- $sucuri_fileinfo->ignore_files = true;
5944
- $sucuri_fileinfo->ignore_directories = true;
5945
- $sucuri_fileinfo->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
5946
- $directory_list = $sucuri_fileinfo->get_diretories_only( ABSPATH );
5947
 
5948
  if ( $directory_list ) {
5949
  $response['is_not_ignored'] = $directory_list;
@@ -6205,7 +6433,7 @@ class SucuriScanInterface {
6205
  * @return void
6206
  */
6207
  public static function initialize(){
6208
- if ( SucuriScan::is_behind_cloudproxy() ) {
6209
  $_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
6210
  $_SERVER['REMOTE_ADDR'] = SucuriScan::get_remote_addr();
6211
  }
@@ -6248,6 +6476,8 @@ class SucuriScanInterface {
6248
  if (
6249
  function_exists( 'add_menu_page' )
6250
  && $sucuriscan_pages
 
 
6251
  ) {
6252
  // Add main menu link.
6253
  add_menu_page(
@@ -6259,9 +6489,7 @@ class SucuriScanInterface {
6259
  SUCURISCAN_URL . '/inc/images/menu-icon.png'
6260
  );
6261
 
6262
- $sub_pages = is_array( $sucuriscan_pages ) ? $sucuriscan_pages : array();
6263
-
6264
- foreach ( $sub_pages as $sub_page_func => $sub_page_title ) {
6265
  if (
6266
  $sub_page_func == 'sucuriscan_scanner'
6267
  && SucuriScanTemplate::is_sitecheck_disabled()
@@ -6293,9 +6521,9 @@ class SucuriScanInterface {
6293
  */
6294
  public static function handle_old_plugins(){
6295
  if ( class_exists( 'SucuriScanFileInfo' ) ) {
6296
- $sucuri_fileinfo = new SucuriScanFileInfo();
6297
- $sucuri_fileinfo->ignore_files = false;
6298
- $sucuri_fileinfo->ignore_directories = false;
6299
 
6300
  $plugins = array(
6301
  'sucuri-wp-plugin/sucuri.php',
@@ -6310,7 +6538,7 @@ class SucuriScanInterface {
6310
  deactivate_plugins( $plugin );
6311
  }
6312
 
6313
- $plugin_removed = $sucuri_fileinfo->remove_directory_tree( $plugin_directory );
6314
  }
6315
  }
6316
  }
@@ -8144,10 +8372,10 @@ function sucuriscan_harden_errorlog(){
8144
 
8145
  // Search error log files in the project.
8146
  if ( $scan_errorlogs != 'disabled' ) {
8147
- $sucuri_fileinfo = new SucuriScanFileInfo();
8148
- $sucuri_fileinfo->ignore_files = false;
8149
- $sucuri_fileinfo->ignore_directories = false;
8150
- $error_logs = $sucuri_fileinfo->find_file( $log_filename );
8151
  $total_log_files = count( $error_logs );
8152
  } else {
8153
  $hardened = 2;
@@ -8333,12 +8561,12 @@ function sucuriscan_integrity_form_submissions(){
8333
  function sucuriscan_get_integrity_tree( $dir = './', $recursive = false ){
8334
  $abs_path = rtrim( ABSPATH, '/' );
8335
 
8336
- $sucuri_fileinfo = new SucuriScanFileInfo();
8337
- $sucuri_fileinfo->ignore_files = false;
8338
- $sucuri_fileinfo->ignore_directories = false;
8339
- $sucuri_fileinfo->run_recursively = $recursive;
8340
- $sucuri_fileinfo->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
8341
- $integrity_tree = $sucuri_fileinfo->get_directory_tree_md5( $dir, true );
8342
 
8343
  if ( ! $integrity_tree ) {
8344
  $integrity_tree = array();
@@ -8915,6 +9143,21 @@ function sucuriscan_posthack_page(){
8915
  echo SucuriScanTemplate::get_template( 'posthack', $template_variables );
8916
  }
8917
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8918
  /**
8919
  * Check whether the "I understand this operation" checkbox was marked or not.
8920
  *
@@ -9057,7 +9300,7 @@ function sucuriscan_posthack_users( $process_form = false ){
9057
  $max_per_page
9058
  );
9059
 
9060
- if ( $total_items > SUCURISCAN_MAX_PAGINATION_BUTTONS ) {
9061
  $template_variables['ResetPassword.PaginationVisibility'] = 'visible';
9062
  }
9063
  }
@@ -9142,38 +9385,51 @@ function sucuriscan_posthack_plugins( $process_form = false ){
9142
  'ResetPlugin.CacheLifeTime' => 'unknown',
9143
  );
9144
 
9145
-
9146
  if ( defined( 'SUCURISCAN_GET_PLUGINS_LIFETIME' ) ) {
9147
  $template_variables['ResetPlugin.CacheLifeTime'] = SUCURISCAN_GET_PLUGINS_LIFETIME;
9148
  }
9149
 
9150
  sucuriscan_posthack_reinstall_plugins( $process_form );
9151
- $all_plugins = SucuriScanAPI::get_plugins();
9152
- $counter = 0;
9153
 
9154
- foreach ( $all_plugins as $plugin_path => $plugin_data ) {
9155
- $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
9156
- $plugin_type_class = ( $plugin_data['PluginType'] == 'free' ) ? 'primary' : 'warning';
9157
- $input_disabled = ( $plugin_data['PluginType'] == 'free' ) ? '' : 'disabled="disabled"';
9158
- $plugin_status = $plugin_data['IsPluginActive'] ? 'active' : 'not active';
9159
- $plugin_status_class = $plugin_data['IsPluginActive'] ? 'success' : 'default';
9160
-
9161
- $template_variables['ResetPlugin.PluginList'] .= SucuriScanTemplate::get_snippet('posthack-resetplugins', array(
9162
- 'ResetPlugin.CssClass' => $css_class,
9163
- 'ResetPlugin.Disabled' => $input_disabled,
9164
- 'ResetPlugin.PluginPath' => SucuriScan::escape( $plugin_path ),
9165
- 'ResetPlugin.Plugin' => SucuriScan::excerpt( $plugin_data['Name'], 35 ),
9166
- 'ResetPlugin.Version' => $plugin_data['Version'],
9167
- 'ResetPlugin.Type' => $plugin_data['PluginType'],
9168
- 'ResetPlugin.TypeClass' => $plugin_type_class,
9169
- 'ResetPlugin.Status' => $plugin_status,
9170
- 'ResetPlugin.StatusClass' => $plugin_status_class,
9171
- ));
9172
 
9173
- $counter += 1;
9174
- }
 
 
 
 
 
 
 
 
9175
 
9176
- return SucuriScanTemplate::get_section( 'posthack-resetplugins', $template_variables );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9177
  }
9178
 
9179
  /**
@@ -9192,10 +9448,10 @@ function sucuriscan_posthack_reinstall_plugins( $process_form = false ){
9192
 
9193
  if ( $plugin_list = SucuriScanRequest::post( 'plugin_path', '_array' ) ) {
9194
  // Create an instance of the FileInfo interface.
9195
- $sucuri_fileinfo = new SucuriScanFileInfo();
9196
- $sucuri_fileinfo->ignore_files = false;
9197
- $sucuri_fileinfo->ignore_directories = false;
9198
- $sucuri_fileinfo->skip_directories = false;
9199
 
9200
  // Get (possible) cached information from the installed plugins.
9201
  $all_plugins = SucuriScanAPI::get_plugins();
@@ -9213,7 +9469,7 @@ function sucuriscan_posthack_reinstall_plugins( $process_form = false ){
9213
  // First, remove all files/sub-folders from the plugin's directory.
9214
  if ( substr_count( $plugin_path, '/' ) >= 1 ) {
9215
  $plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin_path );
9216
- $sucuri_fileinfo->remove_directory_tree( $plugin_directory );
9217
  }
9218
 
9219
  // Install a fresh copy of the plugin's files.
@@ -9872,8 +10128,16 @@ function sucuriscan_failed_logins_panel(){
9872
  'FailedLogins.NoItemsVisibility' => 'visible',
9873
  'FailedLogins.WarningVisibility' => 'visible',
9874
  'FailedLogins.CollectPasswordsVisibility' => 'visible',
 
 
9875
  );
9876
 
 
 
 
 
 
 
9877
  $max_failed_logins = SucuriScanOption::get_option( ':maximum_failed_logins' );
9878
  $notify_bruteforce_attack = SucuriScanOption::get_option( ':notify_bruteforce_attack' );
9879
  $failed_logins = sucuriscan_get_failed_logins();
@@ -9897,37 +10161,50 @@ function sucuriscan_failed_logins_panel(){
9897
  if ( $failed_logins ) {
9898
  $counter = 0;
9899
 
9900
- foreach ( $failed_logins['entries'] as $login_data ) {
9901
- $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
9902
- $wrong_user_password = '<span class="sucuriscan-label-default">hidden</span>';
 
 
9903
 
9904
- if ( sucuriscan_collect_wrong_passwords() === true ) {
9905
- if (
9906
- isset($login_data['user_password'])
9907
- && ! empty($login_data['user_password'])
9908
- ) {
9909
- $wrong_user_password = SucuriScan::escape( $login_data['user_password'] );
9910
- } else {
9911
- $wrong_user_password = '<span class="sucuriscan-label-info">empty</span>';
 
9912
  }
9913
- }
9914
 
9915
- $template_variables['FailedLogins.List'] .= SucuriScanTemplate::get_snippet('lastlogins-failedlogins', array(
9916
- 'FailedLogins.CssClass' => $css_class,
9917
- 'FailedLogins.Num' => ($counter + 1),
9918
- 'FailedLogins.Username' => SucuriScan::escape( $login_data['user_login'] ),
9919
- 'FailedLogins.Password' => $wrong_user_password,
9920
- 'FailedLogins.RemoteAddr' => SucuriScan::escape( $login_data['remote_addr'] ),
9921
- 'FailedLogins.Datetime' => SucuriScan::datetime( $login_data['attempt_time'] ),
9922
- 'FailedLogins.UserAgent' => SucuriScan::escape( $login_data['user_agent'] ),
9923
- ));
9924
 
9925
- $counter += 1;
 
9926
  }
9927
 
9928
  if ( $counter > 0 ) {
9929
  $template_variables['FailedLogins.NoItemsVisibility'] = 'hidden';
9930
  }
 
 
 
 
 
 
 
 
 
 
9931
  }
9932
 
9933
  $template_variables['FailedLogins.MaxFailedLogins'] = $max_failed_logins;
@@ -10025,10 +10302,15 @@ function sucuriscan_get_failed_logins( $get_old_logs = false ){
10025
  );
10026
 
10027
  // Read and parse all the entries found in the datastore file.
10028
- foreach ( $lines as $i => $line ) {
10029
- if ( $i >= $default_content_n ) {
10030
- $login_data = @json_decode( trim( $line ), true );
 
 
 
 
10031
  $login_data['attempt_date'] = date( 'r', $login_data['attempt_time'] );
 
10032
 
10033
  if ( ! $login_data['user_agent'] ) {
10034
  $login_data['user_agent'] = 'Unknown';
@@ -10794,6 +11076,16 @@ function sucuriscan_settings_form_submissions( $page_nonce = null ){
10794
  SucuriScanEvent::report_info_event( $message );
10795
  SucuriScanInterface::info( $message );
10796
  }
 
 
 
 
 
 
 
 
 
 
10797
  }
10798
  }
10799
 
@@ -10906,10 +11198,10 @@ function sucuriscan_settings_general(){
10906
  'ReverseProxySwitchValue' => 'disable',
10907
  'ReverseProxySwitchCssClass' => 'button-danger',
10908
  /* API Proxy Settings */
10909
- 'APIProxy.Host' => 'n/a',
10910
- 'APIProxy.Port' => 'n/a',
10911
- 'APIProxy.Username' => 'n/a',
10912
- 'APIProxy.Password' => 'n/a',
10913
  'APIProxy.PasswordType' => 'default',
10914
  'APIProxy.PasswordText' => 'empty',
10915
  );
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.10
8
  Author URI: http://sucuri.net
9
  */
10
 
66
  /**
67
  * Current version of the plugin's code.
68
  */
69
+ define( 'SUCURISCAN_VERSION', '1.7.10' );
70
 
71
  /**
72
  * The name of the Sucuri plugin main file.
197
  'sucuriscan_notify_website_updated' => 'Receive email alerts when the WordPress version is updated',
198
  'sucuriscan_notify_settings_updated' => 'Receive email alerts when your website settings are updated',
199
  'sucuriscan_notify_theme_editor' => 'Receive email alerts when a file is modified with theme/plugin editor',
200
+ 'sucuriscan_notify_plugin_installed' => 'plugin:Receive email alerts when a <strong>plugin is installed</strong>',
201
+ 'sucuriscan_notify_plugin_activated' => 'plugin:Receive email alerts when a <strong>plugin is activated</strong>',
202
+ 'sucuriscan_notify_plugin_deactivated' => 'plugin:Receive email alerts when a <strong>plugin is deactivated</strong>',
203
+ 'sucuriscan_notify_plugin_updated' => 'plugin:Receive email alerts when a <strong>plugin is updated</strong>',
204
+ 'sucuriscan_notify_plugin_deleted' => 'plugin:Receive email alerts when a <strong>plugin is deleted</strong>',
205
+ 'sucuriscan_notify_widget_added' => 'widget:Receive email alerts when a <strong>widget is added</strong> to a sidebar',
206
+ 'sucuriscan_notify_widget_deleted' => 'widget:Receive email alerts when a <strong>widget is deleted</strong> from a sidebar',
207
+ 'sucuriscan_notify_theme_installed' => 'theme:Receive email alerts when a <strong>theme is installed</strong>',
208
+ 'sucuriscan_notify_theme_activated' => 'theme:Receive email alerts when a <strong>theme is activated</strong>',
209
+ 'sucuriscan_notify_theme_updated' => 'theme:Receive email alerts when a <strong>theme is updated</strong>',
210
+ 'sucuriscan_notify_theme_deleted' => 'theme:Receive email alerts when a <strong>theme is deleted</strong>',
211
  );
212
 
213
  $sucuriscan_schedule_allowed = array(
282
  add_action( 'admin_enqueue_scripts', 'SucuriScanInterface::enqueue_scripts', 1 );
283
  add_action( 'admin_menu', 'SucuriScanInterface::add_interface_menu' );
284
 
285
+ /**
286
+ * Attach Ajax requests to a custom page handler.
287
+ */
288
+ foreach ( $sucuriscan_pages as $page_func => $page_title ) {
289
+ $ajax_func = $page_func . '_ajax';
290
+
291
+ if ( function_exists( $ajax_func ) ) {
292
+ add_action( 'wp_ajax_' . $ajax_func, $ajax_func );
293
+ }
294
+ }
295
+
296
  /**
297
  * Function call interceptors.
298
  *
806
  */
807
  public static function datetime( $timestamp = 0 ){
808
  if ( is_numeric( $timestamp ) && $timestamp > 0 ) {
809
+ $gmt_offset = get_option( 'gmt_offset' );
810
  $date_format = get_option( 'date_format' );
811
  $time_format = get_option( 'time_format' );
812
+ $tz_format = sprintf( '%s %s', $date_format, $time_format );
813
 
814
+ if ( is_numeric( $gmt_offset ) ) {
815
+ $timestamp += ( $gmt_offset * 3600 );
816
+ }
817
+
818
+ return date_i18n( $tz_format, $timestamp );
819
  }
820
 
821
  return null;
1647
  /**
1648
  * Skip some specific directories and file paths from the filesystem scan.
1649
  *
1650
+ * @param string $directory Directory where the scanner is located at the moment.
1651
+ * @param string $filename Name of the folder or file being scanned at the moment.
1652
+ * @return boolean Either TRUE or FALSE representing that the scan should ignore this folder or not.
1653
  */
1654
  private function ignore_folderpath( $directory = '', $filename = '' ){
1655
  // Ignoring current and parent folders.
1698
  return true;
1699
  }
1700
 
1701
+ // Ignore files specified by the administrator.
1702
+ if ( ! empty($this->ignored_directories) ) {
1703
+ foreach ( $this->ignored_directories['directories'] as $ignored_dir ) {
1704
+ if ( strpos( $ignored_dir, $filename ) !== false ) {
1705
+ return true;
1706
+ }
1707
+ }
1708
+ }
1709
+
1710
  // Any file maching one of these rules WILL NOT be ignored.
1711
  if (
1712
  ( strpos( $filename, '.php' ) !== false) ||
1750
  return $dirs;
1751
  }
1752
 
1753
+ /**
1754
+ * Returns a list of lines matching the specified pattern in all the files found
1755
+ * in the specified directory, each entry in the list contains the relative path
1756
+ * of the file and the number of the line where the pattern was found, as well
1757
+ * as the string around the pattern in that line.
1758
+ *
1759
+ * @param string $directory Directory where the scanner is located at the moment.
1760
+ * @param string $pattern Text that will be searched inside each file.
1761
+ * @return array Associative list with the file path and line number of the match.
1762
+ */
1763
+ public function grep_pattern( $directory = '', $pattern = '' ){
1764
+ $dir_tree = $this->get_directory_tree( $directory );
1765
+ $pattern = '/.*' . str_replace( '/', '\/', $pattern ) . '.*/';
1766
+ $results = array();
1767
+
1768
+ if (
1769
+ class_exists( 'SplFileObject' )
1770
+ && class_exists( 'RegexIterator' )
1771
+ && SucuriScan::is_valid_pattern( $pattern )
1772
+ ) {
1773
+ foreach ( $dir_tree as $file_path ) {
1774
+ try {
1775
+ $fobject = new SplFileObject( $file_path );
1776
+ $fstream = new RegexIterator( $fobject, $pattern, RegexIterator::MATCH );
1777
+
1778
+ foreach ( $fstream as $key => $ltext ) {
1779
+ $lnumber = ( $key + 1 );
1780
+ $ltext = str_replace( "\n", '', $ltext );
1781
+ $fpath = str_replace( $directory, '', $file_path );
1782
+ $loutput = sprintf( '%s:%d:%s', $fpath, $lnumber, $ltext );
1783
+ $results[] = array(
1784
+ 'file_path' => $file_path,
1785
+ 'relative_path' => $fpath,
1786
+ 'line_number' => $lnumber,
1787
+ 'line_text' => $ltext,
1788
+ 'output' => $loutput,
1789
+ );
1790
+ }
1791
+ } catch ( RuntimeException $exception ) {
1792
+ SucuriScanEvent::report_exception( $exception );
1793
+ }
1794
+ }
1795
+ }
1796
+
1797
+ return $results;
1798
+ }
1799
+
1800
  /**
1801
  * Remove a directory recursively.
1802
  *
2496
  'sucuriscan_site_version' => '0.0',
2497
  'sucuriscan_sitecheck_counter' => 0,
2498
  'sucuriscan_sitecheck_scanner' => 'enabled',
2499
+ 'sucuriscan_verify_ssl_cert' => 'false',
2500
  );
2501
 
2502
  return $defaults;
2998
  ) {
2999
  self::report_site_version();
3000
 
3001
+ $file_info = new SucuriScanFileInfo();
3002
+ $file_info->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
3003
+ $signatures = $file_info->get_directory_tree_md5( ABSPATH );
3004
 
3005
  if ( $signatures ) {
3006
  $hashes_sent = SucuriScanAPI::send_hashes( $signatures );
3309
  $user = get_userdata( $user_id );
3310
 
3311
  if ( $user instanceof WP_User ) {
3312
+ $website = SucuriScan::get_domain();
3313
+ $user_login = $user->user_login;
3314
+ $display_name = $user->display_name;
3315
  $new_password = wp_generate_password( 15, true, false );
3316
 
3317
+ $message = SucuriScanTemplate::get_section( 'notification-resetpwd', array(
3318
+ 'ResetPassword.UserName' => $user_login,
3319
+ 'ResetPassword.DisplayName' => $display_name,
3320
+ 'ResetPassword.Password' => $new_password,
3321
+ 'ResetPassword.Website' => $website,
3322
+ ) );
3323
 
3324
  $data_set = array( 'Force' => true ); // Skip limit for emails per hour.
3325
  SucuriScanMail::send_mail( $user->user_email, 'Password changed', $message, $data_set );
4226
  * @param string $method HTTP method that will be used to send the request.
4227
  * @param array $params Parameters for the request defined in an associative array of key-value.
4228
  * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
4229
+ * @return array Response object after the HTTP request is executed.
4230
  */
4231
  private static function api_call( $url = '', $method = 'GET', $params = array(), $args = array() ){
4232
  if ( ! $url ) {
4268
  } elseif ( $method == 'POST' ) {
4269
  $req_args['body'] = $params;
4270
  $response = wp_remote_post( $url, $req_args );
4271
+ } else {
4272
+ $response = false;
4273
+ SucuriScanInterface::error( 'HTTP method not allowed: ' . $method );
4274
  }
4275
 
4276
+ return self::process_response( $response, $params, $args );
4277
+ }
 
 
 
 
 
 
 
4278
 
4279
+ /**
4280
+ * Test ability of the site to send HTTP requests.
4281
+ *
4282
+ * @return string Response data from the remote service.
4283
+ */
4284
+ public static function test_api_call(){
4285
+ $params = array();
4286
+ $params['first'] = microtime();
4287
+ $params['lorem'] = microtime();
4288
+ $params['middle'] = microtime();
4289
+ $params['foobar'] = microtime();
4290
+ $params['last'] = microtime();
4291
+ $response_data = '{invalid_data}';
4292
 
4293
+ $response = self::api_call( 'http://httpbin.org/post', 'POST', $params );
4294
+
4295
+ if (
4296
+ is_array( $response )
4297
+ && array_key_exists( 'body_raw', $response )
4298
+ && is_string( $response['body_raw'] )
4299
+ ) {
4300
+ $response_data = $response['body_raw'];
4301
  }
4302
 
4303
+ return $response_data;
4304
  }
4305
 
4306
  /**
4405
  * @param array $params Parameters for the request defined in an associative array of key-value.
4406
  * @param boolean $send_api_key Whether the API key should be added to the request parameters or not.
4407
  * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
4408
+ * @return array Response object after the HTTP request is executed.
4409
  */
4410
  public static function api_call_wordpress( $method = 'GET', $params = array(), $send_api_key = true, $args = array() ){
4411
  $url = SUCURISCAN_API;
4432
  *
4433
  * @param string $method HTTP method that will be used to send the request.
4434
  * @param array $params Parameters for the request defined in an associative array of key-value.
4435
+ * @return array Response object after the HTTP request is executed.
4436
  */
4437
  public static function api_call_cloudproxy( $method = 'GET', $params = array() ){
4438
  $send_request = false;
4462
  return false;
4463
  }
4464
 
4465
+ /**
4466
+ * Execute some actions according to the response message.
4467
+ *
4468
+ * @param array $response Response object after the HTTP request is executed.
4469
+ * @param array $params Parameters for the request defined in an associative array of key-value.
4470
+ * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
4471
+ * @return array Response object with some modifications.
4472
+ */
4473
+ private static function process_response( $response = array(), $params = array(), $args = array() ){
4474
+ /**
4475
+ * Convert the error message generated by the code base functions after the HTTP
4476
+ * request is executed to a valid response object that will allow this code
4477
+ * process the data according to the specified standards.
4478
+ */
4479
+ if ( is_wp_error( $response ) ) {
4480
+ // Extract information from the error object.
4481
+ $error_message = $response->get_error_message();
4482
+ $request_action = isset( $params['a'] ) ? $params['a'] : 'unknown';
4483
+
4484
+ // Build a fake request response with custom data.
4485
+ $data_set = array(
4486
+ 'status' => 0,
4487
+ 'action' => $request_action,
4488
+ 'messages' => array( $error_message ),
4489
+ 'request_time' => SucuriScan::local_time(),
4490
+ 'output' => new stdClass(),
4491
+ 'verbose' => 0,
4492
+ );
4493
+
4494
+ // Build the response object and encode data.
4495
+ $response = array();
4496
+ $response['body'] = json_encode( $data_set );
4497
+ $response['headers']['date'] = date( 'r' );
4498
+ $response['headers']['connection'] = 'close';
4499
+ $response['headers']['content-type'] = 'application/json';
4500
+ $response['headers']['content-length'] = strlen( $response['body'] );
4501
+ $response['response']['code'] = 500;
4502
+ $response['response']['message'] = 'ERROR';
4503
+ }
4504
+
4505
+ /**
4506
+ * Process the response object.
4507
+ *
4508
+ * Some response messages and even errors require extra steps of processing to,
4509
+ * for example, try to fix automatically issues related with disconnections,
4510
+ * timeouts, SSL certificate verifications, etc. Some of these actions can not
4511
+ * be fixed if the server where the website is being hosted has a special
4512
+ * configuration, which then requires the human interaction of the admin user,
4513
+ * they will see extra information explaining the response and how to proceed
4514
+ * with it.
4515
+ */
4516
+ if (
4517
+ is_array( $response )
4518
+ && array_key_exists( 'body', $response )
4519
+ && array_key_exists( 'headers', $response )
4520
+ && array_key_exists( 'response', $response )
4521
+ ) {
4522
+ $response['body_raw'] = $response['body'];
4523
+
4524
+ // Check if the response data is JSON-encoded, then decode it.
4525
+ if (
4526
+ isset($response['headers']['content-type'])
4527
+ && $response['headers']['content-type'] == 'application/json'
4528
+ ) {
4529
+ $assoc = ( isset($args['assoc']) && $args['assoc'] === true ) ? true : false;
4530
+ $response['body'] = @json_decode( $response['body_raw'], $assoc );
4531
+ } elseif ( self::is_serialized( $response['body'] ) ) {
4532
+ // Check if the response data is serialized (which we will consider as insecure).
4533
+ $response['body_raw'] = null;
4534
+ $response['body'] = 'ERROR:Serialized data is not supported.';
4535
+ }
4536
+
4537
+ return $response;
4538
+ }
4539
+
4540
+ return false;
4541
+ }
4542
+
4543
  /**
4544
  * Determine whether an API response was successful or not checking the expected
4545
  * generic variables and types, in case of an error a notification will appears
4546
  * in the administrator panel explaining the result of the operation.
4547
  *
4548
+ * @param array $response Response object after the HTTP request is executed.
4549
  * @return boolean Either true or false in case of success or failure of the API response (respectively).
4550
  */
4551
  private static function handle_response( $response = array() ){
4559
 
4560
  // Check whether the message list is empty or not.
4561
  if ( isset($response['body']->messages[0]) ) {
4562
+ $action_message = $response['body']->messages[0] . '.';
4563
  }
4564
 
4565
+ // Keep a copy of the original API response message.
4566
+ $raw_message = $action_message;
4567
+
4568
  // Special response for invalid API keys.
4569
+ if ( stripos( $raw_message, 'log file not found' ) !== false ) {
4570
  SucuriScanOption::delete_option( ':api_key' );
4571
 
4572
  $action_message .= ' This generally happens when you add an invalid API key, the'
4575
  . ' key to your email address.';
4576
  }
4577
 
4578
+ // Special response for invalid CloudProxy API keys.
4579
+ if ( stripos( $raw_message, 'wrong api key' ) !== false ) {
4580
+ SucuriScanOption::delete_option( ':cloudproxy_apikey' );
4581
+ SucuriScanOption::delete_option( ':revproxy' );
4582
+
4583
+ $action_message .= ' The CloudProxy API key does not seems to be valid.';
4584
+ }
4585
+
4586
+ // Special response for connection time outs.
4587
+ if ( stripos( $raw_message, 'timed out' ) !== false ) {
4588
+ $current_timeout = SucuriScanOption::get_option( ':request_timeout' );
4589
+
4590
+ if ( $current_timeout < 300 ) {
4591
+ SucuriScanOption::update_option( ':request_timeout', 300 );
4592
+ }
4593
+
4594
+ $action_message .= ' This generally happens when the API service fails to respond'
4595
+ . ' in time, you currently have configured the plugin to discard the network'
4596
+ . ' connection after ' . $current_timeout . ' seconds. Wait a few minutes until'
4597
+ . ' the issue is resolved by itself, or change the timeout limit from the general'
4598
+ . ' settings page of the plugin, the option is named "API request timeout".';
4599
+ }
4600
+
4601
+ // Stop SSL peer verification on connection failures.
4602
+ if (
4603
+ stripos( $raw_message, 'no alternative certificate' )
4604
+ || stripos( $raw_message, 'error setting certificate' )
4605
+ ) {
4606
+ SucuriScanOption::update_option( ':verify_ssl_cert', 'false' );
4607
+
4608
+ $action_message .= 'There were some issues with the SSL certificate either in this'
4609
+ . ' server or with the remote API service. The automatic verification of the'
4610
+ . ' certificates has been deactivated to reduce the noise during the execution'
4611
+ . ' of the HTTP requests.';
4612
+ }
4613
+
4614
+ SucuriScanInterface::error(
4615
+ sprintf(
4616
+ '(%d) %s: %s',
4617
+ SucuriScan::local_time(),
4618
+ ucwords( $response['body']->action ),
4619
+ $action_message
4620
+ )
4621
+ );
4622
  }
4623
  } else {
4624
  SucuriScanInterface::error( 'Could not determine the status of an API call.' );
4761
  if ( preg_match( $log_pattern, $log, $log_match ) ) {
4762
  $log_data = array(
4763
  'event' => 'notice',
4764
+ 'date' => '',
4765
+ 'time' => '',
4766
  'datetime' => '',
4767
  'timestamp' => 0,
4768
  'account' => $log_match[3],
4773
  'file_list_count' => 0,
4774
  );
4775
 
4776
+ // Extract and fix the date and time using the Eastern time zone.
4777
+ $datetime = sprintf( '%s %s EDT', $log_match[1], $log_match[2] );
4778
+ $log_data['timestamp'] = strtotime( $datetime );
4779
+ $log_data['datetime'] = date( 'Y-m-d H:i:s', $log_data['timestamp'] );
4780
+ $log_data['date'] = date( 'Y-m-d', $log_data['timestamp'] );
4781
+ $log_data['time'] = date( 'H:i:s', $log_data['timestamp'] );
4782
 
4783
  // Extract more information from the generic audit logs.
4784
+ $log_data['message'] = str_replace( '<br>', '; ', $log_data['message'] );
4785
+
4786
  if ( preg_match( $generic_pattern, $log_data['message'], $log_extra ) ) {
4787
  $log_data['event'] = strtolower( $log_extra[1] );
4788
  $log_data['message'] = trim( $log_extra[3] );
4821
  if ( preg_match( $extra_pattern, $log_data['message'], $log_extra ) ) {
4822
  $log_data['message'] = $log_extra[1];
4823
  $log_extra[2] = str_replace( ', new size', '; new size', $log_extra[2] );
4824
+ $log_extra[2] = str_replace( ",\x20", ";\x20", $log_extra[2] );
4825
  $log_data['file_list'] = explode( ',', $log_extra[2] );
4826
  $log_data['file_list_count'] = count( $log_data['file_list'] );
4827
  }
5695
  return $url_path;
5696
  }
5697
 
5698
+ /**
5699
+ * Generate an URL pointing to the page indicated in the function and that must
5700
+ * be loaded through the Ajax handler of the administrator panel.
5701
+ *
5702
+ * @param string $page Short name of the page that will be generated.
5703
+ * @return string Full string containing the link of the page.
5704
+ */
5705
+ public static function get_ajax_url( $page = '' ){
5706
+ $url_path = admin_url( 'admin-ajax.php?page=sucuriscan' );
5707
+
5708
+ if ( ! empty($page) ) {
5709
+ $url_path .= '_' . strtolower( $page );
5710
+ }
5711
+
5712
+ return $url_path;
5713
+ }
5714
+
5715
  /**
5716
  * Complement the list of pseudo-variables that will be used in the base
5717
  * template files, this will also generate the navigation bar and detect which
5753
 
5754
  $params[ $pseudo_var ] = self::get_url( $unique_name );
5755
 
5756
+ // Copy URL variable and create an Ajax handler.
5757
+ $pseudo_var_ajax = 'Ajax' . $pseudo_var;
5758
+ $params[ $pseudo_var_ajax ] = self::get_ajax_url( $unique_name );
5759
+
5760
  $navbar_item_css_class = 'nav-tab';
5761
 
5762
  if ( $params['CurrentPageFunc'] == $sub_page_func ) {
6167
  }
6168
 
6169
  // Scan the project and file all directories.
6170
+ $file_info = new SucuriScanFileInfo();
6171
+ $file_info->ignore_files = true;
6172
+ $file_info->ignore_directories = true;
6173
+ $file_info->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
6174
+ $directory_list = $file_info->get_diretories_only( ABSPATH );
6175
 
6176
  if ( $directory_list ) {
6177
  $response['is_not_ignored'] = $directory_list;
6433
  * @return void
6434
  */
6435
  public static function initialize(){
6436
+ if ( SucuriScan::support_reverse_proxy() ) {
6437
  $_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
6438
  $_SERVER['REMOTE_ADDR'] = SucuriScan::get_remote_addr();
6439
  }
6476
  if (
6477
  function_exists( 'add_menu_page' )
6478
  && $sucuriscan_pages
6479
+ && is_array( $sucuriscan_pages )
6480
+ && array_key_exists( 'sucuriscan', $sucuriscan_pages )
6481
  ) {
6482
  // Add main menu link.
6483
  add_menu_page(
6489
  SUCURISCAN_URL . '/inc/images/menu-icon.png'
6490
  );
6491
 
6492
+ foreach ( $sucuriscan_pages as $sub_page_func => $sub_page_title ) {
 
 
6493
  if (
6494
  $sub_page_func == 'sucuriscan_scanner'
6495
  && SucuriScanTemplate::is_sitecheck_disabled()
6521
  */
6522
  public static function handle_old_plugins(){
6523
  if ( class_exists( 'SucuriScanFileInfo' ) ) {
6524
+ $file_info = new SucuriScanFileInfo();
6525
+ $file_info->ignore_files = false;
6526
+ $file_info->ignore_directories = false;
6527
 
6528
  $plugins = array(
6529
  'sucuri-wp-plugin/sucuri.php',
6538
  deactivate_plugins( $plugin );
6539
  }
6540
 
6541
+ $plugin_removed = $file_info->remove_directory_tree( $plugin_directory );
6542
  }
6543
  }
6544
  }
8372
 
8373
  // Search error log files in the project.
8374
  if ( $scan_errorlogs != 'disabled' ) {
8375
+ $file_info = new SucuriScanFileInfo();
8376
+ $file_info->ignore_files = false;
8377
+ $file_info->ignore_directories = false;
8378
+ $error_logs = $file_info->find_file( $log_filename );
8379
  $total_log_files = count( $error_logs );
8380
  } else {
8381
  $hardened = 2;
8561
  function sucuriscan_get_integrity_tree( $dir = './', $recursive = false ){
8562
  $abs_path = rtrim( ABSPATH, '/' );
8563
 
8564
+ $file_info = new SucuriScanFileInfo();
8565
+ $file_info->ignore_files = false;
8566
+ $file_info->ignore_directories = false;
8567
+ $file_info->run_recursively = $recursive;
8568
+ $file_info->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
8569
+ $integrity_tree = $file_info->get_directory_tree_md5( $dir, true );
8570
 
8571
  if ( ! $integrity_tree ) {
8572
  $integrity_tree = array();
9143
  echo SucuriScanTemplate::get_template( 'posthack', $template_variables );
9144
  }
9145
 
9146
+ /**
9147
+ * Handle an Ajax request for this specific page.
9148
+ *
9149
+ * @return mixed.
9150
+ */
9151
+ function sucuriscan_posthack_ajax(){
9152
+ SucuriScanInterface::check_permissions();
9153
+
9154
+ if ( SucuriScanInterface::check_nonce() ) {
9155
+ sucuriscan_posthack_plugins_ajax();
9156
+ }
9157
+
9158
+ wp_die();
9159
+ }
9160
+
9161
  /**
9162
  * Check whether the "I understand this operation" checkbox was marked or not.
9163
  *
9300
  $max_per_page
9301
  );
9302
 
9303
+ if ( $total_items > $max_per_page ) {
9304
  $template_variables['ResetPassword.PaginationVisibility'] = 'visible';
9305
  }
9306
  }
9385
  'ResetPlugin.CacheLifeTime' => 'unknown',
9386
  );
9387
 
 
9388
  if ( defined( 'SUCURISCAN_GET_PLUGINS_LIFETIME' ) ) {
9389
  $template_variables['ResetPlugin.CacheLifeTime'] = SUCURISCAN_GET_PLUGINS_LIFETIME;
9390
  }
9391
 
9392
  sucuriscan_posthack_reinstall_plugins( $process_form );
 
 
9393
 
9394
+ return SucuriScanTemplate::get_section( 'posthack-resetplugins', $template_variables );
9395
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9396
 
9397
+ /**
9398
+ * Process the Ajax request to retrieve the plugins metadata.
9399
+ *
9400
+ * @return string HTML code for a table with the plugins metadata.
9401
+ */
9402
+ function sucuriscan_posthack_plugins_ajax(){
9403
+ if ( SucuriScanRequest::post( 'form_action' ) == 'get_plugins_data' ) {
9404
+ $all_plugins = SucuriScanAPI::get_plugins();
9405
+ $response = '';
9406
+ $counter = 0;
9407
 
9408
+ foreach ( $all_plugins as $plugin_path => $plugin_data ) {
9409
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
9410
+ $plugin_type_class = ( $plugin_data['PluginType'] == 'free' ) ? 'primary' : 'warning';
9411
+ $input_disabled = ( $plugin_data['PluginType'] == 'free' ) ? '' : 'disabled="disabled"';
9412
+ $plugin_status = $plugin_data['IsPluginActive'] ? 'active' : 'not active';
9413
+ $plugin_status_class = $plugin_data['IsPluginActive'] ? 'success' : 'default';
9414
+
9415
+ $response .= SucuriScanTemplate::get_snippet('posthack-resetplugins', array(
9416
+ 'ResetPlugin.CssClass' => $css_class,
9417
+ 'ResetPlugin.Disabled' => $input_disabled,
9418
+ 'ResetPlugin.PluginPath' => SucuriScan::escape( $plugin_path ),
9419
+ 'ResetPlugin.Plugin' => SucuriScan::excerpt( $plugin_data['Name'], 35 ),
9420
+ 'ResetPlugin.Version' => $plugin_data['Version'],
9421
+ 'ResetPlugin.Type' => $plugin_data['PluginType'],
9422
+ 'ResetPlugin.TypeClass' => $plugin_type_class,
9423
+ 'ResetPlugin.Status' => $plugin_status,
9424
+ 'ResetPlugin.StatusClass' => $plugin_status_class,
9425
+ ));
9426
+
9427
+ $counter += 1;
9428
+ }
9429
+
9430
+ print( $response );
9431
+ exit(0);
9432
+ }
9433
  }
9434
 
9435
  /**
9448
 
9449
  if ( $plugin_list = SucuriScanRequest::post( 'plugin_path', '_array' ) ) {
9450
  // Create an instance of the FileInfo interface.
9451
+ $file_info = new SucuriScanFileInfo();
9452
+ $file_info->ignore_files = false;
9453
+ $file_info->ignore_directories = false;
9454
+ $file_info->skip_directories = false;
9455
 
9456
  // Get (possible) cached information from the installed plugins.
9457
  $all_plugins = SucuriScanAPI::get_plugins();
9469
  // First, remove all files/sub-folders from the plugin's directory.
9470
  if ( substr_count( $plugin_path, '/' ) >= 1 ) {
9471
  $plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin_path );
9472
+ $file_info->remove_directory_tree( $plugin_directory );
9473
  }
9474
 
9475
  // Install a fresh copy of the plugin's files.
10128
  'FailedLogins.NoItemsVisibility' => 'visible',
10129
  'FailedLogins.WarningVisibility' => 'visible',
10130
  'FailedLogins.CollectPasswordsVisibility' => 'visible',
10131
+ 'FailedLogins.PaginationLinks' => '',
10132
+ 'FailedLogins.PaginationVisibility' => 'hidden',
10133
  );
10134
 
10135
+ // Define variables for the pagination.
10136
+ $page_number = SucuriScanTemplate::get_page_number();
10137
+ $max_per_page = SUCURISCAN_MAX_PAGINATION_BUTTONS;
10138
+ $page_offset = ( $page_number - 1 ) * $max_per_page;
10139
+ $page_limit = ( $page_offset + $max_per_page );
10140
+
10141
  $max_failed_logins = SucuriScanOption::get_option( ':maximum_failed_logins' );
10142
  $notify_bruteforce_attack = SucuriScanOption::get_option( ':notify_bruteforce_attack' );
10143
  $failed_logins = sucuriscan_get_failed_logins();
10161
  if ( $failed_logins ) {
10162
  $counter = 0;
10163
 
10164
+ for ( $key = $page_offset; $key < $page_limit; $key++ ) {
10165
+ if ( array_key_exists( $key, $failed_logins['entries'] ) ) {
10166
+ $login_data = $failed_logins['entries'][ $key ];
10167
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
10168
+ $wrong_user_password = '<span class="sucuriscan-label-default">hidden</span>';
10169
 
10170
+ if ( sucuriscan_collect_wrong_passwords() === true ) {
10171
+ if (
10172
+ isset($login_data['user_password'])
10173
+ && ! empty($login_data['user_password'])
10174
+ ) {
10175
+ $wrong_user_password = SucuriScan::escape( $login_data['user_password'] );
10176
+ } else {
10177
+ $wrong_user_password = '<span class="sucuriscan-label-info">empty</span>';
10178
+ }
10179
  }
 
10180
 
10181
+ $template_variables['FailedLogins.List'] .= SucuriScanTemplate::get_snippet('lastlogins-failedlogins', array(
10182
+ 'FailedLogins.CssClass' => $css_class,
10183
+ 'FailedLogins.Num' => $login_data['attempt_count'],
10184
+ 'FailedLogins.Username' => SucuriScan::escape( $login_data['user_login'] ),
10185
+ 'FailedLogins.Password' => $wrong_user_password,
10186
+ 'FailedLogins.RemoteAddr' => SucuriScan::escape( $login_data['remote_addr'] ),
10187
+ 'FailedLogins.Datetime' => SucuriScan::datetime( $login_data['attempt_time'] ),
10188
+ 'FailedLogins.UserAgent' => SucuriScan::escape( $login_data['user_agent'] ),
10189
+ ));
10190
 
10191
+ $counter += 1;
10192
+ }
10193
  }
10194
 
10195
  if ( $counter > 0 ) {
10196
  $template_variables['FailedLogins.NoItemsVisibility'] = 'hidden';
10197
  }
10198
+
10199
+ $template_variables['FailedLogins.PaginationLinks'] = SucuriScanTemplate::get_pagination(
10200
+ '%%SUCURI.URL.Lastlogins%%#failed-logins',
10201
+ $failed_logins['count'],
10202
+ $max_per_page
10203
+ );
10204
+
10205
+ if ( $failed_logins['count'] > $max_per_page ) {
10206
+ $template_variables['FailedLogins.PaginationVisibility'] = 'visible';
10207
+ }
10208
  }
10209
 
10210
  $template_variables['FailedLogins.MaxFailedLogins'] = $max_failed_logins;
10302
  );
10303
 
10304
  // Read and parse all the entries found in the datastore file.
10305
+ $offset = count( $lines ) - 1;
10306
+
10307
+ for ( $key = $offset; $key >= 0; $key-- ) {
10308
+ $line = trim( $lines[ $key ] );
10309
+ $login_data = @json_decode( $line, true );
10310
+
10311
+ if ( is_array( $login_data ) ) {
10312
  $login_data['attempt_date'] = date( 'r', $login_data['attempt_time'] );
10313
+ $login_data['attempt_count'] = ( $key + 1 );
10314
 
10315
  if ( ! $login_data['user_agent'] ) {
10316
  $login_data['user_agent'] = 'Unknown';
11076
  SucuriScanEvent::report_info_event( $message );
11077
  SucuriScanInterface::info( $message );
11078
  }
11079
+
11080
+ // Debug ability of the plugin to send HTTP requests correctly.
11081
+ if ( $debug_request = SucuriScanRequest::post( ':debug_request' ) ) {
11082
+ SucuriScanInterface::info(
11083
+ sprintf(
11084
+ '<pre>%s</pre>',
11085
+ SucuriScanAPI::test_api_call()
11086
+ )
11087
+ );
11088
+ }
11089
  }
11090
  }
11091
 
11198
  'ReverseProxySwitchValue' => 'disable',
11199
  'ReverseProxySwitchCssClass' => 'button-danger',
11200
  /* API Proxy Settings */
11201
+ 'APIProxy.Host' => 'no_proxy_host',
11202
+ 'APIProxy.Port' => 'no_proxy_port',
11203
+ 'APIProxy.Username' => 'no_proxy_username',
11204
+ 'APIProxy.Password' => 'no_proxy_password',
11205
  'APIProxy.PasswordType' => 'default',
11206
  'APIProxy.PasswordText' => 'empty',
11207
  );