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

Version Description

  • Internal code cleanup and re-organization.
  • More white lists for the integrity checks.
  • Additional settings to customize some of the warnings.
Download this release

Release Info

Developer dd@sucuri.net
Plugin Icon 128x128 Sucuri Security – Auditing, Malware Scanner and Security Hardening
Version 1.6.6
Comparing to
See all releases

Code changes from version 1.6.5 to 1.6.6

inc/css/index.html ADDED
@@ -0,0 +1 @@
 
1
+ <!-- Avoid the directory listing. -->
inc/css/index.php DELETED
@@ -1,15 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Avoid directory listing.
5
- *
6
- * @package Sucuri Plugin - SiteCheck Malware Scanner
7
- * @author Yorman Arias <yorman.arias@sucuri.net>
8
- * @author Daniel Cid <dcid@sucuri.net>
9
- * @copyright Since 2010-2014 Sucuri Inc.
10
- * @license Released under the GPL - see LICENSE file for details.
11
- * @link https://wordpress.sucuri.net/
12
- * @since File available since Release 0.1
13
- */
14
-
15
- if( !defined('SUCURISCAN') ){ exit(0); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/css/sucuriscan-default-css.css CHANGED
@@ -16,7 +16,13 @@
16
  .sucuriscan-pull-right{float:right}
17
  .sucuriscan-list li{list-style:disc;margin:0 0 5px 15px}
18
  .sucuriscan-gradient, .sucuriscan-modal-header, .sucuriscan-maincontent .sucuriscan-table tr > th{background:#f1f1f1;background-image:-webkit-gradient(linear,left bottom,left top,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-moz-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-o-linear-gradient(bottom,#ececec,#f9f9f9);background-image:linear-gradient(to top,#ececec,#f9f9f9)}
19
- /* WordPress Extra Buttons */
 
 
 
 
 
 
20
  .wp-core-ui .button-danger.button-danger{background:#cc2e2e;border-color:#a20000;box-shadow:inset 0 1px 0 rgba(230, 120, 120, 0.6)}
21
  .wp-core-ui .button-danger.focus, .wp-core-ui .button-danger.hover, .wp-core-ui .button-danger:focus, .wp-core-ui .button-danger:hover{background:#be1e1e}
22
  .wp-core-ui .button-danger.focus, .wp-core-ui .button-danger:focus{border-color:#500e0e}
@@ -97,9 +103,10 @@
97
  /* WordPress Alerts */
98
  div.sucuriscan-alert{position:relative;margin:0 0 20px 0}
99
  div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:18px;font-weight:bold;text-decoration:none}
100
- .sucuriscan-inline-alert, .sucuriscan-inline-alert-updated, .sucuriscan-inline-alert-error{background:#fff;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);padding:0;border-left:4px solid #ddd}
101
- .sucuriscan-inline-alert > p, .sucuriscan-inline-alert-updated > p, .sucuriscan-inline-alert-error > p{margin:0;padding:8px 12px;border:1px solid #ddd;border-left:0}
102
  .sucuriscan-inline-alert-updated{border-left-color:#7ad03a}
 
103
  .sucuriscan-inline-alert-error{border-left-color:#dd3d36}
104
  /* Tabulation Panels */
105
  .sucuriscan-tabs{}
@@ -124,6 +131,8 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
124
  .sucuriscan-loading .title{font-size:26px;margin-bottom:10px}
125
  .sucuriscan-loading .description{font-size:18px}
126
  .sucuriscan-sitelogo{width:190px;height:100px;background:url('http://sitecheck.sucuri.net/images/sucuri-sprite.png') no-repeat;margin:0 auto}
 
 
127
  /* Scanner Results */
128
  .sucuriscan-maincontent .sucuriscan-border{border:0;border-left:4px solid #ddd}
129
  .sucuriscan-maincontent .sucuriscan-border > h3, .sucuriscan-maincontent .sucuriscan-border > .inside{border-top:1px solid #e5e5e5;border-right:1px solid #e5e5e5}
@@ -212,7 +221,7 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
212
  .sucuriscan-maincontent .sucuriscan-settings form{display:inline-block}
213
  .sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{min-width:220px}
214
  .sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:0}
215
- .sucuriscan-wpconfig-textarea{width:600px;height:525px;background:#f5f5f5;font-size:12px;line-height:1.4em;resize:none;margin:15px 0 0 0;padding:10px}
216
  /* Responsive Styles */
217
  @media (max-width: 620px) {
218
  .sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:block}
16
  .sucuriscan-pull-right{float:right}
17
  .sucuriscan-list li{list-style:disc;margin:0 0 5px 15px}
18
  .sucuriscan-gradient, .sucuriscan-modal-header, .sucuriscan-maincontent .sucuriscan-table tr > th{background:#f1f1f1;background-image:-webkit-gradient(linear,left bottom,left top,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-moz-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-o-linear-gradient(bottom,#ececec,#f9f9f9);background-image:linear-gradient(to top,#ececec,#f9f9f9)}
19
+ /* WordPress Extra Buttons (success) */
20
+ .wp-core-ui .button-success.button-success{background:#74cc2e;border-color:#2da200;box-shadow:inset 0 1px 0 rgba(155, 230, 120, 0.6)}
21
+ .wp-core-ui .button-success.focus, .wp-core-ui .button-success.hover, .wp-core-ui .button-success:focus, .wp-core-ui .button-success:hover{background:#4bbe1e}
22
+ .wp-core-ui .button-success.focus, .wp-core-ui .button-success:focus{border-color:#23500e}
23
+ .wp-core-ui .button-success.active, .wp-core-ui .button-success.active:focus, .wp-core-ui .button-success.active:hover, .wp-core-ui .button-success:active{background:#47a61b;border-color:#358400}
24
+ .wp-core-ui .button-success-disabled, .wp-core-ui .button-success.disabled, .wp-core-ui .button-success:disabled, .wp-core-ui .button-success[disabled]{color:#b2e794 !important;background:#74ba29 !important;border-color:#3f7f1b !important}
25
+ /* WordPress Extra Buttons (danger) */
26
  .wp-core-ui .button-danger.button-danger{background:#cc2e2e;border-color:#a20000;box-shadow:inset 0 1px 0 rgba(230, 120, 120, 0.6)}
27
  .wp-core-ui .button-danger.focus, .wp-core-ui .button-danger.hover, .wp-core-ui .button-danger:focus, .wp-core-ui .button-danger:hover{background:#be1e1e}
28
  .wp-core-ui .button-danger.focus, .wp-core-ui .button-danger:focus{border-color:#500e0e}
103
  /* WordPress Alerts */
104
  div.sucuriscan-alert{position:relative;margin:0 0 20px 0}
105
  div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:18px;font-weight:bold;text-decoration:none}
106
+ .sucuriscan-inline-alert, .sucuriscan-inline-alert-updated, .sucuriscan-inline-alert-error, .sucuriscan-inline-alert-warning{background:#fff;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);padding:0;border-left:4px solid #ddd}
107
+ .sucuriscan-inline-alert > p, .sucuriscan-inline-alert-updated > p, .sucuriscan-inline-alert-error > p, .sucuriscan-inline-alert-warning > p{margin:0;padding:8px 12px;border:1px solid #ddd;border-left:0}
108
  .sucuriscan-inline-alert-updated{border-left-color:#7ad03a}
109
+ .sucuriscan-inline-alert-warning{border-left-color:#ffba00}
110
  .sucuriscan-inline-alert-error{border-left-color:#dd3d36}
111
  /* Tabulation Panels */
112
  .sucuriscan-tabs{}
131
  .sucuriscan-loading .title{font-size:26px;margin-bottom:10px}
132
  .sucuriscan-loading .description{font-size:18px}
133
  .sucuriscan-sitelogo{width:190px;height:100px;background:url('http://sitecheck.sucuri.net/images/sucuri-sprite.png') no-repeat;margin:0 auto}
134
+ .sucuriscan-sitecheck-form{margin:20px 0 0 0}
135
+ .sucuriscan-sitecheck-form .button.button-hero{padding:0 46px}
136
  /* Scanner Results */
137
  .sucuriscan-maincontent .sucuriscan-border{border:0;border-left:4px solid #ddd}
138
  .sucuriscan-maincontent .sucuriscan-border > h3, .sucuriscan-maincontent .sucuriscan-border > .inside{border-top:1px solid #e5e5e5;border-right:1px solid #e5e5e5}
221
  .sucuriscan-maincontent .sucuriscan-settings form{display:inline-block}
222
  .sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{min-width:220px}
223
  .sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:0}
224
+ .sucuriscan-maincontent .sucuriscan-wpcron-list{margin-top:0}
225
  /* Responsive Styles */
226
  @media (max-width: 620px) {
227
  .sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:block}
inc/images/index.html ADDED
@@ -0,0 +1 @@
 
1
+ <!-- Avoid the directory listing. -->
inc/images/index.php DELETED
@@ -1,15 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Avoid directory listing.
5
- *
6
- * @package Sucuri Plugin - SiteCheck Malware Scanner
7
- * @author Yorman Arias <yorman.arias@sucuri.net>
8
- * @author Daniel Cid <dcid@sucuri.net>
9
- * @copyright Since 2010-2014 Sucuri Inc.
10
- * @license Released under the GPL - see LICENSE file for details.
11
- * @link https://wordpress.sucuri.net/
12
- * @since File available since Release 0.1
13
- */
14
-
15
- if( !defined('SUCURISCAN') ){ exit(0); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/index.html ADDED
@@ -0,0 +1 @@
 
1
+ <!-- Avoid the directory listing. -->
inc/index.php DELETED
@@ -1,15 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Avoid directory listing.
5
- *
6
- * @package Sucuri Plugin - SiteCheck Malware Scanner
7
- * @author Yorman Arias <yorman.arias@sucuri.net>
8
- * @author Daniel Cid <dcid@sucuri.net>
9
- * @copyright Since 2010-2014 Sucuri Inc.
10
- * @license Released under the GPL - see LICENSE file for details.
11
- * @link https://wordpress.sucuri.net/
12
- * @since File available since Release 0.1
13
- */
14
-
15
- if( !defined('SUCURISCAN') ){ exit(0); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/js/index.html ADDED
@@ -0,0 +1 @@
 
1
+ <!-- Avoid the directory listing. -->
inc/js/index.php DELETED
@@ -1,15 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Avoid directory listing.
5
- *
6
- * @package Sucuri Plugin - SiteCheck Malware Scanner
7
- * @author Yorman Arias <yorman.arias@sucuri.net>
8
- * @author Daniel Cid <dcid@sucuri.net>
9
- * @copyright Since 2010-2014 Sucuri Inc.
10
- * @license Released under the GPL - see LICENSE file for details.
11
- * @link https://wordpress.sucuri.net/
12
- * @since File available since Release 0.1
13
- */
14
-
15
- if( !defined('SUCURISCAN') ){ exit(0); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tpl/index.html ADDED
@@ -0,0 +1 @@
 
1
+ <!-- Avoid the directory listing. -->
inc/tpl/index.php DELETED
@@ -1,15 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Avoid directory listing.
5
- *
6
- * @package Sucuri Plugin - SiteCheck Malware Scanner
7
- * @author Yorman Arias <yorman.arias@sucuri.net>
8
- * @author Daniel Cid <dcid@sucuri.net>
9
- * @copyright Since 2010-2014 Sucuri Inc.
10
- * @license Released under the GPL - see LICENSE file for details.
11
- * @link https://wordpress.sucuri.net/
12
- * @since File available since Release 0.1
13
- */
14
-
15
- if( !defined('SUCURISCAN') ){ exit(0); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tpl/infosys-cronjobs.html.tpl CHANGED
@@ -1,19 +1,67 @@
1
 
2
- <table class="wp-list-table widefat sucuriscan-wpcron-list">
3
- <thead>
4
- <tr>
5
- <th colspan="4">WordPress Cronjobs (%%SUCURI.Cronjobs.Total%% tasks)</th>
6
- </tr>
7
- <tr>
8
- <th>Task</th>
9
- <th>Schedule</th>
10
- <th>Next due (GMT/UTC)</th>
11
- <th>WordPress Hook</th>
12
- <!-- <th>Hook arguments</th> -->
13
- </tr>
14
- </thead>
15
-
16
- <tbody>
17
- %%SUCURI.Cronjobs.List%%
18
- </tbody>
19
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ <div id="poststuff">
3
+ <div class="postbox sucuriscan-border sucuriscan-table-description sucuriscan-%%SUCURI.IgnoreRules.TableVisibility%%">
4
+ <h3>Scheduled Tasks (%%SUCURI.Cronjobs.Total%% tasks)</h3>
5
+
6
+ <div class="inside">
7
+ <p>
8
+ <strong>Scheduled Tasks</strong> are rules registered in your database by a
9
+ plugin, theme, or the base system itself; they are used to automatically execute
10
+ actions defined in the code every certain amount of time. A good use of these
11
+ rules is to generate backup files of your site, execute a security scanner, or
12
+ remove unused elements like drafts.
13
+ </p>
14
+
15
+ <div class="sucuriscan-inline-alert-warning">
16
+ <p>
17
+ Note that there are some scheduled tasks <em>(registered by the base
18
+ system)</em> that can not be removed permanently using this tool, tasks such as
19
+ the <strong>addon update</strong> and <strong>version checker</strong> are
20
+ required by the site to work correctly.
21
+ </p>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+
27
+ <form action="%%SUCURI.URL.Infosys%%#wordpress-cronjobs" method="post">
28
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
29
+
30
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-wpcron-list">
31
+ <thead>
32
+ <tr>
33
+ <th class="manage-column column-cb check-column">
34
+ <label class="screen-reader-text" for="cb-select-all-1">Select All</label>
35
+ <input id="cb-select-all-1" type="checkbox">
36
+ </th>
37
+ <th>Task</th>
38
+ <th>Schedule</th>
39
+ <th>Next due</th>
40
+ <th>Arguments</th>
41
+ </tr>
42
+ </thead>
43
+
44
+ <tbody>
45
+ %%SUCURI.Cronjobs.List%%
46
+ </tbody>
47
+
48
+ <tfoot>
49
+ <tr>
50
+ <td colspan="5">
51
+ <label>
52
+ <select name="sucuriscan_cronjob_action">
53
+ <option value="">Choose action</option>
54
+ <option value="runnow">Execute now</option>
55
+ <option value="hourly">Re-schedule to hourly</option>
56
+ <option value="twicedaily">Re-schedule to twicedaily</option>
57
+ <option value="daily">Re-schedule to daily</option>
58
+ <option value="remove">Remove</option>
59
+ </select>
60
+ </label>
61
+ <button type="submit" class="button button-primary">Send action</button>
62
+ </td>
63
+ </tr>
64
+ </tfoot>
65
+ </table>
66
+
67
+ </form>
inc/tpl/infosys-cronjobs.snippet.tpl CHANGED
@@ -1,8 +1,10 @@
1
 
2
  <tr class="%%SUCURI.Cronjob.CssClass%%">
3
- <td>%%SUCURI.Cronjob.Task%%</td>
4
- <td>%%SUCURI.Cronjob.Schedule%%</td>
5
- <td>%%SUCURI.Cronjob.Nexttime%%</td>
6
  <td><span class="sucuriscan-monospace">%%SUCURI.Cronjob.Hook%%</span></td>
7
- <!-- <td>%%SUCURI.Cronjob.Arguments%%</td> -->
 
 
8
  </tr>
1
 
2
  <tr class="%%SUCURI.Cronjob.CssClass%%">
3
+ <td class="check-column">
4
+ <input type="checkbox" name="sucuriscan_cronjobs[]" value="%%SUCURI.Cronjob.Hook%%" />
5
+ </td>
6
  <td><span class="sucuriscan-monospace">%%SUCURI.Cronjob.Hook%%</span></td>
7
+ <td>%%SUCURI.Cronjob.Schedule%%</td>
8
+ <td>%%SUCURI.Cronjob.NextTime%%</td>
9
+ <td>%%SUCURI.Cronjob.Arguments%%</td>
10
  </tr>
inc/tpl/infosys-htaccess.html.tpl CHANGED
@@ -1,14 +1,15 @@
1
 
2
  <div id="poststuff" class="sucuriscan-infosys-htaccess">
3
  <div class="postbox">
4
- <h3>HTAccess Integrity</h3>
5
 
6
  <div class="inside">
7
  <p>
8
- The <code>.htaccess</code> is a distributed configuration file, and is how Apache handles
9
- configuration changes on a per-directory basis. WordPress uses this file to manipulate how
10
- Apache serves files from its root directory, and subdirectories thereof. Most notably, WP
11
- modifies this file to be able to handle pretty permalinks.
 
12
  </p>
13
 
14
  <div class="sucuriscan-inline-alert-%%SUCURI.HTAccess.MessageType%% sucuriscan-%%SUCURI.HTAccess.MessageVisible%%">
1
 
2
  <div id="poststuff" class="sucuriscan-infosys-htaccess">
3
  <div class="postbox">
4
+ <h3>Access File Integrity</h3>
5
 
6
  <div class="inside">
7
  <p>
8
+ The <code>.htaccess</code> is a distributed configuration file, and is how the
9
+ Apache web server handles configuration changes on a per-directory basis.
10
+ WordPress uses this file to manipulate how Apache serves files from its root
11
+ directory and subdirectories thereof; most notably, it modifies this file to be
12
+ able to handle pretty permalinks.
13
  </p>
14
 
15
  <div class="sucuriscan-inline-alert-%%SUCURI.HTAccess.MessageType%% sucuriscan-%%SUCURI.HTAccess.MessageVisible%%">
inc/tpl/infosys-serverinfo.html.tpl CHANGED
@@ -1,69 +1,6 @@
1
 
2
  <table class="wp-list-table widefat sucuriscan-server-info">
3
  <tbody>
4
- <tr class="alternate">
5
- <td>Sucuri Plugin version</td>
6
- <td><span class="sucuriscan-monospace">%%SUCURI.PluginVersion%%</span></td>
7
- </tr>
8
- <tr>
9
- <td>Sucuri Plugin MD5Sum (sucuri.php)</td>
10
- <td><span class="sucuriscan-monospace">%%SUCURI.PluginMD5%%</span></td>
11
- </tr>
12
- <tr class="alternate">
13
- <td>Sucuri Plugin Last-time scan</td>
14
- <td><span class="sucuriscan-monospace">%%SUCURI.PluginRuntimeDatetime%%</span></td>
15
- </tr>
16
- <tr>
17
- <td>Operating System</td>
18
- <td><span class="sucuriscan-monospace">%%SUCURI.OperatingSystem%%</span></td>
19
- </tr>
20
- <tr class="alternate">
21
- <td>Server</td>
22
- <td><span class="sucuriscan-monospace">%%SUCURI.Server%%</span></td>
23
- </tr>
24
- <tr>
25
- <td>Memory usage</td>
26
- <td><span class="sucuriscan-monospace">%%SUCURI.MemoryUsage%%</span></td>
27
- </tr>
28
- <tr class="alternate">
29
- <td>MYSQL Version</td>
30
- <td><span class="sucuriscan-monospace">%%SUCURI.MySQLVersion%%</span></td>
31
- </tr>
32
- <tr>
33
- <td>SQL Mode</td>
34
- <td><span class="sucuriscan-monospace">%%SUCURI.SQLMode%%</span></td>
35
- </tr>
36
- <tr class="alternate">
37
- <td>PHP Version</td>
38
- <td><span class="sucuriscan-monospace">%%SUCURI.PHPVersion%%</span></td>
39
- </tr>
40
- <tr>
41
- <td>PHP Safe Mode</td>
42
- <td><span class="sucuriscan-monospace">%%SUCURI.SafeMode%%</span></td>
43
- </tr>
44
- <tr class="alternate">
45
- <td>PHP Allow URL fopen</td>
46
- <td><span class="sucuriscan-monospace">%%SUCURI.AllowUrlFopen%%</span></td>
47
- </tr>
48
- <tr>
49
- <td>PHP Memory Limit</td>
50
- <td><span class="sucuriscan-monospace">%%SUCURI.MemoryLimit%%</span></td>
51
- </tr>
52
- <tr class="alternate">
53
- <td>PHP Max Upload Size</td>
54
- <td><span class="sucuriscan-monospace">%%SUCURI.UploadMaxFilesize%%</span></td>
55
- </tr>
56
- <tr>
57
- <td>PHP Max Post Size</td>
58
- <td><span class="sucuriscan-monospace">%%SUCURI.PostMaxSize%%</span></td>
59
- </tr>
60
- <tr class="alternate">
61
- <td>PHP Max Script Execute Time</td>
62
- <td><span class="sucuriscan-monospace">%%SUCURI.MaxExecutionTime%%</span></td>
63
- </tr>
64
- <tr>
65
- <td>PHP Max Input Time</td>
66
- <td><span class="sucuriscan-monospace">%%SUCURI.MaxInputTime%%</span></td>
67
- </tr>
68
  </tbody>
69
  </table>
1
 
2
  <table class="wp-list-table widefat sucuriscan-server-info">
3
  <tbody>
4
+ %%SUCURI.ServerInfo.Variables%%
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  </tbody>
6
  </table>
inc/tpl/infosys-serverinfo.snippet.tpl ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.ServerInfo.CssClass%%">
3
+ <td>%%SUCURI.ServerInfo.Title%%</td>
4
+ <td><span class="sucuriscan-monospace">%%SUCURI.ServerInfo.Value%%</span></td>
5
+ </tr>
inc/tpl/infosys-wpconfig.html.tpl CHANGED
@@ -1,14 +1,8 @@
1
 
2
- <table class="wp-list-table widefat sucuriscan-wpconfig-rules">
3
  <thead>
4
- <th colspan="7" class="thead-with-button">
5
- <span>WP-Config Variables</span>
6
- <div class="thead-topright-action">
7
- <a href="%%SUCURI.WordpressConfig.ThickboxURL%%" title="WordPress Config Variables" class="button button-primary thickbox">View File</a>
8
- </div>
9
- </th>
10
  <tr>
11
- <th>Variable Name</th>
12
  <th>Value</th>
13
  </tr>
14
  </thead>
@@ -17,7 +11,3 @@
17
  %%SUCURI.WordpressConfig.Rules%%
18
  </tbody>
19
  </table>
20
-
21
- <div id="sucuriscan-wpconfig-content" style="display:none">
22
- <textarea class="sucuriscan-full-textarea sucuriscan-wpconfig-textarea sucuriscan-monospace">%%SUCURI.WordpressConfig.Content%%</textarea>
23
- </div>
1
 
2
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-wpconfig-rules">
3
  <thead>
 
 
 
 
 
 
4
  <tr>
5
+ <th>Variable</th>
6
  <th>Value</th>
7
  </tr>
8
  </thead>
11
  %%SUCURI.WordpressConfig.Rules%%
12
  </tbody>
13
  </table>
 
 
 
 
inc/tpl/infosys-wpconfig.snippet.tpl CHANGED
@@ -1,4 +1,5 @@
 
1
  <tr class="%%SUCURI.WordpressConfig.CssClass%%">
2
- <td class="sucuriscan-monospace"><strong>%%SUCURI.WordpressConfig.VariableName%%</strong></td>
3
- <td class="sucuriscan-monospace">%%SUCURI.WordpressConfig.VariableValue%%</td>
4
  </tr>
1
+
2
  <tr class="%%SUCURI.WordpressConfig.CssClass%%">
3
+ <td><span class="sucuriscan-monospace">%%SUCURI.WordpressConfig.VariableName%%</span></td>
4
+ <td><span class="%%SUCURI.WordpressConfig.VariableCssClass%%">%%SUCURI.WordpressConfig.VariableValue%%</span></td>
5
  </tr>
inc/tpl/infosys.html.tpl CHANGED
@@ -5,13 +5,13 @@
5
  <a href="#" data-tabname="server-info">Plugin &amp; Server Info</a>
6
  </li>
7
  <li>
8
- <a href="#" data-tabname="wordpress-cronjobs">WordPress Cronjobs</a>
9
  </li>
10
  <li>
11
- <a href="#" data-tabname="htaccess-integrity">HTAccess Integrity</a>
12
  </li>
13
  <li>
14
- <a href="#" data-tabname="wpconfig-vars">WP Config Variables</a>
15
  </li>
16
  </ul>
17
 
5
  <a href="#" data-tabname="server-info">Plugin &amp; Server Info</a>
6
  </li>
7
  <li>
8
+ <a href="#" data-tabname="wordpress-cronjobs">Scheduled Tasks</a>
9
  </li>
10
  <li>
11
+ <a href="#" data-tabname="htaccess-integrity">Access File Integrity</a>
12
  </li>
13
  <li>
14
+ <a href="#" data-tabname="wpconfig-vars">Config. Variables</a>
15
  </li>
16
  </ul>
17
 
inc/tpl/malwarescan.html.tpl CHANGED
@@ -4,14 +4,11 @@
4
 
5
  <p class="description">Visit our <a href="http://sucuri.net/signup?fromloader" target="_blank">coverage &amp; pricing</a> page for details on how sucuri can help you.</p>
6
 
7
- <p><img src="http://sitecheck.sucuri.net/images/loading.gif" alt="Loading..." /></p>
8
-
9
- <div class="sucuriscan-sitelogo">&nbsp;</div>
10
-
11
- <form method="post" name="sucuriscan_sitecheck_form">
12
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
13
  <input type="hidden" name="sucuriscan_malware_scan" value="1" />
 
14
  </form>
15
 
16
- <script type="text/javascript">setTimeout(function(){ document.forms.sucuriscan_sitecheck_form.submit() }, 3000)</script>
17
  </div>
4
 
5
  <p class="description">Visit our <a href="http://sucuri.net/signup?fromloader" target="_blank">coverage &amp; pricing</a> page for details on how sucuri can help you.</p>
6
 
7
+ <form action="%%SUCURI.URL.Scanner%%" method="post" name="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
  </div>
inc/tpl/monitoring.html.tpl CHANGED
@@ -1,34 +1,34 @@
1
 
2
  <div id="poststuff">
3
 
4
- <div class="postbox sucuriscan-monitoring-instructions sucuriscan-%%SUCURI.Monitoring.InstructionsVisibility%%">
5
- <h3>Instructions to enable CloudProxy WAF</h3>
6
 
7
  <div class="inside">
8
  <p>
9
- A powerful <b>WAF</b> <em>(Web Application Firewall)</em> and <b>Intrusion Prevention</b>
10
- system for any WordPress user. If you do not have an account, you can sign up for one here:
11
- <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri CloudProxy</a>
 
 
 
 
12
  </p>
13
 
14
- <ol>
15
- <li>
16
- Sign up for a Sucuri CloudProxy account here:
17
- <a href="https://login.sucuri.net/signup2/create?CloudProxy" target="_blank">Sign up</a>
18
- </li>
19
- <li>
20
- Change your DNS to point your site to one of our servers. This link explains
21
- <a href="https://dashboard.sucuri.net/cloudproxy/" target="_blank"> CloudProxy Dashboard</a>
22
- or use our documentation here <a href="http://kb.sucuri.net/cloudproxy" target="_blank">
23
- KB CloudProxy</a>.
24
- </li>
25
- <li>You are all set. There is nothing else to do.</li>
26
- </ol>
27
 
28
  <p>
29
- Once enabled, our firewall will act as a shield, protecting your site from attacks
30
- and preventing malware infections and reinfections. It will block SQL injection attempts,
31
- brute force attacks, XSS, RFI, backdoors and many other threats against your site.
 
 
32
  </p>
33
  </div>
34
  </div>
1
 
2
  <div id="poststuff">
3
 
4
+ <div class="postbox sucuriscan-border sucuriscan-border-info sucuriscan-%%SUCURI.Monitoring.InstructionsVisibility%%">
5
+ <h3>Activation instructions</h3>
6
 
7
  <div class="inside">
8
  <p>
9
+ A powerful <b>WAF</b> <em>(Web Application Firewall)</em> and <b>Intrusion
10
+ Prevention</b> system for any WordPress user and many other platforms. This page
11
+ will help you to configure and monitor your site through <strong>Sucuri
12
+ CloudProxy</strong>. Once enabled, our firewall will act as a shield, protecting
13
+ your site from attacks and preventing malware infections and reinfections. It
14
+ will block SQL injection attempts, brute force attacks, XSS, RFI, backdoors and
15
+ many other threats against your site.
16
  </p>
17
 
18
+ <p>
19
+ Add your <strong>API key</strong> in the form bellow, click in the
20
+ <em>activate</em> button and after that your site will start communicating with
21
+ the official CloudProxy API service. Your account settings, whitelisted IP
22
+ addresses, audit logs, and the cache status will be displayed when the API key
23
+ is validated.
24
+ </p>
 
 
 
 
 
 
25
 
26
  <p>
27
+ <em>[1]</em> More information about <a href="http://cloudproxy.sucuri.net/" target="_blank">CloudProxy</a>.<br>
28
+ <em>[2]</em> Configuration instructions and videos in the official <a href="http://kb.sucuri.net/cloudproxy"
29
+ target="_blank">Knowledge Base</a> site.<br>
30
+ <em>[3]</em> <a href="https://login.sucuri.net/signup2/create?CloudProxy" target="_blank">Sign up</a> for a new
31
+ account and start protecting your site with CloudProxy.
32
  </p>
33
  </div>
34
  </div>
inc/tpl/posthack-resetpassword.html.tpl CHANGED
@@ -7,12 +7,20 @@
7
  <input type="hidden" name="sucuriscan_reset_password" value="1" />
8
 
9
  <p>
10
- Use this tool to reset the current password for some specific users or for all
11
- of them. We will send an email to each of those users adivising the password change
12
- that includes the new password automatically generated by WordPress. After the
13
- password reset your current session will be closed and you'll need to login again.
14
  </p>
15
 
 
 
 
 
 
 
 
 
 
16
  <table class="wp-list-table widefat sucuriscan-table">
17
  <thead>
18
  <tr>
7
  <input type="hidden" name="sucuriscan_reset_password" value="1" />
8
 
9
  <p>
10
+ You can generate a new random password for the user accounts that you select
11
+ from the list. An email with the new password will be sent to the email address
12
+ of each chosen users.
 
13
  </p>
14
 
15
+ <div class="sucuriscan-inline-alert-warning">
16
+ <p>
17
+ If you choose to change the password of your own user, then your current session
18
+ will expire inmediately. You will need to log into the admin panel with the new
19
+ password that will be sent to your email. If you are unsure of this, do not
20
+ select your account from the list.
21
+ </p>
22
+ </div>
23
+
24
  <table class="wp-list-table widefat sucuriscan-table">
25
  <thead>
26
  <tr>
inc/tpl/posthack-updatesecretkeys.html.tpl CHANGED
@@ -1,17 +1,39 @@
1
 
2
- <div id="poststuff" class="sucuriscan-update-secret-keys">
3
  <div class="postbox">
4
  <div class="inside">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  <form method="post">
6
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
7
  <input type="hidden" name="sucuriscan_update_wpconfig" value="1" />
8
 
9
- <p>
10
- Use this button to update the security keys stored in the <code>wp-config.php</code>
11
- file, we will use the official WordPress Secret-Key API Generator. After the
12
- update your current session will be closed and you'll need to login again.
13
- </p>
14
-
15
  <p>
16
  <label>
17
  <input type="hidden" name="sucuriscan_process_form" value="0" />
@@ -20,7 +42,7 @@
20
  </label>
21
  </p>
22
 
23
- <input type="submit" value="Update WP-Config Keys" class="button button-primary" />
24
  </form>
25
 
26
  <div class="sucuriscan_wpconfig_keys_updated sucuriscan-%%SUCURI.WPConfigUpdate.Visibility%%">
1
 
2
+ <div id="poststuff" class="sucuriscan-update-security-keys">
3
  <div class="postbox">
4
  <div class="inside">
5
+ <p>
6
+ The secret or security keys are a list of constants added to your site to ensure
7
+ better encryption of information stored in the user's cookies. A secret key
8
+ makes your site harder to hack and access by adding random elements to the
9
+ password. You do not have to remember the keys, just write a random,
10
+ complicated, and long string in the <code>wp-config.php</code> file. You can
11
+ change these keys at any point in time to invalidate all existing cookies,
12
+ forcing all users to login again.
13
+ </p>
14
+
15
+ <div class="sucuriscan-inline-alert-warning">
16
+ <p>Your session will expire immediately after the security keys are changed.</p>
17
+ </div>
18
+
19
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-security-keys-table">
20
+ <thead>
21
+ <tr>
22
+ <th>Key</th>
23
+ <th>Value</th>
24
+ <th>Status</th>
25
+ </tr>
26
+ </thead>
27
+
28
+ <tbody>
29
+ %%SUCURI.SecurityKeys.List%%
30
+ </tbody>
31
+ </table>
32
+
33
  <form method="post">
34
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
35
  <input type="hidden" name="sucuriscan_update_wpconfig" value="1" />
36
 
 
 
 
 
 
 
37
  <p>
38
  <label>
39
  <input type="hidden" name="sucuriscan_process_form" value="0" />
42
  </label>
43
  </p>
44
 
45
+ <input type="submit" value="Generate New Security Keys" class="button button-primary" />
46
  </form>
47
 
48
  <div class="sucuriscan_wpconfig_keys_updated sucuriscan-%%SUCURI.WPConfigUpdate.Visibility%%">
inc/tpl/posthack-updatesecretkeys.snippet.tpl ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.SecurityKey.CssClass%%">
3
+ <td><span class="sucuriscan-monospace">%%SUCURI.SecurityKey.KeyName%%</span></td>
4
+ <td><span class="sucuriscan-monospace">%%SUCURI.SecurityKey.KeyValue%%</span></td>
5
+ <td><span class="sucuriscan-label-%%SUCURI.SecurityKey.KeyStatusCssClass%%">%%SUCURI.SecurityKey.KeyStatusText%%</span></td>
6
+ </tr>
inc/tpl/posthack.html.tpl CHANGED
@@ -2,7 +2,7 @@
2
  <div class="sucuriscan-tabs">
3
  <ul>
4
  <li>
5
- <a href="#" data-tabname="update-secret-keys">Update WordPress Keys</a>
6
  </li>
7
  <li>
8
  <a href="#" data-tabname="reset-users-password">Reset User's Password</a>
@@ -13,7 +13,7 @@
13
  </ul>
14
 
15
  <div class="sucuriscan-tab-containers">
16
- <div id="sucuriscan-update-secret-keys">
17
  %%SUCURI.UpdateSecretKeys%%
18
  </div>
19
 
2
  <div class="sucuriscan-tabs">
3
  <ul>
4
  <li>
5
+ <a href="#" data-tabname="update-security-keys">Security Keys</a>
6
  </li>
7
  <li>
8
  <a href="#" data-tabname="reset-users-password">Reset User's Password</a>
13
  </ul>
14
 
15
  <div class="sucuriscan-tab-containers">
16
+ <div id="sucuriscan-update-security-keys">
17
  %%SUCURI.UpdateSecretKeys%%
18
  </div>
19
 
inc/tpl/settings-general.html.tpl CHANGED
@@ -6,7 +6,7 @@
6
  <tr>
7
  <th colspan="3" class="thead-with-button">
8
  <span>Plugin Settings</span>
9
- <form method="post" class="thead-topright-action">
10
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
11
  <button type="submit" name="sucuriscan_reset_options" class="button-primary">Reset plugin options</button>
12
  </form>
@@ -34,18 +34,18 @@
34
  <span class="sucuriscan-monospace">%%SUCURI.APIKey%%</span>
35
  </td>
36
  <td class="td-with-button">
37
- <form method="post" class="sucuriscan-%%SUCURI.APIKey.RecoverVisibility%%">
38
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
39
- <button type="submit" name="sucuriscan_recover_api_key" class="button-primary">Recover</button>
40
  </form>
41
 
42
- <form method="post" class="sucuriscan-%%SUCURI.APIKey.ManualKeyFormVisibility%%">
43
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
44
  <input type="text" name="sucuriscan_manual_api_key" class="input-text" placeholder="API key sent to your email" />
45
  <button type="submit" class="button-primary">Save</button>
46
  </form>
47
 
48
- <form method="post" class="sucuriscan-%%SUCURI.APIKey.RemoveVisibility%%">
49
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
50
  <button type="submit" name="sucuriscan_remove_api_key" class="button-primary button-danger">Remove</button>
51
  </form>
@@ -53,21 +53,10 @@
53
  </tr>
54
 
55
  <tr>
56
- <td>Last Scanning</td>
57
- <td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
58
- <td class="td-with-button">
59
- <form action="%%SUCURI.URL.Home%%" method="post">
60
- <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
61
- <button type="submit" name="sucuriscan_force_scan" class="button-primary">Force Scan</button>
62
- </form>
63
- </td>
64
- </tr>
65
-
66
- <tr class="alternate">
67
  <td>Notify events to</td>
68
  <td><a href="mailto:%%SUCURI.NotifyTo%%">%%SUCURI.NotifyTo%%</a></td>
69
  <td class="td-with-button">
70
- <form method="post">
71
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
72
  <input type="text" name="sucuriscan_notify_to" class="input-text" placeholder="Send notifications to..." />
73
  <button type="submit" class="button-primary">Change</button>
@@ -75,11 +64,11 @@
75
  </td>
76
  </tr>
77
 
78
- <tr>
79
  <td>Alerts per hour</td>
80
  <td>%%SUCURI.EmailsPerHour%%</td>
81
  <td class="td-with-button">
82
- <form method="post">
83
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
84
  <select name="sucuriscan_emails_per_hour">
85
  %%SUCURI.EmailsPerHourOptions%%
@@ -89,11 +78,11 @@
89
  </td>
90
  </tr>
91
 
92
- <tr class="alternate">
93
  <td>Consider brute-force after</td>
94
  <td>%%SUCURI.MaximumFailedLogins%%</td>
95
  <td class="td-with-button">
96
- <form method="post">
97
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
98
  <select name="sucuriscan_maximum_failed_logins">
99
  %%SUCURI.MaximumFailedLoginsOptions%%
@@ -103,11 +92,11 @@
103
  </td>
104
  </tr>
105
 
106
- <tr>
107
  <td>Verify SSL Cert</td>
108
  <td>%%SUCURI.VerifySSLCert%%</td>
109
  <td class="td-with-button">
110
- <form method="post">
111
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
112
  <select name="sucuriscan_verify_ssl_cert">
113
  %%SUCURI.VerifySSLCertOptions%%
@@ -117,11 +106,58 @@
117
  </td>
118
  </tr>
119
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  <tr class="alternate">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  <td>Scanning frequency</td>
122
- <td><span class="sucuriscan-monospace">%%SUCURI.ScanningFrequency%%</span></td>
123
  <td class="td-with-button">
124
- <form method="post">
125
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
126
  <select name="sucuriscan_scan_frequency">
127
  %%SUCURI.ScanningFrequencyOptions%%
@@ -131,11 +167,11 @@
131
  </td>
132
  </tr>
133
 
134
- <tr class="sucuriscan-%%SUCURI.ScanningInterfaceVisibility%%">
135
  <td>Scanning interface</td>
136
- <td><span class="sucuriscan-monospace">%%SUCURI.ScanningInterface%%</span></td>
137
  <td class="td-with-button">
138
- <form method="post">
139
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
140
  <select name="sucuriscan_scan_interface">
141
  %%SUCURI.ScanningInterfaceOptions%%
6
  <tr>
7
  <th colspan="3" class="thead-with-button">
8
  <span>Plugin Settings</span>
9
+ <form action="%%SUCURI.URL.Settings%%" method="post" class="thead-topright-action">
10
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
11
  <button type="submit" name="sucuriscan_reset_options" class="button-primary">Reset plugin options</button>
12
  </form>
34
  <span class="sucuriscan-monospace">%%SUCURI.APIKey%%</span>
35
  </td>
36
  <td class="td-with-button">
37
+ <form action="%%SUCURI.URL.Settings%%" method="post" class="sucuriscan-%%SUCURI.APIKey.RecoverVisibility%%">
38
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
39
+ <button type="submit" name="sucuriscan_recover_key" class="button-primary">Recover</button>
40
  </form>
41
 
42
+ <form action="%%SUCURI.URL.Settings%%" method="post" class="sucuriscan-%%SUCURI.APIKey.ManualKeyFormVisibility%%">
43
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
44
  <input type="text" name="sucuriscan_manual_api_key" class="input-text" placeholder="API key sent to your email" />
45
  <button type="submit" class="button-primary">Save</button>
46
  </form>
47
 
48
+ <form action="%%SUCURI.URL.Settings%%" method="post" class="sucuriscan-%%SUCURI.APIKey.RemoveVisibility%%">
49
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
50
  <button type="submit" name="sucuriscan_remove_api_key" class="button-primary button-danger">Remove</button>
51
  </form>
53
  </tr>
54
 
55
  <tr>
 
 
 
 
 
 
 
 
 
 
 
56
  <td>Notify events to</td>
57
  <td><a href="mailto:%%SUCURI.NotifyTo%%">%%SUCURI.NotifyTo%%</a></td>
58
  <td class="td-with-button">
59
+ <form action="%%SUCURI.URL.Settings%%" method="post">
60
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
61
  <input type="text" name="sucuriscan_notify_to" class="input-text" placeholder="Send notifications to..." />
62
  <button type="submit" class="button-primary">Change</button>
64
  </td>
65
  </tr>
66
 
67
+ <tr class="alternate">
68
  <td>Alerts per hour</td>
69
  <td>%%SUCURI.EmailsPerHour%%</td>
70
  <td class="td-with-button">
71
+ <form action="%%SUCURI.URL.Settings%%" method="post">
72
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
73
  <select name="sucuriscan_emails_per_hour">
74
  %%SUCURI.EmailsPerHourOptions%%
78
  </td>
79
  </tr>
80
 
81
+ <tr>
82
  <td>Consider brute-force after</td>
83
  <td>%%SUCURI.MaximumFailedLogins%%</td>
84
  <td class="td-with-button">
85
+ <form action="%%SUCURI.URL.Settings%%" method="post">
86
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
87
  <select name="sucuriscan_maximum_failed_logins">
88
  %%SUCURI.MaximumFailedLoginsOptions%%
92
  </td>
93
  </tr>
94
 
95
+ <tr class="alternate">
96
  <td>Verify SSL Cert</td>
97
  <td>%%SUCURI.VerifySSLCert%%</td>
98
  <td class="td-with-button">
99
+ <form action="%%SUCURI.URL.Settings%%" method="post">
100
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
101
  <select name="sucuriscan_verify_ssl_cert">
102
  %%SUCURI.VerifySSLCertOptions%%
106
  </td>
107
  </tr>
108
 
109
+ <tr>
110
+ <td>Filesystem scanner</td>
111
+ <td>%%SUCURI.FsScannerStatus%%</td>
112
+ <td class="td-with-button">
113
+ <form action="%%SUCURI.URL.Settings%%" method="post">
114
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
115
+ <input type="hidden" name="sucuriscan_fs_scanner" value="%%SUCURI.FsScannerSwitchValue%%" />
116
+ <button type="submit" class="button-primary %%SUCURI.FsScannerSwitchCssClass%%">%%SUCURI.FsScannerSwitchText%%</button>
117
+ </form>
118
+ </td>
119
+ </tr>
120
+
121
  <tr class="alternate">
122
+ <td>Scan modified files</td>
123
+ <td>%%SUCURI.ScanModfilesStatus%%</td>
124
+ <td class="td-with-button">
125
+ <form action="%%SUCURI.URL.Settings%%" method="post">
126
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
127
+ <input type="hidden" name="sucuriscan_scan_modfiles" value="%%SUCURI.ScanModfilesSwitchValue%%" />
128
+ <button type="submit" class="button-primary %%SUCURI.ScanModfilesSwitchCssClass%%">%%SUCURI.ScanModfilesSwitchText%%</button>
129
+ </form>
130
+ </td>
131
+ </tr>
132
+
133
+ <tr>
134
+ <td>Integrity checking</td>
135
+ <td>%%SUCURI.ScanChecksumsStatus%%</td>
136
+ <td class="td-with-button">
137
+ <form action="%%SUCURI.URL.Settings%%" method="post">
138
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
139
+ <input type="hidden" name="sucuriscan_scan_checksums" value="%%SUCURI.ScanChecksumsSwitchValue%%" />
140
+ <button type="submit" class="button-primary %%SUCURI.ScanChecksumsSwitchCssClass%%">%%SUCURI.ScanChecksumsSwitchText%%</button>
141
+ </form>
142
+ </td>
143
+ </tr>
144
+
145
+ <tr class="alternate">
146
+ <td>Last Scanning</td>
147
+ <td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
148
+ <td class="td-with-button">
149
+ <form action="%%SUCURI.URL.Home%%" method="post">
150
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
151
+ <button type="submit" name="sucuriscan_force_scan" class="button-primary">Force Scan</button>
152
+ </form>
153
+ </td>
154
+ </tr>
155
+
156
+ <tr>
157
  <td>Scanning frequency</td>
158
+ <td>%%SUCURI.ScanningFrequency%%</td>
159
  <td class="td-with-button">
160
+ <form action="%%SUCURI.URL.Settings%%" method="post">
161
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
162
  <select name="sucuriscan_scan_frequency">
163
  %%SUCURI.ScanningFrequencyOptions%%
167
  </td>
168
  </tr>
169
 
170
+ <tr class="alternate sucuriscan-%%SUCURI.ScanningInterfaceVisibility%%">
171
  <td>Scanning interface</td>
172
+ <td>%%SUCURI.ScanningInterface%%</td>
173
  <td class="td-with-button">
174
+ <form action="%%SUCURI.URL.Settings%%" method="post">
175
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
176
  <select name="sucuriscan_scan_interface">
177
  %%SUCURI.ScanningInterfaceOptions%%
inc/tpl/{setup_notice.html.tpl → setup-notice.html.tpl} RENAMED
@@ -14,7 +14,7 @@
14
  <div class="sucuriscan-pull-right sucuriscan-setup-form">
15
  <form action="%%SUCURI.URL.Settings%%" method="post">
16
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
17
- <button type="submit" name="sucuriscan_wordpress_apikey" class="button button-primary button-hero">
18
  <span class="sucuriscan-button-title">Generate API key</span>
19
  <span class="sucuriscan-button-subtitle">for <b>%%SUCURI.CleanDomain%%</b> / <b>%%SUCURI.AdminEmail%%</b></span>
20
  </button>
14
  <div class="sucuriscan-pull-right sucuriscan-setup-form">
15
  <form action="%%SUCURI.URL.Settings%%" method="post">
16
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
17
+ <button type="submit" name="sucuriscan_plugin_api_key" class="button button-primary button-hero">
18
  <span class="sucuriscan-button-title">Generate API key</span>
19
  <span class="sucuriscan-button-subtitle">for <b>%%SUCURI.CleanDomain%%</b> / <b>%%SUCURI.AdminEmail%%</b></span>
20
  </button>
index.html ADDED
@@ -0,0 +1 @@
 
1
+ <!-- Avoid the directory listing. -->
index.php DELETED
@@ -1,15 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Avoid directory listing.
5
- *
6
- * @package Sucuri Plugin - SiteCheck Malware Scanner
7
- * @author Yorman Arias <yorman.arias@sucuri.net>
8
- * @author Daniel Cid <dcid@sucuri.net>
9
- * @copyright Since 2010-2014 Sucuri Inc.
10
- * @license Released under the GPL - see LICENSE file for details.
11
- * @link https://wordpress.sucuri.net/
12
- * @since File available since Release 0.1
13
- */
14
-
15
- if( !defined('SUCURISCAN') ){ exit(0); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: dd@sucuri.net
3
  Donate Link: http://sitecheck.sucuri.net
4
  Tags: malware, security, firewall, scan, spam, virus, sucuri, protection
5
  Requires at least:3.2
6
- Stable tag:1.6.5
7
- Tested up to: 3.9.1
8
 
9
  The Sucuri Security - Auditing, SiteCheck Malware Scanner and Hardening is a security plugin enables you to scan your WordPress site using Sucuri SiteCheck for security and malware issues, and also verifies the security integrity of your core files right in your dashboard. It includes audit trails and post-hack security ions to help you reset passwords and secret keys in case it has been already hacked, or infected with malware.
10
 
@@ -66,6 +66,11 @@ the compromise on your site).
66
 
67
  == Changelog ==
68
 
 
 
 
 
 
69
  = 1.6.5 =
70
  * Fixed integrity checking display.
71
 
3
  Donate Link: http://sitecheck.sucuri.net
4
  Tags: malware, security, firewall, scan, spam, virus, sucuri, protection
5
  Requires at least:3.2
6
+ Stable tag:1.6.6
7
+ Tested up to: 3.9.2
8
 
9
  The Sucuri Security - Auditing, SiteCheck Malware Scanner and Hardening is a security plugin enables you to scan your WordPress site using Sucuri SiteCheck for security and malware issues, and also verifies the security integrity of your core files right in your dashboard. It includes audit trails and post-hack security ions to help you reset passwords and secret keys in case it has been already hacked, or infected with malware.
10
 
66
 
67
  == Changelog ==
68
 
69
+ = 1.6.6 =
70
+ * Internal code cleanup and re-organization.
71
+ * More white lists for the integrity checks.
72
+ * Additional settings to customize some of the warnings.
73
+
74
  = 1.6.5 =
75
  * Fixed integrity checking display.
76
 
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 Security</a> <em>(Auditing, Malware Scanner and Hardening)</em> plugin enables you to scan your WordPress site using <a href="http://sitecheck.sucuri.net/" target="_blank">Sucuri SiteCheck</a> right in your dashboard. 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.6.5
8
  Author URI: http://sucuri.net
9
  */
10
 
@@ -12,7 +12,7 @@ Author URI: http://sucuri.net
12
  /**
13
  * Main file to control the plugin.
14
  *
15
- * @package Sucuri Plugin - SiteCheck Malware Scanner
16
  * @author Yorman Arias <yorman.arias@sucuri.net>
17
  * @author Daniel Cid <dcid@sucuri.net>
18
  * @copyright Since 2010-2014 Sucuri Inc.
@@ -22,26 +22,51 @@ Author URI: http://sucuri.net
22
  */
23
 
24
 
25
- /* No direct access. */
26
- if(!function_exists('add_action'))
27
- {
28
- exit(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  /**
32
- * Unique name of the plugin through out all the code.
 
 
 
 
 
 
33
  */
34
- define('SUCURISCAN','sucuriscan');
35
 
36
  /**
37
- * Current version of the plugin's code.
38
  */
39
- define('SUCURISCAN_VERSION','1.6.5');
40
 
41
  /**
42
- * The local URL where the plugin's files and assets are served.
43
  */
44
- define('SUCURI_URL', rtrim(plugin_dir_url( __FILE__ ),'/') );
45
 
46
  /**
47
  * The name of the Sucuri plugin main file.
@@ -63,6 +88,11 @@ define('SUCURISCAN_PLUGIN_PATH', WP_PLUGIN_DIR.'/'.SUCURISCAN_PLUGIN_FOLDER);
63
  */
64
  define('SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE);
65
 
 
 
 
 
 
66
  /**
67
  * Checksum of this file to check the integrity of the plugin.
68
  */
@@ -113,6 +143,184 @@ define('SUCURISCAN_SITECHECK_LIFETIME', 1200);
113
  */
114
  define('SUCURISCAN_GET_PLUGINS_LIFETIME', 1800);
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  /**
117
  * Miscellaneous library.
118
  *
@@ -128,6 +336,43 @@ class SucuriScan {
128
  public function __construct(){
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  /**
132
  * Generates a lowercase random string with an specific length.
133
  *
@@ -176,165 +421,489 @@ class SucuriScan {
176
  }
177
 
178
  /**
179
- * Check the nonce comming from any of the settings pages.
180
  *
181
- * @return boolean TRUE if the nonce is valid, FALSE otherwise.
182
  */
183
- public static function sucuriscan_check_options_wpnonce(){
184
- // Create the option_page value if permalink submission.
185
  if(
186
- !isset($_POST['option_page'])
187
- && isset($_POST['permalink_structure'])
188
  ){
189
- $_POST['option_page'] = 'permalink';
190
  }
191
 
192
- // Check if the option_page has an allowed value.
193
- if( isset($_POST['option_page']) ){
194
- $nonce='_wpnonce';
195
- $action = '';
196
 
197
- switch( $_POST['option_page'] ){
198
- case 'general': /* no_break */
199
- case 'writing': /* no_break */
200
- case 'reading': /* no_break */
201
- case 'discussion': /* no_break */
202
- case 'media': /* no_break */
203
- case 'options': /* no_break */
204
- $action = $_POST['option_page'] . '-options';
205
- break;
206
- case 'permalink':
207
- $action = 'update-permalink';
208
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  }
210
 
211
- // Check the nonce validity.
212
- if(
213
- !empty($action)
214
- && isset($_REQUEST[$nonce])
215
- && wp_verify_nonce($_REQUEST[$nonce], $action)
216
- ){
217
- return TRUE;
218
  }
219
  }
220
 
221
  return FALSE;
222
  }
223
 
224
- }
 
 
 
 
 
 
 
 
 
 
 
225
 
226
- /**
227
- * Class to process files and folders.
228
- *
229
- * Here are implemented the functions needed to open, scan, read, create files
230
- * and folders using the built-in PHP class SplFileInfo. The SplFileInfo class
231
- * offers a high-level object oriented interface to information for an individual
232
- * file.
233
- */
234
- class SucuriScanFileInfo extends SucuriScan {
 
 
235
 
236
  /**
237
- * Whether the list of files that can be ignored from the filesystem scan will
238
- * be used to return the directory tree, this should be disabled when scanning a
239
- * directory without the need to filter the items in the list.
240
  *
241
- * @var boolean
242
  */
243
- public $ignore_files = TRUE;
 
 
244
 
245
  /**
246
- * Whether the list of folders that can be ignored from the filesystem scan will
247
- * be used to return the directory tree, this should be disabled when scanning a
248
- * path without the need to filter the items in the list.
249
  *
250
- * @var boolean
251
  */
252
- public $ignore_directories = TRUE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  /**
255
- * Whether the filesystem scanner should run recursively or not.
256
  *
257
- * @var boolean
258
  */
259
- public $run_recursively = TRUE;
 
 
 
 
 
 
260
 
261
  /**
262
- * Class constructor.
 
 
263
  */
264
- public function __construct(){
 
 
 
 
 
 
 
 
 
 
265
  }
266
 
267
  /**
268
- * Retrieve a long text string with signatures of all the files contained
269
- * in the main and subdirectories of the folder specified, also the filesize
270
- * and md5sum of that file. Some folders and files will be ignored depending
271
- * on some rules defined by the developer.
272
  *
273
- * @param string $directory Parent directory where the filesystem scan will start.
274
- * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
275
- * @param boolean $as_array Whether the result of the operation will be returned as an array or string.
276
- * @return array List of files in the main and subdirectories of the folder specified.
277
  */
278
- public function get_directory_tree_md5( $directory='', $scan_with='spl', $as_array=FALSE ){
279
- $project_signatures = '';
280
- $abs_path = rtrim( ABSPATH, '/' );
281
- $files = $this->get_directory_tree($directory, $scan_with);
282
- sort($files);
283
-
284
- if( $as_array ){
285
- $project_signatures = array();
286
  }
287
 
288
- foreach( $files as $filepath){
289
- $file_checksum = @md5_file($filepath);
290
- $filesize = @filesize($filepath);
291
 
292
- if( $as_array ){
293
- $basename = str_replace( $abs_path . '/', '', $filepath );
294
- $project_signatures[$basename] = array(
295
- 'filepath' => $filepath,
296
- 'checksum' => $file_checksum,
297
- 'filesize' => $filesize,
298
- 'filetime' => filectime($filepath),
299
- );
300
- } else {
301
- $filepath = str_replace( $abs_path, $abs_path . '/', $filepath );
302
- $project_signatures .= sprintf(
303
- "%s%s%s%s\n",
304
- $file_checksum,
305
- $filesize,
306
- chr(32),
307
- $filepath
308
- );
309
- }
310
- }
311
 
312
- return $project_signatures;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  }
314
 
315
  /**
316
- * Retrieve a list with all the files contained in the main and subdirectories
317
- * of the folder specified. Some folders and files will be ignored depending
318
- * on some rules defined by the developer.
319
  *
320
- * @param string $directory Parent directory where the filesystem scan will start.
321
- * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
322
- * @return array List of files in the main and subdirectories of the folder specified.
 
323
  */
324
- public function get_directory_tree($directory='', $scan_with='spl'){
325
- if( file_exists($directory) && is_dir($directory) ){
326
- $tree = array();
 
327
 
328
- switch( $scan_with ){
329
- case 'spl':
330
- if( $this->is_spl_available() ){
331
- $tree = $this->get_directory_tree_with_spl($directory);
332
- } else {
333
- $tree = $this->get_directory_tree($directory, 'opendir');
334
- }
335
- break;
336
 
337
- case 'glob':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  $tree = $this->get_directory_tree_with_glob($directory);
339
  break;
340
 
@@ -613,339 +1182,99 @@ class SucuriScanFileInfo extends SucuriScan {
613
  return $all_removed;
614
  }
615
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  }
617
 
618
  /**
619
- * Class responsible for the processing of all the tasks associated to the database.
 
 
 
 
 
 
 
620
  *
621
- * Here are implemented the functions needed to rename tables, generate random names,
622
- * change the Wordpress table prefix and modify the name of all the options linked to
623
- * the previous database prefix.
624
  */
625
- class SucuriScanDatabase extends SucuriScan {
626
 
627
  /**
628
- * List all database tables in a clean array of strings.
 
 
 
 
 
 
 
 
629
  *
630
- * @return array Array of strings.
631
  */
632
- public function get_dbtables(){
633
- global $wpdb;
634
-
635
- $table_names = array();
636
- $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N);
637
-
638
- foreach($tables as $table){
639
- $table_names[] = $table[0];
640
- }
641
 
642
- return $table_names;
643
- }
 
 
 
 
644
 
645
  /**
646
- * Set a new database table prefix to improve the security.
647
  *
648
- * @return void
 
 
 
649
  */
650
- public function new_table_prefix(){
651
- $new_table_prefix = $this->random_char( rand(4,7) ).'_';
652
- $this->set_table_prefix($new_table_prefix);
653
- }
654
 
655
  /**
656
- * Reset the database table prefix with the default value 'wp_'.
657
  *
 
658
  * @return void
659
  */
660
- public function reset_table_prefix(){
661
- $this->set_table_prefix('wp_');
 
 
662
  }
663
 
664
  /**
665
- * Set a new table prefix and changes table names, options, configuration files, etc.
666
  *
667
- * @param string $new_table_prefix The new table prefix.
668
- * @return void
669
  */
670
- private function set_table_prefix( $new_table_prefix='wp_' ){
671
- $resp_parts = array();
672
-
673
- // Set the new table prefix in the configuration file.
674
- $resp_parts[] = $this->new_table_prefix_wpconfig($new_table_prefix);
675
-
676
- // Update options table with the new table prefix.
677
- $resp_parts[] = $this->new_table_prefix_optionstable($new_table_prefix);
678
-
679
- // Update usermeta table with the new table prefix.
680
- $resp_parts[] = $this->new_table_prefix_usermetatable($new_table_prefix);
681
-
682
- // Rename table names with the new table prefix.
683
- $resp_parts[] = $this->new_table_prefix_tablerename($new_table_prefix);
684
 
685
- foreach( $resp_parts as $response ){
686
- if( $response['process'] !== TRUE ){
687
- sucuriscan_error( $response['message'] );
688
- }
689
- }
690
  }
691
 
692
  /**
693
- * Using the new database table prefix, it modifies the main configuration file with that new value.
694
  *
695
- * @param string $new_table_prefix
696
- * @return array An array with two default indexes containing the result of the operation and a message.
697
- */
698
- private function new_table_prefix_wpconfig( $new_table_prefix='' ){
699
- global $wpdb;
700
-
701
- $response = array( 'process'=>FALSE, 'message'=>'' );
702
- $wp_config_path = sucuriscan_get_wpconfig_path();
703
-
704
- if( file_exists($wp_config_path) ){
705
- @chmod($wp_config_path, 0777);
706
-
707
- if( is_writable($wp_config_path) ){
708
- $new_wpconfig = '';
709
- $wpconfig_lines = @file($wp_config_path);
710
-
711
- foreach( $wpconfig_lines as $line ){
712
- $line = str_replace("\n", '', $line);
713
-
714
- if( preg_match('/.*\$table_prefix([ ]+)?=.*/', $line, $match) ){
715
- $line = str_replace($wpdb->prefix, $new_table_prefix, $match[0]);
716
- }
717
-
718
- $new_wpconfig .= "{$line}\n";
719
- }
720
-
721
- $handle = fopen($wp_config_path, 'w');
722
- @fwrite($handle, $new_wpconfig);
723
- @fclose($handle);
724
- @chmod($wp_config_path, 0644);
725
-
726
- $response['process'] = TRUE;
727
- $response['message'] = 'Main configuration file modified.';
728
- } else {
729
- $response['message'] = 'Main configuration file is not writable, you will need to put the new
730
- table prefix <code>'.$new_table_prefix.'</code> manually in <code>wp-config.php</code>.';
731
- }
732
- } else {
733
- $response['message'] = 'Main configuration file was not located: <code>'.$wp_config_path.'</code>.';
734
- }
735
-
736
- return $response;
737
- }
738
-
739
- /**
740
- * Returns a list of all the tables in the selected database containing the same prefix.
741
- *
742
- * @param string $prefix A text string used to filter the tables with a specific prefix.
743
- * @return array A list of all the tables with the prefix specified.
744
- */
745
- public function get_prefixed_tables( $prefix='' ){
746
- global $wpdb;
747
-
748
- $tables = array();
749
- $prefix = empty($prefix) ? $wpdb->prefix : $prefix;
750
- $db_tables = $this->get_dbtables();
751
-
752
- foreach( $db_tables as $table_name ){
753
- if( preg_match("/^{$prefix}/", $table_name) ){
754
- $tables[] = $table_name;
755
- }
756
- }
757
-
758
- return $tables;
759
- }
760
-
761
- /**
762
- * Using the new database table prefix, it modifies the name of all tables with the new value.
763
- *
764
- * @param string $new_table_prefix
765
- * @return array An array with two default indexes containing the result of the operation and a message.
766
- */
767
- private function new_table_prefix_tablerename( $new_table_prefix='' ){
768
- global $wpdb;
769
-
770
- $response = array( 'process'=>FALSE, 'message'=>'' );
771
- $db_tables = $this->get_prefixed_tables();
772
-
773
- $renamed_count = 0;
774
- $total_tables = count($db_tables);
775
- $tables_not_renamed = array();
776
-
777
- foreach( $db_tables as $table_name ){
778
- $table_new_name = $new_table_prefix . str_replace($wpdb->prefix, '', $table_name);
779
- $sql = 'RENAME TABLE `%s` TO `%s`';
780
-
781
- /* Don't use WPDB->Prepare() */
782
- if( $wpdb->query(sprintf($sql, $table_name, $table_new_name))===FALSE ){
783
- $tables_not_renamed[] = $table_name;
784
- } else {
785
- $renamed_count += 1;
786
- }
787
- }
788
-
789
- $response['message'] = 'Database tables renamed: '.$renamed_count.' out of '.$total_tables;
790
-
791
- if( $renamed_count>0 && $renamed_count==$total_tables ){
792
- $response['process'] = TRUE;
793
- $error = $wpdb->set_prefix($new_table_prefix);
794
-
795
- if( is_wp_error($error) ){
796
- foreach( $error->errors as $error_index=>$error_data ){
797
- if( is_array($error_data) ){
798
- foreach( $error_data as $error_data_value ){
799
- $response['message'] .= chr(32) . $error_data_value . '.';
800
- }
801
- }
802
- }
803
- }
804
- } else {
805
- $response['message'] .= '<br>These tables were not renamed, you will need to do it manually:';
806
- $response['message'] .= chr(32) . implode( ',' . chr(32), $table_not_renamed );
807
- }
808
-
809
- return $response;
810
- }
811
-
812
- /**
813
- * Using the new database table prefix, it modifies the name of all options with the new value.
814
- *
815
- * @param string $new_table_prefix
816
- * @return array An array with two default indexes containing the result of the operation and a message.
817
- */
818
- private function new_table_prefix_optionstable( $new_table_prefix='' ){
819
- global $wpdb;
820
-
821
- $response = array( 'process'=>TRUE, 'message'=>'' );
822
- $results = $wpdb->get_results("SELECT option_id, option_name FROM {$wpdb->prefix}options WHERE option_name LIKE '{$wpdb->prefix}%'");
823
-
824
- foreach( $results as $row ){
825
- $row->new_option_name = $new_table_prefix.str_replace($wpdb->prefix, '', $row->option_name);
826
- $sql = "UPDATE {$wpdb->prefix}options SET option_name=%s WHERE option_id=%s LIMIT 1";
827
-
828
- if( $wpdb->query($wpdb->prepare($sql, $row->new_option_name, $row->option_id))===FALSE ){
829
- $response['process'] = FALSE;
830
- }
831
- }
832
-
833
- $response['message'] = $response['process']
834
- ? 'Database table options updated.'
835
- : 'Some entries in the database table <strong>Options</strong> were not updated';
836
-
837
- return $response;
838
- }
839
-
840
- /**
841
- * Using the new database table prefix, it modifies the name of all usermeta keys with the new value.
842
- *
843
- * @param string $new_table_prefix
844
- * @return array An array with two default indexes containing the result of the operation and a message.
845
- */
846
- private function new_table_prefix_usermetatable( $new_table_prefix='' ){
847
- global $wpdb;
848
-
849
- $response = array( 'process'=>TRUE, 'message'=>'' );
850
- $results = $wpdb->get_results("SELECT umeta_id, meta_key FROM {$wpdb->prefix}usermeta WHERE meta_key LIKE '{$wpdb->prefix}%'");
851
-
852
- foreach( $results as $row ){
853
- $row->new_meta_key = $new_table_prefix.str_replace($wpdb->prefix, '', $row->meta_key);
854
- $sql = "UPDATE {$wpdb->prefix}usermeta SET meta_key=%s WHERE umeta_id=%s LIMIT 1";
855
-
856
- if( $wpdb->query($wpdb->prepare($sql, $row->new_meta_key, $row->umeta_id))===FALSE ){
857
- $response['process'] = FALSE;
858
- }
859
- }
860
-
861
- $response['message'] = $response['process']
862
- ? 'Database table usermeta updated.'
863
- : 'Some entries in the database table <strong>UserMeta</strong> were not updated';
864
-
865
- return $response;
866
- }
867
-
868
- }
869
-
870
- /**
871
- * File-based cache library.
872
- *
873
- * WP_Object_Cache [1] is WordPress' class for caching data which may be
874
- * computationally expensive to regenerate, such as the result of complex
875
- * database queries. However the object cache is non-persistent. This means that
876
- * data stored in the cache resides in memory only and only for the duration of
877
- * the request. Cached data will not be stored persistently across page loads
878
- * unless of the installation of a 3party persistent caching plugin [2].
879
- *
880
- * [1] http://codex.wordpress.org/Class_Reference/WP_Object_Cache
881
- * [2] http://codex.wordpress.org/Class_Reference/WP_Object_Cache#Persistent_Caching
882
- */
883
- class SucuriScanCache extends SucuriScan {
884
-
885
- /**
886
- * The unique name (or identifier) of the file with the data.
887
- *
888
- * The file should be located in the same folder where the dynamic data
889
- * generated by the plugin is stored, and using the following format [1], it
890
- * most be a PHP file because it is expected to have an exit point in the first
891
- * line of the file causing it to stop the execution if a unauthorized user
892
- * tries to access it directly.
893
- *
894
- * [1] /public/data/sucuri-DATASTORE.php
895
- *
896
- * @var null|string
897
- */
898
- private $datastore = NULL;
899
-
900
- /**
901
- * The full path of the datastore file.
902
- *
903
- * @var string
904
- */
905
- private $datastore_path = '';
906
-
907
- /**
908
- * Whether the datastore file is usable or not.
909
- *
910
- * This variable will only be TRUE if the datastore file specified exists, is
911
- * writable and readable, in any other case it will always be FALSE.
912
- *
913
- * @var boolean
914
- */
915
- private $usable_datastore = FALSE;
916
-
917
- /**
918
- * Class constructor.
919
- *
920
- * @param string $datastore Unique name (or identifier) of the file with the data.
921
- * @return void
922
- */
923
- public function __construct( $datastore='' ){
924
- $this->datastore = $datastore;
925
- $this->datastore_path = $this->datastore_file_path();
926
- $this->usable_datastore = (bool) $this->datastore_path;
927
- }
928
-
929
- /**
930
- * Default attributes for every datastore file.
931
- *
932
- * @return string Default attributes for every datastore file.
933
- */
934
- private function datastore_default_info(){
935
- $attrs = array(
936
- 'datastore' => $this->datastore,
937
- 'created_on' => time(),
938
- 'updated_on' => time(),
939
- );
940
-
941
- return $attrs;
942
- }
943
-
944
- /**
945
- * Default content of every datastore file.
946
- *
947
- * @param array $finfo Rainbow table with the key names and decoded values.
948
- * @return string Default content of every datastore file.
949
  */
950
  private function datastore_info( $finfo=array() ){
951
  $attrs = $this->datastore_default_info();
@@ -1058,12 +1387,10 @@ class SucuriScanCache extends SucuriScan {
1058
  );
1059
 
1060
  if( $this->usable_datastore ){
1061
- $data_lines = @file($this->datastore_path);
1062
 
1063
  if( !empty($data_lines) ){
1064
  foreach( $data_lines as $line ){
1065
- $line = trim($line);
1066
-
1067
  if( preg_match('/^\/\/ ([a-z_]+)=(.*);$/', $line, $match) ){
1068
  $data_object['info'][$match[1]] = $match[2];
1069
  }
@@ -1073,7 +1400,7 @@ class SucuriScanCache extends SucuriScan {
1073
  $this->valid_key_name($match[1])
1074
  && !array_key_exists($match[1], $data_object)
1075
  ){
1076
- $data_object['entries'][$match[1]] = json_decode( $match[2], $assoc );
1077
  }
1078
  }
1079
  }
@@ -1248,3206 +1575,3379 @@ class SucuriScanCache extends SucuriScan {
1248
  }
1249
 
1250
  /**
1251
- * Check whether the current site is working as a multi-site instance.
1252
  *
1253
- * @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
1254
- */
1255
- function sucuriscan_is_multisite(){
1256
- if( function_exists('is_multisite') && is_multisite() ){ return TRUE; }
1257
- return FALSE;
1258
- }
1259
-
1260
- /**
1261
- * Check whether the IP address specified is a valid IPv4 format.
 
 
 
 
 
 
 
1262
  *
1263
- * @param string $remote_addr The host IP address.
1264
- * @return boolean TRUE if the address specified is a valid IPv4 format, FALSE otherwise.
1265
  */
1266
- function sucuriscan_is_valid_ipv4( $remote_addr='' ){
1267
- if( preg_match('/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/', $remote_addr, $match) ){
1268
- for( $i=0; $i<4; $i++ ){
1269
- if( $match[$i] > 255 ){ return FALSE; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1270
  }
1271
 
1272
- return TRUE;
1273
  }
1274
 
1275
- return FALSE;
1276
- }
1277
-
1278
- if( !function_exists('sucuriscan_init') ){
1279
  /**
1280
- * Initialization code for the plugin.
1281
  *
1282
- * The initial variables and information needed by the plugin during the
1283
- * execution of other functions will be generated. Things like the real IP
1284
- * address of the client when it has been forwarded or it's behind an external
1285
- * service like a Proxy.
1286
  *
1287
- * @return void
 
 
 
 
 
 
 
 
1288
  */
1289
- function sucuriscan_init(){
1290
- if(
1291
- isset($_SERVER['HTTP_X_FORWARDED_FOR'])
1292
- && preg_match("/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/", $_SERVER['HTTP_X_FORWARDED_FOR'])
1293
- && sucuriscan_is_valid_ipv4($_SERVER['HTTP_X_FORWARDED_FOR'])
1294
- ){
1295
- $_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
1296
- $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
1297
- }
1298
- }
1299
 
1300
- add_action('init', 'sucuriscan_init', 1);
1301
- }
1302
 
1303
- if( !function_exists('sucuriscan_create_uploaddir') ){
1304
  /**
1305
- * Create a folder in the WordPress upload directory where the plugin will
1306
- * store all the temporal or dynamic information.
1307
  *
1308
- * @return void
 
 
 
 
 
 
 
 
 
1309
  */
1310
- function sucuriscan_create_uploaddir(){
1311
- $plugin_upload_folder = sucuriscan_dir_filepath();
 
1312
 
1313
- if( !file_exists($plugin_upload_folder) ){
1314
- if( @mkdir($plugin_upload_folder) ){
1315
- // Create last-logins datastore file.
1316
- sucuriscan_lastlogins_datastore_exists();
1317
 
1318
- // Create a htaccess file to deny access from all.
1319
- @file_put_contents(
1320
- $plugin_upload_folder . '/.htaccess',
1321
- "Order Deny,Allow\nDeny from all",
1322
- LOCK_EX
1323
- );
1324
 
1325
- // Create an index.html to avoid directory listing.
1326
- @file_put_contents(
1327
- $plugin_upload_folder . '/index.html',
1328
- '<!-- Attemp to prevent the directory listing. -->',
1329
- LOCK_EX
1330
- );
1331
- } else {
1332
- sucuriscan_error(
1333
- 'Data folder does not exists and could not be created. You will need to
1334
- create this folder manually and give it write permissions:<br><br><code>'
1335
- . $plugin_upload_folder . '</code>'
1336
- );
1337
- }
 
 
1338
  }
1339
- }
1340
 
1341
- add_action('admin_init', 'sucuriscan_create_uploaddir');
1342
- }
1343
 
1344
- if( !function_exists('sucuriscan_admin_script_style_registration') ){
1345
  /**
1346
- * Define which javascript and css files will be loaded in the header of the page.
1347
- * @return void
 
 
 
 
 
1348
  */
1349
- function sucuriscan_admin_script_style_registration(){
1350
- $asset_version = '';
1351
-
1352
- if( strlen(SUCURISCAN_PLUGIN_CHECKSUM) >= 7 ){
1353
- $asset_version = substr(SUCURISCAN_PLUGIN_CHECKSUM, 0, 7);
1354
  }
1355
 
1356
- wp_register_style( 'sucuriscan', SUCURI_URL . '/inc/css/sucuriscan-default-css.css', array(), $asset_version );
1357
- wp_register_script( 'sucuriscan', SUCURI_URL . '/inc/js/sucuriscan-scripts.js', array(), $asset_version );
1358
 
1359
- wp_enqueue_style( 'sucuriscan' );
1360
- wp_enqueue_script( 'sucuriscan' );
 
 
 
1361
  }
1362
 
1363
- add_action( 'admin_enqueue_scripts', 'sucuriscan_admin_script_style_registration', 1 );
1364
- }
1365
-
1366
- /**
1367
- * Returns the system filepath to the relevant user uploads directory for this
1368
- * site. This is a multisite capable function.
1369
- *
1370
- * @param string $path The relative path that needs to be completed to get the absolute path.
1371
- * @return string The full filesystem path including the directory specified.
1372
- */
1373
- function sucuriscan_dir_filepath($path = ''){
1374
- return SucuriScan::datastore_folder_path($path);
1375
- }
1376
-
1377
- /**
1378
- * List an associative array with the sub-pages of this plugin.
1379
- *
1380
- * @param boolean $for_navbar Either TRUE or FALSE indicanting that the first page will be named Dashboard.
1381
- * @return array List of pages and sub-pages of this plugin.
1382
- */
1383
- function sucuriscan_pages( $for_navbar=FALSE ){
1384
- $pages = array(
1385
- 'sucuriscan' => 'Dashboard',
1386
- 'sucuriscan_scanner' => 'Malware Scan',
1387
- 'sucuriscan_monitoring' => 'Firewall (WAF)',
1388
- 'sucuriscan_hardening' => 'Hardening',
1389
- 'sucuriscan_posthack' => 'Post-Hack',
1390
- 'sucuriscan_lastlogins' => 'Last Logins',
1391
- 'sucuriscan_settings' => 'Settings',
1392
- 'sucuriscan_infosys' => 'Site Info',
1393
- );
1394
-
1395
- return $pages;
1396
- }
1397
 
1398
- /**
1399
- * Generate the menu and submenus for the plugin in the admin interface.
1400
- *
1401
- * @return void
1402
- */
1403
- function sucuriscan_menu(){
1404
- // Add main menu link.
1405
- add_menu_page(
1406
- 'Sucuri Security',
1407
- 'Sucuri Security',
1408
- 'manage_options',
1409
- 'sucuriscan',
1410
- 'sucuriscan_page',
1411
- SUCURI_URL . '/inc/images/menu-icon.png'
1412
- );
1413
 
1414
- $sub_pages = sucuriscan_pages();
 
 
 
1415
 
1416
- foreach( $sub_pages as $sub_page_func => $sub_page_title ){
1417
- $page_func = $sub_page_func . '_page';
 
1418
 
1419
- add_submenu_page(
1420
- 'sucuriscan',
1421
- $sub_page_title,
1422
- $sub_page_title,
1423
- 'manage_options',
1424
- $sub_page_func,
1425
- $page_func
1426
- );
1427
  }
1428
- }
1429
 
1430
- if( !function_exists('sucuriscan_handle_old_plugin') ){
1431
  /**
1432
- * Remove the old Sucuri plugins considering that with the new version (after
1433
- * 1.6.0) all the functionality of the others will be merged here, this will
1434
- * remove duplicated functionality, duplicated bugs and/or duplicated
1435
- * maintenance reports allowing us to focus in one unique project.
1436
  *
1437
- * @return void
 
1438
  */
1439
- function sucuriscan_handle_old_plugin(){
1440
- $sucuri_fileinfo = new SucuriScanFileInfo();
1441
- $sucuri_fileinfo->ignore_files = FALSE;
1442
- $sucuri_fileinfo->ignore_directories = FALSE;
1443
-
1444
- $plugins = array(
1445
- 'sucuri-wp-plugin/sucuri.php',
1446
- 'sucuri-cloudproxy-waf/cloudproxy.php',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1447
  );
1448
 
1449
- foreach( $plugins as $plugin ){
1450
- $plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin );
1451
-
1452
- if( file_exists($plugin_directory) ){
1453
- if( is_plugin_active($plugin) ){
1454
- deactivate_plugins($plugin);
1455
  }
 
 
 
 
1456
 
1457
- $plugin_removed = $sucuri_fileinfo->remove_directory_tree($plugin_directory);
 
 
1458
  }
1459
  }
 
 
1460
  }
1461
 
1462
- add_action('admin_init', 'sucuriscan_handle_old_plugin');
1463
- }
 
 
 
 
 
 
 
 
1464
 
1465
- /**
1466
- * Initialize the execute of the main plugin's functions.
1467
- *
1468
- * This will load the menu options in the WordPress administrator panel, and
1469
- * execute the bootstrap function of the plugin.
1470
- */
1471
- add_action('admin_menu', 'sucuriscan_menu');
1472
- add_action('sucuriscan_scheduled_scan', 'sucuriscan_filesystem_scan');
1473
- remove_action('wp_head', 'wp_generator');
1474
 
1475
- /**
1476
- * Validate email address.
1477
- *
1478
- * This use the native PHP function filter_var which is available in PHP >=
1479
- * 5.2.0 if it is not found in the interpreter this function will sue regular
1480
- * expressions to check whether the email address passed is valid or not.
1481
- *
1482
- * @see http://www.php.net/manual/en/function.filter-var.php
1483
- *
1484
- * @param string $email The string that will be validated as an email address.
1485
- * @return boolean TRUE if the email address passed to the function is valid, FALSE if not.
1486
- */
1487
- function is_valid_email( $email='' ){
1488
- if( function_exists('filter_var') ){
1489
- return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
1490
- } else {
1491
- $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix';
1492
- return (bool) preg_match($pattern, $email);
1493
  }
1494
- }
1495
 
1496
- /**
1497
- * Cut a long text to the length specified, and append suspensive points at the end.
1498
- *
1499
- * @param string $text String of characters that will be cut.
1500
- * @param integer $length Maximum length of the returned string, default is 10.
1501
- * @return string Short version of the text specified.
1502
- */
1503
- function sucuriscan_excerpt( $text='', $length=10 ){
1504
- $text_length = strlen($text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1505
 
1506
- if( $text_length > $length ){
1507
- return substr( $text, 0, $length ) . '...';
1508
  }
1509
 
1510
- return $text;
1511
- }
 
 
 
 
 
 
 
 
 
 
 
1512
 
1513
- /**
1514
- * Check whether the email notifications will be sent in HTML or Plain/Text.
1515
- *
1516
- * @return boolean Whether the emails will be in HTML or Plain/Text.
1517
- */
1518
- function sucuriscan_prettify_mails(){
1519
- return ( sucuriscan_get_option('sucuriscan_prettify_mails') === 'enabled' );
1520
- }
1521
 
1522
- /**
1523
- * Check whether the SSL certificates will be verified while executing a HTTP
1524
- * request or not. This is only for customization of the administrator, in fact
1525
- * not verifying the SSL certificates can lead to a "Man in the Middle" attack.
1526
- *
1527
- * @return boolean Whether the SSL certs will be verified while sending a request.
1528
- */
1529
- function sucuriscan_verify_ssl_cert(){
1530
- return ( sucuriscan_get_option('sucuriscan_verify_ssl_cert') === 'true' );
1531
- }
 
 
 
1532
 
1533
- /**
1534
- * Send a message to a specific email address.
1535
- *
1536
- * @param string $email The email address of the recipient that will receive the message.
1537
- * @param string $subject The reason of the message that will be sent.
1538
- * @param string $message Body of the message that will be sent.
1539
- * @param array $data_set Optional parameter to add more information to the notification.
1540
- * @return boolean Whether the email contents were sent successfully.
1541
- */
1542
- function sucuriscan_send_mail( $email='', $subject='', $message='', $data_set=array() ){
1543
- $headers = array();
1544
- $subject = ucwords(strtolower($subject));
1545
- $wp_domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : get_option('siteurl');
1546
- $force = FALSE;
1547
- $debug = FALSE;
1548
-
1549
- // Check whether the mail will be printed in the site instead of sent.
1550
- if(
1551
- isset($data_set['Debug'])
1552
- && $data_set['Debug'] == TRUE
1553
- ){
1554
- $debug = TRUE;
1555
- unset($data_set['Debug']);
1556
- }
1557
 
1558
- // Check whether the mail will be even if the limit per hour was reached or not.
1559
- if(
1560
- isset($data_set['Force'])
1561
- && $data_set['Force'] == TRUE
1562
- ){
1563
- $force = TRUE;
1564
- unset($data_set['Force']);
1565
  }
1566
 
1567
- // Check whether the email notifications will be sent in HTML or Plain/Text.
1568
- if( sucuriscan_prettify_mails() ){
1569
- $headers = array( 'Content-type: text/html' );
1570
- $data_set['PrettifyType'] = 'pretty';
1571
- }
 
 
 
 
1572
 
1573
- if( !sucuriscan_emails_per_hour_reached() || $force || $debug ){
1574
- $message = sucuriscan_prettify_mail($subject, $message, $data_set);
 
1575
 
1576
- if( $debug ){ die($message); }
 
1577
 
1578
- $email_sent = wp_mail(
1579
- $email,
1580
- "Sucuri WP Notification: {$wp_domain} - {$subject}",
1581
- $message,
1582
- $headers
1583
- );
 
 
1584
 
1585
- if( $email_sent ){
1586
- $emails_sent_num = (int) sucuriscan_get_option('sucuriscan_emails_sent');
1587
- update_option( 'sucuriscan_emails_sent', $emails_sent_num + 1 );
1588
- update_option( 'sucuriscan_last_email_at', time() );
1589
 
1590
- return TRUE;
 
 
 
 
 
 
 
 
 
 
 
1591
  }
1592
- } else {
1593
- // sucuriscan_error( 'Cant send more emails for the next hour' );
1594
- }
1595
 
1596
- return FALSE;
1597
- }
1598
 
1599
- /**
1600
- * Check whether the maximum quantity of emails per hour was reached.
1601
- *
1602
- * @return boolean Whether the quota emails per hour was reached.
1603
- */
1604
- function sucuriscan_emails_per_hour_reached(){
1605
- $max_emails_per_hour = sucuriscan_get_option('sucuriscan_emails_per_hour');
 
 
1606
 
1607
- if( $max_emails_per_hour != 'unlimited' ){
1608
- // Check if we are still in that sixty minutes.
1609
- $current_time = time();
1610
- $last_email_at = sucuriscan_get_option('sucuriscan_last_email_at');
1611
- $diff_time = abs( $current_time - $last_email_at );
1612
 
1613
- if( $diff_time <= 3600 ){
1614
- // Check if the quantity of emails sent is bigger than the configured.
1615
- $emails_sent = (int) sucuriscan_get_option('sucuriscan_emails_sent');
1616
- $max_emails_per_hour = intval($max_emails_per_hour);
1617
 
1618
- if( $emails_sent >= $max_emails_per_hour ){
1619
- return TRUE;
1620
  }
1621
- } else {
1622
- // Reset the counter of emails sent.
1623
- update_option( 'sucuriscan_emails_sent', 0 );
1624
  }
 
 
1625
  }
1626
 
1627
- return FALSE;
1628
- }
 
 
 
 
 
 
1629
 
1630
- /**
1631
- * Generate a HTML version of the message that will be sent through an email.
1632
- *
1633
- * @param string $subject The reason of the message that will be sent.
1634
- * @param string $message Body of the message that will be sent.
1635
- * @param array $data_set Optional parameter to add more information to the notification.
1636
- * @return string The message formatted in a HTML template.
1637
- */
1638
- function sucuriscan_prettify_mail( $subject='', $message='', $data_set=array() ){
1639
- $prettify_type = isset($data_set['PrettifyType']) ? $data_set['PrettifyType'] : 'simple';
1640
- $template_name = 'notification-' . $prettify_type;
1641
- $remote_addr = sucuriscan_get_remoteaddr();
1642
- $user = wp_get_current_user();
1643
- $display_name = '';
1644
 
1645
- if(
1646
- $user instanceof WP_User
1647
- && isset($user->user_login)
1648
- && !empty($user->user_login)
1649
- ){
1650
- $display_name = sprintf( 'User: %s (%s)', $user->display_name, $user->user_login );
1651
  }
1652
 
1653
- $mail_variables = array(
1654
- 'TemplateTitle' => 'Sucuri WP Notification',
1655
- 'Subject' => $subject,
1656
- 'Website' => get_option('siteurl'),
1657
- 'RemoteAddress' => $remote_addr,
1658
- 'Message' => $message,
1659
- 'User' => $display_name,
1660
- 'Time' => current_time('mysql'),
1661
- );
 
 
 
 
1662
 
1663
- foreach($data_set as $var_key=>$var_value){
1664
- $mail_variables[$var_key] = $var_value;
1665
  }
1666
 
1667
- return sucuriscan_get_section( $template_name, $mail_variables );
1668
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1669
 
1670
- /**
1671
- * Prints a HTML alert in the WordPress admin interface.
1672
- *
1673
- * @param string $type The type of alert, it can be either Updated or Error.
1674
- * @param string $message The message that will be printed in the alert.
1675
- * @return void
1676
- */
1677
- function sucuriscan_admin_notice($type='updated', $message=''){
1678
- $alert_id = rand(100, 999);
1679
- if( !empty($message) ): ?>
1680
- <div id="sucuriscan-alert-<?php echo $alert_id; ?>" class="<?php echo $type; ?> sucuriscan-alert sucuriscan-alert-<?php echo $type; ?>">
1681
- <a href="javascript:void(0)" class="close" onclick="sucuriscan_alert_close('<?php echo $alert_id; ?>')">&times;</a>
1682
- <p><?php _e($message); ?></p>
1683
- </div>
1684
- <?php endif;
1685
- }
1686
 
1687
- /**
1688
- * Prints a HTML alert of type ERROR in the WordPress admin interface.
1689
- *
1690
- * @param string $error_msg The message that will be printed in the alert.
1691
- * @return void
1692
- */
1693
- function sucuriscan_error( $error_msg='' ){
1694
- sucuriscan_admin_notice( 'error', '<b>Sucuri:</b> ' . $error_msg );
1695
- }
1696
 
1697
- /**
1698
- * Prints a HTML alert of type INFO in the WordPress admin interface.
1699
- *
1700
- * @param string $info_msg The message that will be printed in the alert.
1701
- * @return void
1702
- */
1703
- function sucuriscan_info( $info_msg='' ){
1704
- sucuriscan_admin_notice( 'updated', '<b>Sucuri:</b> ' . $info_msg );
1705
- }
1706
 
1707
- /**
1708
- * Verify the nonce of the previous page after a form submission. If the
1709
- * validation fails the execution of the script will be stopped and a dead page
1710
- * will be printed to the client using the official WordPress method.
1711
- *
1712
- * @return boolean Either TRUE or FALSE if the nonce is valid or not respectively.
1713
- */
1714
- function sucuriscan_check_page_nonce(){
1715
- if( !empty($_POST) ){
1716
- $nonce_name = 'sucuriscan_page_nonce';
1717
 
1718
- if( !isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name], $nonce_name) ){
1719
- wp_die(__('WordPress Nonce verification failed, try again going back and checking the form.') );
 
 
1720
 
1721
- return FALSE;
1722
  }
1723
- }
1724
 
1725
- return TRUE;
1726
- }
1727
-
1728
- /**
1729
- * Replace all pseudo-variables from a string of characters.
1730
- *
1731
- * @param string $content The content of a template file which contains pseudo-variables.
1732
- * @param array $params List of pseudo-variables that will be replaced in the template.
1733
- * @return string The content of the template with the pseudo-variables replated.
1734
- */
1735
- function sucuriscan_replace_pseudovars( $content='', $params=array() ){
1736
- if( is_array($params) ){
1737
- foreach( $params as $tpl_key => $tpl_value ){
1738
- $tpl_key = '%%SUCURI.' . $tpl_key . '%%';
1739
- $content = str_replace( $tpl_key, $tpl_value, $content );
1740
  }
1741
 
1742
- return $content;
1743
  }
1744
 
1745
- return FALSE;
1746
  }
1747
 
1748
  /**
1749
- * Complement the list of pseudo-variables that will be used in the base
1750
- * template files, this will also generate the navigation bar and detect which
1751
- * items in it are selected by the current page.
 
 
 
 
 
 
 
 
1752
  *
1753
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1754
- * @return array A complementary list of pseudo-variables for the template files.
1755
  */
1756
- function sucuriscan_links_and_navbar( $params=array() ){
1757
- $params = is_array($params) ? $params : array();
1758
- $sub_pages = sucuriscan_pages(TRUE);
1759
 
1760
- $params['Navbar'] = '';
1761
- $params['CurrentPageFunc'] = isset($_GET['page']) ? $_GET['page'] : '';
1762
-
1763
- foreach( $sub_pages as $sub_page_func => $sub_page_title ){
1764
- $func_parts = explode( '_', $sub_page_func, 2 );
 
 
1765
 
1766
- if( isset($func_parts[1]) ){
1767
- $unique_name = $func_parts[1];
1768
- $pseudo_var = 'URL.' . ucwords($unique_name);
1769
- } else {
1770
- $unique_name = '';
1771
- $pseudo_var = 'URL.Home';
1772
  }
1773
 
1774
- $params[$pseudo_var] = sucuriscan_get_url($unique_name);
 
1775
 
1776
- $navbar_item_css_class = 'nav-tab';
 
 
 
 
 
 
 
 
 
 
1777
 
1778
- if( $params['CurrentPageFunc'] == $sub_page_func ){
1779
- $navbar_item_css_class .= chr(32) . 'nav-tab-active';
 
 
 
 
1780
  }
1781
 
1782
- $params['Navbar'] .= sprintf(
1783
- '<a class="%s" href="%s">%s</a>' . "\n",
1784
- $navbar_item_css_class,
1785
- $params[$pseudo_var],
1786
- $sub_page_title
1787
- );
1788
- }
1789
 
1790
- return $params;
1791
- }
 
 
1792
 
1793
- /**
1794
- * Generate a HTML code using a template and replacing all the pseudo-variables
1795
- * by the dynamic variables provided by the developer through one of the parameters
1796
- * of the function.
1797
- *
1798
- * @param string $template Filename of the template that will be used to generate the page.
1799
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1800
- * @param boolean $type Either page, section or snippet indicating the type of template that will be retrieved.
1801
- * @return string The formatted HTML page after replace all the pseudo-variables.
1802
- */
1803
- function sucuriscan_get_template( $template='', $params=array(), $type='page' ){
1804
- switch( $type ){
1805
- case 'page': /* no_break */
1806
- case 'section':
1807
- $template_path_pattern = '%s/%s/inc/tpl/%s.html.tpl';
1808
- break;
1809
- case 'snippet':
1810
- $template_path_pattern = '%s/%s/inc/tpl/%s.snippet.tpl';
1811
- break;
1812
- }
1813
 
1814
- $template_content = '';
1815
- $template_path = sprintf( $template_path_pattern, WP_PLUGIN_DIR, SUCURISCAN_PLUGIN_FOLDER, $template );
1816
- $params = is_array($params) ? $params : array();
1817
 
1818
- if( file_exists($template_path) && is_readable($template_path) ){
1819
- $template_content = file_get_contents($template_path);
 
 
 
 
 
 
 
 
1820
 
1821
- $current_page = isset($_GET['page']) ? htmlentities($_GET['page']) : '';
1822
- $params['CurrentURL'] = sprintf( '%s/wp-admin/admin.php?page=%s', site_url(), $current_page );
1823
- $params['SucuriURL'] = SUCURI_URL;
1824
 
1825
- // Replace the global pseudo-variables in the section/snippets templates.
1826
- if(
1827
- $template == 'base'
1828
- && isset($params['PageContent'])
1829
- && preg_match('/%%SUCURI\.(.+)%%/', $params['PageContent'])
1830
- ){
1831
- $params['PageContent'] = sucuriscan_replace_pseudovars( $params['PageContent'], $params );
1832
  }
1833
 
1834
- $template_content = sucuriscan_replace_pseudovars( $template_content, $params );
1835
  }
1836
 
1837
- if( $template == 'base' || $type != 'page' ){
1838
- return $template_content;
1839
- }
 
 
 
 
 
 
 
1840
 
1841
- return sucuriscan_get_base_template( $template_content, $params );
1842
- }
 
 
 
 
1843
 
1844
- /**
1845
- * Gather and generate the information required globally by all the template files.
1846
- *
1847
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1848
- * @return array A complementary list of pseudo-variables for the template files.
1849
- */
1850
- function sucuriscan_shared_params( $params=array() ){
1851
- $params = is_array($params) ? $params : array();
1852
 
1853
- // Base parameters, required to render all the pages.
1854
- $params = sucuriscan_links_and_navbar($params);
1855
 
1856
- // Global parameters, used through out all the pages.
1857
- $params['PageTitle'] = isset($params['PageTitle']) ? '('.$params['PageTitle'].')' : '';
1858
- $params['PageNonce'] = wp_create_nonce('sucuriscan_page_nonce');
1859
- $params['PageStyleClass'] = isset($params['PageStyleClass']) ? $params['PageStyleClass'] : 'base';
1860
- $params['CleanDomain'] = sucuriscan_get_domain();
1861
- $params['AdminEmail'] = sucuriscan_get_site_email();
 
 
 
 
 
1862
 
1863
- return $params;
1864
- }
1865
 
1866
- /**
1867
- * Generate a HTML code using a template and replacing all the pseudo-variables
1868
- * by the dynamic variables provided by the developer through one of the parameters
1869
- * of the function.
1870
- *
1871
- * @param string $html The HTML content of a template file with its pseudo-variables parsed.
1872
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1873
- * @return string The formatted HTML content of the base template.
1874
- */
1875
- function sucuriscan_get_base_template( $html='', $params=array() ){
1876
- $params = is_array($params) ? $params : array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1877
 
1878
- $params = sucuriscan_shared_params($params);
1879
- $params['PageContent'] = $html;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1880
 
1881
- return sucuriscan_get_template( 'base', $params );
1882
- }
1883
 
1884
- /**
1885
- * Generate a HTML code using a template and replacing all the pseudo-variables
1886
- * by the dynamic variables provided by the developer through one of the parameters
1887
- * of the function.
1888
- *
1889
- * @param string $template Filename of the template that will be used to generate the page.
1890
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1891
- * @return string The formatted HTML page after replace all the pseudo-variables.
1892
- */
1893
- function sucuriscan_get_section($template='', $params=array()){
1894
- $params = sucuriscan_shared_params($params);
 
 
 
 
 
 
1895
 
1896
- return sucuriscan_get_template( $template, $params, 'section' );
1897
- }
 
 
 
 
 
 
1898
 
1899
- /**
1900
- * Generate a HTML code using a template and replacing all the pseudo-variables
1901
- * by the dynamic variables provided by the developer through one of the parameters
1902
- * of the function.
1903
- *
1904
- * @param string $template Filename of the template that will be used to generate the page.
1905
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1906
- * @return string The formatted HTML page after replace all the pseudo-variables.
1907
- */
1908
- function sucuriscan_get_modal($template='', $params=array()){
1909
- $required = array(
1910
- 'Title' => 'Lorem ipsum dolor sit amet',
1911
- 'CssClass' => '',
1912
- 'Content' => '<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
1913
- eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
1914
- veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
1915
- consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
1916
- cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
1917
- proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>',
1918
- );
1919
 
1920
- if( !empty($template) && $template != 'none' ){
1921
- $params['Content'] = sucuriscan_get_section($template);
1922
- }
1923
 
1924
- foreach( $required as $param_name => $param_value ){
1925
- if( !isset($params[$param_name]) ){
1926
- $params[$param_name] = $param_value;
1927
  }
1928
- }
1929
 
1930
- $params = sucuriscan_shared_params($params);
1931
-
1932
- return sucuriscan_get_template( 'modalwindow', $params, 'section' );
1933
- }
1934
 
1935
- /**
1936
- * Generate a HTML code using a template and replacing all the pseudo-variables
1937
- * by the dynamic variables provided by the developer through one of the parameters
1938
- * of the function.
1939
- *
1940
- * @param string $template Filename of the template that will be used to generate the page.
1941
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1942
- * @return string The formatted HTML page after replace all the pseudo-variables.
1943
- */
1944
- function sucuriscan_get_snippet($template='', $params=array()){
1945
- return sucuriscan_get_template( $template, $params, 'snippet' );
1946
- }
1947
 
1948
- /**
1949
- * Generate an URL pointing to the page indicated in the function and that must
1950
- * be loaded through the administrator panel.
1951
- *
1952
- * @param string $page Short name of the page that will be generated.
1953
- * @return string Full string containing the link of the page.
1954
- */
1955
- function sucuriscan_get_url($page=''){
1956
- $url_path = admin_url('admin.php?page=sucuriscan');
1957
 
1958
- if( !empty($page) ){
1959
- $url_path .= '_' . $page;
1960
- }
1961
 
1962
- return $url_path;
1963
- }
 
 
 
1964
 
1965
- /**
1966
- * Retrieve a new set of keys for the WordPress configuration file using the
1967
- * official API provided by WordPress itself.
1968
- *
1969
- * @return array A list of the new set of keys generated by WordPress API.
1970
- */
1971
- function sucuriscan_get_new_config_keys(){
1972
- $request = wp_remote_get('https://api.wordpress.org/secret-key/1.1/salt/');
1973
 
1974
- if( !is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200 ){
1975
- if( preg_match_all("/define\('([A-Z_]+)',[ ]+'(.*)'\);/", $request['body'], $match) ){
1976
- $new_keys = array();
1977
 
1978
- foreach($match[1] as $i=>$value){
1979
- $new_keys[$value] = $match[2][$i];
1980
  }
1981
-
1982
- return $new_keys;
1983
  }
1984
- }
1985
 
1986
- return FALSE;
1987
- }
1988
 
1989
- /**
1990
- * Modify the WordPress configuration file and change the keys that were defined
1991
- * by a new random-generated list of keys retrieved from the official WordPress
1992
- * API. The result of the operation will be either FALSE in case of error, or an
1993
- * array containing multiple indexes explaining the modification, among them you
1994
- * will find the old and new keys.
1995
- *
1996
- * @return false|array Either FALSE in case of error, or an array with the old and new keys.
1997
- */
1998
- function sucuriscan_set_new_config_keys(){
1999
- $new_wpconfig = '';
2000
- $wp_config_path = ABSPATH.'wp-config.php';
2001
-
2002
- if( file_exists($wp_config_path) ){
2003
- $wp_config_lines = file($wp_config_path);
2004
- $new_keys = sucuriscan_get_new_config_keys();
2005
- $old_keys = array();
2006
- $old_keys_string = $new_keys_string = '';
2007
-
2008
- foreach($wp_config_lines as $wp_config_line){
2009
- $wp_config_line = str_replace("\n", '', $wp_config_line);
2010
-
2011
- if( preg_match("/define\('([A-Z_]+)',([ ]+)'(.*)'\);/", $wp_config_line, $match) ){
2012
- $key_name = $match[1];
2013
- if( array_key_exists($key_name, $new_keys) ){
2014
- $white_spaces = $match[2];
2015
- $old_keys[$key_name] = $match[3];
2016
- $wp_config_line = "define('{$key_name}',{$white_spaces}'{$new_keys[$key_name]}');";
2017
-
2018
- $old_keys_string .= "define('{$key_name}',{$white_spaces}'{$old_keys[$key_name]}');\n";
2019
- $new_keys_string .= "{$wp_config_line}\n";
 
 
 
 
2020
  }
 
 
2021
  }
2022
 
2023
- $new_wpconfig .= "{$wp_config_line}\n";
2024
- }
 
 
 
 
 
 
2025
 
2026
- $response = array(
2027
- 'updated' => is_writable($wp_config_path),
2028
- 'old_keys' => $old_keys,
2029
- 'old_keys_string' => $old_keys_string,
2030
- 'new_keys' => $new_keys,
2031
- 'new_keys_string' => $new_keys_string,
2032
- 'new_wpconfig' => $new_wpconfig,
2033
- );
2034
 
2035
- if( $response['updated'] ){
2036
- file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
2037
  }
2038
- return $response;
 
2039
  }
2040
- return FALSE;
2041
  }
2042
 
2043
  /**
2044
- * Generate and set a new password for a specific user not in session.
 
 
 
 
 
 
2045
  *
2046
- * @param integer $user_id The user identifier that will be changed, this must be different than the user in session.
2047
- * @return boolean Either TRUE or FALSE in case of success or error respectively.
 
 
 
2048
  */
2049
- function sucuriscan_new_password($user_id=0){
2050
- $user_id = intval($user_id);
2051
- $current_user = wp_get_current_user();
2052
 
2053
- if( $user_id>0 && $user_id!=$current_user->ID ){
2054
- $user = get_userdata($user_id);
2055
- $new_password = wp_generate_password(15, TRUE, FALSE);
 
 
 
 
 
 
2056
 
2057
- $data_set = array( 'User'=>$user->display_name );
2058
- $message = "The password for your user account in the website mentioned has been changed by an administrator,
2059
- this is the new password automatically generated by the system, please update ASAP.<br>
2060
- <div style='display:inline-block;background:#ddd;font-family:monaco,monospace,courier;
2061
- font-size:30px;margin:0;padding:15px;border:1px solid #999'>{$new_password}</div>";
2062
- sucuriscan_send_mail($user->user_email, 'Changed password', $message, $data_set);
2063
 
2064
- wp_set_password($new_password, $user_id);
 
 
 
 
 
 
 
2065
 
2066
- return TRUE;
 
 
 
 
 
 
 
 
 
 
2067
  }
2068
- return FALSE;
2069
- }
2070
 
2071
- /**
2072
- * Retrieve the real ip address of the user in the current request.
2073
- *
2074
- * @return string The real ip address of the user in the current request.
2075
- */
2076
- function sucuriscan_get_remoteaddr(){
2077
- $alternatives = array(
2078
- 'HTTP_X_REAL_IP',
2079
- 'HTTP_CLIENT_IP',
2080
- 'HTTP_X_FORWARDED_FOR',
2081
- 'HTTP_X_FORWARDED',
2082
- 'HTTP_FORWARDED_FOR',
2083
- 'HTTP_FORWARDED',
2084
- 'REMOTE_ADDR',
2085
- 'SUCURI_RIP',
2086
- );
2087
- foreach($alternatives as $alternative){
2088
- if( !isset($_SERVER[$alternative]) ){ continue; }
2089
 
2090
- $remote_addr = preg_replace('/[^0-9a-z.,: ]/', '', $_SERVER[$alternative]);
2091
- if($remote_addr) break;
 
2092
  }
2093
 
2094
- if( $remote_addr == '::1' ){
2095
- $remote_addr = '127.0.0.1';
 
 
 
 
 
 
2096
  }
2097
 
2098
- return $remote_addr;
2099
- }
 
 
 
 
 
 
 
2100
 
2101
- /**
2102
- * Retrieve the user-agent from the current request.
2103
- *
2104
- * @return string The user-agent from the current request.
2105
- */
2106
- function sucuriscan_get_useragent(){
2107
- if( isset($_SERVER['HTTP_USER_AGENT']) ){
2108
- return esc_attr($_SERVER['HTTP_USER_AGENT']);
 
 
 
2109
  }
2110
 
2111
- return FALSE;
2112
- }
 
 
 
 
 
 
 
2113
 
2114
- /**
2115
- * Check whether the site is behing the Sucuri CloudProxy network.
2116
- *
2117
- * @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
2118
- */
2119
- function sucuriscan_is_behind_cloudproxy(){
2120
- $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
2121
- if( preg_match('/^(.*):([0-9]+)/', $http_host, $match) ){ $http_host = $match[1]; }
2122
- $host_by_name = gethostbyname($http_host);
2123
- $host_by_addr = gethostbyaddr($host_by_name);
2124
 
2125
- if(
2126
- isset($_SERVER['SUCURIREAL_REMOTE_ADDR'])
2127
- || preg_match('/^cloudproxy([0-9]+)\.sucuri\.net$/', $host_by_addr)
2128
- ){
2129
- return TRUE;
 
2130
  }
2131
 
2132
- return FALSE;
2133
- }
 
 
 
 
 
 
2134
 
2135
- /**
2136
- * Find and retrieve the current version of Wordpress installed.
2137
- *
2138
- * @return string The version number of Wordpress installed.
2139
- */
2140
- function sucuriscan_get_wpversion(){
2141
- $version = get_option('version');
2142
- if( $version ){ return $version; }
 
2143
 
2144
- $wp_version_path = ABSPATH . WPINC . '/version.php';
2145
- if( file_exists($wp_version_path) ){
2146
- include($wp_version_path);
2147
- if( isset($wp_version) ){ return $wp_version; }
2148
  }
2149
 
2150
- return md5_file(ABSPATH . WPINC . '/class-wp.php');
2151
- }
 
 
 
 
 
 
 
2152
 
2153
- /**
2154
- * Find and retrieve the absolute path of the WordPress configuration file.
2155
- *
2156
- * @return string Absolute path of the WordPress configuration file.
2157
- */
2158
- function sucuriscan_get_wpconfig_path(){
2159
- $wp_config_path = ABSPATH.'wp-config.php';
 
 
2160
 
2161
- // if wp-config.php doesn't exist/not readable check one directory up
2162
- if( !is_readable($wp_config_path)){
2163
- $wp_config_path = ABSPATH.'/../wp-config.php';
 
 
 
 
 
2164
  }
2165
 
2166
- return $wp_config_path;
2167
- }
 
 
 
 
 
 
 
2168
 
2169
- /**
2170
- * Find and retrieve the absolute path of the main WordPress htaccess file.
2171
- *
2172
- * @return string Absolute path of the main WordPress htaccess file.
2173
- */
2174
- function sucuriscan_get_htaccess_path(){
2175
- $base_dirs = array(
2176
- rtrim(ABSPATH, '/'),
2177
- dirname(ABSPATH),
2178
- dirname(dirname(ABSPATH))
2179
- );
2180
 
2181
- foreach($base_dirs as $base_dir){
2182
- $htaccess_path = sprintf('%s/.htaccess', $base_dir);
2183
- if( file_exists($htaccess_path) ){
2184
- return $htaccess_path;
2185
- }
2186
  }
2187
 
2188
- return FALSE;
2189
- }
2190
-
2191
- /**
2192
- * Get the email address set by the administrator to receive the notifications
2193
- * sent by the plugin, if the email is missing the WordPress email address is
2194
- * chosen by default.
2195
- *
2196
- * @return string The administrator email address.
2197
- */
2198
- function sucuriscan_get_site_email(){
2199
- $email = get_option('admin_email');
2200
 
2201
- if( is_valid_email($email) ){
2202
- return $email;
 
2203
  }
2204
 
2205
- return FALSE;
2206
- }
 
 
 
 
 
 
 
2207
 
2208
- /**
2209
- * Get the clean version of the current domain.
2210
- *
2211
- * @return string The domain of the current site.
2212
- */
2213
- function sucuriscan_get_domain(){
2214
- $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
2215
- $domain_name = preg_replace( '/^www\./', '', $http_host );
2216
 
2217
- return $domain_name;
2218
- }
 
 
 
 
 
 
 
2219
 
2220
- /**
2221
- * Generate an user-agent for the HTTP requests.
2222
- *
2223
- * @return string An user-agent for the HTTP requests.
2224
- */
2225
- function sucuriscan_user_agent(){
2226
- global $wp_version;
2227
 
2228
- $user_agent = 'WordPress/' . $wp_version . '; ' . sucuriscan_get_domain();
 
 
 
 
 
 
 
 
2229
 
2230
- return $user_agent;
2231
- }
 
2232
 
2233
- /**
2234
- * Return the time passed since the specified timestamp until now.
2235
- *
2236
- * @param integer $timestamp The Unix time number of the date/time before now.
2237
- * @return string The time passed since the timestamp specified.
2238
- */
2239
- function sucuriscan_time_ago($timestamp=0){
2240
- if( !is_numeric($timestamp) ){
2241
- $timestamp = strtotime($timestamp);
2242
- }
2243
 
2244
- $diff = abs( time() - intval($timestamp) );
 
 
2245
 
2246
- if( $diff == 0 ){ return 'just now'; }
 
 
2247
 
2248
- $intervals = array(
2249
- 1 => array('year', 31556926),
2250
- $diff < 31556926 => array('month', 2628000),
2251
- $diff < 2629744 => array('week', 604800),
2252
- $diff < 604800 => array('day', 86400),
2253
- $diff < 86400 => array('hour', 3600),
2254
- $diff < 3600 => array('minute', 60),
2255
- $diff < 60 => array('second', 1)
2256
- );
 
 
 
 
2257
 
2258
- $value = floor($diff/$intervals[1][1]);
2259
- $time_ago = sprintf(
2260
- '%s %s%s ago',
2261
- $value,
2262
- $intervals[1][0],
2263
- ( $value > 1 ? 's' : '' )
2264
- );
 
 
 
 
 
 
 
 
 
2265
 
2266
- return $time_ago;
2267
- }
2268
 
2269
- /**
2270
- * Convert an string of characters into a valid variable name.
2271
- *
2272
- * @see http://www.php.net/manual/en/language.variables.basics.php
2273
- *
2274
- * @param string $string A text containing alpha-numeric and special characters.
2275
- * @return string A valid variable name.
2276
- */
2277
- function sucuriscan_str_human2var($string=''){
2278
- $pattern = '/[^a-zA-Z0-9_]/';
2279
- $var_name = preg_replace($pattern, '_', strtolower($string));
2280
 
2281
- return $var_name;
2282
- }
 
 
 
 
 
 
 
2283
 
2284
- /**
2285
- * Retrieve specific options from the database.
2286
- *
2287
- * Considering the case in which this plugin is installed in a multisite instance
2288
- * of Wordpress, the allowed values for the first parameter of this function will
2289
- * be treated like this:
2290
- *
2291
- * <ul>
2292
- * <li>all_sucuriscan_options: Will retrieve all the option values created by this plugin in the main site (aka. network),</li>
2293
- * <li>site_options: Will retrieve all the option values stored in the current site visited by the user (aka. sub-site) excluding the transient options,</li>
2294
- * <li>sucuriscan_option: Will retrieve one specific option from the network site only if the option starts with the prefix <i>sucuri_<i>.</li>
2295
- * </ul>
2296
- *
2297
- * @param string $filter_by Criteria to filter the results, valid values: all_sucuriscan_options, site_options, sucuri_option.
2298
- * @param string $option_name Optional parameter with the name of the option that will be filtered.
2299
- * @return array List of options retrieved from the query in the database.
2300
- */
2301
- function sucuriscan_get_options_from_db( $filter_by='', $option_name='' ){
2302
- global $wpdb;
2303
 
2304
- $output = FALSE;
2305
- switch($filter_by){
2306
- case 'all_sucuriscan_options':
2307
- $output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name LIKE 'sucuriscan%' ORDER BY option_id ASC");
2308
- break;
2309
- case 'site_options':
2310
- $output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name NOT LIKE '%_transient_%' ORDER BY option_id ASC");
2311
- break;
2312
- case 'sucuriscan_option':
2313
- $row = $wpdb->get_row( $wpdb->prepare("SELECT option_value FROM {$wpdb->base_prefix}options WHERE option_name = %s LIMIT 1", $option_name) );
2314
- if( $row ){ $output = $row->option_value; }
2315
- break;
2316
- }
2317
 
2318
- return $output;
2319
- }
2320
 
2321
- /**
2322
- * Alias function for the method Common::SucuriScan_Get_Options()
2323
- *
2324
- * This function search the specified option in the database, not only the options
2325
- * set by the plugin but all the options set for the site. If the value retrieved
2326
- * is FALSE the method tries to search for a default value.
2327
- *
2328
- * @param string $option_name Optional parameter that you can use to filter the results to one option.
2329
- * @return string The value (or default value) of the option specified.
2330
- */
2331
- function sucuriscan_get_option( $option_name='' ){
2332
- return sucuriscan_get_options($option_name);
2333
- }
 
 
 
 
2334
 
2335
- /**
2336
- * Retrieve all the options created by this Plugin from the Wordpress database.
2337
- *
2338
- * The function acts as an alias of WP::get_option() and if the returned value
2339
- * is FALSE it tries to search for a default value to complement the information.
2340
- *
2341
- * @param string $option_name Optional parameter that you can use to filter the results to one option.
2342
- * @return array Either FALSE or an Array containing all the sucuri options in the database.
2343
- */
2344
- function sucuriscan_get_options( $option_name='' ){
2345
- if( !empty($option_name) ){
2346
- return sucuriscan_get_single_option($option_name);
2347
- }
2348
 
2349
- $settings = array();
2350
- $results = sucuriscan_get_options_from_db('all_sucuriscan_options');
2351
- foreach( $results as $row ){
2352
- $settings[$row->option_name] = $row->option_value;
2353
- }
 
2354
 
2355
- return sucuriscan_get_default_options($settings);
2356
- }
 
 
 
 
 
2357
 
2358
- /**
2359
- * Retrieve a single option from the database.
2360
- *
2361
- * @param string $option_name Name of the option that will be retrieved.
2362
- * @return string Value of the option stored in the database, FALSE if not found.
2363
- */
2364
- function sucuriscan_get_single_option( $option_name='' ){
2365
- $is_sucuri_option = preg_match('/^sucuriscan_/', $option_name) ? TRUE : FALSE;
2366
 
2367
- if( sucuriscan_is_multisite() && $is_sucuri_option ){
2368
- $option_value = sucuriscan_get_options_from_db('sucuriscan_option', $option_name);
2369
- }else{
2370
- $option_value = get_option($option_name);
2371
- }
 
 
 
 
 
 
 
 
 
 
 
2372
 
2373
- if( $option_value === FALSE && $is_sucuri_option ){
2374
- $option_value = sucuriscan_get_default_options($option_name);
2375
- }
 
 
 
 
 
 
2376
 
2377
- return $option_value;
2378
- }
2379
 
2380
- /**
2381
- * Retrieve the default values for some specific options.
2382
- *
2383
- * @param string|array $settings Either an array that will be complemented or a string with the name of the option.
2384
- * @return string|array The default values for the specified options.
2385
- */
2386
- function sucuriscan_get_default_options( $settings='' ){
2387
- $admin_email = get_option('admin_email');
2388
- $default_options = array(
2389
- 'sucuriscan_api_key' => FALSE,
2390
- 'sucuriscan_account' => $admin_email,
2391
- 'sucuriscan_scan_frequency' => 'hourly',
2392
- 'sucuriscan_scan_interface' => 'spl',
2393
- 'sucuriscan_runtime' => 0,
2394
- 'sucuriscan_lastlogin_redirection' => 'enabled',
2395
- 'sucuriscan_notify_to' => $admin_email,
2396
- 'sucuriscan_emails_sent' => 0,
2397
- 'sucuriscan_emails_per_hour' => 5,
2398
- 'sucuriscan_last_email_at' => time(),
2399
- 'sucuriscan_prettify_mails' => 'enabled',
2400
- 'sucuriscan_notify_success_login' => 'enabled',
2401
- 'sucuriscan_notify_failed_login' => 'enabled',
2402
- 'sucuriscan_notify_post_publication' => 'enabled',
2403
- 'sucuriscan_notify_theme_editor' => 'enabled',
2404
- 'sucuriscan_maximum_failed_logins' => 30,
2405
- 'sucuriscan_ignored_events' => '',
2406
- 'sucuriscan_verify_ssl_cert' => 'true',
2407
- );
2408
 
2409
- if( is_array($settings) ){
2410
- foreach( $default_options as $option_name => $option_value ){
2411
- if( !isset($settings[$option_name]) ){
2412
- $settings[$option_name] = $option_value;
 
 
 
 
 
 
 
 
 
 
2413
  }
2414
  }
2415
- return $settings;
2416
- }
2417
 
2418
- if( is_string($settings) ){
2419
- if( isset($default_options[$settings]) ){
2420
- return $default_options[$settings];
 
 
 
 
 
 
 
 
2421
  }
2422
- }
2423
 
2424
- return FALSE;
2425
- }
 
 
 
 
 
 
 
 
 
 
2426
 
2427
- /**
2428
- * Retrieve all the options stored by Wordpress in the database. The options
2429
- * containing the word "transient" are excluded from the results, this function
2430
- * is compatible with multisite instances.
2431
- *
2432
- * @return array All the options stored by Wordpress in the database, except the transient options.
2433
- */
2434
- function sucuriscan_get_wp_options(){
2435
- $settings = array();
2436
 
2437
- $results = sucuriscan_get_options_from_db('site_options');
2438
- foreach( $results as $row ){
2439
- $settings[$row->option_name] = $row->option_value;
2440
- }
 
 
 
 
 
 
2441
 
2442
- return $settings;
2443
- }
 
 
2444
 
2445
- /**
2446
- * Check what Wordpress options were changed comparing the values in the database
2447
- * with the values sent through a simple request using a GET or POST method.
2448
- *
2449
- * @param array $request The content of the global variable GET or POST considering SERVER[REQUEST_METHOD].
2450
- * @return array A list of all the options that were changes through this request.
2451
- */
2452
- function sucuriscan_what_options_were_changed( $request=array() ){
2453
- $options_changed = array(
2454
- 'original' => array(),
2455
- 'changed' => array()
2456
- );
2457
- $wp_options = sucuriscan_get_wp_options();
2458
 
2459
- foreach( $request as $req_name => $req_value ){
2460
- if(
2461
- array_key_exists($req_name, $wp_options)
2462
- && $wp_options[$req_name] != $req_value
 
 
 
 
 
 
2463
  ){
2464
- $options_changed['original'][$req_name] = $wp_options[$req_name];
2465
- $options_changed['changed'][$req_name] = $req_value;
 
2466
  }
2467
- }
2468
- return $options_changed;
2469
- }
2470
 
2471
- /**
2472
- * Get a list of the post types ignored to receive email notifications when the
2473
- * "new site content" hook is triggered.
2474
- *
2475
- * @return array List of ignored posts-types to send notifications.
2476
- */
2477
- function sucuriscan_get_ignored_events(){
2478
- $post_types = sucuriscan_get_option('sucuriscan_ignored_events');
2479
- $post_types_arr = @unserialize($post_types);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2480
 
2481
- if( !is_array($post_types_arr) ){ $post_types_arr = array(); }
 
 
2482
 
2483
- return $post_types_arr;
2484
- }
 
 
 
 
 
 
 
 
2485
 
2486
- /**
2487
- * Add a new post type to the list of ignored events to send notifications.
2488
- *
2489
- * @param string $event_name Unique post-type name.
2490
- * @return boolean Whether the event was ignored or not.
2491
- */
2492
- function sucuriscan_add_ignored_event( $event_name='' ){
2493
- $post_types = get_post_types();
 
 
2494
 
2495
- // Check if the event is a registered post-type.
2496
- if( array_key_exists($event_name, $post_types) ){
2497
- $ignored_events = sucuriscan_get_ignored_events();
2498
 
2499
- // Check if the event is not ignored already.
2500
- if( !array_key_exists($event_name, $ignored_events) ){
2501
- $ignored_events[$event_name] = time();
2502
- $saved = update_option( 'sucuriscan_ignored_events', serialize($ignored_events) );
 
 
 
 
 
 
 
 
 
 
 
 
 
2503
 
2504
- return $saved;
 
 
 
 
2505
  }
 
2506
  }
2507
 
2508
- return FALSE;
2509
  }
2510
 
2511
  /**
2512
- * Remove a post type from the list of ignored events to send notifications.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2513
  *
2514
- * @param string $event_name Unique post-type name.
2515
- * @return boolean Whether the event was removed from the list or not.
2516
  */
2517
- function sucuriscan_remove_ignored_event( $event_name='' ){
2518
- $ignored_events = sucuriscan_get_ignored_events();
2519
 
2520
- if( array_key_exists($event_name, $ignored_events) ){
2521
- unset( $ignored_events[$event_name] );
2522
- $saved = update_option( 'sucuriscan_ignored_events', serialize($ignored_events) );
2523
-
2524
- return $saved;
 
 
 
 
2525
  }
2526
 
2527
- return FALSE;
2528
- }
 
 
 
 
 
 
 
 
 
2529
 
2530
- /**
2531
- * Check whether an event is being ignored to send notifications or not.
2532
- *
2533
- * @param string $event_name Unique post-type name.
2534
- * @return boolean Whether an event is being ignored or not.
2535
- */
2536
- function sucuriscan_is_ignored_event( $event_name='' ){
2537
- $event_name = strtolower($event_name);
2538
- $ignored_events = sucuriscan_get_ignored_events();
2539
 
2540
- if( array_key_exists($event_name, $ignored_events) ){
2541
- return TRUE;
2542
- }
2543
-
2544
- return FALSE;
2545
- }
2546
-
2547
- if( !function_exists('sucuriscan_plugin_setup_notice') ){
2548
  /**
2549
- * Display a notice message with instructions to continue the setup of the
2550
- * plugin, this includes the generation of the API key and other steps that need
2551
- * to be done to fully activate this plugin.
2552
  *
2553
- * @return void
 
 
 
 
 
 
 
2554
  */
2555
- function sucuriscan_plugin_setup_notice(){
2556
- if(
2557
- current_user_can('manage_options')
2558
- && !sucuriscan_wordpress_apikey()
2559
- && !isset($_POST['sucuriscan_wordpress_apikey'])
2560
- && !isset($_POST['sucuriscan_recover_api_key'])
2561
- ){
2562
- echo sucuriscan_get_section('setup_notice');
2563
- }
2564
- }
2565
-
2566
- $sucuriscan_admin_notice_name = sucuriscan_is_multisite() ? 'network_admin_notices' : 'admin_notices';
2567
- add_action( $sucuriscan_admin_notice_name, 'sucuriscan_plugin_setup_notice' );
2568
- }
2569
-
2570
- /**
2571
- * Check the plugins directory and retrieve all plugin files with plugin data.
2572
- * This function will also retrieve the URL and name of the repository/page
2573
- * where it is being published at the WordPress plugins market.
2574
- *
2575
- * @return array Key is the plugin file path and the value is an array of the plugin data.
2576
- */
2577
- function sucuriscan_get_plugins(){
2578
- $sucuri_cache = new SucuriScanCache('plugindata');
2579
- $cached_data = $sucuri_cache->get( 'plugins', SUCURISCAN_GET_PLUGINS_LIFETIME, 'array' );
2580
 
2581
- // Return the previously cached results of this function.
2582
- if( $cached_data !== FALSE ){
2583
- return $cached_data;
2584
- }
 
 
2585
 
2586
- // Get the plugin's basic information from WordPress transient data.
2587
- $plugins = get_plugins();
2588
- $pattern = '/^http:\/\/wordpress\.org\/plugins\/(.*)\/$/';
2589
- $wp_market = 'http://wordpress.org/plugins/%s/';
2590
 
2591
- // Loop through each plugin data and complement its information with more attributes.
2592
- foreach( $plugins as $plugin_path => $plugin_data ){
2593
- // Default values for the plugin extra attributes.
2594
- $repository = '';
2595
- $repository_name = '';
2596
- $is_free_plugin = FALSE;
2597
 
2598
- // If the plugin's info object has already a plugin_uri.
2599
- if(
2600
- isset($plugin_data['PluginURI'])
2601
- && preg_match($pattern, $plugin_data['PluginURI'], $match)
2602
- ){
2603
- $repository = $match[0];
2604
- $repository_name = $match[1];
2605
- $is_free_plugin = TRUE;
2606
  }
2607
 
2608
- // Retrieve the WordPress plugin page from the plugin's filename.
2609
- else {
2610
- if( strpos($plugin_path, '/') !== FALSE ){
2611
- $plugin_path_parts = explode('/', $plugin_path, 2);
 
 
 
2612
  } else {
2613
- $plugin_path_parts = explode('.', $plugin_path, 2);
2614
- }
2615
-
2616
- if( isset($plugin_path_parts[0]) ){
2617
- $possible_repository = sprintf($wp_market, $plugin_path_parts[0]);
2618
- $resp = wp_remote_head($possible_repository);
2619
 
 
2620
  if(
2621
- !is_wp_error($resp)
2622
- && $resp['response']['code'] == 200
2623
  ){
2624
- $repository = $possible_repository;
2625
- $repository_name = $plugin_path_parts[0];
2626
- $is_free_plugin = TRUE;
 
 
 
2627
  }
 
 
2628
  }
 
 
2629
  }
2630
 
2631
- // Complement the plugin's information with these attributes.
2632
- $plugins[$plugin_path]['Repository'] = $repository;
2633
- $plugins[$plugin_path]['RepositoryName'] = $repository_name;
2634
- $plugins[$plugin_path]['IsFreePlugin'] = $is_free_plugin;
2635
- $plugins[$plugin_path]['PluginType'] = ( $is_free_plugin ? 'free' : 'premium' );
2636
- $plugins[$plugin_path]['IsPluginActive'] = FALSE;
 
 
 
 
 
 
 
 
 
 
 
2637
 
2638
- if( is_plugin_active($plugin_path) ){
2639
- $plugins[$plugin_path]['IsPluginActive'] = TRUE;
2640
  }
 
 
2641
  }
2642
 
2643
- // Add the information of the plugins to the file-based cache.
2644
- $sucuri_cache->add( 'plugins', $plugins );
 
 
 
 
 
 
 
 
 
2645
 
2646
- return $plugins;
2647
- }
2648
 
2649
- /**
2650
- * Retrieve plugin installer pages from WordPress Plugins API.
2651
- *
2652
- * It is possible for a plugin to override the Plugin API result with three
2653
- * filters. Assume this is for plugins, which can extend on the Plugin Info to
2654
- * offer more choices. This is very powerful and must be used with care, when
2655
- * overriding the filters.
2656
- *
2657
- * The first filter, 'plugins_api_args', is for the args and gives the action as
2658
- * the second parameter. The hook for 'plugins_api_args' must ensure that an
2659
- * object is returned.
2660
- *
2661
- * The second filter, 'plugins_api', is the result that would be returned.
2662
- *
2663
- * @param string $repository_name Frienly name of the plugin.
2664
- * @return object Object on success, WP_Error on failure.
2665
- */
2666
- function sucuriscan_get_remote_plugin_data( $repository_name='' ){
2667
- $repository_base = 'http://api.wordpress.org/plugins/info/1.0/%s/';
2668
- $repository_url = sprintf( $repository_base, $repository_name );
2669
- $resp = wp_remote_get($repository_url);
 
2670
 
2671
- if( !is_wp_error($resp) ){
2672
- $plugin_data = @unserialize($resp['body']);
2673
 
2674
- if( $plugin_data instanceof stdClass ){
2675
- return $plugin_data;
 
 
 
 
2676
  }
 
 
2677
  }
2678
 
2679
- return FALSE;
2680
- }
 
 
 
 
 
 
 
2681
 
2682
- /**
2683
- * Detect which number in a pagination was clicked.
2684
- *
2685
- * @return integer Page number of the link clicked in a pagination.
2686
- */
2687
- function sucuriscan_get_page_number(){
2688
- $page_number = 1;
2689
 
2690
- // Check if there page was specified in the request.
2691
- if(
2692
- isset($_GET['num'])
2693
- && preg_match('/^[0-9]{1,2}$/', $_GET['num'])
2694
- && $_GET['num'] <= 10
2695
- ){
2696
- $page_number = intval($_GET['num']);
2697
  }
2698
 
2699
- return $page_number;
2700
- }
 
 
 
 
 
 
 
 
 
 
 
2701
 
2702
- /**
2703
- * Generate the HTML code to display a pagination.
2704
- *
2705
- * @param string $base_url Base URL for the links before the page number.
2706
- * @param integer $total_items Total quantity of items retrieved from a query.
2707
- * @param integer $max_per_page Maximum number of items that will be shown per page.
2708
- * @return string HTML code for a pagination generated using the provided data.
2709
- */
2710
- function sucuriscan_generate_pagination( $base_url='', $total_items=0, $max_per_page=1 ){
2711
- // Calculate the number of links for the pagination.
2712
- $html_links = '';
2713
- $page_number = sucuriscan_get_page_number();
2714
- $max_pages = ceil($total_items / $max_per_page);
2715
 
2716
- // Generate the HTML links for the pagination.
2717
- for( $j=1; $j<=$max_pages; $j++ ){
2718
- $link_class = 'sucuriscan-pagination-link';
2719
 
2720
- if( $page_number == $j ){
2721
- $link_class .= chr(32) . 'sucuriscan-pagination-active';
2722
  }
2723
 
2724
- $html_links .= sprintf(
2725
- '<li><a href="%s&num=%d" class="%s">%s</a></li>',
2726
- $base_url, $j, $link_class, $j
2727
- );
2728
  }
2729
 
2730
- return $html_links;
2731
- }
 
 
 
 
 
 
 
2732
 
2733
- /**
2734
- * Display the page with a temporary message explaining the action that will be
2735
- * performed once the hidden form is submitted to retrieve the scanning results
2736
- * from the public SiteCheck API.
2737
- *
2738
- * @return void
2739
- */
2740
- function sucuriscan_scanner_page(){
2741
- if(
2742
- sucuriscan_check_page_nonce()
2743
- && isset($_POST['sucuriscan_malware_scan'])
2744
- ){
2745
- sucuriscan_sitecheck_info();
2746
- } else {
2747
- echo sucuriscan_get_template('malwarescan');
2748
- }
2749
- }
2750
 
2751
- /**
2752
- * Display the result of site scan made through SiteCheck.
2753
- *
2754
- * @return void
2755
- */
2756
- function sucuriscan_sitecheck_info(){
2757
- $sucuri_cache = new SucuriScanCache('sitecheck');
2758
- $scan_results = $sucuri_cache->get( 'scan_results', SUCURISCAN_SITECHECK_LIFETIME, 'array' );
2759
- $clean_domain = sucuriscan_get_domain();
2760
- $display_results = FALSE;
2761
 
2762
- ob_start();
 
 
2763
 
2764
- if( !$scan_results ){
2765
- $remote_url = 'http://sitecheck.sucuri.net/scanner/?serialized&clear&fromwp&scan='.$clean_domain;
2766
- $scan_results = wp_remote_get($remote_url, array('timeout' => 180));
2767
 
2768
- if( is_wp_error($scan_results) ){
2769
- sucuriscan_error( $scan_results->get_error_message() );
2770
  }
2771
 
2772
- elseif( isset($scan_results['body']) ){
2773
- if( preg_match('/^ERROR:(.*)/', $scan_results['body'], $error_m) ){
2774
- sucuriscan_error( 'The site <code>' . $clean_domain . '</code> was not scanned: ' . $error_m[1] );
2775
- }
2776
-
2777
- else {
2778
- $scan_results = @unserialize($scan_results['body']);
2779
- $display_results = TRUE;
2780
 
2781
- if( !$sucuri_cache->add( 'scan_results', $scan_results ) ){
2782
- sucuriscan_error( 'Could not cache the results of the SiteCheck scanning' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2783
  }
 
 
2784
  }
2785
  }
2786
- } else {
2787
- $display_results = TRUE;
2788
- // sucuriscan_info( 'SiteCheck results retrieved from cache.' );
2789
  }
2790
- ?>
2791
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2792
 
2793
- <?php if( $display_results ): ?>
 
2794
 
2795
- <?php
2796
- $res = ( is_array($scan_results) ? $scan_results : array() );
2797
 
2798
- // Check for general warnings, and return the information for Infected/Clean site.
2799
- $malware_warns_exist = isset($res['MALWARE']['WARN']) ? TRUE : FALSE;
2800
- $blacklist_warns_exist = isset($res['BLACKLIST']['WARN']) ? TRUE : FALSE;
2801
- $outdated_warns_exist = isset($res['OUTDATEDSCAN']) ? TRUE : FALSE;
2802
- $recommendations_exist = isset($res['RECOMMENDATIONS']) ? TRUE : FALSE;
 
 
2803
 
2804
- // Check whether this WordPress installation needs an update.
2805
- global $wp_version;
2806
- $wordpress_updated = FALSE;
2807
- $updates = function_exists('get_core_updates') ? get_core_updates() : array();
 
2808
 
2809
- if( !is_array($updates) || empty($updates) || $updates[0]->response=='latest' ){
2810
- $wordpress_updated = TRUE;
 
 
 
2811
  }
2812
 
2813
- if( TRUE ){
2814
- // Initialize the CSS classes with default values.
2815
- $sucuriscan_css_blacklist = 'sucuriscan-border-good';
2816
- $sucuriscan_css_malware = 'sucuriscan-border-good';
2817
- $sitecheck_results_tab = '';
2818
- $blacklist_status_tab = '';
2819
- $website_details_tab = '';
2820
 
2821
- // Generate the CSS classes for the blacklist status.
2822
- if( $blacklist_warns_exist ){
2823
- $sucuriscan_css_blacklist = 'sucuriscan-border-bad';
2824
- $blacklist_status_tab = 'sucuriscan-red-tab';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2825
  }
 
2826
 
2827
- // Generate the CSS classes for the SiteCheck scanning results.
2828
- if( $malware_warns_exist ){
2829
- $sucuriscan_css_malware = 'sucuriscan-border-bad';
2830
- $sitecheck_results_tab = 'sucuriscan-red-tab';
2831
- }
2832
 
2833
- // Generate the CSS classes for the outdated/recommendations panel.
2834
- if( $outdated_warns_exist || $recommendations_exist ){
2835
- $website_details_tab = 'sucuriscan-red-tab';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2836
  }
2837
 
2838
- $sucuriscan_css_wpupdate = $wordpress_updated ? 'sucuriscan-border-good' : 'sucuriscan-border-bad';
2839
  }
2840
- ?>
2841
 
2842
- <div id="poststuff">
2843
- <div class="postbox sucuriscan-border sucuriscan-border-info sucuriscan-malwarescan-message">
2844
- <h3>SiteCheck Scanner</h3>
2845
 
2846
- <div class="inside">
2847
- <p>
2848
- If your site was recently hacked, you can see which files were modified
2849
- recently, to assist with any investigation.
2850
- </p>
2851
- </div>
2852
- </div>
2853
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
2854
 
 
 
2855
 
2856
- <div class="sucuriscan-tabs">
 
 
 
 
 
 
 
 
 
 
2857
 
 
 
 
2858
 
2859
- <ul>
2860
- <li class="<?php _e($sitecheck_results_tab) ?>">
2861
- <a href="#" data-tabname="sitecheck-results">Remote Scanner Results</a>
2862
- </li>
2863
- <li class="<?php _e($website_details_tab) ?>">
2864
- <a href="#" data-tabname="website-details">Website Details</a>
2865
- </li>
2866
- <li>
2867
- <a href="#" data-tabname="website-links">IFrames / Links / Scripts</a>
2868
- </li>
2869
- <li class="<?php _e($blacklist_status_tab) ?>">
2870
- <a href="#" data-tabname="blacklist-status">Blacklist Status</a>
2871
- </li>
2872
- <li>
2873
- <a href="#" data-tabname="modified-files">Modified Files</a>
2874
- </li>
2875
- </ul>
2876
 
 
 
 
2877
 
2878
- <div class="sucuriscan-tab-containers">
 
2879
 
 
 
 
 
 
 
 
 
2880
 
2881
- <div id="sucuriscan-sitecheck-results">
2882
- <div id="poststuff">
2883
- <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_malware) ?>">
2884
- <h3>
2885
- <?php if( $malware_warns_exist ): ?>
2886
- Site compromised (malware was identified)
2887
- <?php else: ?>
2888
- Site clean (no malware was identified)
2889
- <?php endif; ?>
2890
- </h3>
2891
 
2892
- <div class="inside">
2893
 
2894
- <?php if( !$malware_warns_exist ): ?>
2895
- <p>
2896
- <span><strong>Malware:</strong> Clean.</span><br>
2897
- <span><strong>Malicious javascript:</strong> Clean.</span><br>
2898
- <span><strong>Malicious iframes:</strong> Clean.</span><br>
2899
- <span><strong>Suspicious redirections (htaccess):</strong> Clean.</span><br>
2900
- <span><strong>Blackhat SEO Spam:</strong> Clean.</span><br>
2901
- <span><strong>Anomaly detection:</strong> Clean.</span>
2902
- </p>
2903
- <?php else: ?>
2904
- <ul>
2905
- <?php
2906
- foreach( $res['MALWARE']['WARN'] as $malres ){
2907
- if( !is_array($malres) ){
2908
- echo '<li>' . htmlspecialchars($malres) . '</li>';
2909
- } else {
2910
- $mwdetails = explode("\n", htmlspecialchars($malres[1]));
2911
- $mw_name_link = isset($mwdetails[0]) ? substr($mwdetails[0], 1) : '';
2912
 
2913
- if( preg_match('/(.*)\. Details: (.*)/', $mw_name_link, $mw_match) ){
2914
- $mw_name_link = sprintf(
2915
- '%s. Details: <a href="%s" target="_blank">%s</a>',
2916
- $mw_match[1], $mw_match[2], $mw_match[2]
2917
- );
2918
- }
2919
 
2920
- echo '<li>'. htmlspecialchars($malres[0]) . "\n<br>" . $mw_name_link . "</li>\n";
2921
- }
2922
- }
2923
- ?>
2924
- </ul>
2925
- <?php endif; ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2926
 
2927
- <p>
2928
- <i>
2929
- More details here: <a href="http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?>"
2930
- target="_blank">http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?></a>
2931
- </i>
2932
- </p>
2933
 
2934
- <hr />
 
 
2935
 
2936
- <p>
2937
- <i>
2938
- If our free scanner did not detect any issue, you may have a more complicated
2939
- and hidden problem. You can <a href="http://sucuri.net/signup" target="_blank">
2940
- sign up</a> with Sucuri for a complete and in depth scan+cleanup (not included
2941
- in the free checks).
2942
- </i>
2943
- </p>
2944
 
2945
- </div>
2946
- </div>
2947
- </div>
2948
- </div>
2949
 
 
 
2950
 
2951
- <div id="sucuriscan-website-details">
2952
- <table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-details">
2953
- <thead>
2954
- <tr>
2955
- <th colspan="2" class="thead-with-button">
2956
- <span>System Information</span>
2957
- <?php if( !$wordpress_updated ): ?>
2958
- <a href="<?php echo admin_url('update-core.php'); ?>" class="button button-primary thead-topright-action">
2959
- Update to <?php _e($updates[0]->version) ?>
2960
- </a>
2961
- <?php endif; ?>
2962
- </th>
2963
- </tr>
2964
- </thead>
 
 
 
 
2965
 
2966
- <tbody>
2967
- <!-- List of generic information from the site. -->
2968
- <?php
2969
- $possible_keys = array(
2970
- 'DOMAIN' => 'Domain Scanned',
2971
- 'IP' => 'Site IP Address',
2972
- 'HOSTING' => 'Hosting Company',
2973
- 'CMS' => 'CMS Found',
2974
- );
2975
- $possible_url_keys = array(
2976
- 'IFRAME' => 'List of iframes found',
2977
- 'JSEXTERNAL' => 'List of external scripts included',
2978
- 'JSLOCAL' => 'List of scripts included',
2979
- 'URL' => 'List of links found',
2980
- );
2981
- ?>
2982
 
2983
- <?php foreach( $possible_keys as $result_key=>$result_title ): ?>
2984
- <?php if( isset($res['SCAN'][$result_key]) ): ?>
2985
- <?php $result_value = implode(', ', $res['SCAN'][$result_key]); ?>
2986
- <tr>
2987
- <td><?php _e($result_title) ?></td>
2988
- <td><span class="sucuriscan-monospace"><?php _e($result_value) ?></span></td>
2989
- </tr>
2990
- <?php endif; ?>
2991
- <?php endforeach; ?>
2992
 
2993
- <tr>
2994
- <td>WordPress Version</td>
2995
- <td><span class="sucuriscan-monospace"><?php _e($wp_version) ?></span></td>
2996
- </tr>
2997
- <tr>
2998
- <td>PHP Version</td>
2999
- <td><span class="sucuriscan-monospace"><?php _e(phpversion()) ?></span></td>
3000
- </tr>
 
3001
 
3002
- <!-- List of application details from the site. -->
3003
- <tr>
3004
- <th colspan="2">Web application details</th>
3005
- </tr>
3006
- <?php foreach( $res['WEBAPP'] as $webapp_key=>$webapp_details ): ?>
3007
- <?php if( is_array($webapp_details) ): ?>
3008
- <?php foreach( $webapp_details as $i=>$details ): ?>
3009
- <?php if( is_array($details) ){ $details = isset($details[0]) ? $details[0] : ''; } ?>
3010
- <tr>
3011
- <td colspan="2">
3012
- <span class="sucuriscan-monospace"><?php _e($details) ?></span>
3013
- </td>
3014
- </tr>
3015
- <?php endforeach; ?>
3016
- <?php endif; ?>
3017
- <?php endforeach; ?>
3018
 
3019
- <?php foreach( $res['SYSTEM']['NOTICE'] as $j=>$notice ): ?>
3020
- <?php if( is_array($notice) ){ $notice = implode(', ', $notice); } ?>
3021
- <tr>
3022
- <td colspan="2">
3023
- <span class="sucuriscan-monospace"><?php _e($notice) ?></span>
3024
- </td>
3025
- </tr>
3026
- <?php endforeach; ?>
3027
 
3028
- <!-- Possible recommendations or outdated software on the site. -->
3029
- <?php if( $outdated_warns_exist || $recommendations_exist ): ?>
3030
- <tr>
3031
- <th colspan="2">Recommendations for the site</th>
3032
- </tr>
3033
- <?php endif; ?>
3034
 
3035
- <!-- Possible outdated software on the site. -->
3036
- <?php if( $outdated_warns_exist ): ?>
3037
- <?php foreach( $res['OUTDATEDSCAN'] as $outdated ): ?>
3038
- <?php if( count($outdated) >= 3 ): ?>
3039
- <tr>
3040
- <td colspan="2" class="sucuriscan-border-bad">
3041
- <strong><?php _e($outdated[0]) ?></strong>
3042
- <em>(<?php _e($outdated[2]) ?>)</em>
3043
- <span><?php _e($outdated[1]) ?></span>
3044
- </td>
3045
- </tr>
3046
- <?php endif; ?>
3047
- <?php endforeach; ?>
3048
- <?php endif; ?>
3049
 
3050
- <!-- Possible recommendations for the site. -->
3051
- <?php if( $recommendations_exist ): ?>
3052
- <?php foreach( $res['RECOMMENDATIONS'] as $recommendation ): ?>
3053
- <?php if( count($recommendation) >= 3 ): ?>
3054
- <tr>
3055
- <td colspan="2" class="sucuriscan-border-bad">
3056
- <?php printf(
3057
- '<strong>%s</strong><br><span>%s</span><br><a href="%s" target="_blank">%s</a>',
3058
- $recommendation[0],
3059
- $recommendation[1],
3060
- $recommendation[2],
3061
- $recommendation[2]
3062
- ); ?>
3063
- </td>
3064
- </tr>
3065
- <?php endif; ?>
3066
- <?php endforeach; ?>
3067
- <?php endif; ?>
3068
- </tbody>
3069
- </table>
3070
- </div>
3071
 
 
 
 
 
 
 
3072
 
3073
- <div id="sucuriscan-website-links">
3074
- <table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-links">
3075
- <tbody>
3076
- <?php foreach( $possible_url_keys as $result_url_key=>$result_url_title ): ?>
 
3077
 
3078
- <?php if( isset($res['LINKS'][$result_url_key]) ): ?>
3079
- <tr>
3080
- <th colspan="2">
3081
- <?php printf(
3082
- '%s (%d found)',
3083
- __($result_url_title),
3084
- count($res['LINKS'][$result_url_key])
3085
- ) ?>
3086
- </th>
3087
- </tr>
3088
 
3089
- <?php foreach( $res['LINKS'][$result_url_key] as $url_path ): ?>
3090
- <tr>
3091
- <td colspan="2">
3092
- <span class="sucuriscan-monospace sucuriscan-wraptext"><?php _e($url_path) ?></span>
3093
- </td>
3094
- </tr>
3095
- <?php endforeach; ?>
3096
- <?php endif; ?>
3097
 
3098
- <?php endforeach; ?>
3099
- </tbody>
3100
- </table>
3101
- </div>
3102
 
 
 
 
 
 
 
 
 
 
 
3103
 
3104
- <div id="sucuriscan-blacklist-status">
3105
- <div id="poststuff">
3106
- <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_blacklist) ?>">
3107
- <h3>
3108
- <?php if( $blacklist_warns_exist ): ?>
3109
- Site blacklisted
3110
- <?php else: ?>
3111
- Site blacklist-free
3112
- <?php endif; ?>
3113
- </h3>
3114
 
3115
- <div class="inside">
3116
- <ul>
3117
- <?php
3118
- foreach(array(
3119
- 'INFO' => 'CLEAN',
3120
- 'WARN' => 'WARNING'
3121
- ) as $type => $group_title){
3122
- if( isset($res['BLACKLIST'][$type]) ){
3123
- foreach( $res['BLACKLIST'][$type] as $blres ){
3124
- $report_site = htmlspecialchars($blres[0]);
3125
- $report_url = htmlspecialchars($blres[1]);
3126
- printf(
3127
- '<li><b>%s:</b> %s.<br>Details at <a href="%s" target="_blank">%s</a></li>',
3128
- $group_title, $report_site, $report_url, $report_url
3129
- );
3130
- }
3131
- }
3132
- }
3133
- ?>
3134
- </ul>
3135
- </div>
3136
- </div>
3137
- </div>
3138
- </div>
3139
-
3140
-
3141
- <div id="sucuriscan-modified-files">
3142
- <?php echo sucuriscan_modified_files(); ?>
3143
- </div>
3144
-
3145
-
3146
- </div>
3147
- </div>
3148
-
3149
- <?php if( $malware_warns_exist || $blacklist_warns_exist ): ?>
3150
- <a href="http://sucuri.net/signup/" target="_blank" class="button button-primary button-hero sucuriscan-cleanup-btn">
3151
- Get your site protected with Sucuri
3152
- </a>
3153
- <?php endif; ?>
3154
-
3155
- <?php endif; ?>
3156
 
 
 
 
 
3157
 
3158
- <?php
3159
- $_html = ob_get_contents();
3160
- ob_end_clean();
3161
- echo sucuriscan_get_base_template($_html, array(
3162
- 'PageTitle' => 'Malware Scan',
3163
- 'PageContent' => $_html,
3164
- 'PageStyleClass' => 'scanner-results',
3165
- ));
3166
- return;
3167
- }
3168
 
3169
- /**
3170
- * Retrieves a URL using a changeable HTTP method, returning results in an
3171
- * array. Results include HTTP headers and content.
3172
- *
3173
- * @see http://codex.wordpress.org/Function_Reference/wp_remote_post
3174
- * @see http://codex.wordpress.org/Function_Reference/wp_remote_get
3175
- *
3176
- * @param string $url The target URL where the request will be sent.
3177
- * @param string $method HTTP method that will be used to send the request.
3178
- * @param array $params Parameters for the request defined in an associative array of key-value.
3179
- * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
3180
- * @return array Array of results including HTTP headers or WP_Error if the request failed.
3181
- */
3182
- function sucuriscan_api_call( $url='', $method='GET', $params=array(), $args=array() ){
3183
- if( !$url ){ return FALSE; }
3184
-
3185
- $req_args = array(
3186
- 'method' => $method,
3187
- 'timeout' => 90,
3188
- 'redirection' => 2,
3189
- 'httpversion' => '1.0',
3190
- 'user-agent' => sucuriscan_user_agent(),
3191
- 'blocking' => TRUE,
3192
- 'headers' => array(),
3193
- 'cookies' => array(),
3194
- 'compress' => FALSE,
3195
- 'decompress' => FALSE,
3196
- 'sslverify' => sucuriscan_verify_ssl_cert(),
3197
- );
3198
 
3199
- // Update the request arguments with the values passed tot he function.
3200
- foreach( $args as $arg_name => $arg_value ){
3201
- if( array_key_exists($arg_name, $req_args) ){
3202
- $req_args[$arg_name] = $arg_value;
3203
- }
3204
- }
 
3205
 
3206
- if( $method == 'GET' ){
3207
- $url = sprintf( '%s?%s', $url, http_build_query($params) );
3208
- $response = wp_remote_get( $url, $req_args );
3209
- }
3210
 
3211
- elseif( $method == 'POST' ){
3212
- $req_args['body'] = $params;
3213
- $response = wp_remote_post( $url, $req_args );
3214
- }
 
 
 
 
 
 
3215
 
3216
- if( isset($response) ){
3217
- if( is_wp_error($response) ){
3218
- sucuriscan_error(sprintf(
3219
- 'Something went wrong with an API call (%s action): %s',
3220
- ( isset($params['a']) ? $params['a'] : 'unknown' ),
3221
- $response->get_error_message()
3222
- ));
3223
- } else {
3224
- $response['body_raw'] = $response['body'];
3225
 
3226
- if(
3227
- isset($response['headers']['content-type'])
3228
- && $response['headers']['content-type'] = 'application/json'
3229
- ){
3230
- $response['body'] = json_decode($response['body_raw']);
3231
  }
3232
-
3233
- return $response;
3234
  }
3235
- } else {
3236
- sucuriscan_error( 'HTTP method not allowed: ' . $method );
3237
- }
3238
-
3239
- return FALSE;
3240
- }
3241
 
3242
- /**
3243
- * Store the API key locally.
3244
- *
3245
- * @param string $api_key An unique string of characters to identify this installation.
3246
- * @param boolean $validate Whether the format of the key should be validated before store it.
3247
- * @return boolean Either TRUE or FALSE if the key was saved successfully or not respectively.
3248
- */
3249
- function sucuriscan_set_api_key( $api_key='', $validate=FALSE ){
3250
- if( $validate ){
3251
- if( !preg_match('/^([a-z0-9]{32})$/', $api_key) ){
3252
- sucuriscan_error( 'Invalid API key format' );
3253
- return FALSE;
3254
  }
3255
- }
3256
 
3257
- if( !empty($api_key) ){
3258
- sucuriscan_notify_event( 'plugin_change', 'API key updated successfully: ' . $api_key );
3259
  }
3260
 
3261
- return (bool) update_option( 'sucuriscan_api_key', $api_key );
3262
- }
3263
-
3264
- /**
3265
- * Retrieve the API key from the local storage.
3266
- *
3267
- * @return string|boolean The API key or FALSE if it does not exists.
3268
- */
3269
- function sucuriscan_wordpress_apikey(){
3270
- $api_key = sucuriscan_get_option('sucuriscan_api_key');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3271
 
3272
- if( $api_key && strlen($api_key) > 10 ){
3273
- return $api_key;
3274
  }
3275
 
3276
- return FALSE;
3277
  }
3278
 
3279
  /**
3280
- * Check whether the CloudProxy API key is valid or not.
3281
  *
3282
- * @param string $api_key The CloudProxy API key.
3283
- * @param boolean $return_match Whether the parts of the API key must be returned or not.
3284
- * @return boolean TRUE if the API key specified is valid, FALSE otherwise.
 
3285
  */
3286
- function sucuriscan_valid_cloudproxy_apikey( $api_key='', $return_match=FALSE ){
3287
- $pattern = '/^([a-z0-9]{32})\/([a-z0-9]{32})$/';
3288
-
3289
- if( $api_key && preg_match($pattern, $api_key, $match) ){
3290
- if( $return_match ){ return $match; }
3291
 
3292
- return TRUE;
 
 
 
 
 
 
3293
  }
3294
 
3295
- return FALSE;
3296
- }
3297
-
3298
- /**
3299
- * Check and return the API key for the plugin.
3300
- *
3301
- * In this plugin the key is a pair of two strings concatenated by a single
3302
- * slash, the first part of it is in fact the key and the second part is the
3303
- * unique identifier of the site in the remote server.
3304
- *
3305
- * @return array|boolean FALSE if the key is invalid or not present, an array otherwise.
3306
- */
3307
- function sucuriscan_cloudproxy_apikey(){
3308
- $option_name = 'sucuriscan_cloudproxy_apikey';
3309
- $api_key = sucuriscan_get_option($option_name);
3310
-
3311
- // Check if the cloudproxy-waf plugin was previously installed.
3312
- if( !$api_key ){
3313
- $api_key = sucuriscan_get_option('sucuriwaf_apikey');
 
 
 
 
 
3314
 
3315
- if( $api_key ){
3316
- update_option( $option_name, $api_key );
3317
- delete_option('sucuriwaf_apikey');
 
 
 
 
3318
  }
3319
- }
3320
 
3321
- // Check the validity of the API key.
3322
- $match = sucuriscan_valid_cloudproxy_apikey( $api_key, TRUE );
 
 
 
 
 
3323
 
3324
- if( $match ){
3325
- return array(
3326
- 'string' => $match[1].'/'.$match[2],
3327
- 'k' => $match[1],
3328
- 's' => $match[2]
3329
- );
3330
- }
3331
 
3332
- return FALSE;
3333
- }
3334
 
3335
- /**
3336
- * Call an action from the remote API interface of our WordPress service.
3337
- *
3338
- * @param string $method HTTP method that will be used to send the request.
3339
- * @param array $params Parameters for the request defined in an associative array of key-value.
3340
- * @param boolean $send_api_key Whether the API key should be added to the request parameters or not.
3341
- * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
3342
- * @return array Array of results including HTTP headers or WP_Error if the request failed.
3343
- */
3344
- function sucuriscan_api_call_wordpress( $method='GET', $params=array(), $send_api_key=TRUE, $args=array() ){
3345
- $url = SUCURISCAN_API;
3346
- $params[SUCURISCAN_API_VERSION] = 1;
3347
- $params['p'] = 'wordpress';
3348
 
3349
- if( $send_api_key ){
3350
- $api_key = sucuriscan_wordpress_apikey();
 
 
3351
 
3352
- if( !$api_key ){ return FALSE; }
 
 
3353
 
3354
- $params['k'] = $api_key;
3355
  }
3356
 
3357
- $response = sucuriscan_api_call( $url, $method, $params, $args );
3358
-
3359
- return $response;
3360
- }
 
 
 
 
 
 
 
 
 
 
3361
 
3362
- /**
3363
- * Call an action from the remote API interface of our CloudProxy service.
3364
- *
3365
- * @param string $method HTTP method that will be used to send the request.
3366
- * @param array $params Parameters for the request defined in an associative array of key-value.
3367
- * @return array Array of results including HTTP headers or WP_Error if the request failed.
3368
- */
3369
- function sucuriscan_api_call_cloudproxy( $method='GET', $params=array() ){
3370
- $send_request = FALSE;
3371
 
3372
- if( isset($params['k']) && isset($params['s']) ){
3373
- $send_request = TRUE;
3374
- } else {
3375
- $api_key = sucuriscan_cloudproxy_apikey();
 
 
 
 
 
3376
 
3377
- if( $api_key ){
3378
- $send_request = TRUE;
3379
- $params['k'] = $api_key['k'];
3380
- $params['s'] = $api_key['s'];
3381
  }
3382
- }
3383
 
3384
- if( $send_request ){
3385
- $url = SUCURISCAN_CLOUDPROXY_API;
3386
- $params[SUCURISCAN_CLOUDPROXY_API_VERSION] = 1;
3387
 
3388
- $response = sucuriscan_api_call( $url, $method, $params );
 
 
 
 
 
 
3389
 
3390
- return $response;
3391
- }
 
 
 
3392
 
3393
- return FALSE;
3394
- }
 
 
3395
 
3396
- /**
3397
- * Determine whether an API response was successful or not checking the expected
3398
- * generic variables and types, in case of an error a notification will appears
3399
- * in the administrator panel explaining the result of the operation.
3400
- *
3401
- * @param array $response Array of results including HTTP headers or WP_Error if the request failed.
3402
- * @return boolean Either TRUE or FALSE in case of success or failure of the API response (respectively).
3403
- */
3404
- function sucuriscan_handle_response( $response=array() ){
3405
- if( $response ){
3406
- if( $response['body'] instanceof stdClass ){
3407
- if( isset($response['body']->status) ){
3408
- if( $response['body']->status == 1 ){
3409
  return TRUE;
3410
- } else {
3411
- sucuriscan_error( ucwords($response['body']->action) . ': ' . $response['body']->messages[0] );
3412
  }
3413
  } else {
3414
- sucuriscan_error( 'Could not determine the status of an API call.' );
 
3415
  }
3416
- } else {
3417
- sucuriscan_error( 'Unknown API content-type, it was not a JSON-encoded response.' );
3418
  }
 
 
3419
  }
3420
 
3421
- return FALSE;
3422
  }
3423
 
3424
  /**
3425
- * Send a request to the API to register this site.
3426
  *
3427
- * @return boolean TRUE if the API key was generated, FALSE otherwise.
 
 
 
 
 
 
 
 
3428
  */
3429
- function sucuriscan_register_site(){
3430
- $response = sucuriscan_api_call_wordpress( 'POST', array(
3431
- 'e' => sucuriscan_get_site_email(),
3432
- 's' => sucuriscan_get_domain(),
3433
- 'a' => 'register_site',
3434
- ), FALSE );
3435
-
3436
- if( sucuriscan_handle_response($response) ){
3437
- sucuriscan_set_api_key( $response['body']->output->api_key );
3438
- sucuriscan_create_scheduled_task();
3439
- sucuriscan_notify_event( 'plugin_change', 'Site registered and API key generated' );
3440
- sucuriscan_info( 'The API key for your site was successfully generated and saved.');
3441
 
3442
- return TRUE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3443
  }
3444
 
3445
- return FALSE;
3446
- }
 
 
 
 
 
 
3447
 
3448
- /**
3449
- * Send a request to recover a previously registered API key.
3450
- *
3451
- * @return boolean TRUE if the API key was sent to the administrator email, FALSE otherwise.
3452
- */
3453
- function sucuriscan_recover_api_key(){
3454
- $clean_domain = sucuriscan_get_domain();
3455
 
3456
- $response = sucuriscan_api_call_wordpress( 'GET', array(
3457
- 'e' => sucuriscan_get_site_email(),
3458
- 's' => $clean_domain,
3459
- 'a' => 'recover_key',
3460
- ), FALSE );
 
3461
 
3462
- if( sucuriscan_handle_response($response) ){
3463
- sucuriscan_notify_event( 'plugin_change', 'API key recovered for domain: ' . $clean_domain );
3464
- sucuriscan_info( $response['body']->output->message );
3465
 
3466
- return TRUE;
 
 
 
 
 
 
 
3467
  }
3468
 
3469
- return FALSE;
3470
- }
 
 
 
 
 
 
 
3471
 
3472
- /**
3473
- * Send a request to the API to store and analyze the events of the site. An
3474
- * event can be anything from a simple request, an internal modification of the
3475
- * settings or files in the administrator panel, or a notification generated by
3476
- * this plugin.
3477
- *
3478
- * @param string $event The information gathered through out the normal functioning of the site.
3479
- * @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
3480
- */
3481
- function sucuriscan_send_log( $event='' ){
3482
- if( !empty($event) ){
3483
- $response = sucuriscan_api_call_wordpress( 'POST', array(
3484
- 'a' => 'send_log',
3485
- 'm' => $event,
3486
- ), TRUE, array( 'timeout' => 20 ) );
3487
-
3488
- if( sucuriscan_handle_response($response) ){
3489
- return TRUE;
3490
  }
 
 
3491
  }
3492
 
3493
- return FALSE;
3494
- }
 
 
 
 
 
 
 
 
3495
 
3496
- /**
3497
- * Retrieve the event logs registered by the API service.
3498
- *
3499
- * @param integer $lines How many lines from the log file will be retrieved.
3500
- * @return string The response of the API service.
3501
- */
3502
- function sucuriscan_get_logs( $lines=50 ){
3503
- $response = sucuriscan_api_call_wordpress( 'GET', array(
3504
- 'a' => 'get_logs',
3505
- 'l' => $lines,
3506
- ) );
3507
-
3508
- if( sucuriscan_handle_response($response) ){
3509
- $response['body']->output_data = array();
3510
- $log_pattern = '/^([0-9-: ]+) (.*) : (.*)/';
3511
- $extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
3512
-
3513
- foreach( $response['body']->output as $log ){
3514
- if( preg_match($log_pattern, $log, $log_match) ){
3515
- $log_data = array(
3516
- 'datetime' => $log_match[1],
3517
- 'timestamp' => strtotime($log_match[1]),
3518
- 'account' => $log_match[2],
3519
- 'message' => $log_match[3],
3520
- 'extra' => FALSE,
3521
- 'extra_total' => 0,
3522
- );
3523
 
3524
- $log_data['message'] = str_replace( ', new size', '; new size', $log_data['message'] );
 
3525
 
3526
- if( preg_match($extra_pattern, $log_data['message'], $log_extra) ){
3527
- $log_data['message'] = $log_extra[1];
3528
- $log_data['extra'] = explode(',', $log_extra[2]);
3529
- $log_data['extra_total'] = count($log_data['extra']);
3530
- }
3531
 
3532
- $response['body']->output_data[] = $log_data;
 
 
 
 
 
 
 
 
3533
  }
3534
- }
3535
 
3536
- return $response['body'];
3537
- }
3538
 
3539
- return FALSE;
3540
- }
3541
 
3542
- /**
3543
- * Send a request to the API to store and analyze the file's hashes of the site.
3544
- * This will be the core of the monitoring tools and will enhance the
3545
- * information of the audit logs alerting the administrator of suspicious
3546
- * changes in the system.
3547
- *
3548
- * @param string $hashes The information gathered after the scanning of the site's files.
3549
- * @return boolean TRUE if the hashes were stored, FALSE otherwise.
3550
- */
3551
- function sucuriscan_send_hashes( $hashes='' ){
3552
- if( !empty($hashes) ){
3553
- $response = sucuriscan_api_call_wordpress( 'POST', array(
3554
- 'a' => 'send_hashes',
3555
- 'h' => $hashes,
3556
- ) );
3557
 
3558
- if( sucuriscan_handle_response($response) ){
3559
- return TRUE;
 
 
 
 
3560
  }
 
 
3561
  }
3562
 
3563
- return FALSE;
3564
- }
 
 
 
 
 
 
 
 
 
3565
 
3566
- /**
3567
- * Checks last time we ran to avoid running twice (or too often).
3568
- *
3569
- * @param integer $runtime When the filesystem scan must be scheduled to run.
3570
- * @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
3571
- * @return boolean Either TRUE or FALSE representing the success or fail of the operation respectively.
3572
- */
3573
- function sucuriscan_verify_run( $runtime=0, $force_scan=FALSE ){
3574
- $runtime_name = 'sucuriscan_runtime';
3575
- $last_run = sucuriscan_get_option($runtime_name);
3576
- $current_time = time();
3577
 
3578
- if( $last_run && !$force_scan ){
3579
- $runtime_diff = $current_time - $runtime;
3580
 
3581
- if( $last_run >= $runtime_diff ){
3582
- return FALSE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3583
  }
3584
- }
3585
 
3586
- update_option( $runtime_name, $current_time );
3587
- return TRUE;
3588
- }
3589
 
3590
- /**
3591
- * Check whether the current WordPress version must be reported to the API
3592
- * service or not, this is to avoid duplicated information in the audit logs.
3593
- *
3594
- * @return boolean TRUE if the current WordPress version must be reported, FALSE otherwise.
3595
- */
3596
- function sucuriscan_report_wpversion(){
3597
- $option_name = 'sucuriscan_wp_version';
3598
- $reported_version = sucuriscan_get_option($option_name);
3599
- $wp_version = sucuriscan_get_wpversion();
3600
 
3601
- if( $reported_version != $wp_version ){
3602
- sucuriscan_send_log( 'WordPress version: ' . $wp_version );
3603
- update_option( $option_name, $wp_version );
3604
 
3605
- return TRUE;
3606
- }
 
 
 
 
3607
 
3608
- return FALSE;
3609
- }
 
 
 
 
 
 
3610
 
3611
- /**
3612
- * Schedule the task to run the first filesystem scan.
3613
- *
3614
- * @return void
3615
- */
3616
- function sucuriscan_create_scheduled_task(){
3617
- $task_name = 'sucuriscan_scheduled_scan';
3618
 
3619
- if( !wp_next_scheduled($task_name) ){
3620
- wp_schedule_event( time() + 10, 'twicedaily', $task_name );
3621
- }
3622
 
3623
- wp_schedule_single_event( time() + 300, $task_name );
3624
- sucuriscan_info( 'The first filesystem scan was scheduled.' );
3625
- }
3626
 
3627
- /**
3628
- * Gather all the checksums (aka. file hashes) of this site, send them, and
3629
- * analyze them using the Sucuri Monitoring service, this will generate the
3630
- * audit logs for this site and be part of the integrity checks.
3631
- *
3632
- * @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
3633
- * @return boolean TRUE if the filesystem scan was successful, FALSE otherwise.
3634
- */
3635
- function sucuriscan_filesystem_scan( $force_scan=FALSE ){
3636
- $minimum_runtime = SUCURISCAN_MINIMUM_RUNTIME;
 
3637
 
3638
- if(
3639
- sucuriscan_wordpress_apikey()
3640
- && class_exists('SucuriScanFileInfo')
3641
- && sucuriscan_verify_run( $minimum_runtime, $force_scan )
3642
- ){
3643
- sucuriscan_report_wpversion();
3644
 
3645
- $sucuri_fileinfo = new SucuriScanFileInfo();
3646
- $scan_interface = sucuriscan_get_option('sucuriscan_scan_interface');
3647
- $signatures = $sucuri_fileinfo->get_directory_tree_md5(ABSPATH, $scan_interface);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3648
 
3649
- if( $signatures ){
3650
- $hashes_sent = sucuriscan_send_hashes( $signatures );
 
3651
 
3652
- if( $hashes_sent ){
3653
- sucuriscan_info( 'Successful filesystem scan' );
3654
- return TRUE;
3655
- } else {
3656
- sucuriscan_error( 'The file hashes could not be stored.' );
3657
  }
3658
- } else {
3659
- sucuriscan_error( 'The file hashes could not be retrieved, the filesystem scan failed.' );
3660
  }
3661
- }
3662
 
3663
- return FALSE;
3664
- }
3665
 
3666
- /**
3667
- * Generates an audit event log (to be sent later).
3668
- *
3669
- * @param integer $severity Importance of the event that will be reported, values from one to five.
3670
- * @param string $location In which part of the system was the event triggered.
3671
- * @param string $message The explanation of the event.
3672
- * @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
3673
- */
3674
- function sucuriscan_report_event( $severity=0, $location='', $message='' ){
3675
- $user = wp_get_current_user();
3676
- $username = FALSE;
3677
- $current_time = date( 'Y-m-d H:i:s' );
3678
- $remote_ip = sucuriscan_get_remoteaddr();
3679
-
3680
- // Fixing severity value.
3681
- $severity = (int) $severity;
3682
- if( $severity > 0 ){ $severity = 1; }
3683
- elseif( $severity > 5 ){ $severity = 5; }
3684
-
3685
- // Identify current user in session.
3686
- if(
3687
- $user instanceof WP_User
3688
- && isset($user->user_login)
3689
- && !empty($user->user_login)
3690
- ){
3691
- if( $user->user_login != $user->display_name ){
3692
- $username = sprintf( ' %s (%s),', $user->display_name, $user->user_login );
3693
- } else {
3694
- $username = sprintf( ' %s,', $user->user_login );
3695
- }
3696
  }
3697
 
3698
- // Convert the severity number into a readable string.
3699
- switch( $severity ){
3700
- case 0: $severity_name = 'Debug'; break;
3701
- case 1: $severity_name = 'Notice'; break;
3702
- case 2: $severity_name = 'Info'; break;
3703
- case 3: $severity_name = 'Warning'; break;
3704
- case 4: $severity_name = 'Error'; break;
3705
- case 5: $severity_name = 'Critical'; break;
3706
- default: $severity_name = 'Info'; break;
 
 
3707
  }
3708
 
3709
- $message = str_replace( array("\n", "\r"), array('', ''), $message );
3710
- $event_message = sprintf(
3711
- '%s:%s %s; %s',
3712
- $severity_name,
3713
- $username,
3714
- $remote_ip,
3715
- $message
3716
- );
 
3717
 
3718
- return sucuriscan_send_log($event_message);
3719
- }
3720
 
3721
- /**
3722
- * Send a notification to the administrator of the specified events, only if
3723
- * the administrator accepted to receive alerts for this type of events.
3724
- *
3725
- * @param string $event The name of the event that was triggered.
3726
- * @param string $content Body of the email that will be sent to the administrator.
3727
- * @return void
3728
- */
3729
- function sucuriscan_notify_event( $event='', $content='' ){
3730
- $event_name = 'sucuriscan_notify_' . $event;
3731
- $notify = sucuriscan_get_option($event_name);
3732
- $email = sucuriscan_get_option('sucuriscan_notify_to');
3733
- $email_params = array();
3734
 
3735
- if( $notify == 'enabled' ){
3736
- if( $event == 'post_publication' ){
3737
- $event = 'post_update';
 
3738
  }
3739
 
3740
- elseif( $event == 'failed_login' ){
3741
- $content .= '<br><br><em>Explanation: Someone failed to login to your site. If you
3742
- are getting too many of these messages, it is likely your site is under a brute
3743
- force attack. You can disable the notifications for failed logins from
3744
- <a href="' . sucuriscan_get_url('settings') . '" target="_blank">here</a>.
3745
- More details at <a href="http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing"
3746
- target="_blank">Password Guessing Brute Force Attacks</a>.</em>';
3747
- }
3748
 
3749
- // Send a notification even if the limit of emails per hour was reached.
3750
- elseif( $event == 'bruteforce_attack' ){
3751
- $email_params['Force'] = TRUE;
3752
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3753
 
3754
- $title = sprintf( 'Sucuri notification (%s)', str_replace('_', chr(32), $event) );
3755
- $mail_sent = sucuriscan_send_mail( $email, $title, $content, $email_params );
 
 
 
3756
 
3757
- return $mail_sent;
3758
  }
3759
 
3760
- return FALSE;
3761
  }
3762
 
3763
  /**
3764
- * Retrieve the public settings of the account associated with the API keys
3765
- * registered by the administrator of the site. This function will send a HTTP
3766
- * request to the remote API service and process its response, when successful
3767
- * it will return an array/object containing the public attributes of the site.
3768
  *
3769
- * @param boolean $api_key The CloudProxy API key.
3770
- * @return array A hash with the settings of a CloudProxy account.
 
 
 
3771
  */
3772
- function sucuriscan_cloudproxy_settings( $api_key=FALSE ){
3773
- $params = array( 'a' => 'show_settings' );
3774
 
3775
- if( $api_key ){
3776
- $params = array_merge( $params, $api_key );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3777
  }
3778
 
3779
- $response = sucuriscan_api_call_cloudproxy( 'GET', $params );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3780
 
3781
- if( sucuriscan_handle_response($response) ){
3782
- return $response['body']->output;
3783
  }
3784
 
3785
- return FALSE;
3786
- }
 
 
 
 
 
3787
 
3788
- /**
3789
- * Flush the cache of the site(s) associated with the API key.
3790
- *
3791
- * @param boolean $api_key The CloudProxy API key.
3792
- * @return string Message explaining the result of the operation.
3793
- */
3794
- function sucuriscan_cloudproxy_clear_cache( $api_key=FALSE ){
3795
- $params = array( 'a' => 'clear_cache' );
 
 
 
 
 
3796
 
3797
- if( $api_key ){
3798
- $params = array_merge( $params, $api_key );
3799
- }
3800
 
3801
- $response = sucuriscan_api_call_cloudproxy( 'GET', $params );
 
3802
 
3803
- if( sucuriscan_handle_response($response) ){
3804
- return $response['body'];
 
 
 
 
 
 
 
 
3805
  }
3806
 
3807
- return FALSE;
3808
- }
 
 
 
 
 
 
 
 
 
 
 
3809
 
3810
- /**
3811
- * Retrieve the audit logs of the account associated with the API keys
3812
- * registered b the administrator of the site. This function will send a HTTP
3813
- * request to the remote API service and process its response, when successful
3814
- * it will return an array/object containing a list of requests blocked by our
3815
- * CloudProxy.
3816
- *
3817
- * By default the logs that will be retrieved are from today, if you need to see
3818
- * the logs of previous days you will need to add a new parameter to the request
3819
- * URL named "date" with format yyyy-mm-dd.
3820
- *
3821
- * @param boolean $api_key The CloudProxy API key.
3822
- * @param string $date An optional date to filter the result to a specific timespan: yyyy-mm-dd.
3823
- * @return array A list of objects with the detailed version of each request blocked by our service.
3824
- */
3825
- function sucuriscan_cloudproxy_logs( $api_key=FALSE, $date='' ){
3826
- $params = array(
3827
- 'a' => 'audit_trails',
3828
- 'date' => date('Y-m-d'),
3829
- );
3830
 
3831
- if( preg_match('/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/', $date) ){
3832
- $params['date'] = $date;
3833
- }
3834
 
3835
- if( $api_key ){
3836
- $params = array_merge( $params, $api_key );
 
 
 
 
 
 
 
3837
  }
3838
 
3839
- $response = sucuriscan_api_call_cloudproxy( 'GET', $params );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3840
 
3841
- if( sucuriscan_handle_response($response) ){
3842
- return $response['body']->output;
 
 
 
 
 
 
 
 
 
 
 
 
3843
  }
3844
 
3845
- return FALSE;
3846
- }
 
 
 
 
 
3847
 
3848
- $sucuriscan_hooks = array(
3849
- 'add_attachment',
3850
- 'create_category',
3851
- 'delete_post',
3852
- 'private_to_published',
3853
- 'publish_page',
3854
- 'publish_post',
3855
- 'publish_phone',
3856
- 'xmlrpc_publish_post',
3857
- 'add_link',
3858
- 'switch_theme',
3859
- 'delete_user',
3860
- 'retrieve_password',
3861
- 'user_register',
3862
- 'wp_login',
3863
- 'wp_login_failed',
3864
- 'login_form_resetpass',
3865
- );
3866
 
3867
- /**
3868
- * Send to Sucuri servers an alert advising that an attachment was added to a post.
3869
- *
3870
- * @param integer $id The post identifier.
3871
- * @return void
3872
- */
3873
- function sucuriscan_hook_add_attachment( $id=0 ){
3874
- $data = ( is_int($id) ? get_post($id) : FALSE );
3875
- $title = ( $data ? $data->post_title : 'Unknown' );
 
 
3876
 
3877
- $message = 'Media file added #'.$id.' ('.$title.')';
3878
- sucuriscan_report_event( 1, 'core', $message );
3879
- sucuriscan_notify_event( 'post_publication', $message );
3880
- }
3881
 
3882
- /**
3883
- * Send to Sucuri servers an alert advising that a category was created.
3884
- *
3885
- * @param integer $id The identifier of the category created.
3886
- * @return void
3887
- */
3888
- function sucuriscan_hook_create_category( $id=0 ){
3889
- $title = ( is_int($id) ? get_cat_name($id) : 'Unknown' );
3890
 
3891
- $message = 'Category created #'.$id.' ('.$title.')';
3892
- sucuriscan_report_event( 1, 'core', $message );
3893
- sucuriscan_notify_event( 'post_publication', $message );
3894
- }
3895
 
3896
- /**
3897
- * Send to Sucuri servers an alert advising that a post was deleted.
3898
- *
3899
- * @param integer $id The identifier of the post deleted.
3900
- * @return void
3901
- */
3902
- function sucuriscan_hook_delete_post( $id=0 ){
3903
- sucuriscan_report_event( 3, 'core', 'Post deleted #'.$id );
3904
- }
 
 
 
 
 
 
 
3905
 
3906
- /**
3907
- * Send to Sucuri servers an alert advising that the state of a post was changed
3908
- * from private to published. This will only applies for posts not pages.
3909
- *
3910
- * @param integer $id The identifier of the post changed.
3911
- * @return void
3912
- */
3913
- function sucuriscan_hook_private_to_published( $id=0 ){
3914
- $data = ( is_int($id) ? get_post($id) : FALSE );
3915
 
3916
- if( $data ){
3917
- $title = $data->post_title;
3918
- $p_type = ucwords($data->post_type);
3919
- } else {
3920
- $title = 'Unknown';
3921
- $p_type = 'Publication';
 
 
3922
  }
3923
 
3924
- // Check whether the post-type is being ignored to send notifications.
3925
- if( !sucuriscan_is_ignored_event($p_type) ){
3926
- $message = $p_type.' changed from private to published #'.$id.' ('.$title.')';
3927
- sucuriscan_report_event( 2, 'core', $message );
3928
- sucuriscan_notify_event( 'post_publication', $message );
 
 
 
 
 
 
 
 
 
 
 
 
3929
  }
 
3930
  }
3931
 
3932
  /**
3933
- * Send to Sucuri servers an alert advising that a post was published.
 
 
3934
  *
3935
- * @param integer $id The identifier of the post or page published.
3936
  * @return void
3937
  */
3938
- function sucuriscan_hook_publish( $id=0 ){
3939
- $data = ( is_int($id) ? get_post($id) : FALSE );
3940
 
3941
- if( $data ){
3942
- $title = $data->post_title;
3943
- $p_type = ucwords($data->post_type);
3944
- $action = ( $data->post_date == $data->post_modified ? 'created' : 'updated' );
 
 
 
 
 
 
 
 
 
 
3945
  } else {
3946
- $title = 'Unknown';
3947
- $p_type = 'Publication';
3948
- $action = 'published';
 
3949
  }
3950
-
3951
- $message = $p_type.' was '.$action.' #'.$id.' ('.$title.')';
3952
- sucuriscan_report_event( 2, 'core', $message );
3953
- sucuriscan_notify_event( 'post_publication', $message );
3954
  }
3955
 
3956
  /**
3957
- * Alias function for hook_publish()
3958
  *
3959
- * @param integer $id The identifier of the post or page published.
3960
  * @return void
3961
  */
3962
- function sucuriscan_hook_publish_page( $id=0 ){ sucuriscan_hook_publish($id); }
 
 
 
3963
 
3964
- /**
3965
- * Alias function for hook_publish()
3966
- *
3967
- * @param integer $id The identifier of the post or page published.
3968
- * @return void
3969
- */
3970
- function sucuriscan_hook_publish_post( $id=0 ){ sucuriscan_hook_publish($id); }
3971
 
3972
- /**
3973
- * Alias function for hook_publish()
3974
- *
3975
- * @param integer $id The identifier of the post or page published.
3976
- * @return void
3977
- */
3978
- function sucuriscan_hook_publish_phone( $id=0 ){ sucuriscan_hook_publish($id); }
3979
 
3980
- /**
3981
- * Alias function for hook_publish()
3982
- *
3983
- * @param integer $id The identifier of the post or page published.
3984
- * @return void
3985
- */
3986
- function sucuriscan_hook_xmlrpc_publish_post( $id=0 ){ sucuriscan_hook_publish($id); }
3987
-
3988
- /**
3989
- * Send to Sucuri servers an alert advising that a new link was added to the bookmarks.
3990
- *
3991
- * @param integer $id Identifier of the new link created;
3992
- * @return void
3993
- */
3994
- function sucuriscan_hook_add_link( $id=0 ){
3995
- $data = ( is_int($id) ? get_bookmark($id) : FALSE );
3996
 
3997
- if( $data ){
3998
- $title = $data->link_name;
3999
- $url = $data->link_url;
4000
- } else {
4001
- $title = 'Unknown';
4002
- $url = 'undefined/url';
4003
  }
4004
 
4005
- $message = 'New link added #'.$id.' ('.$title.': '.$url.')';
4006
- sucuriscan_report_event( 2, 'core', $message );
4007
- sucuriscan_notify_event( 'post_publication', $message );
4008
- }
4009
-
4010
- /**
4011
- * Send to Sucuri servers an alert advising that the theme of the site was changed.
4012
- *
4013
- * @param string $title The name of the new theme selected to used through out the site.
4014
- * @return void
4015
- */
4016
- function sucuriscan_hook_switch_theme( $title='' ){
4017
- if( empty($title) ){ $title = 'Unknown'; }
4018
 
4019
- $message = 'Theme switched to: '.$title;
4020
- sucuriscan_report_event( 3, 'core', $message );
4021
- sucuriscan_notify_event( 'theme_switched', $message );
4022
- }
4023
 
4024
- /**
4025
- * Send to Sucuri servers an alert advising that a user account was deleted.
4026
- *
4027
- * @param integer $id The identifier of the user account deleted.
4028
- * @return void
4029
- */
4030
- function sucuriscan_hook_delete_user( $id=0 ){
4031
- sucuriscan_report_event( 3, 'core', 'User account deleted #'.$id );
4032
- }
4033
 
4034
- /**
4035
- * Send to Sucuri servers an alert advising that an attempt to retrieve the password
4036
- * of an user account was tried.
4037
- *
4038
- * @param string $title The name of the user account involved in the trasaction.
4039
- * @return void
4040
- */
4041
- function sucuriscan_hook_retrieve_password( $title='' ){
4042
- if( empty($title) ){ $title = 'Unknown'; }
4043
 
4044
- sucuriscan_report_event( 3, 'core', 'Password retrieval attempt for user: '.$title );
4045
- }
 
 
4046
 
4047
- /**
4048
- * Send to Sucuri servers an alert advising that a new user account was created.
4049
- *
4050
- * @param integer $id The identifier of the new user account created.
4051
- * @return void
4052
- */
4053
- function sucuriscan_hook_user_register( $id=0 ){
4054
- $data = ( is_int($id) ? get_userdata($id) : FALSE );
4055
- $title = ( $data ? $data->display_name : 'Unknown' );
4056
 
4057
- $message = 'New user account registered #'.$id.' ('.$title.')';
4058
- sucuriscan_report_event( 3, 'core', $message );
4059
- sucuriscan_notify_event( 'user_registration', $message );
4060
- }
 
 
 
4061
 
4062
- /**
4063
- * Send to Sucuri servers an alert advising that an attempt to login into the
4064
- * administration panel was successful.
4065
- *
4066
- * @param string $title The name of the user account involved in the transaction.
4067
- * @return void
4068
- */
4069
- function sucuriscan_hook_wp_login( $title='' ){
4070
- if( empty($title) ){ $title = 'Unknown'; }
4071
 
4072
- $message = 'User logged in: '.$title;
4073
- sucuriscan_report_event( 2, 'core', $message );
4074
- sucuriscan_notify_event( 'success_login', $message );
4075
- }
 
4076
 
4077
- /**
4078
- * Send to Sucuri servers an alert advising that an attempt to login into the
4079
- * administration panel failed.
4080
- *
4081
- * @param string $title The name of the user account involved in the transaction.
4082
- * @return void
4083
- */
4084
- function sucuriscan_hook_wp_login_failed( $title='' ){
4085
- if( empty($title) ){ $title = 'Unknown'; }
4086
 
4087
- $message = 'User authentication failed: '.$title;
4088
- sucuriscan_report_event( 2, 'core', $message );
4089
- sucuriscan_notify_event( 'failed_login', $message );
4090
 
4091
- // Log the failed login in the internal datastore for future reports.
4092
- $logged = sucuriscan_log_failed_login($title);
 
4093
 
4094
- // Check if the quantity of failed logins will be considered as a brute-force attack.
4095
- if( $logged ){
4096
- $failed_logins = sucuriscan_get_failed_logins();
 
 
 
 
 
4097
 
4098
- if( $failed_logins ){
4099
- $max_time = 3600;
4100
- $maximum_failed_logins = sucuriscan_get_option('sucuriscan_maximum_failed_logins');
4101
 
4102
- /**
4103
- * If the time passed is within the hour, and the quantity of failed logins
4104
- * registered in the datastore file is bigger than the maximum quantity of
4105
- * failed logins allowed per hour (value configured by the administrator in the
4106
- * settings page), then send an email notification reporting the event and
4107
- * specifying that it may be a brute-force attack against the login page.
4108
- */
4109
- if(
4110
- $failed_logins['diff_time'] <= $max_time
4111
- && $failed_logins['count'] >= $maximum_failed_logins
4112
- ){
4113
- sucuriscan_report_failed_logins($failed_logins);
4114
- }
4115
 
4116
- /**
4117
- * If there time passed is superior to the hour, then reset the content of the
4118
- * datastore file containing the failed logins so far, any entry in that file
4119
- * will not be considered as part of a brute-force attack (if it exists) because
4120
- * the time passed between the first and last login attempt is big enough to
4121
- * mitigate the attack. We will consider the current failed login event as the
4122
- * first entry of that file in case of future attempts during the next sixty
4123
- * minutes.
4124
- */
4125
- elseif( $failed_logins['diff_time'] > $max_time ){
4126
- sucuriscan_reset_failed_logins();
4127
- sucuriscan_log_failed_login($title);
4128
- }
4129
- }
4130
- }
4131
- }
4132
 
4133
- /**
4134
- * Send to Sucuri servers an alert advising that an attempt to reset the password
4135
- * of an user account was executed.
4136
- *
4137
- * @return void
4138
- */
4139
- function sucuriscan_hook_login_form_resetpass(){
4140
- // Detecting WordPress 2.8.3 vulnerability - $key is array.
4141
- if( isset($_GET['key']) && is_array($_GET['key']) ){
4142
- sucuriscan_report_event( 3, 'core', 'Attempt to reset password by attacking WP/2.8.3 bug' );
4143
- }
4144
- }
 
 
 
 
 
4145
 
4146
- // Configure the hooks defined above to be triggered automatically.
4147
- if( isset($sucuriscan_hooks) ){
4148
- foreach( $sucuriscan_hooks as $hook_name ){
4149
- $hook_func = 'sucuriscan_hook_' . $hook_name;
4150
 
4151
- if( function_exists($hook_func) ){
4152
- add_action( $hook_name, $hook_func, 50 );
4153
- }
4154
- }
4155
- }
4156
 
4157
- if( !function_exists('sucuriscan_hook_undefined_actions') ){
4158
 
4159
- /**
4160
- * Send a notifications to the administrator of some specific events that are
4161
- * not triggered through an hooked action, but through a simple request in the
4162
- * admin interface.
4163
- *
4164
- * @return integer Either one or zero representing the success or fail of the operation.
4165
- */
4166
- function sucuriscan_hook_undefined_actions(){
 
 
4167
 
4168
- // Plugin activation and/or deactivation.
4169
- if(
4170
- current_user_can('activate_plugins')
4171
- && (
4172
- ( isset($_GET['action']) && preg_match('/^(activate|deactivate)$/', $_GET['action']) ) ||
4173
- ( isset($_POST['action']) && preg_match('/^(activate|deactivate)-selected$/', $_POST['action']))
4174
- )
4175
- ){
4176
- $plugin_list = array();
4177
 
4178
- if(
4179
- isset($_GET['plugin'])
4180
- && !empty($_GET['plugin'])
4181
- && strpos($_SERVER['REQUEST_URI'], 'plugins.php') !== FALSE
4182
- ){
4183
- $action_d = $_GET['action'] . 'd';
4184
- $plugin_list[] = $_GET['plugin'];
4185
- }
 
 
 
 
 
 
 
 
 
 
4186
 
4187
- elseif( isset($_POST['checked']) ){
4188
- $action_d = str_replace('-selected', 'd', $_POST['action']);
4189
- $plugin_list = $_POST['checked'];
4190
- }
 
 
4191
 
4192
- foreach( $plugin_list as $plugin ){
4193
- $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4194
- $message = sprintf(
4195
- 'Plugin %s: %s (v%s; %s)',
4196
- $action_d,
4197
- $plugin_info['Name'],
4198
- $plugin_info['Version'],
4199
- esc_attr($plugin)
4200
- );
4201
 
4202
- sucuriscan_report_event( 3, 'core', $message );
4203
- sucuriscan_notify_event( 'plugin_' . $action_d, $message );
4204
- }
4205
- }
 
 
4206
 
4207
- // Plugin update request.
4208
- elseif(
4209
- current_user_can('update_plugins')
4210
- && (
4211
- ( isset($_GET['action']) && preg_match('/(upgrade-plugin|do-plugin-upgrade)/', $_GET['action']) ) ||
4212
- ( isset($_POST['action']) && $_POST['action'] == 'update-selected' )
4213
- )
4214
- ){
4215
- $plugin_list = array();
4216
 
4217
- if(
4218
- isset($_GET['plugin'])
4219
- && !empty($_GET['plugin'])
4220
- && strpos($_SERVER['REQUEST_URI'], 'wp-admin/update.php') !== FALSE
4221
- ){
4222
- $plugin_list[] = $_GET['plugin'];
4223
- }
 
4224
 
4225
- elseif( isset($_POST['checked']) ){
4226
- $plugin_list = $_POST['checked'];
4227
- }
 
4228
 
4229
- foreach( $plugin_list as $plugin ){
4230
- $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4231
- $message = sprintf(
4232
- 'Plugin request to be updated: %s (v%s; %s)',
4233
- $plugin_info['Name'],
4234
- $plugin_info['Version'],
4235
- esc_attr($plugin)
4236
- );
4237
 
4238
- sucuriscan_report_event( 3, 'core', $message );
4239
- sucuriscan_notify_event( 'plugin_updated', $message );
4240
- }
4241
- }
 
 
 
 
 
 
 
 
 
 
4242
 
4243
- // Plugin installation request.
4244
- elseif(
4245
- current_user_can('install_plugins')
4246
- && isset($_GET['action'])
4247
- && preg_match('/^(install|upload)-plugin$/', $_GET['action'])
4248
- && current_user_can('install_plugins')
4249
- ){
4250
- if( isset($_FILES['pluginzip']) ){
4251
- $plugin = $_FILES['pluginzip']['name'];
4252
- } elseif( isset($_GET['plugin']) ){
4253
- $plugin = $_GET['plugin'];
4254
- } else {
4255
- $plugin = 'Unknown';
4256
- }
 
 
4257
 
4258
- $message = 'Plugin request to be installed: ' . esc_attr($plugin);
4259
- sucuriscan_report_event( 3, 'core', $message );
4260
- sucuriscan_notify_event( 'plugin_installed', $message );
4261
- }
 
 
 
 
 
4262
 
4263
- // Plugin deletion request.
4264
- elseif(
4265
- current_user_can('delete_plugins')
4266
- && isset($_POST['action'])
4267
- && $_POST['action'] == 'delete-selected'
4268
- && isset($_POST['verify-delete'])
4269
- && $_POST['verify-delete'] == 1
4270
- ){
4271
- $plugin_list = (array) $_POST['checked'];
4272
 
4273
- foreach( $plugin_list as $plugin ){
4274
- $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4275
- $message = sprintf(
4276
- 'Plugin request to be deleted: %s (v%s; %s)',
4277
- $plugin_info['Name'],
4278
- $plugin_info['Version'],
4279
- esc_attr($plugin)
4280
- );
 
 
 
 
 
 
 
 
4281
 
4282
- sucuriscan_report_event( 3, 'core', $message );
4283
- sucuriscan_notify_event( 'plugin_deleted', $message );
4284
- }
4285
- }
 
 
 
 
4286
 
4287
- // Plugin editor request.
4288
- elseif(
4289
- current_user_can('edit_plugins')
4290
- && isset($_POST['action'])
4291
- && $_POST['action'] == 'update'
4292
- && isset($_POST['file'])
4293
- && isset($_POST['plugin'])
4294
- && strpos($_SERVER['REQUEST_URI'], 'plugin-editor.php') !== FALSE
4295
- ){
4296
- $message = 'Plugin editor modification: ' . esc_attr($_POST['file']);
4297
- sucuriscan_report_event( 3, 'core', $message );
4298
- sucuriscan_notify_event( 'theme_editor', $message );
4299
- }
4300
 
4301
- // Theme editor request.
4302
- elseif(
4303
- current_user_can('edit_themes')
4304
- && isset($_POST['action'])
4305
- && $_POST['action'] == 'update'
4306
- && isset($_POST['file'])
4307
- && isset($_POST['theme'])
4308
- && strpos($_SERVER['REQUEST_URI'], 'theme-editor.php') !== FALSE
4309
- ){
4310
- $message = 'Theme editor modification: ' . esc_attr($_POST['theme']) . '/' . esc_attr($_POST['file']);
4311
- sucuriscan_report_event( 3, 'core', $message );
4312
- sucuriscan_notify_event( 'theme_editor', $message );
4313
- }
 
4314
 
4315
- // Theme activation and/or deactivation (same hook for switch_theme).
4316
- // Theme installation request (hook not available).
4317
- // Theme deletion request (hook not available).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4318
 
4319
- // Theme update request.
4320
- elseif(
4321
- current_user_can('update_themes')
4322
- && isset($_GET['action'])
4323
- && preg_match('/^(upgrade-theme|do-theme-upgrade)$/', $_GET['action'])
4324
- && isset($_POST['checked'])
4325
- ){
4326
- $theme_list = (array) $_POST['checked'];
4327
 
4328
- foreach( $theme_list as $theme ){
4329
- $theme_info = wp_get_theme($theme);
4330
- $theme_name = ucwords($theme);
4331
- $theme_version = '0.0';
4332
 
4333
- if( $theme_info->exists() ){
4334
- $theme_name = $theme_info->get('Name');
4335
- $theme_version = $theme_info->get('Version');
4336
- }
 
 
 
 
 
 
4337
 
4338
- $message = sprintf(
4339
- 'Theme request to be updated: %s (v%s; %s)',
4340
- $theme_name,
4341
- $theme_version,
4342
- esc_attr($theme)
4343
- );
 
 
4344
 
4345
- sucuriscan_report_event( 3, 'core', $message );
4346
- sucuriscan_notify_event( 'theme_updated', $message );
4347
- }
4348
- }
4349
 
4350
- // WordPress update request.
4351
- elseif(
4352
- current_user_can('update_core')
4353
- && isset($_GET['action'])
4354
- && $_GET['action'] == 'do-core-reinstall'
4355
- && isset($_POST['upgrade'])
4356
- && isset($_POST['version'])
4357
- ){
4358
- $message = 'WordPress updated (or re-installed) to version: ' . esc_attr($_POST['version']);
4359
- sucuriscan_report_event( 3, 'core', $message );
4360
- sucuriscan_notify_event( 'website_updated', $message );
4361
- }
4362
 
4363
- // Widget addition or deletion.
4364
- elseif(
4365
- current_user_can('edit_theme_options')
4366
- && isset($_POST['action'])
4367
- && $_POST['action'] == 'save-widget'
4368
- && isset($_POST['id_base'])
4369
- && isset($_POST['sidebar'])
4370
- ){
4371
- if(
4372
- isset($_POST['delete_widget'])
4373
- && $_POST['delete_widget'] == 1
4374
- ){
4375
- $action_d = 'deleted';
4376
- $action_text = 'deleted from';
4377
- } else {
4378
- $action_d = 'added';
4379
- $action_text = 'added to';
4380
- }
4381
 
4382
- $message = sprintf(
4383
- 'Widget %s (%s) %s %s (#%d; size %dx%d)',
4384
- esc_attr($_POST['id_base']),
4385
- esc_attr($_POST['widget-id']),
4386
- $action_text,
4387
- esc_attr($_POST['sidebar']),
4388
- esc_attr($_POST['widget_number']),
4389
- esc_attr($_POST['widget-width']),
4390
- esc_attr($_POST['widget-height'])
4391
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4392
 
4393
- sucuriscan_report_event( 3, 'core', $message );
4394
- sucuriscan_notify_event( 'widget_' . $action_d, $message );
4395
- }
4396
 
4397
- // Detect any Wordpress settings modification.
4398
- elseif(
4399
- isset($_POST['option_page'])
4400
- && current_user_can('manage_options')
4401
- && SucuriScan::sucuriscan_check_options_wpnonce()
4402
- ){
4403
- // Get the settings available in the database and compare them with the submission.
4404
- $all_options = sucuriscan_get_wp_options();
4405
- $options_changed = sucuriscan_what_options_were_changed($_POST);
4406
- $options_changed_str = '';
4407
- $options_changed_count = 0;
4408
 
4409
- // Generate the list of options changed.
4410
- foreach( $options_changed['original'] as $option_name => $option_value ){
4411
- $options_changed_count += 1;
4412
- $options_changed_str .= sprintf(
4413
- "The value of the option <b>%s</b> was changed from <b>'%s'</b> to <b>'%s'</b>.<br>\n",
4414
- $option_name, $option_value, $options_changed['changed'][$option_name]
4415
- );
4416
- }
4417
 
4418
- // Get the option group (name of the page where the request was originated).
4419
- $option_page = isset($_POST['option_page']) ? $_POST['option_page'] : 'options';
4420
- $page_referer = FALSE;
4421
 
4422
- // Check which of these option groups where modified.
4423
- switch( $option_page ){
4424
- case 'options':
4425
- $page_referer = 'Global';
4426
- break;
4427
- case 'general': /* no_break */
4428
- case 'writing': /* no_break */
4429
- case 'reading': /* no_break */
4430
- case 'discussion': /* no_break */
4431
- case 'media': /* no_break */
4432
- case 'permalink':
4433
- $page_referer = ucwords($option_page);
4434
- break;
4435
- default:
4436
- $page_referer = 'Common';
4437
- break;
4438
- }
4439
 
4440
- if( $page_referer && $options_changed_count > 0 ){
4441
- $message = $page_referer.' settings changed';
4442
- sucuriscan_report_event( 3, 'core', $message );
4443
- sucuriscan_notify_event( 'settings_updated', $message . "<br>\n" . $options_changed_str );
4444
- }
4445
- }
4446
 
4447
- }
4448
 
4449
- add_action( 'admin_init', 'sucuriscan_hook_undefined_actions' );
4450
- add_action( 'login_form', 'sucuriscan_hook_undefined_actions' );
 
 
 
 
 
 
 
4451
  }
4452
 
4453
  /**
@@ -4460,15 +4960,13 @@ if( !function_exists('sucuriscan_hook_undefined_actions') ){
4460
  * @return void
4461
  */
4462
  function sucuriscan_monitoring_page(){
4463
- if( !current_user_can('manage_options') ){
4464
- wp_die(__('You do not have sufficient permissions to access this page: Firewall (WAF)') );
4465
- }
4466
 
4467
  // Process all form submissions.
4468
  sucuriscan_monitoring_form_submissions();
4469
 
4470
  // Get the dynamic values for the template variables.
4471
- $api_key = sucuriscan_cloudproxy_apikey();
4472
 
4473
  // Page pseudo-variables initialization.
4474
  $template_variables = array(
@@ -4490,49 +4988,48 @@ function sucuriscan_monitoring_page(){
4490
  $template_variables['Monitoring.InstructionsVisibility'] = 'hidden';
4491
  }
4492
 
4493
- echo sucuriscan_get_template('monitoring', $template_variables);
4494
  }
4495
 
4496
  /**
4497
  * Process the requests sent by the form submissions originated in the monitoring
4498
- * page, all forms must have a nonce field that will be checked agains the one
4499
  * generated in the template render function.
4500
  *
4501
  * @return void
4502
  */
4503
  function sucuriscan_monitoring_form_submissions(){
4504
 
4505
- if( sucuriscan_check_page_nonce() ){
4506
 
4507
  // Add and/or Update the Sucuri WAF API Key (do it before anything else).
4508
- $option_name = 'sucuriscan_cloudproxy_apikey';
4509
-
4510
- if( isset($_POST[$option_name]) ){
4511
- $api_key = $_POST[$option_name];
4512
 
4513
- if( sucuriscan_valid_cloudproxy_apikey($api_key) ){
4514
- update_option($option_name, $api_key);
4515
- sucuriscan_info( 'Sucuri CloudProxy WAF API key saved successfully' );
 
4516
  } elseif( empty($api_key) ){
4517
- delete_option($option_name);
4518
- sucuriscan_info( 'Sucuri CloudProxy WAF API key removed successfully' );
4519
  } else {
4520
- sucuriscan_error( 'Invalid CloudProxy API key, check your settings and try again.' );
4521
  }
4522
  }
4523
 
4524
  // Flush the cache of the site(s) associated with the API key.
4525
- if( isset($_POST['sucuriscan_clear_cache']) ){
4526
- $clear_cache_resp = sucuriscan_cloudproxy_clear_cache();
4527
 
4528
  if( $clear_cache_resp ){
4529
  if( isset($clear_cache_resp->messages[0]) ){
4530
- sucuriscan_info($clear_cache_resp->messages[0]);
4531
  } else {
4532
- sucuriscan_error('Could not clear the cache of your site, try later again.');
4533
  }
4534
  } else {
4535
- sucuriscan_error( 'CloudProxy WAF is not enabled on your site, or your API key is invalid.' );
4536
  }
4537
  }
4538
 
@@ -4554,7 +5051,7 @@ function sucuriscan_monitoring_settings( $api_key='' ){
4554
  );
4555
 
4556
  if( $api_key ){
4557
- $settings = sucuriscan_cloudproxy_settings($api_key);
4558
 
4559
  $template_variables['Monitoring.APIKey'] = $api_key['string'];
4560
 
@@ -4586,7 +5083,7 @@ function sucuriscan_monitoring_settings( $api_key='' ){
4586
  }
4587
 
4588
  // Parse the snippet template and replace the pseudo-variables.
4589
- $template_variables['Monitoring.SettingOptions'] .= sucuriscan_get_snippet('monitoring-settings', array(
4590
  'Monitoring.OptionCssClass' => $css_class,
4591
  'Monitoring.OptionName' => $option_title,
4592
  'Monitoring.OptionValue' => $option_value,
@@ -4596,7 +5093,7 @@ function sucuriscan_monitoring_settings( $api_key='' ){
4596
  }
4597
  }
4598
 
4599
- return sucuriscan_get_section( 'monitoring-settings', $template_variables );
4600
  }
4601
 
4602
  /**
@@ -4634,10 +5131,10 @@ function sucuriscan_explain_monitoring_settings( $settings=array() ){
4634
  }
4635
 
4636
  /**
4637
- * Get an explaination of the meaning of the value set for the account's attribute cache_mode.
4638
  *
4639
  * @param string $mode The value set for the cache settings of the site.
4640
- * @return string Explaination of the meaning of the cache_mode value.
4641
  */
4642
  function sucuriscan_cache_mode_title( $mode='' ){
4643
  $title = '';
@@ -4645,8 +5142,8 @@ function sucuriscan_cache_mode_title( $mode='' ){
4645
  switch( $mode ){
4646
  case 'docache': $title = 'Enabled (recommended)'; break;
4647
  case 'sitecache': $title = 'Site caching (using your site headers)'; break;
4648
- case 'nocache': $title = 'Minimial (only for a few minutes)'; break;
4649
- case 'nocacheatall': $title = 'Caching didabled (use with caution)'; break;
4650
  default: $title = 'Unknown'; break;
4651
  }
4652
 
@@ -4668,33 +5165,29 @@ function sucuriscan_monitoring_logs( $api_key='' ){
4668
  'AuditLogs.PaginationVisibility' => 'hidden',
4669
  'AuditLogs.AuditPagination' => '',
4670
  'AuditLogs.TargetDate' => '',
4671
- 'AuditLogs.DateYears' => sucuriscan_monitoring_dates('years'),
4672
- 'AuditLogs.DateMonths' => sucuriscan_monitoring_dates('months'),
4673
- 'AuditLogs.DateDays' => sucuriscan_monitoring_dates('days'),
4674
  );
4675
 
4676
  $date = date('Y-m-d');
4677
 
4678
  if( $api_key ){
4679
- // Retrieve the date filter from the request (if any).
4680
- if( isset($_GET['date']) ){
4681
- $date = $_GET['date'];
4682
  }
4683
 
4684
- elseif(
4685
- isset($_POST['sucuriscan_year']) &&
4686
- isset($_POST['sucuriscan_month']) &&
4687
- isset($_POST['sucuriscan_day'])
4688
- ){
4689
- $date = sprintf(
4690
- '%s-%s-%s',
4691
- $_POST['sucuriscan_year'],
4692
- $_POST['sucuriscan_month'],
4693
- $_POST['sucuriscan_day']
4694
- );
4695
  }
4696
 
4697
- $logs_data = sucuriscan_cloudproxy_logs( $api_key, $date );
4698
 
4699
  if( $logs_data ){
4700
  add_thickbox(); /* Include the Thickbox library. */
@@ -4706,8 +5199,11 @@ function sucuriscan_monitoring_logs( $api_key='' ){
4706
  }
4707
 
4708
  $template_variables['AuditLogs.TargetDate'] = htmlentities($date);
 
 
 
4709
 
4710
- return sucuriscan_get_section( 'monitoring-logs', $template_variables );
4711
  }
4712
 
4713
  /**
@@ -4741,14 +5237,14 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
4741
  $filter_by_keyword = FALSE;
4742
  $filter_query = FALSE;
4743
 
4744
- if( isset($_POST['sucuriscan_monitoring_denial_type']) ){
4745
  $filter_by_denial_type = TRUE;
4746
- $filter_query = htmlentities($_POST['sucuriscan_monitoring_denial_type']);
4747
  }
4748
 
4749
- if( isset($_POST['sucuriscan_monitoring_log_filter']) ){
4750
  $filter_by_keyword = TRUE;
4751
- $filter_query = htmlentities($_POST['sucuriscan_monitoring_log_filter']);
4752
  }
4753
 
4754
  foreach( $access_logs as $access_log ){
@@ -4761,7 +5257,7 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
4761
  // If there is a filter, check the access_log data and break the operation if needed.
4762
  if( $filter_query ){
4763
  if( $filter_by_denial_type ){
4764
- $denial_type_slug = sucuriscan_str_human2var($access_log->sucuri_block_reason);
4765
 
4766
  if( $denial_type_slug != $filter_query ){ continue; }
4767
  }
@@ -4791,7 +5287,7 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
4791
  $audit_log_snippet[$attr_title] = $attr_value;
4792
  }
4793
 
4794
- $logs_html .= sucuriscan_get_snippet('monitoring-logs', $audit_log_snippet);
4795
  $counter += 1;
4796
  }
4797
  }
@@ -4810,12 +5306,11 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
4810
  */
4811
  function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=TRUE ){
4812
  $types = array();
4813
- $selected = '';
4814
 
4815
  if( $access_logs && !empty($access_logs) ){
4816
  foreach( $access_logs as $access_log ){
4817
  if( !array_key_exists($access_log->sucuri_block_reason, $types) ){
4818
- $denial_type_k = sucuriscan_str_human2var($access_log->sucuri_block_reason);
4819
  $types[$denial_type_k] = $access_log->sucuri_block_reason;
4820
  }
4821
  }
@@ -4823,14 +5318,16 @@ function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=TRUE
4823
 
4824
  if( $in_html ){
4825
  $html_types = '<option value="">Filter</option>';
4826
-
4827
- if( isset($_REQUEST['sucuriscan_monitoring_denial_type']) ){
4828
- $selected = htmlentities($_REQUEST['sucuriscan_monitoring_denial_type']);
4829
- }
4830
 
4831
  foreach( $types as $type_key => $type_value ){
4832
- $selected_tag = ( $type_key == $selected ) ? 'selected="selected"' : '';
4833
- $html_types .= sprintf( '<option value="%s" %s>%s</option>', $type_key, $selected_tag, $type_value );
 
 
 
 
 
4834
  }
4835
 
4836
  return $html_types;
@@ -4843,24 +5340,33 @@ function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=TRUE
4843
  * Get a list of years, months or days depending of the type specified.
4844
  *
4845
  * @param string $type Either years, months or days.
 
4846
  * @param boolean $in_html Whether the list should be converted to a HTML select options or not.
4847
  * @return array Either an array with the expected values, or a HTML code.
4848
  */
4849
- function sucuriscan_monitoring_dates( $type='', $in_html=TRUE ){
4850
  $options = array();
4851
  $selected = '';
4852
 
 
 
 
 
 
 
 
 
 
 
4853
  switch( $type ){
4854
  case 'years':
 
4855
  $current_year = (int) date('Y');
4856
  $max_years = 5; /* Maximum number of years to keep the logs. */
4857
  $options = range( ($current_year - $max_years), $current_year );
4858
-
4859
- if( isset($_REQUEST['sucuriscan_year']) ){
4860
- $selected = $_REQUEST['sucuriscan_year'];
4861
- }
4862
  break;
4863
  case 'months':
 
4864
  $options = array(
4865
  '01' => 'January',
4866
  '02' => 'February',
@@ -4875,17 +5381,10 @@ function sucuriscan_monitoring_dates( $type='', $in_html=TRUE ){
4875
  '11' => 'November',
4876
  '12' => 'December'
4877
  );
4878
-
4879
- if( isset($_REQUEST['sucuriscan_month']) ){
4880
- $selected = $_REQUEST['sucuriscan_month'];
4881
- }
4882
  break;
4883
  case 'days':
4884
  $options = range(1, 31);
4885
-
4886
- if( isset($_REQUEST['sucuriscan_day']) ){
4887
- $selected = $_REQUEST['sucuriscan_day'];
4888
- }
4889
  break;
4890
  }
4891
 
@@ -4916,15 +5415,13 @@ function sucuriscan_monitoring_dates( $type='', $in_html=TRUE ){
4916
  * @return void
4917
  */
4918
  function sucuriscan_hardening_page(){
 
4919
 
4920
- if( !current_user_can('manage_options') ){
4921
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Hardening') );
4922
- }
4923
-
4924
- if( isset($_POST['wpsucuri-doharden']) ){
4925
- if( !wp_verify_nonce($_POST['sucuriscan_hardening_nonce'], 'sucuriscan_hardening_nonce') ){
4926
- unset($_POST['wpsucuri-doharden']);
4927
- }
4928
  }
4929
 
4930
  ob_start();
@@ -4932,8 +5429,8 @@ function sucuriscan_hardening_page(){
4932
 
4933
  <div id="poststuff">
4934
  <form method="post">
4935
- <input type="hidden" name="sucuriscan_hardening_nonce" value="<?php echo wp_create_nonce('sucuriscan_hardening_nonce'); ?>" />
4936
- <input type="hidden" name="wpsucuri-doharden" value="wpsucuri-doharden" />
4937
 
4938
  <?php
4939
  sucuriscan_harden_version();
@@ -4955,7 +5452,7 @@ function sucuriscan_hardening_page(){
4955
  <?php
4956
  $_html = ob_get_contents();
4957
  ob_end_clean();
4958
- echo sucuriscan_get_base_template($_html, array(
4959
  'PageTitle' => 'Hardening',
4960
  'PageContent' => $_html,
4961
  'PageStyleClass' => 'hardening'
@@ -5019,8 +5516,7 @@ function sucuriscan_harden_status( $title='', $status=0, $type='', $messageok=''
5019
  * @return void
5020
  */
5021
  function sucuriscan_harden_version(){
5022
- global $wp_version;
5023
-
5024
  $updates = get_core_updates();
5025
  $cp = ( !is_array($updates) || empty($updates) ? 1 : 0 );
5026
 
@@ -5033,21 +5529,20 @@ function sucuriscan_harden_version(){
5033
  }
5034
  }
5035
 
5036
- if( strcmp($wp_version, '3.7') < 0 ){
5037
  $cp = 0;
5038
  }
5039
 
5040
- $wp_version = htmlspecialchars($wp_version);
5041
  $initial_msg = 'Why keep your site updated? WordPress is an open-source
5042
  project which means that with every update the details of the changes made
5043
  to the source code are made public, if there were security fixes then
5044
  someone with malicious intent can use this information to attack any site
5045
  that has not been upgraded.';
5046
- $messageok = sprintf('Your WordPress installation (%s) is current.', $wp_version);
5047
  $messagewarn = sprintf(
5048
  'Your current version (%s) is not current.<br>
5049
  <a href="update-core.php" class="button-primary">Update now!</a>',
5050
- $wp_version
5051
  );
5052
 
5053
  sucuriscan_harden_status( 'Verify WordPress version', $cp, NULL, $messageok, $messagewarn, $initial_msg );
@@ -5084,33 +5579,34 @@ function sucuriscan_harden_removegenerator(){
5084
  function sucuriscan_harden_upload(){
5085
  $cp = 1;
5086
  $upmsg = NULL;
5087
- $htaccess_upload = dirname(sucuriscan_dir_filepath())."/.htaccess";
 
5088
 
5089
  if( !is_readable($htaccess_upload) ){
5090
  $cp = 0;
5091
  } else {
5092
  $cp = 0;
5093
- $fcontent = file($htaccess_upload);
5094
 
5095
  foreach( $fcontent as $fline ){
5096
- if( strpos($fline, 'deny from all') !== FALSE ){
5097
  $cp = 1;
5098
  break;
5099
  }
5100
  }
5101
  }
5102
 
5103
- if( isset($_POST['wpsucuri-doharden']) ){
5104
- if( isset($_POST['sucuriscan_harden_upload']) && $cp == 0 ){
5105
  if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
5106
- $upmsg = sucuriscan_error('ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5107
  } else {
5108
- $upmsg = sucuriscan_info('COMPLETE: Upload directory successfully hardened');
5109
  $cp = 1;
5110
  }
5111
  }
5112
 
5113
- elseif( isset($_POST['sucuriscan_harden_upload_unharden']) ){
5114
  $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
5115
  $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
5116
 
@@ -5122,11 +5618,11 @@ function sucuriscan_harden_upload(){
5122
  @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5123
  }
5124
 
5125
- sucuriscan_info('Hardening removed for the WordPress upload directory <code>/wp-content/uploads</code>');
5126
  } else {
5127
- sucuriscan_error(
5128
- '<code>wp-content/uploads/.htaccess</code> does not exists or is not
5129
- writable, you will need to remove the following code (manually):
5130
  <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5131
  );
5132
  }
@@ -5162,27 +5658,27 @@ function sucuriscan_harden_wpcontent(){
5162
  $cp = 0;
5163
  } else {
5164
  $cp = 0;
5165
- $fcontent = file($htaccess_upload);
5166
 
5167
  foreach( $fcontent as $fline ){
5168
- if( strpos($fline, 'deny from all') !== FALSE ){
5169
  $cp = 1;
5170
  break;
5171
  }
5172
  }
5173
  }
5174
 
5175
- if( isset($_POST['wpsucuri-doharden']) ){
5176
- if( isset($_POST['sucuriscan_harden_wpcontent']) && $cp == 0 ){
5177
  if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
5178
- $upmsg = sucuriscan_error('ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5179
  } else {
5180
- $upmsg = sucuriscan_info('COMPLETE: wp-content directory successfully hardened');
5181
  $cp = 1;
5182
  }
5183
  }
5184
 
5185
- elseif( isset($_POST['sucuriscan_harden_wpcontent_unharden']) ){
5186
  $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
5187
  $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
5188
 
@@ -5194,11 +5690,11 @@ function sucuriscan_harden_wpcontent(){
5194
  @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5195
  }
5196
 
5197
- sucuriscan_info('WP-Content directory protection reverted.');
5198
  } else {
5199
- sucuriscan_info(
5200
- '<code>wp-content/.htaccess</code> does not exists or is not writable,
5201
- you will need to remove the following code manually there:
5202
  <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5203
  );
5204
  }
@@ -5237,27 +5733,27 @@ function sucuriscan_harden_wpincludes(){
5237
  $cp = 0;
5238
  } else {
5239
  $cp = 0;
5240
- $fcontent = file($htaccess_upload);
5241
 
5242
  foreach( $fcontent as $fline ){
5243
- if( strpos($fline, 'deny from all') !== FALSE ){
5244
  $cp = 1;
5245
  break;
5246
  }
5247
  }
5248
  }
5249
 
5250
- if( isset($_POST['wpsucuri-doharden']) ){
5251
- if( isset($_POST['sucuriscan_harden_wpincludes']) && $cp == 0 ){
5252
  if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>\n<Files wp-tinymce.php>\nallow from all\n</Files>\n")===FALSE ){
5253
- $upmsg = sucuriscan_error('ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5254
  } else {
5255
- $upmsg = sucuriscan_info('COMPLETE: wp-includes directory successfully hardened.');
5256
  $cp = 1;
5257
  }
5258
  }
5259
 
5260
- elseif( isset($_POST['sucuriscan_harden_wpincludes_unharden']) ){
5261
  $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
5262
  $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
5263
 
@@ -5270,11 +5766,11 @@ function sucuriscan_harden_wpincludes(){
5270
 
5271
  @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5272
  }
5273
- sucuriscan_info('WP-Includes directory protection reverted.');
5274
  } else {
5275
- sucuriscan_error(
5276
- '<code>wp-includes/.htaccess</code> does not exists or is not
5277
- writable, you will need to remove the following code manually
5278
  there: <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5279
  );
5280
  }
@@ -5320,7 +5816,7 @@ function sucuriscan_harden_phpversion(){
5320
  */
5321
  function sucuriscan_cloudproxy_enabled(){
5322
  $btn_string = '';
5323
- $enabled = sucuriscan_is_behind_cloudproxy();
5324
  $status = 1;
5325
 
5326
  if( $enabled !== TRUE ){
@@ -5352,40 +5848,22 @@ function sucuriscan_cloudproxy_enabled(){
5352
  * @return void
5353
  */
5354
  function sucuriscan_harden_secretkeys(){
5355
- $wp_config_path = sucuriscan_get_wpconfig_path();
 
5356
 
5357
  if( $wp_config_path ){
5358
  $cp = 1;
5359
  $message = 'The main configuration file was found at: <code>'.$wp_config_path.'</code><br>';
5360
 
5361
- $secret_key_names = array(
5362
- 'AUTH_KEY',
5363
- 'SECURE_AUTH_KEY',
5364
- 'LOGGED_IN_KEY',
5365
- 'NONCE_KEY',
5366
- 'AUTH_SALT',
5367
- 'SECURE_AUTH_SALT',
5368
- 'LOGGED_IN_SALT',
5369
- 'NONCE_SALT',
5370
- );
5371
-
5372
- foreach( $secret_key_names as $key_name){
5373
- if( !defined($key_name) ){
5374
- $cp = 0;
5375
- $message .= 'The secret key <strong>'.$key_name.'</strong> is not defined.<br>';
5376
- } elseif( constant($key_name) == 'put your unique phrase here' ){
5377
- $cp = 0;
5378
- $message .= 'The secret key <strong>'.$key_name.'</strong> is not properly set.<br>';
5379
- }
5380
- }
5381
-
5382
- if( $cp == 0 ){
5383
- $admin_url = admin_url('admin.php?page=sucuriscan_posthack');
5384
- $message .= '<br><a href="'.$admin_url.'" class="button button-primary">Update WP-Config Keys</a><br>';
5385
  }
5386
  }else{
5387
  $cp = 0;
5388
- $message = 'The <code>wp-config</code> file was not found.<br>';
5389
  }
5390
 
5391
  $message .= '<br>It checks whether you have proper random keys/salts created for WordPress. A
@@ -5394,13 +5872,16 @@ function sucuriscan_harden_secretkeys(){
5394
  random elements to the password. In simple terms, a secret key is a password with
5395
  elements that make it harder to generate enough options to break through your
5396
  security barriers.';
 
 
 
5397
 
5398
  sucuriscan_harden_status(
5399
- 'Secret keys validity',
5400
  $cp,
5401
  NULL,
5402
- 'WordPress secret keys and salts properly created',
5403
- 'WordPress secret keys and salts not set, we recommend creating them for security reasons',
5404
  $message,
5405
  NULL
5406
  );
@@ -5417,18 +5898,19 @@ function sucuriscan_harden_readme(){
5417
  $upmsg = NULL;
5418
  $cp = is_readable(ABSPATH.'/readme.html') ? 0 : 1;
5419
 
5420
- if( isset($_POST['wpsucuri-doharden']) ){
5421
- if( isset($_POST['sucuriscan_harden_readme']) && $cp == 0 ){
 
5422
  if( @unlink(ABSPATH.'/readme.html') === FALSE ){
5423
- $upmsg = sucuriscan_error('Unable to remove <code>readme.html</code> file.');
5424
  } else {
5425
  $cp = 1;
5426
- $upmsg = sucuriscan_info('<code>readme.html</code> file removed successfully.');
5427
  }
5428
  }
5429
 
5430
- elseif( isset($_POST['sucuriscan_harden_readme_unharden']) ){
5431
- sucuriscan_error('We can not revert this action, you should create the <code>readme.html</code> file at your own.');
5432
  }
5433
  }
5434
 
@@ -5453,14 +5935,19 @@ function sucuriscan_harden_adminuser(){
5453
  global $wpdb;
5454
 
5455
  $upmsg = NULL;
5456
- $res = $wpdb->get_results("SELECT user_login FROM {$wpdb->prefix}users WHERE user_login = 'admin'");
5457
- $account_removed = ( count($res) == 0 ? 1 : 0 );
 
 
 
 
 
5458
 
5459
- if( $account_removed == 0 ){
5460
- $upmsg = '<i><strong>We do not offer the option</strong> to automatically change the user name.
5461
  Go to the <a href="'.admin_url('users.php').'" target="_blank">user list</a> and create a new
5462
- admin user name. Once created, log in as that user and remove the default "admin" from there
5463
- (make sure to assign all the admin posts to the new user too!).</i>';
5464
  }
5465
 
5466
  sucuriscan_harden_status(
@@ -5482,14 +5969,14 @@ function sucuriscan_harden_adminuser(){
5482
  function sucuriscan_harden_fileeditor(){
5483
  $file_editor_disabled = defined('DISALLOW_FILE_EDIT') ? DISALLOW_FILE_EDIT : FALSE;
5484
 
5485
- if( isset($_POST['wpsucuri-doharden']) ){
5486
  $current_time = date('r');
5487
- $wp_config_path = sucuriscan_get_wpconfig_path();
5488
 
5489
  $wp_config_writable = ( file_exists($wp_config_path) && is_writable($wp_config_path) ) ? TRUE : FALSE;
5490
  $new_wpconfig = $wp_config_writable ? file_get_contents($wp_config_path) : '';
5491
 
5492
- if( isset($_POST['sucuriscan_harden_fileeditor']) ){
5493
  if( $wp_config_writable ){
5494
  if( preg_match('/(.*define\(.DB_COLLATE..*)/', $new_wpconfig, $match) ){
5495
  $disallow_fileedit_definition = "\n\ndefine('DISALLOW_FILE_EDIT', TRUE); // Sucuri Security: {$current_time}\n";
@@ -5497,37 +5984,37 @@ function sucuriscan_harden_fileeditor(){
5497
  }
5498
 
5499
  @file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
5500
- sucuriscan_info( 'WP-Config file updated successfully, the plugin and theme editor were disabled.' );
5501
  $file_editor_disabled = TRUE;
5502
  } else {
5503
- sucuriscan_error( 'The <code>wp-config.php</code> file is not in the default location
5504
  or is not writable, you will need to put the following code manually there:
5505
  <code>define("DISALLOW_FILE_EDIT", TRUE);</code>' );
5506
  }
5507
  }
5508
 
5509
- elseif( isset($_POST['sucuriscan_harden_fileeditor_unharden']) ){
5510
  if( preg_match("/(.*define\('DISALLOW_FILE_EDIT', TRUE\);.*)/", $new_wpconfig, $match) ){
5511
  if( $wp_config_writable ){
5512
  $new_wpconfig = str_replace("\n{$match[1]}", '', $new_wpconfig);
5513
  file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
5514
- sucuriscan_info( 'WP-Config file updated successfully, the plugin and theme editor were enabled.' );
5515
  $file_editor_disabled = FALSE;
5516
  } else {
5517
- sucuriscan_error( 'The <code>wp-config.php</code> file is not in the default location
5518
  or is not writable, you will need to remove the following code manually from there:
5519
  <code>define("DISALLOW_FILE_EDIT", TRUE);</code>' );
5520
  }
5521
  } else {
5522
- sucuriscan_error( 'We did not find a definition to disallow the file editor.' );
5523
  }
5524
  }
5525
  }
5526
 
5527
- $message = 'Occasionally you may wish to disable the plugin or theme editor to prevent overzealous users
5528
- from being able to edit sensitive files and potentially crash the site. Disabling these also
5529
- provides an additional layer of security if a hacker gains access to a well-privileged user
5530
- account.';
5531
 
5532
  sucuriscan_harden_status(
5533
  'Plugin &amp; Theme editor',
@@ -5573,11 +6060,15 @@ function sucuriscan_harden_dbtables(){
5573
  * @return void
5574
  */
5575
  function sucuriscan_page(){
5576
- if( !current_user_can('manage_options') ){
5577
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Integrity Check') );
5578
- }
5579
 
5580
- sucuriscan_integrity_form_submissions();
 
 
 
 
 
 
5581
 
5582
  $template_variables = array(
5583
  'WordpressVersion' => sucuriscan_wordpress_outdated(),
@@ -5585,30 +6076,7 @@ function sucuriscan_page(){
5585
  'CoreFiles' => sucuriscan_core_files(),
5586
  );
5587
 
5588
- echo sucuriscan_get_template('integrity', $template_variables);
5589
- }
5590
-
5591
- /**
5592
- * Process the requests sent by the form submissions originated in the integrity
5593
- * page, all forms must have a nonce field that will be checked agains the one
5594
- * generated in the template render function.
5595
- *
5596
- * @return void
5597
- */
5598
- function sucuriscan_integrity_form_submissions(){
5599
- if( sucuriscan_check_page_nonce() ){
5600
-
5601
- // Manually force a filesystem scan (by an administrator user).
5602
- if( isset($_POST['sucuriscan_force_scan']) ){
5603
- if( current_user_can('manage_options') ){
5604
- sucuriscan_notify_event( 'plugin_change', 'Filesystem scan forced at: ' . date('r') );
5605
- sucuriscan_filesystem_scan(TRUE);
5606
- } else {
5607
- sucuriscan_error( 'Your privileges are not sufficient to execute this action.' );
5608
- }
5609
- }
5610
-
5611
- }
5612
  }
5613
 
5614
  /**
@@ -5645,9 +6113,9 @@ function sucuriscan_auditlogs(){
5645
 
5646
  // Initialize the values for the pagination.
5647
  $max_per_page = SUCURISCAN_AUDITLOGS_PER_PAGE;
5648
- $page_number = sucuriscan_get_page_number();
5649
  $logs_limit = $page_number * $max_per_page;
5650
- $audit_logs = sucuriscan_get_logs($logs_limit);
5651
 
5652
  $show_all = TRUE;
5653
 
@@ -5696,7 +6164,7 @@ function sucuriscan_auditlogs(){
5696
  $snippet_data['AuditLog.Extra'] .= '<small>For Mac users, this is a scrollable container</small>';
5697
  }
5698
 
5699
- $template_variables['AuditLogs.List'] .= sucuriscan_get_snippet('integrity-auditlogs', $snippet_data);
5700
  $counter_i += 1;
5701
  }
5702
  }
@@ -5706,15 +6174,15 @@ function sucuriscan_auditlogs(){
5706
 
5707
  if( $total_items > 0 ){
5708
  $template_variables['AuditLogs.PaginationVisibility'] = 'visible';
5709
- $template_variables['AuditLogs.PaginationLinks'] = sucuriscan_generate_pagination(
5710
  '%%SUCURI.URL.Home%%',
5711
- $max_per_page * 5, /* Temporary value while we get the total logs. */
5712
  $max_per_page
5713
  );
5714
  }
5715
  }
5716
 
5717
- return sucuriscan_get_section('integrity-auditlogs', $template_variables);
5718
  }
5719
 
5720
  /**
@@ -5723,13 +6191,12 @@ function sucuriscan_auditlogs(){
5723
  * @return string Panel with a warning advising that WordPress is outdated.
5724
  */
5725
  function sucuriscan_wordpress_outdated(){
5726
- global $wp_version;
5727
-
5728
  $updates = get_core_updates();
5729
  $cp = ( !is_array($updates) || empty($updates) ? 1 : 0 );
5730
 
5731
  $template_variables = array(
5732
- 'WordPress.Version' => htmlspecialchars($wp_version),
5733
  'WordPress.UpgradeURL' => admin_url('update-core.php'),
5734
  'WordPress.UpdateVisibility' => 'hidden',
5735
  'WordPressBeta.Visibility' => 'hidden',
@@ -5751,7 +6218,7 @@ function sucuriscan_wordpress_outdated(){
5751
  }
5752
  }
5753
 
5754
- if( strcmp($wp_version, '3.7') < 0 ){
5755
  $cp = 0;
5756
  }
5757
 
@@ -5759,7 +6226,7 @@ function sucuriscan_wordpress_outdated(){
5759
  $template_variables['WordPress.UpdateVisibility'] = 'visible';
5760
  }
5761
 
5762
- return sucuriscan_get_section('integrity-wpoutdate', $template_variables);
5763
  }
5764
 
5765
  /**
@@ -5770,7 +6237,7 @@ function sucuriscan_wordpress_outdated(){
5770
  * @return void
5771
  */
5772
  function sucuriscan_core_files(){
5773
- global $wp_version;
5774
 
5775
  $template_variables = array(
5776
  'CoreFiles.List' => '',
@@ -5779,8 +6246,8 @@ function sucuriscan_core_files(){
5779
  'CoreFiles.BadVisibility' => 'hidden',
5780
  );
5781
 
5782
- if( $wp_version ){
5783
- $latest_hashes = sucuriscan_check_wp_integrity($wp_version);
5784
 
5785
  if( $latest_hashes ){
5786
  $counter = 0;
@@ -5813,7 +6280,7 @@ function sucuriscan_core_files(){
5813
  }
5814
 
5815
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
5816
- $template_variables['CoreFiles.List'] .= sucuriscan_get_snippet('integrity-corefiles', array(
5817
  'CoreFiles.CssClass' => $css_class,
5818
  'CoreFiles.StatusType' => $list_type,
5819
  'CoreFiles.StatusAbbr' => substr($list_type, 0, 1),
@@ -5829,41 +6296,11 @@ function sucuriscan_core_files(){
5829
  $template_variables['CoreFiles.BadVisibility'] = 'visible';
5830
  }
5831
  } else {
5832
- sucuriscan_error( 'Error retrieving the WordPress core hashes, try again.' );
5833
- }
5834
- }
5835
-
5836
- return sucuriscan_get_section('integrity-corefiles', $template_variables);
5837
- }
5838
-
5839
- /**
5840
- * Retrieve a list with the checksums of the files in a specific version of WordPress.
5841
- *
5842
- * @see Release Archive http://wordpress.org/download/release-archive/
5843
- *
5844
- * @param integer $version Valid version number of the WordPress project.
5845
- * @return object Associative object with the relative filepath and the checksums of the project files.
5846
- */
5847
- function sucuriscan_get_official_checksums( $version=0 ){
5848
- $api_url = sprintf('http://api.wordpress.org/core/checksums/1.0/?version=%s&locale=en_US', $version);
5849
- $request = wp_remote_get($api_url);
5850
-
5851
- if( !is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200 ){
5852
- $json_data = json_decode($request['body']);
5853
-
5854
- if( $json_data->checksums !== FALSE ){
5855
- $checksums = $json_data->checksums;
5856
-
5857
- // Convert the object list to an array for better handle of the data.
5858
- if( $checksums instanceof stdClass ){
5859
- $checksums = (array) $checksums;
5860
- }
5861
-
5862
- return $checksums;
5863
  }
5864
  }
5865
 
5866
- return FALSE;
5867
  }
5868
 
5869
  /**
@@ -5882,7 +6319,7 @@ function sucuriscan_get_official_checksums( $version=0 ){
5882
  * @return array Associative array with these keys: modified, stable, removed, added.
5883
  */
5884
  function sucuriscan_check_wp_integrity( $version=0 ){
5885
- $latest_hashes = sucuriscan_get_official_checksums($version);
5886
 
5887
  if( !$latest_hashes ){ return FALSE; }
5888
 
@@ -5941,27 +6378,29 @@ function sucuriscan_check_wp_integrity( $version=0 ){
5941
  function sucuriscan_ignore_integrity_filepath( $filepath='' ){
5942
  // List of files that will be ignored from the integrity checking.
5943
  $ignore_files = array(
5944
- 'favicon.ico',
5945
- '.htaccess',
5946
- 'sitemap.xml',
5947
- 'sitemap.xml.gz',
5948
- 'wp-config.php',
5949
- 'wp-pass.php',
5950
- 'wp-rss.php',
5951
- 'wp-feed.php',
5952
- 'wp-register.php',
5953
- 'wp-atom.php',
5954
- 'wp-commentsrss2.php',
5955
- 'wp-rss2.php',
5956
- 'wp-rdf.php',
 
 
 
 
5957
  );
5958
 
5959
- if(
5960
- in_array($filepath, $ignore_files)
5961
- || strpos($filepath, 'wp-content/themes') !== FALSE
5962
- || strpos($filepath, 'wp-content/plugins') !== FALSE
5963
- ){
5964
- return TRUE;
5965
  }
5966
 
5967
  return FALSE;
@@ -5984,9 +6423,9 @@ function sucuriscan_modified_files(){
5984
  // Find files modified in the last days.
5985
  $back_days = 7;
5986
 
5987
- // Correct the ranges of the search to be between one and sixty days.
5988
- if( sucuriscan_check_page_nonce() && isset($_POST['sucuriscan_last_days']) ){
5989
- $back_days = intval($_POST['sucuriscan_last_days']);
5990
  if ( $back_days <= 0 ){ $back_days = 1; }
5991
  elseif( $back_days >= 60 ){ $back_days = 60; }
5992
  }
@@ -6000,32 +6439,41 @@ function sucuriscan_modified_files(){
6000
  );
6001
  }
6002
 
6003
- // Scan the files of the site.
6004
- $template_variables['ModifiedFiles.Days'] = $back_days;
6005
- $wp_content_hashes = sucuriscan_get_integrity_tree( ABSPATH.'wp-content', true );
6006
- $back_days = current_time('timestamp') - ( $back_days * 86400);
6007
- $counter = 0;
6008
 
6009
- foreach( $wp_content_hashes as $file_path => $file_info ){
6010
- if( $file_info['filetime'] >= $back_days ){
6011
- $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6012
- $mod_date = date('d/M/Y H:i:s', $file_info['filetime']);
6013
 
6014
- $template_variables['ModifiedFiles.List'] .= sucuriscan_get_snippet('integrity-modifiedfiles', array(
6015
- 'ModifiedFiles.CssClass' => $css_class,
6016
- 'ModifiedFiles.CheckSum' => $file_info['checksum'],
6017
- 'ModifiedFiles.FilePath' => $file_path,
6018
- 'ModifiedFiles.DateTime' => $mod_date
6019
- ));
6020
- $counter += 1;
6021
- }
6022
- }
 
 
 
 
 
 
 
 
6023
 
6024
- if( $counter > 0 ){
6025
- $template_variables['ModifiedFiles.NoFilesVisibility'] = 'hidden';
 
 
6026
  }
6027
 
6028
- return sucuriscan_get_section('integrity-modifiedfiles', $template_variables);
6029
  }
6030
 
6031
  /**
@@ -6034,9 +6482,7 @@ function sucuriscan_modified_files(){
6034
  * @return void
6035
  */
6036
  function sucuriscan_posthack_page(){
6037
- if( !current_user_can('manage_options') ){
6038
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Post-Hack') );
6039
- }
6040
 
6041
  $process_form = sucuriscan_posthack_process_form();
6042
 
@@ -6048,7 +6494,7 @@ function sucuriscan_posthack_page(){
6048
  'ResetPlugins' => sucuriscan_posthack_plugins($process_form),
6049
  );
6050
 
6051
- echo sucuriscan_get_template('posthack', $template_variables);
6052
  }
6053
 
6054
  /**
@@ -6057,11 +6503,16 @@ function sucuriscan_posthack_page(){
6057
  * @return boolean TRUE if a form submission should be processed, FALSE otherwise.
6058
  */
6059
  function sucuriscan_posthack_process_form(){
6060
- if( sucuriscan_check_page_nonce() && isset($_POST['sucuriscan_process_form']) ){
6061
- if( $_POST['sucuriscan_process_form'] == 1 ){
 
 
 
 
 
6062
  return TRUE;
6063
  } else {
6064
- sucuriscan_error('You need to confirm that you understand the risk of this operation.');
6065
  }
6066
  }
6067
 
@@ -6078,32 +6529,74 @@ function sucuriscan_update_secret_keys( $process_form=FALSE ){
6078
  $template_variables = array(
6079
  'WPConfigUpdate.Visibility' => 'hidden',
6080
  'WPConfigUpdate.NewConfig' => '',
 
6081
  );
6082
 
6083
  // Update all WordPress secret keys.
6084
- if( $process_form && isset($_POST['sucuriscan_update_wpconfig']) ){
6085
- $wpconfig_process = sucuriscan_set_new_config_keys();
6086
 
6087
  if( $wpconfig_process ){
6088
  $template_variables['WPConfigUpdate.Visibility'] = 'visible';
6089
 
6090
  if( $wpconfig_process['updated'] === TRUE ){
6091
- sucuriscan_info( 'WordPress secret keys updated successfully (check bellow the summary of the operation).' );
6092
  $template_variables['WPConfigUpdate.NewConfig'] .= "// Old Keys\n";
6093
  $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['old_keys_string'];
6094
  $template_variables['WPConfigUpdate.NewConfig'] .= "//\n";
6095
  $template_variables['WPConfigUpdate.NewConfig'] .= "// New Keys\n";
6096
  $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['new_keys_string'];
6097
  } else {
6098
- sucuriscan_error( '<code>wp-config.php</code> file is not writable, replace the old configuration file with the new values shown bellow.' );
 
 
 
6099
  $template_variables['WPConfigUpdate.NewConfig'] = $wpconfig_process['new_wpconfig'];
6100
  }
6101
  } else {
6102
- sucuriscan_error('<code>wp-config.php</code> file was not found in the default location.' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6103
  }
6104
  }
6105
 
6106
- return sucuriscan_get_section('posthack-updatesecretkeys', $template_variables);
6107
  }
6108
 
6109
  /**
@@ -6132,22 +6625,21 @@ function sucuriscan_posthack_users( $process_form=FALSE ){
6132
  $user->user_registered_formatted = date('D, M/Y H:i', $user->user_registered_timestamp);
6133
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6134
 
6135
- $user_snippet = sucuriscan_get_snippet('posthack-resetpassword', array(
6136
  'ResetPassword.UserId' => $user->ID,
6137
- 'ResetPassword.Username' => $user->user_login,
6138
- 'ResetPassword.Displayname' => $user->display_name,
6139
- 'ResetPassword.Email' => $user->user_email,
6140
  'ResetPassword.Registered' => $user->user_registered_formatted,
6141
  'ResetPassword.Roles' => implode(', ', $user->roles),
6142
- 'ResetPassword.CssClass' => $css_class
6143
  ));
6144
 
6145
- $template_variables['ResetPassword.UserList'] .= $user_snippet;
6146
  $counter += 1;
6147
  }
6148
  }
6149
 
6150
- return sucuriscan_get_section('posthack-resetpassword', $template_variables);
6151
  }
6152
 
6153
  /**
@@ -6157,15 +6649,16 @@ function sucuriscan_posthack_users( $process_form=FALSE ){
6157
  * @return void
6158
  */
6159
  function sucuriscan_reset_user_password( $process_form=FALSE ){
6160
- if( $process_form && isset($_POST['sucuriscan_reset_password']) ){
6161
- $user_identifiers = isset($_POST['user_ids']) ? $_POST['user_ids'] : array();
6162
- $pwd_changed = $pwd_not_changed = array();
 
6163
 
6164
  if( is_array($user_identifiers) && !empty($user_identifiers) ){
6165
  arsort($user_identifiers);
6166
 
6167
  foreach( $user_identifiers as $user_id ){
6168
- if( sucuriscan_new_password($user_id) ){
6169
  $pwd_changed[] = $user_id;
6170
  } else {
6171
  $pwd_not_changed[] = $user_id;
@@ -6173,14 +6666,14 @@ function sucuriscan_reset_user_password( $process_form=FALSE ){
6173
  }
6174
 
6175
  if( !empty($pwd_changed) ){
6176
- sucuriscan_info( 'Password changed successfully for users: ' . implode(', ',$pwd_changed) );
6177
  }
6178
 
6179
  if( !empty($pwd_not_changed) ){
6180
- sucuriscan_error( 'Password change failed for users: ' . implode(', ',$pwd_not_changed) );
6181
  }
6182
  } else {
6183
- sucuriscan_error( 'You did not select a user from the list.' );
6184
  }
6185
  }
6186
  }
@@ -6197,7 +6690,7 @@ function sucuriscan_posthack_plugins( $process_form=FALSE ){
6197
  );
6198
 
6199
  sucuriscan_posthack_reinstall_plugins($process_form);
6200
- $all_plugins = sucuriscan_get_plugins();
6201
  $counter = 0;
6202
 
6203
  foreach( $all_plugins as $plugin_path => $plugin_data ){
@@ -6207,11 +6700,11 @@ function sucuriscan_posthack_plugins( $process_form=FALSE ){
6207
  $plugin_status = $plugin_data['IsPluginActive'] ? 'active' : 'not active';
6208
  $plugin_status_class = $plugin_data['IsPluginActive'] ? 'success' : 'default';
6209
 
6210
- $template_variables['ResetPlugin.PluginList'] .= sucuriscan_get_snippet('posthack-resetplugins', array(
6211
  'ResetPlugin.CssClass' => $css_class,
6212
  'ResetPlugin.Disabled' => $input_disabled,
6213
- 'ResetPlugin.PluginPath' => $plugin_path,
6214
- 'ResetPlugin.Plugin' => sucuriscan_excerpt($plugin_data['Name'], 35),
6215
  'ResetPlugin.Version' => $plugin_data['Version'],
6216
  'ResetPlugin.Type' => $plugin_data['PluginType'],
6217
  'ResetPlugin.TypeClass' => $plugin_type_class,
@@ -6222,7 +6715,7 @@ function sucuriscan_posthack_plugins( $process_form=FALSE ){
6222
  $counter += 1;
6223
  }
6224
 
6225
- return sucuriscan_get_section('posthack-resetplugins', $template_variables);
6226
  }
6227
 
6228
  /**
@@ -6239,17 +6732,14 @@ function sucuriscan_posthack_reinstall_plugins( $process_form=FALSE ){
6239
  include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
6240
  include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); // For plugins_api.
6241
 
6242
- if(
6243
- isset($_POST['plugin_path'])
6244
- && !empty($_POST['plugin_path'])
6245
- ){
6246
  // Create an instance of the FileInfo interface.
6247
  $sucuri_fileinfo = new SucuriScanFileInfo();
6248
  $sucuri_fileinfo->ignore_files = FALSE;
6249
  $sucuri_fileinfo->ignore_directories = FALSE;
6250
 
6251
  // Get (possible) cached information from the installed plugins.
6252
- $all_plugins = sucuriscan_get_plugins();
6253
 
6254
  // Loop through all the installed plugins.
6255
  foreach( $_POST['plugin_path'] as $plugin_path ){
@@ -6258,7 +6748,7 @@ function sucuriscan_posthack_reinstall_plugins( $process_form=FALSE ){
6258
 
6259
  // Check if the plugin can be downloaded from the free market.
6260
  if( $plugin_data['IsFreePlugin'] === TRUE ){
6261
- $plugin_info = sucuriscan_get_remote_plugin_data($plugin_data['RepositoryName']);
6262
 
6263
  if( $plugin_info ){
6264
  // First, remove all files/sub-folders from the plugin's directory.
@@ -6270,13 +6760,13 @@ function sucuriscan_posthack_reinstall_plugins( $process_form=FALSE ){
6270
  $upgrader = new Plugin_Upgrader($upgrader_skin);
6271
  $upgrader->install($plugin_info->download_link);
6272
  } else {
6273
- sucuriscan_error( 'Could not establish a stable connection with the WordPress plugins market.' );
6274
  }
6275
  }
6276
  }
6277
  }
6278
  } else {
6279
- sucuriscan_error( 'You did not select a free plugin to reinstall.' );
6280
  }
6281
  }
6282
  }
@@ -6289,9 +6779,7 @@ function sucuriscan_posthack_reinstall_plugins( $process_form=FALSE ){
6289
  * @return string Last-logings for the administrator accounts.
6290
  */
6291
  function sucuriscan_lastlogins_page(){
6292
- if( !current_user_can('manage_options') ){
6293
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Last-Logins') );
6294
- }
6295
 
6296
  // Page pseudo-variables initialization.
6297
  $template_variables = array(
@@ -6302,7 +6790,7 @@ function sucuriscan_lastlogins_page(){
6302
  'FailedLogins' => sucuriscan_failed_logins_panel(),
6303
  );
6304
 
6305
- echo sucuriscan_get_template('lastlogins', $template_variables);
6306
  }
6307
 
6308
  /**
@@ -6326,8 +6814,8 @@ function sucuriscan_lastlogins_admins(){
6326
  $admin->lastlogins = $last_logins['entries'];
6327
 
6328
  $user_snippet = array(
6329
- 'AdminUsers.Username' => $admin->user_login,
6330
- 'AdminUsers.Email' => $admin->user_email,
6331
  'AdminUsers.LastLogins' => '',
6332
  'AdminUsers.RegisteredAt' => 'Undefined',
6333
  'AdminUsers.UserURL' => admin_url('user-edit.php?user_id='.$admin->ID),
@@ -6343,19 +6831,19 @@ function sucuriscan_lastlogins_admins(){
6343
 
6344
  foreach( $admin->lastlogins as $lastlogin ){
6345
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6346
- $user_snippet['AdminUsers.LastLogins'] .= sucuriscan_get_snippet('lastlogins-admins-lastlogin', array(
6347
- 'AdminUsers.RemoteAddr' => $lastlogin->user_remoteaddr,
6348
- 'AdminUsers.Datetime' => $lastlogin->user_lastlogin,
6349
  'AdminUsers.CssClass' => $css_class,
6350
  ));
6351
  $counter += 1;
6352
  }
6353
  }
6354
 
6355
- $template_variables['AdminUsers.List'] .= sucuriscan_get_snippet('lastlogins-admins', $user_snippet);
6356
  }
6357
 
6358
- return sucuriscan_get_section('lastlogins-admins', $template_variables);
6359
  }
6360
 
6361
  /**
@@ -6367,7 +6855,7 @@ function sucuriscan_lastlogins_admins(){
6367
  */
6368
  function sucuriscan_lastlogins_all(){
6369
  $max_per_page = SUCURISCAN_LASTLOGINS_USERSLIMIT;
6370
- $page_number = sucuriscan_get_page_number();
6371
  $offset = ($max_per_page * $page_number) - $max_per_page;
6372
 
6373
  $template_variables = array(
@@ -6380,7 +6868,7 @@ function sucuriscan_lastlogins_all(){
6380
  );
6381
 
6382
  if( !sucuriscan_lastlogins_datastore_is_writable() ){
6383
- sucuriscan_error( 'Last-logins datastore file is not writable: <code>'.sucuriscan_lastlogins_datastore_filepath().'</code>' );
6384
  }
6385
 
6386
  $counter = 0;
@@ -6389,6 +6877,9 @@ function sucuriscan_lastlogins_all(){
6389
 
6390
  if( $last_logins['total'] > $max_per_page ){
6391
  $template_variables['UserList.PaginationVisibility'] = 'visible';
 
 
 
6392
  $template_variables['UserList.NoItemsVisibility'] = 'hidden';
6393
  }
6394
 
@@ -6403,32 +6894,32 @@ function sucuriscan_lastlogins_all(){
6403
  'UserList.Displayname' => '',
6404
  'UserList.Email' => '',
6405
  'UserList.Registered' => '',
6406
- 'UserList.RemoteAddr' => $user->user_remoteaddr,
6407
- 'UserList.Hostname' => $user->user_hostname,
6408
- 'UserList.Datetime' => $user->user_lastlogin,
6409
- 'UserList.TimeAgo' => sucuriscan_time_ago($user->user_lastlogin),
6410
  'UserList.UserURL' => admin_url('user-edit.php?user_id='.$user->user_id),
6411
  'UserList.CssClass' => $css_class,
6412
  );
6413
 
6414
  if( $user->user_exists ){
6415
- $user_dataset['UserList.Username'] = $user->user_login;
6416
- $user_dataset['UserList.Displayname'] = $user->display_name;
6417
- $user_dataset['UserList.Email'] = $user->user_email;
6418
- $user_dataset['UserList.Registered'] = $user->user_registered;
6419
  }
6420
 
6421
- $template_variables['UserList'] .= sucuriscan_get_snippet('lastlogins-all', $user_dataset);
6422
  }
6423
 
6424
  // Generate the pagination for the list.
6425
- $template_variables['UserList.Pagination'] = sucuriscan_generate_pagination(
6426
  '%%SUCURI.URL.Lastlogins%%',
6427
  $last_logins['total'],
6428
  $max_per_page
6429
  );
6430
 
6431
- return sucuriscan_get_section('lastlogins-all', $template_variables);
6432
  }
6433
 
6434
  /**
@@ -6437,7 +6928,7 @@ function sucuriscan_lastlogins_all(){
6437
  * @return string Absolute filepath where the user's last login information is stored.
6438
  */
6439
  function sucuriscan_lastlogins_datastore_filepath(){
6440
- return sucuriscan_dir_filepath( 'sucuri-lastlogins.php' );
6441
  }
6442
 
6443
  /**
@@ -6466,12 +6957,17 @@ function sucuriscan_lastlogins_datastore_exists(){
6466
  */
6467
  function sucuriscan_lastlogins_datastore_is_writable(){
6468
  $datastore_filepath = sucuriscan_lastlogins_datastore_exists();
 
6469
  if($datastore_filepath){
6470
  if( !is_writable($datastore_filepath) ){
6471
  @chmod($datastore_filepath, 0644);
6472
  }
6473
- return is_writable($datastore_filepath) ? $datastore_filepath : FALSE;
 
 
 
6474
  }
 
6475
  return FALSE;
6476
  }
6477
 
@@ -6483,9 +6979,11 @@ function sucuriscan_lastlogins_datastore_is_writable(){
6483
  */
6484
  function sucuriscan_lastlogins_datastore_is_readable(){
6485
  $datastore_filepath = sucuriscan_lastlogins_datastore_exists();
 
6486
  if( $datastore_filepath && is_readable($datastore_filepath) ){
6487
  return $datastore_filepath;
6488
  }
 
6489
  return FALSE;
6490
  }
6491
 
@@ -6501,7 +6999,7 @@ if( !function_exists('sucuri_set_lastlogin') ){
6501
 
6502
  if($datastore_filepath){
6503
  $current_user = get_user_by('login', $user_login);
6504
- $remote_addr = sucuriscan_get_remoteaddr();
6505
 
6506
  $login_info = array(
6507
  'user_id' => $current_user->ID,
@@ -6537,7 +7035,7 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
6537
 
6538
  if( $datastore_filepath ){
6539
  $parsed_lines = 0;
6540
- $data_lines = @file($datastore_filepath);
6541
 
6542
  if( $data_lines ){
6543
  /**
@@ -6566,7 +7064,7 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
6566
  for( $i=$offset; $i<$total_lines; $i++ ){
6567
  $line = $reversed_lines[$i] ? trim($reversed_lines[$i]) : '';
6568
 
6569
- if( preg_match('/^a:/', $line) ){
6570
  $last_login = @unserialize($line);
6571
 
6572
  // Only administrators can see all login stats.
@@ -6596,7 +7094,7 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
6596
  $parsed_lines += 1;
6597
  }
6598
 
6599
- if( preg_match('/^([0-9]+)$/', $limit) && $limit>0 ){
6600
  if( $parsed_lines >= $limit ){ break; }
6601
  }
6602
  }
@@ -6626,8 +7124,7 @@ if( !function_exists('sucuri_login_redirect') ){
6626
  return $login_url;
6627
  }
6628
 
6629
- $lastlogin_redirection = sucuriscan_get_option('sucuriscan_lastlogin_redirection');
6630
- if( $lastlogin_redirection == 'enabled' ){
6631
  add_filter('login_redirect', 'sucuriscan_login_redirect', 10, 3);
6632
  }
6633
  }
@@ -6639,7 +7136,10 @@ if( !function_exists('sucuri_get_user_lastlogin') ){
6639
  * @return void
6640
  */
6641
  function sucuriscan_get_user_lastlogin(){
6642
- if( isset($_GET['sucuriscan_lastlogin']) && current_user_can('manage_options') ){
 
 
 
6643
  $current_user = wp_get_current_user();
6644
 
6645
  // Select the penultimate entry, not the last one.
@@ -6648,10 +7148,10 @@ if( !function_exists('sucuri_get_user_lastlogin') ){
6648
  if( isset($last_logins['entries'][1]) ){
6649
  $row = $last_logins['entries'][1];
6650
 
6651
- $message_tpl = 'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>';
6652
  $lastlogin_message = sprintf( $message_tpl, date('d/M/Y H:i'), $row->user_remoteaddr, $row->user_hostname );
6653
- $lastlogin_message .= chr(32).'(<a href="'.site_url('wp-admin/admin.php?page='.SUCURISCAN.'_lastlogins').'">view all logs</a>)';
6654
- sucuriscan_info( $lastlogin_message );
6655
  }
6656
  }
6657
  }
@@ -6672,6 +7172,7 @@ function sucuriscan_loggedin_users_panel(){
6672
  );
6673
 
6674
  $logged_in_users = sucuriscan_get_online_users(TRUE);
 
6675
  if( is_array($logged_in_users) && !empty($logged_in_users) ){
6676
  $template_variables['LoggedInUsers.Total'] = count($logged_in_users);
6677
  $counter = 0;
@@ -6681,20 +7182,20 @@ function sucuriscan_loggedin_users_panel(){
6681
  $logged_in_user['last_activity_datetime'] = date('d/M/Y H:i', $logged_in_user['last_activity']);
6682
  $logged_in_user['user_registered_datetime'] = date('d/M/Y H:i', strtotime($logged_in_user['user_registered']));
6683
 
6684
- $template_variables['LoggedInUsers.List'] .= sucuriscan_get_snippet('lastlogins-loggedin', array(
6685
- 'LoggedInUsers.Id' => $logged_in_user['user_id'],
6686
  'LoggedInUsers.UserURL' => admin_url('user-edit.php?user_id='.$logged_in_user['user_id']),
6687
- 'LoggedInUsers.UserLogin' => $logged_in_user['user_login'],
6688
- 'LoggedInUsers.UserEmail' => $logged_in_user['user_email'],
6689
- 'LoggedInUsers.LastActivity' => $logged_in_user['last_activity_datetime'],
6690
- 'LoggedInUsers.Registered' => $logged_in_user['user_registered_datetime'],
6691
- 'LoggedInUsers.RemoveAddr' => $logged_in_user['remote_addr'],
6692
  'LoggedInUsers.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate'
6693
  ));
6694
  }
6695
  }
6696
 
6697
- return sucuriscan_get_section('lastlogins-loggedin', $template_variables);
6698
  }
6699
 
6700
  /**
@@ -6706,7 +7207,7 @@ function sucuriscan_loggedin_users_panel(){
6706
  function sucuriscan_get_online_users( $add_current_user=FALSE ){
6707
  $users = array();
6708
 
6709
- if( sucuriscan_is_multisite() ){
6710
  $users = get_site_transient('online_users');
6711
  } else {
6712
  $users = get_transient('online_users');
@@ -6737,7 +7238,7 @@ function sucuriscan_get_online_users( $add_current_user=FALSE ){
6737
  function sucuriscan_save_online_users( $logged_in_users=array() ){
6738
  $expiration = 30 * 60;
6739
 
6740
- if( sucuriscan_is_multisite() ){
6741
  return set_site_transient('online_users', $logged_in_users, $expiration);
6742
  } else {
6743
  return set_transient('online_users', $logged_in_users, $expiration);
@@ -6752,7 +7253,7 @@ if( !function_exists('sucuriscan_unset_online_user_on_logout') ){
6752
  * @return void
6753
  */
6754
  function sucuriscan_unset_online_user_on_logout(){
6755
- $remote_addr = sucuriscan_get_remoteaddr();
6756
  $current_user = wp_get_current_user();
6757
  $user_id = $current_user->ID;
6758
 
@@ -6802,7 +7303,7 @@ if( !function_exists('sucuriscan_set_online_user') ){
6802
  // Get logged in user information.
6803
  $current_user = ($user instanceof WP_User) ? $user : wp_get_current_user();
6804
  $current_user_id = $current_user->ID;
6805
- $remote_addr = sucuriscan_get_remoteaddr();
6806
  $current_time = current_time('timestamp');
6807
  $logged_in_users = sucuriscan_get_online_users();
6808
 
@@ -6813,7 +7314,7 @@ if( !function_exists('sucuriscan_set_online_user') ){
6813
  'user_email' => $current_user->user_email,
6814
  'user_registered' => $current_user->user_registered,
6815
  'last_activity' => $current_time,
6816
- 'remote_addr' => $remote_addr
6817
  );
6818
 
6819
  if( !is_array($logged_in_users) || empty($logged_in_users) ){
@@ -6871,8 +7372,8 @@ function sucuriscan_failed_logins_panel(){
6871
  'FailedLogins.WarningVisibility' => 'visible',
6872
  );
6873
 
6874
- $max_failed_logins = sucuriscan_get_option('sucuriscan_maximum_failed_logins');
6875
- $notify_bruteforce_attack = sucuriscan_get_option('sucuriscan_notify_bruteforce_attack');
6876
  $failed_logins = sucuriscan_get_failed_logins();
6877
 
6878
  if( $failed_logins ){
@@ -6881,13 +7382,13 @@ function sucuriscan_failed_logins_panel(){
6881
  foreach( $failed_logins['entries'] as $login_data ){
6882
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6883
 
6884
- $template_variables['FailedLogins.List'] .= sucuriscan_get_snippet('lastlogins-failedlogins', array(
6885
  'FailedLogins.CssClass' => $css_class,
6886
  'FailedLogins.Num' => ($counter + 1),
6887
- 'FailedLogins.Username' => $login_data['user_login'],
6888
- 'FailedLogins.RemoteAddr' => $login_data['remote_addr'],
6889
  'FailedLogins.Datetime' => date('d/M/Y H:i', $login_data['attempt_time']),
6890
- 'FailedLogins.UserAgent' => esc_attr($login_data['user_agent']),
6891
  ));
6892
 
6893
  $counter += 1;
@@ -6904,7 +7405,7 @@ function sucuriscan_failed_logins_panel(){
6904
  $template_variables['FailedLogins.WarningVisibility'] = 'hidden';
6905
  }
6906
 
6907
- return sucuriscan_get_section('lastlogins-failedlogins', $template_variables);
6908
  }
6909
 
6910
  /**
@@ -6919,7 +7420,7 @@ function sucuriscan_failed_logins_panel(){
6919
  * @return string The full (relative) path where the file is located.
6920
  */
6921
  function sucuriscan_failed_logins_datastore_path( $reset=FALSE ){
6922
- $datastore_path = sucuriscan_dir_filepath('sucuri-failedlogins.php');
6923
  $default_content = sucuriscan_failed_logins_default_content();
6924
 
6925
  // Create the file if it does not exists.
@@ -6965,7 +7466,7 @@ function sucuriscan_get_failed_logins(){
6965
  $default_content_n = substr_count($default_content, "\n");
6966
 
6967
  if( $datastore_path ){
6968
- $lines = @file($datastore_path);
6969
 
6970
  if( $lines ){
6971
  $failed_logins = array(
@@ -6979,7 +7480,7 @@ function sucuriscan_get_failed_logins(){
6979
  // Read and parse all the entries found in the datastore file.
6980
  foreach( $lines as $i => $line ){
6981
  if( $i >= $default_content_n ){
6982
- $login_data = json_decode( trim($line), TRUE );
6983
  $login_data['attempt_date'] = date('r', $login_data['attempt_time']);
6984
 
6985
  if( !$login_data['user_agent'] ){
@@ -7022,8 +7523,8 @@ function sucuriscan_log_failed_login( $user_login='' ){
7022
  $login_data = json_encode(array(
7023
  'user_login' => $user_login,
7024
  'attempt_time' => time(),
7025
- 'remote_addr' => sucuriscan_get_remoteaddr(),
7026
- 'user_agent' => sucuriscan_get_useragent(),
7027
  ));
7028
 
7029
  $logged = @file_put_contents( $datastore_path, $login_data . "\n", FILE_APPEND );
@@ -7045,7 +7546,7 @@ function sucuriscan_log_failed_login( $user_login='' ){
7045
  */
7046
  function sucuriscan_report_failed_logins( $failed_logins=array() ){
7047
  if( $failed_logins && $failed_logins['count'] > 0 ){
7048
- $prettify_mails = sucuriscan_prettify_mails();
7049
  $mail_content = '';
7050
 
7051
  if( $prettify_mails ){
@@ -7081,32 +7582,482 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
7081
  }
7082
  }
7083
 
7084
- if( $prettify_mails ){
7085
- $table_html .= '</tbody>';
7086
- $table_html .= '</table>';
7087
- $mail_content = $table_html;
7088
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7089
 
7090
- if( sucuriscan_notify_event( 'bruteforce_attack', $mail_content ) ){
7091
- sucuriscan_reset_failed_logins();
 
 
7092
 
7093
- return TRUE;
7094
- }
 
 
 
 
 
7095
  }
7096
 
7097
- return FALSE;
7098
  }
7099
 
7100
  /**
7101
- * Remove all the entries in the datastore file where the failed logins are
7102
- * being kept. The execution of this function will not delete the file (which is
7103
- * likely the best move) but rather will clean its content and append the
7104
- * default code defined by another function above.
7105
  *
7106
- * @return boolean Whether the datastore file was resetted or not.
7107
  */
7108
- function sucuriscan_reset_failed_logins(){
7109
- return (bool) sucuriscan_failed_logins_datastore_path(TRUE);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7110
  }
7111
 
7112
  /**
@@ -7119,9 +8070,10 @@ function sucuriscan_reset_failed_logins(){
7119
  * @return void
7120
  */
7121
  function sucuriscan_infosys_page(){
7122
- if( !current_user_can('manage_options') ){
7123
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri InfoSys') );
7124
- }
 
7125
 
7126
  // Page pseudo-variables initialization.
7127
  $template_variables = array(
@@ -7132,7 +8084,7 @@ function sucuriscan_infosys_page(){
7132
  'WordpressConfig' => sucuriscan_infosys_wpconfig(),
7133
  );
7134
 
7135
- echo sucuriscan_get_template('infosys', $template_variables);
7136
  }
7137
 
7138
  /**
@@ -7142,7 +8094,7 @@ function sucuriscan_infosys_page(){
7142
  * @return string The HTML code displaying the information about the HTAccess rules.
7143
  */
7144
  function sucuriscan_infosys_htaccess(){
7145
- $htaccess_path = sucuriscan_get_htaccess_path();
7146
 
7147
  $template_variables = array(
7148
  'HTAccess.Content' => '',
@@ -7177,7 +8129,7 @@ function sucuriscan_infosys_htaccess(){
7177
  $template_variables['HTAccess.MessageVisible'] = 'visible';
7178
  }
7179
 
7180
- return sucuriscan_get_section('infosys-htaccess', $template_variables);
7181
  }
7182
 
7183
  /**
@@ -7189,7 +8141,7 @@ function sucuriscan_infosys_htaccess(){
7189
  */
7190
  function sucuriscan_htaccess_is_standard($rules=FALSE){
7191
  if( $rules===FALSE ){
7192
- $htaccess_path = sucuriscan_get_htaccess_path();
7193
  $rules = $htaccess_path ? file_get_contents($htaccess_path) : '';
7194
  }
7195
 
@@ -7236,687 +8188,268 @@ function sucuriscan_infosys_wpconfig(){
7236
  $template_variables = array(
7237
  'WordpressConfig.Rules' => '',
7238
  'WordpressConfig.Total' => 0,
7239
- 'WordpressConfig.Content' => '',
7240
- 'WordpressConfig.ThickboxURL' => '#TB_inline?',
7241
- );
7242
- $ignore_wp_rules = array('DB_PASSWORD');
7243
- $template_variables['WordpressConfig.ThickboxURL'] .= http_build_query(array(
7244
- 'width' => '800',
7245
- 'height' => '550',
7246
- 'inlineId' => 'sucuriscan-wpconfig-content',
7247
- ));
7248
-
7249
- $wp_config_path = sucuriscan_get_wpconfig_path();
7250
- if( $wp_config_path ){
7251
- add_thickbox();
7252
- $wp_config_content = file($wp_config_path);
7253
- $template_variables['WordpressConfig.Content'] = file_get_contents($wp_config_path);
7254
-
7255
- // Read WordPress main configuration file as text plain.
7256
- $wp_config_rules = array();
7257
- foreach( (array)$wp_config_content as $line ){
7258
- $line = str_replace("\n", '', $line);
7259
-
7260
- // Ignore useless lines and append to the clean string the important lines.
7261
- if( preg_match('/^define\(/', $line) ){
7262
- $line = str_replace('define(', '', $line);
7263
- $line = preg_replace('/\);.*/', '', $line);
7264
- $line_parts = explode(',', $line, 2);
7265
- }
7266
- else if( preg_match('/^\$[a-zA-Z_]+/', $line) ){
7267
- $line_parts = explode('=', $line, 2);
7268
- }
7269
- else{ continue; }
7270
-
7271
- // Clean and append the rule to the wp_config_rules variable.
7272
- if( isset($line_parts) && count($line_parts)==2 ){
7273
- $key_name = $key_value = '';
7274
- foreach($line_parts as $i=>$line_part){
7275
- $line_part = trim($line_part);
7276
- $line_part = ltrim($line_part, '$');
7277
- $line_part = rtrim($line_part, ';');
7278
-
7279
- // Remove single/double quotes at the beginning and end of the string.
7280
- $line_part = ltrim($line_part, "'");
7281
- $line_part = rtrim($line_part, "'");
7282
- $line_part = ltrim($line_part, '"');
7283
- $line_part = rtrim($line_part, '"');
7284
-
7285
- // Assign the clean strings to specific variables.
7286
- if( $i==0 ){ $key_name = $line_part; }
7287
- if( $i==1 ){ $key_value = $line_part; }
7288
- }
7289
-
7290
- if( !in_array($key_name, $ignore_wp_rules) ){
7291
- $wp_config_rules[$key_name] = $key_value;
7292
- }
7293
- }
7294
- }
7295
-
7296
- // Pass the WordPress configuration rules to the template and show them.
7297
- $counter = 0;
7298
- foreach( $wp_config_rules as $var_name=>$var_value ){
7299
- $counter += 1;
7300
- $template_variables['WordpressConfig.Total'] += 1;
7301
- $template_variables['WordpressConfig.Rules'] .= sucuriscan_get_snippet('infosys-wpconfig', array(
7302
- 'WordpressConfig.VariableName' => $var_name,
7303
- 'WordpressConfig.VariableValue' => htmlentities($var_value),
7304
- 'WordpressConfig.CssClass' => ( $counter%2 == 0 ) ? '' : 'alternate'
7305
- ));
7306
- }
7307
- }
7308
-
7309
- return sucuriscan_get_section('infosys-wpconfig', $template_variables);
7310
- }
7311
-
7312
- /**
7313
- * Retrieve a list with the scheduled tasks configured for the site.
7314
- *
7315
- * @return array A list of pseudo-variables and values that will replace them in the HTML template.
7316
- */
7317
- function sucuriscan_show_cronjobs(){
7318
- $template_variables = array(
7319
- 'Cronjobs.List' => '',
7320
- 'Cronjobs.Total' => 0,
7321
- );
7322
-
7323
- $cronjobs = _get_cron_array();
7324
- $schedules = wp_get_schedules();
7325
- $date_format = _x('M j, Y - H:i', 'Publish box date format', 'cron-view' );
7326
- $counter = 0;
7327
-
7328
- foreach( $cronjobs as $timestamp=>$cronhooks ){
7329
- foreach( (array)$cronhooks as $hook=>$events ){
7330
- foreach( (array)$events as $key=>$event ){
7331
- $counter += 1;
7332
- $cronjob_snippet = '';
7333
- $template_variables['Cronjobs.Total'] += 1;
7334
- $template_variables['Cronjobs.List'] .= sucuriscan_get_snippet('infosys-cronjobs', array(
7335
- 'Cronjob.Task' => ucwords(str_replace('_',chr(32),$hook)),
7336
- 'Cronjob.Schedule' => $event['schedule'],
7337
- 'Cronjob.Nexttime' => date_i18n($date_format, $timestamp),
7338
- 'Cronjob.Hook' => $hook,
7339
- 'Cronjob.Arguments' => implode(', ', $event['args']),
7340
- 'Cronjob.CssClass' => ( $counter%2 == 0 ) ? '' : 'alternate'
7341
- ));
7342
- }
7343
- }
7344
- }
7345
-
7346
- return sucuriscan_get_section('infosys-cronjobs', $template_variables);
7347
- }
7348
-
7349
- /**
7350
- * Gather information from the server, database engine, and PHP interpreter.
7351
- *
7352
- * @return array A list of pseudo-variables and values that will replace them in the HTML template.
7353
- */
7354
- function sucuriscan_server_info(){
7355
- global $wpdb;
7356
-
7357
- if( current_user_can('manage_options') ){
7358
- $memory_usage = function_exists('memory_get_usage') ? round(memory_get_usage()/1024/1024,2).' MB' : 'N/A';
7359
- $mysql_version = $wpdb->get_var('SELECT VERSION() AS version');
7360
- $mysql_info = $wpdb->get_results('SHOW VARIABLES LIKE "sql_mode"');
7361
- $sql_mode = ( is_array($mysql_info) && !empty($mysql_info[0]->Value) ) ? $mysql_info[0]->Value : 'Not set';
7362
- $runtime_scan = sucuriscan_get_option('sucuriscan_runtime');
7363
- $runtime_scan_human = date( 'd/M/Y H:i:s', $runtime_scan );
7364
-
7365
- $template_variables = array(
7366
- 'PluginVersion' => SUCURISCAN_VERSION,
7367
- 'PluginMD5' => SUCURISCAN_PLUGIN_CHECKSUM,
7368
- 'PluginRuntimeDatetime' => $runtime_scan_human,
7369
- 'OperatingSystem' => sprintf('%s (%d Bit)', PHP_OS, PHP_INT_SIZE*8),
7370
- 'Server' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'Unknown',
7371
- 'MemoryUsage' => $memory_usage,
7372
- 'MySQLVersion' => $mysql_version,
7373
- 'SQLMode' => $sql_mode,
7374
- 'PHPVersion' => PHP_VERSION,
7375
- );
7376
-
7377
- $field_names = array(
7378
- 'safe_mode',
7379
- 'allow_url_fopen',
7380
- 'memory_limit',
7381
- 'upload_max_filesize',
7382
- 'post_max_size',
7383
- 'max_execution_time',
7384
- 'max_input_time',
7385
- );
7386
-
7387
- foreach( $field_names as $php_flag ){
7388
- $php_flag_name = ucwords(str_replace('_', chr(32), $php_flag) );
7389
- $tpl_varname = str_replace(chr(32), '', $php_flag_name);
7390
- $php_flag_value = ini_get($php_flag);
7391
- $template_variables[$tpl_varname] = $php_flag_value ? $php_flag_value : 'N/A';
7392
- }
7393
- }
7394
-
7395
- return sucuriscan_get_section('infosys-serverinfo', $template_variables);
7396
- }
7397
-
7398
- /**
7399
- * Global variables used by the functions bellow.
7400
- *
7401
- * These are lists of options allowed to use in the execution of the monitoring
7402
- * tool, and the administrator can select among them in the settings page.
7403
- *
7404
- * @var array
7405
- */
7406
- $sucuriscan_notify_options = array(
7407
- 'sucuriscan_notify_user_registration' => 'Enable email alerts for new user registration',
7408
- 'sucuriscan_notify_success_login' => 'Enable email alerts for successful logins',
7409
- 'sucuriscan_notify_failed_login' => 'Enable email alerts for failed logins',
7410
- 'sucuriscan_notify_bruteforce_attack' => 'Enable email alerts for login brute-force attack',
7411
- 'sucuriscan_notify_post_publication' => 'Enable email alerts for new site content',
7412
- 'sucuriscan_notify_theme_editor' => 'Enable email alerts when a file is modified via the theme/plugin editor',
7413
- 'sucuriscan_notify_website_updated' => 'Enable email alerts when your website is updated',
7414
- 'sucuriscan_notify_settings_updated' => 'Enable email alerts when your website settings are updated',
7415
- 'sucuriscan_notify_theme_switched' => 'Enable email alerts when the website theme is switched',
7416
- 'sucuriscan_notify_theme_updated' => 'Enable email alerts when a theme is updated',
7417
- 'sucuriscan_notify_widget_added' => 'Enable email alerts when a widget is added to a sidebar',
7418
- 'sucuriscan_notify_widget_deleted' => 'Enable email alerts when a widget is deleted from a sidebar',
7419
- 'sucuriscan_notify_plugin_change' => 'Enable email alerts for Sucuri plugin changes',
7420
- 'sucuriscan_notify_plugin_activated' => 'Enable email alerts when a plugin is activated',
7421
- 'sucuriscan_notify_plugin_deactivated' => 'Enable email alerts when a plugin is deactivated',
7422
- 'sucuriscan_notify_plugin_updated' => 'Enable email alerts when a plugin is updated',
7423
- 'sucuriscan_notify_plugin_installed' => 'Enable email alerts when a plugin is installed',
7424
- 'sucuriscan_notify_plugin_deleted' => 'Enable email alerts when a plugin is deleted',
7425
- 'sucuriscan_prettify_mails' => 'Enable email alerts in HTML (uncheck to get email in text/plain)',
7426
- 'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
7427
- );
7428
-
7429
- $sucuriscan_schedule_allowed = array(
7430
- 'hourly' => 'Every three hours (3 hours)',
7431
- 'twicedaily' => 'Twice daily (12 hours)',
7432
- 'daily' => 'Once daily (24 hours)',
7433
- '_oneoff' => 'Never',
7434
- );
7435
-
7436
- $sucuriscan_interface_allowed = array(
7437
- 'spl' => 'SPL (high performance)',
7438
- 'opendir' => 'OpenDir (medium)',
7439
- 'glob' => 'Glob (low)',
7440
- );
7441
-
7442
- $sucuriscan_emails_per_hour = array(
7443
- '5' => 'Maximum 5 per hour',
7444
- '10' => 'Maximum 10 per hour',
7445
- '20' => 'Maximum 20 per hour',
7446
- '40' => 'Maximum 40 per hour',
7447
- '80' => 'Maximum 80 per hour',
7448
- '160' => 'Maximum 160 per hour',
7449
- 'unlimited' => 'Unlimited',
7450
- );
7451
-
7452
- $sucuriscan_maximum_failed_logins = array(
7453
- '30' => '30 failed logins per hour',
7454
- '60' => '60 failed logins per hour',
7455
- '120' => '120 failed logins per hour',
7456
- '240' => '240 failed logins per hour',
7457
- '480' => '480 failed logins per hour',
7458
- );
7459
-
7460
- $sucuriscan_verify_ssl_cert = array(
7461
- 'true' => 'Verify peer\'s cert',
7462
- 'false' => 'Stop peer\'s cert verification',
7463
- );
7464
-
7465
- /**
7466
- * Print a HTML code with the settings of the plugin.
7467
- *
7468
- * @return void
7469
- */
7470
- function sucuriscan_settings_page(){
7471
- $template_variables = array(
7472
- 'PageTitle' => 'Settings',
7473
- 'Settings.General' => sucuriscan_settings_general(),
7474
- 'Settings.Notifications' => sucuriscan_settings_notifications(),
7475
- 'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
7476
  );
7477
 
7478
- echo sucuriscan_get_template('settings', $template_variables);
7479
- }
7480
-
7481
- /**
7482
- * Process the requests sent by the form submissions originated in the settings
7483
- * page, all forms must have a nonce field that will be checked against the one
7484
- * generated in the template render function.
7485
- *
7486
- * @param boolean $page_nonce True if the nonce is valid, False otherwise.
7487
- * @return void
7488
- */
7489
- function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
7490
-
7491
- global $sucuriscan_schedule_allowed,
7492
- $sucuriscan_interface_allowed,
7493
- $sucuriscan_notify_options,
7494
- $sucuriscan_emails_per_hour,
7495
- $sucuriscan_maximum_failed_logins,
7496
- $sucuriscan_verify_ssl_cert;
7497
-
7498
- // Use this conditional to avoid double checking.
7499
- if( is_null($page_nonce) ){
7500
- $page_nonce = sucuriscan_check_page_nonce();
7501
- }
7502
-
7503
- if( $page_nonce ){
7504
-
7505
- // Recover API key through the email registered previously.
7506
- if( isset($_POST['sucuriscan_recover_api_key']) ){
7507
- sucuriscan_recover_api_key();
7508
- }
7509
-
7510
- // Save API key after it was recovered by the administrator.
7511
- if( isset($_POST['sucuriscan_manual_api_key']) ){
7512
- sucuriscan_set_api_key( $_POST['sucuriscan_manual_api_key'], TRUE );
7513
- sucuriscan_create_scheduled_task();
7514
- }
7515
-
7516
- // Remove API key from the local storage.
7517
- if( isset($_POST['sucuriscan_remove_api_key']) ){
7518
- sucuriscan_set_api_key('');
7519
- wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7520
- sucuriscan_notify_event( 'plugin_change', 'Sucuri API key removed' );
7521
- }
7522
-
7523
- // Modify the schedule of the filesystem scanner.
7524
- if(
7525
- isset($_POST['sucuriscan_scan_frequency'])
7526
- && isset($sucuriscan_schedule_allowed)
7527
- ){
7528
- $frequency = $_POST['sucuriscan_scan_frequency'];
7529
- $current_frequency = sucuriscan_get_option('sucuriscan_scan_frequency');
7530
- $allowed_frequency = array_keys($sucuriscan_schedule_allowed);
7531
-
7532
- if( in_array($frequency, $allowed_frequency) && $current_frequency != $frequency ){
7533
- update_option('sucuriscan_scan_frequency', $frequency);
7534
- wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7535
-
7536
- if( $frequency != '_oneoff' ){
7537
- wp_schedule_event( time()+10, $frequency, 'sucuriscan_scheduled_scan' );
7538
- }
7539
-
7540
- sucuriscan_notify_event( 'plugin_change', 'Filesystem scanning frequency changed to: ' . $frequency );
7541
- sucuriscan_info( 'Filesystem scan scheduled to run <code>'.$frequency.'</code>' );
7542
- }
7543
- }
7544
-
7545
- // Set the method (aka. interface) that will be used to scan the site.
7546
- if(
7547
- isset($_POST['sucuriscan_scan_interface'])
7548
- && isset($sucuriscan_interface_allowed)
7549
- ){
7550
- $interface = trim($_POST['sucuriscan_scan_interface']);
7551
- $allowed_values = array_keys($sucuriscan_interface_allowed);
7552
-
7553
- if( in_array($interface, $allowed_values) ){
7554
- update_option('sucuriscan_scan_interface', $interface);
7555
- sucuriscan_notify_event( 'plugin_change', 'Filesystem scanning interface changed to: ' . $interface );
7556
- sucuriscan_info( 'Filesystem scan interface set to <code>'.$interface.'</code>' );
7557
- }
7558
- }
7559
 
7560
- // Update the value for the maximum emails per hour.
7561
- if( isset($_POST['sucuriscan_emails_per_hour']) ){
7562
- $per_hour = esc_attr($_POST['sucuriscan_emails_per_hour']);
7563
 
7564
- if( array_key_exists($per_hour, $sucuriscan_emails_per_hour) ){
7565
- $per_hour_label = $sucuriscan_emails_per_hour[$per_hour];
7566
- update_option( 'sucuriscan_emails_per_hour', $per_hour );
7567
- sucuriscan_notify_event( 'plugin_change', 'Maximum email notifications per hour changed' );
7568
- sucuriscan_info( 'E-mail notifications: <code>' . $per_hour_label . '</code>' );
7569
- } else {
7570
- sucuriscan_error( 'Invalid value for the maximum emails per hour.' );
7571
  }
7572
- }
7573
-
7574
- // Update the email where the event notifications will be sent.
7575
- if( isset($_POST['sucuriscan_notify_to']) ){
7576
- $new_email = esc_attr($_POST['sucuriscan_notify_to']);
7577
 
7578
- if( is_valid_email($new_email) ){
7579
- update_option( 'sucuriscan_notify_to', $new_email );
7580
- sucuriscan_notify_event( 'plugin_change', 'Email address to get the event notifications was changed' );
7581
- sucuriscan_info( 'All the event notifications will be sent to the email specified.' );
7582
- } else {
7583
- sucuriscan_error( 'Email format not supported.' );
7584
  }
7585
- }
7586
 
7587
- // Update the maximum failed logins per hour before consider it a brute-force attack.
7588
- if( isset($_POST['sucuriscan_maximum_failed_logins']) ){
7589
- $failed_logins = esc_attr($_POST['sucuriscan_maximum_failed_logins']);
7590
 
7591
- if( array_key_exists($failed_logins, $sucuriscan_maximum_failed_logins) ){
7592
- update_option( 'sucuriscan_maximum_failed_logins', $failed_logins );
7593
- sucuriscan_notify_event( 'plugin_change', 'Maximum failed logins before consider it a brute-force attack was changed' );
7594
- sucuriscan_info(
7595
- 'A brute-force attack event will be reported if there are more than '
7596
- . '<code>' . $failed_logins . '</code> failed logins per hour.'
7597
- );
7598
- } else {
7599
- sucuriscan_error( 'Invalid value for the maximum failed logins per hour before consider it a brute-force attack.' );
7600
- }
7601
- }
7602
 
7603
- // Update the configuration for the SSL certificate verification.
7604
- if( isset($_POST['sucuriscan_verify_ssl_cert']) ){
7605
- $verify_ssl_cert = esc_attr($_POST['sucuriscan_verify_ssl_cert']);
 
 
7606
 
7607
- if( array_key_exists($verify_ssl_cert, $sucuriscan_verify_ssl_cert) ){
7608
- update_option( 'sucuriscan_verify_ssl_cert', $verify_ssl_cert );
7609
- $message = 'SSL certificates will not be verified when executing a HTTP request '
7610
- . 'while communicating with the Sucuri API service, nor the official '
7611
- . 'WordPress API.';
7612
- sucuriscan_notify_event( 'plugin_change', $message );
7613
- sucuriscan_info( $message );
7614
- } else {
7615
- sucuriscan_error( 'Invalid value for the SSL certificate verification.' );
7616
- }
7617
- }
7618
 
7619
- // Update the notification settings.
7620
- if(
7621
- isset($_POST['sucuriscan_save_notification_settings'])
7622
- && isset($sucuriscan_notify_options)
7623
- ){
7624
- $options_updated_counter = 0;
 
 
 
 
 
 
 
 
7625
 
7626
- foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
7627
- if( isset($_POST[$alert_type]) ){
7628
- $option_value = ( $_POST[$alert_type] == 1 ? 'enabled' : 'disabled' );
7629
- update_option( $alert_type, $option_value );
7630
- $options_updated_counter += 1;
7631
  }
7632
- }
7633
 
7634
- if( $options_updated_counter > 0 ){
7635
- sucuriscan_notify_event( 'plugin_change', 'Email notification settings changed' );
7636
- sucuriscan_info( 'Notification settings updated.' );
7637
  }
7638
  }
7639
 
7640
- // Reset all the plugin's options.
7641
- if( isset($_POST['sucuriscan_reset_options']) ){
7642
- // Notify the event before the API key is removed.
7643
- $event_msg = 'All plugins options were resetted';
7644
- sucuriscan_report_event( 1, 'core', $event_msg );
7645
- sucuriscan_notify_event( 'plugin_change', $event_msg );
7646
-
7647
- // Remove all plugin's options from the database.
7648
- $options = sucuriscan_get_options_from_db('all_sucuriscan_options');
7649
 
7650
- foreach( $options as $option ){
7651
- delete_option( $option->option_name );
 
7652
  }
7653
 
7654
- // Remove the scheduled tasks.
7655
- wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
 
7656
 
7657
- sucuriscan_info( 'All plugin options were resetted successfully' );
 
 
 
 
 
 
 
7658
  }
 
7659
 
7660
- // Ignore a new event for email notifications.
7661
- if(
7662
- isset($_POST['sucuriscan_ignorerule_action'])
7663
- && isset($_POST['sucuriscan_ignorerule'])
7664
- ){
7665
- if( $_POST['sucuriscan_ignorerule_action'] == 'add' ){
7666
- $event_ignored = sucuriscan_add_ignored_event( $_POST['sucuriscan_ignorerule'] );
7667
 
7668
- if( $event_ignored ){
7669
- sucuriscan_info( 'Post-type ignored successfully.' );
7670
- } else {
7671
- sucuriscan_error( 'The post-type is invalid or it may be already ignored.' );
7672
- }
7673
- } else {
7674
- sucuriscan_remove_ignored_event( $_POST['sucuriscan_ignorerule'] );
7675
- sucuriscan_info( 'Post-type removed from the list successfully.' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7676
  }
7677
  }
7678
-
7679
  }
7680
 
 
7681
  }
7682
 
7683
  /**
7684
- * Read and parse the content of the general settings template.
 
 
7685
  *
7686
- * @return string Parsed HTML code for the general settings panel.
 
7687
  */
7688
- function sucuriscan_settings_general(){
7689
-
7690
- global $sucuriscan_schedule_allowed,
7691
- $sucuriscan_interface_allowed,
7692
- $sucuriscan_emails_per_hour,
7693
- $sucuriscan_maximum_failed_logins,
7694
- $sucuriscan_verify_ssl_cert;
7695
-
7696
- // Check the nonce here to populate the value through other functions.
7697
- $page_nonce = sucuriscan_check_page_nonce();
7698
-
7699
- // Process all form submissions.
7700
- sucuriscan_settings_form_submissions($page_nonce);
7701
 
7702
- // Register the site, get its API key, and store it locally for future usage.
7703
- $api_registered_modal = '';
7704
 
7705
- // Whether the form to manually add the API key should be shown or not.
7706
- $display_manual_key_form = (bool) isset($_POST['sucuriscan_recover_api_key']);
7707
 
7708
- if( $page_nonce && isset($_POST['sucuriscan_wordpress_apikey']) ){
7709
- $registered = sucuriscan_register_site();
7710
 
7711
- if( $registered ){
7712
- $api_registered_modal = sucuriscan_get_modal('settings-apiregistered', array(
7713
- 'Title' => 'Site registered successfully',
7714
- 'CssClass' => 'sucuriscan-apikey-registered',
7715
- ));
7716
- } else {
7717
- $display_manual_key_form = TRUE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7718
  }
7719
- }
7720
-
7721
- // Get initial variables to decide some things bellow.
7722
- $api_key = sucuriscan_wordpress_apikey();
7723
- $scan_freq = sucuriscan_get_option('sucuriscan_scan_frequency');
7724
- $scan_interface = sucuriscan_get_option('sucuriscan_scan_interface');
7725
- $emails_per_hour = sucuriscan_get_option('sucuriscan_emails_per_hour');
7726
- $maximum_failed_logins = sucuriscan_get_option('sucuriscan_maximum_failed_logins');
7727
- $verify_ssl_cert = sucuriscan_get_option('sucuriscan_verify_ssl_cert');
7728
- $runtime_scan = sucuriscan_get_option('sucuriscan_runtime');
7729
- $runtime_scan_human = date( 'd/M/Y H:i:s', $runtime_scan );
7730
-
7731
- // Generate HTML code to configure the scanning frequency from the plugin settings.
7732
- $scan_freq_options = '';
7733
- foreach( $sucuriscan_schedule_allowed as $schedule => $schedule_label ){
7734
- $selected = ( $scan_freq==$schedule ? 'selected="selected"' : '' );
7735
- $scan_freq_options .= sprintf(
7736
- '<option value="%s" %s>%s</option>',
7737
- $schedule, $selected, $schedule_label
7738
- );
7739
- }
7740
 
7741
- // Generate HTML code to configure the scanning interface from the plugin settings.
7742
- $scan_interface_options = '';
7743
- foreach( $sucuriscan_interface_allowed as $interface_name => $interface_desc ){
7744
- $selected = ( $scan_interface == $interface_name ? 'selected="selected"' : '' );
7745
- $scan_interface_options .= sprintf(
7746
- '<option value="%s" %s>%s</option>',
7747
- $interface_name,
7748
- $selected,
7749
- $interface_desc
7750
- );
7751
- }
7752
-
7753
- // Generate the HTML code to configure the emails per hour.
7754
- $emails_per_hour_options = '';
7755
- foreach( $sucuriscan_emails_per_hour as $per_hour => $per_hour_label ){
7756
- $selected = ( $emails_per_hour == $per_hour ? 'selected="selected"' : '' );
7757
- $emails_per_hour_options .= sprintf(
7758
- '<option value="%s" %s>%s</option>',
7759
- $per_hour,
7760
- $selected,
7761
- $per_hour_label
7762
- );
7763
- }
7764
-
7765
- // Generate the HTML code to configure the emails per hour.
7766
- $maximum_failed_logins_options = '';
7767
- foreach( $sucuriscan_maximum_failed_logins as $per_hour => $per_hour_label ){
7768
- $selected = ( $maximum_failed_logins == $per_hour ? 'selected="selected"' : '' );
7769
- $maximum_failed_logins_options .= sprintf(
7770
- '<option value="%s" %s>%s</option>',
7771
- $per_hour,
7772
- $selected,
7773
- $per_hour_label
7774
- );
7775
  }
 
7776
 
7777
- // Generate the HTML code to configure the emails per hour.
7778
- $verify_ssl_cert_options = '';
7779
- foreach( $sucuriscan_verify_ssl_cert as $verify => $verify_label ){
7780
- $selected = ( $verify_ssl_cert == $verify ? 'selected="selected"' : '' );
7781
- $verify_ssl_cert_options .= sprintf(
7782
- '<option value="%s" %s>%s</option>',
7783
- $verify,
7784
- $selected,
7785
- $verify_label
7786
- );
7787
- }
7788
 
7789
  $template_variables = array(
7790
- 'APIKey' => $api_key,
7791
- 'APIKey.RecoverVisibility' => ( $api_key || $display_manual_key_form ? 'hidden' : 'visible' ),
7792
- 'APIKey.ManualKeyFormVisibility' => ( $display_manual_key_form ? 'visible' : 'hidden' ),
7793
- 'APIKey.RemoveVisibility' => ( $api_key ? 'visible' : 'hidden' ),
7794
- 'ScanningFrequency' => 'Undefined',
7795
- 'ScanningFrequencyOptions' => $scan_freq_options,
7796
- 'ScanningInterface' => ( $scan_interface ? $sucuriscan_interface_allowed[$scan_interface] : 'Undefined' ),
7797
- 'ScanningInterfaceOptions' => $scan_interface_options,
7798
- 'ScanningInterfaceVisibility' => ( SucuriScanFileInfo::is_spl_available() ? 'hidden' : 'visible' ),
7799
- 'ScanningRuntime' => $runtime_scan,
7800
- 'ScanningRuntimeHuman' => $runtime_scan_human,
7801
- 'ModalWhenAPIRegistered' => $api_registered_modal,
7802
- 'NotifyTo' => sucuriscan_get_option('sucuriscan_notify_to'),
7803
- 'EmailsPerHour' => 'Undefined',
7804
- 'EmailsPerHourOptions' => $emails_per_hour_options,
7805
- 'MaximumFailedLogins' => 'Undefined',
7806
- 'MaximumFailedLoginsOptions' => $maximum_failed_logins_options,
7807
- 'VerifySSLCert' => 'Undefined',
7808
- 'VerifySSLCertOptions' => $verify_ssl_cert_options,
7809
- 'ModalWhenAPIRegistered' => $api_registered_modal,
7810
  );
7811
 
7812
- if( array_key_exists($scan_freq, $sucuriscan_schedule_allowed) ){
7813
- $template_variables['ScanningFrequency'] = $sucuriscan_schedule_allowed[$scan_freq];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7814
  }
7815
 
7816
- if( array_key_exists($emails_per_hour, $sucuriscan_emails_per_hour) ){
7817
- $template_variables['EmailsPerHour'] = $sucuriscan_emails_per_hour[$emails_per_hour];
7818
  }
7819
 
7820
- if( array_key_exists($maximum_failed_logins, $sucuriscan_maximum_failed_logins) ){
7821
- $template_variables['MaximumFailedLogins'] = $sucuriscan_maximum_failed_logins[$maximum_failed_logins];
7822
  }
7823
 
7824
- if( array_key_exists($verify_ssl_cert, $sucuriscan_verify_ssl_cert) ){
7825
- $template_variables['VerifySSLCert'] = $sucuriscan_verify_ssl_cert[$verify_ssl_cert];
7826
  }
7827
 
7828
- return sucuriscan_get_section('settings-general', $template_variables);
7829
- }
7830
 
7831
- /**
7832
- * Read and parse the content of the notification settings template.
7833
- *
7834
- * @return string Parsed HTML code for the notification settings panel.
7835
- */
7836
- function sucuriscan_settings_notifications(){
7837
- global $sucuriscan_notify_options;
7838
 
7839
- $template_variables = array(
7840
- 'NotificationOptions' => '',
 
 
 
 
 
 
7841
  );
7842
 
 
 
 
 
 
 
7843
  $counter = 0;
7844
 
7845
- foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
7846
- $alert_value = sucuriscan_get_option($alert_type);
7847
- $checked = ( $alert_value == 'enabled' ? 'checked="checked"' : '' );
7848
- $css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
7849
 
7850
- $template_variables['NotificationOptions'] .= sucuriscan_get_snippet('settings-notifications', array(
7851
- 'Notification.CssClass' => $css_class,
7852
- 'Notification.Name' => $alert_type,
7853
- 'Notification.Checked' => $checked,
7854
- 'Notification.Label' => $alert_label,
7855
  ));
7856
  $counter += 1;
7857
  }
7858
 
7859
- return sucuriscan_get_section('settings-notifications', $template_variables);
7860
- }
7861
-
7862
- /**
7863
- * Read and parse the content of the ignored-rules settings template.
7864
- *
7865
- * @return string Parsed HTML code for the ignored-rules settings panel.
7866
- */
7867
- function sucuriscan_settings_ignore_rules(){
7868
- $notify_new_site_content = sucuriscan_get_option('sucuriscan_notify_post_publication');
7869
-
7870
- $template_variables = array(
7871
- 'IgnoreRules.MessageVisibility' => 'visible',
7872
- 'IgnoreRules.TableVisibility' => 'hidden',
7873
- 'IgnoreRules.PostTypes' => '',
7874
- );
7875
-
7876
- if( $notify_new_site_content == 'enabled' ){
7877
- $post_types = get_post_types();
7878
- $ignored_events = sucuriscan_get_ignored_events();
7879
-
7880
- $template_variables['IgnoreRules.MessageVisibility'] = 'hidden';
7881
- $template_variables['IgnoreRules.TableVisibility'] = 'visible';
7882
- $counter = 0;
7883
-
7884
- foreach( $post_types as $post_type => $post_type_object ){
7885
- $counter += 1;
7886
- $css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
7887
- $post_type_title = ucwords( str_replace('_', chr(32), $post_type) );
7888
-
7889
- if( array_key_exists($post_type, $ignored_events) ){
7890
- $is_ignored_text = 'YES';
7891
- $was_ignored_at = @date('d/M/Y - H:i:s', $ignored_events[$post_type]);
7892
- $is_ignored_class = 'danger';
7893
- $button_action = 'remove';
7894
- $button_class = 'button-primary';
7895
- $button_text = 'Allow';
7896
- } else {
7897
- $is_ignored_text = 'NO';
7898
- $button_action = 'add';
7899
- $was_ignored_at = 'Not ignored';
7900
- $is_ignored_class = 'success';
7901
- $button_class = 'button-primary button-danger';
7902
- $button_text = 'Ignore';
7903
- }
7904
-
7905
- $template_variables['IgnoreRules.PostTypes'] .= sucuriscan_get_snippet('settings-ignorerules', array(
7906
- 'IgnoreRules.CssClass' => $css_class,
7907
- 'IgnoreRules.Num' => $counter,
7908
- 'IgnoreRules.PostTypeTitle' => $post_type_title,
7909
- 'IgnoreRules.IsIgnored' => $is_ignored_text,
7910
- 'IgnoreRules.WasIgnoredAt' => $was_ignored_at,
7911
- 'IgnoreRules.IsIgnoredClass' => $is_ignored_class,
7912
- 'IgnoreRules.PostType' => $post_type,
7913
- 'IgnoreRules.Action' => $button_action,
7914
- 'IgnoreRules.ButtonClass' => 'button ' . $button_class,
7915
- 'IgnoreRules.ButtonText' => $button_text,
7916
- ));
7917
- }
7918
- }
7919
-
7920
- return sucuriscan_get_section('settings-ignorerules', $template_variables);
7921
  }
7922
 
4
  Plugin URI: http://wordpress.sucuri.net/
5
  Description: The <a href="http://sucuri.net/" target="_blank">Sucuri Security</a> <em>(Auditing, Malware Scanner and Hardening)</em> plugin enables you to scan your WordPress site using <a href="http://sitecheck.sucuri.net/" target="_blank">Sucuri SiteCheck</a> right in your dashboard. 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.6.6
8
  Author URI: http://sucuri.net
9
  */
10
 
12
  /**
13
  * Main file to control the plugin.
14
  *
15
+ * @package Sucuri Security
16
  * @author Yorman Arias <yorman.arias@sucuri.net>
17
  * @author Daniel Cid <dcid@sucuri.net>
18
  * @copyright Since 2010-2014 Sucuri Inc.
22
  */
23
 
24
 
25
+ /**
26
+ * Plugin dependencies.
27
+ *
28
+ * List of required functions for the execution of this plugin, we are assuming
29
+ * that this site was built on top of the WordPress project, and that it is
30
+ * being loaded through a pluggable system, these functions most be defined
31
+ * before to continue.
32
+ *
33
+ * @var array
34
+ */
35
+ $sucuriscan_dependencies = array(
36
+ 'wp',
37
+ 'wp_die',
38
+ 'add_action',
39
+ 'remove_action',
40
+ 'wp_remote_get',
41
+ 'wp_remote_post',
42
+ );
43
+
44
+ // Terminate execution if any of the functions mentioned above is not defined.
45
+ foreach( $sucuriscan_dependencies as $dependency ){
46
+ if( !function_exists($dependency) ){
47
+ exit(0);
48
+ }
49
  }
50
 
51
  /**
52
+ * Plugin's constants.
53
+ *
54
+ * These constants will hold the basic information of the plugin, file/folder
55
+ * paths, version numbers, read-only variables that will affect the functioning
56
+ * of the rest of the code. The conditional will act as a container helping in
57
+ * the readability of the code considering the total number of lines that this
58
+ * file will have.
59
  */
 
60
 
61
  /**
62
+ * Unique name of the plugin through out all the code.
63
  */
64
+ define('SUCURISCAN', 'sucuriscan');
65
 
66
  /**
67
+ * Current version of the plugin's code.
68
  */
69
+ define('SUCURISCAN_VERSION', '1.6.6');
70
 
71
  /**
72
  * The name of the Sucuri plugin main file.
88
  */
89
  define('SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE);
90
 
91
+ /**
92
+ * The local URL where the plugin's files and assets are served.
93
+ */
94
+ define('SUCURISCAN_URL', rtrim(plugin_dir_url(SUCURISCAN_PLUGIN_FILEPATH), '/') );
95
+
96
  /**
97
  * Checksum of this file to check the integrity of the plugin.
98
  */
143
  */
144
  define('SUCURISCAN_GET_PLUGINS_LIFETIME', 1800);
145
 
146
+ /**
147
+ * Plugin's global variables.
148
+ *
149
+ * These variables will be defined globally to allow the inclusion in multiple
150
+ * functions and classes defined in the libraries loaded by this plugin. The
151
+ * conditional will act as a container helping in the readability of the code
152
+ * considering the total number of lines that this file will have.
153
+ */
154
+ if( defined('SUCURISCAN') ){
155
+
156
+ /**
157
+ * List an associative array with the sub-pages of this plugin.
158
+ *
159
+ * @return array
160
+ */
161
+ $sucuriscan_pages = array(
162
+ 'sucuriscan' => 'Dashboard',
163
+ 'sucuriscan_scanner' => 'Malware Scan',
164
+ 'sucuriscan_monitoring' => 'Firewall (WAF)',
165
+ 'sucuriscan_hardening' => 'Hardening',
166
+ 'sucuriscan_posthack' => 'Post-Hack',
167
+ 'sucuriscan_lastlogins' => 'Last Logins',
168
+ 'sucuriscan_settings' => 'Settings',
169
+ 'sucuriscan_infosys' => 'Site Info',
170
+ );
171
+
172
+ /**
173
+ * Settings options.
174
+ *
175
+ * The following global variables are mostly associative arrays where the key is
176
+ * linked to an option that will be stored in the database, and their
177
+ * correspondent values are the description of the option. These variables will
178
+ * be used in the settings page to offer the user a way to configure the
179
+ * behaviour of the plugin.
180
+ *
181
+ * @var array
182
+ */
183
+
184
+ $sucuriscan_notify_options = array(
185
+ 'sucuriscan_notify_user_registration' => 'Enable email alerts for new user registration',
186
+ 'sucuriscan_notify_success_login' => 'Enable email alerts for successful logins',
187
+ 'sucuriscan_notify_failed_login' => 'Enable email alerts for failed logins',
188
+ 'sucuriscan_notify_bruteforce_attack' => 'Enable email alerts for login brute-force attack',
189
+ 'sucuriscan_notify_post_publication' => 'Enable email alerts for new site content',
190
+ 'sucuriscan_notify_theme_editor' => 'Enable email alerts when a file is modified via the theme/plugin editor',
191
+ 'sucuriscan_notify_website_updated' => 'Enable email alerts when your website is updated',
192
+ 'sucuriscan_notify_settings_updated' => 'Enable email alerts when your website settings are updated',
193
+ 'sucuriscan_notify_theme_switched' => 'Enable email alerts when the website theme is switched',
194
+ 'sucuriscan_notify_theme_updated' => 'Enable email alerts when a theme is updated',
195
+ 'sucuriscan_notify_widget_added' => 'Enable email alerts when a widget is added to a sidebar',
196
+ 'sucuriscan_notify_widget_deleted' => 'Enable email alerts when a widget is deleted from a sidebar',
197
+ 'sucuriscan_notify_plugin_change' => 'Enable email alerts for Sucuri plugin changes',
198
+ 'sucuriscan_notify_plugin_activated' => 'Enable email alerts when a plugin is activated',
199
+ 'sucuriscan_notify_plugin_deactivated' => 'Enable email alerts when a plugin is deactivated',
200
+ 'sucuriscan_notify_plugin_updated' => 'Enable email alerts when a plugin is updated',
201
+ 'sucuriscan_notify_plugin_installed' => 'Enable email alerts when a plugin is installed',
202
+ 'sucuriscan_notify_plugin_deleted' => 'Enable email alerts when a plugin is deleted',
203
+ 'sucuriscan_prettify_mails' => 'Enable email alerts in HTML (uncheck to get email in text/plain)',
204
+ 'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
205
+ );
206
+
207
+ $sucuriscan_schedule_allowed = array(
208
+ 'hourly' => 'Every three hours (3 hours)',
209
+ 'twicedaily' => 'Twice daily (12 hours)',
210
+ 'daily' => 'Once daily (24 hours)',
211
+ '_oneoff' => 'Never',
212
+ );
213
+
214
+ $sucuriscan_interface_allowed = array(
215
+ 'spl' => 'SPL (high performance)',
216
+ 'opendir' => 'OpenDir (medium)',
217
+ 'glob' => 'Glob (low)',
218
+ );
219
+
220
+ $sucuriscan_emails_per_hour = array(
221
+ '5' => 'Maximum 5 per hour',
222
+ '10' => 'Maximum 10 per hour',
223
+ '20' => 'Maximum 20 per hour',
224
+ '40' => 'Maximum 40 per hour',
225
+ '80' => 'Maximum 80 per hour',
226
+ '160' => 'Maximum 160 per hour',
227
+ 'unlimited' => 'Unlimited',
228
+ );
229
+
230
+ $sucuriscan_maximum_failed_logins = array(
231
+ '30' => '30 failed logins per hour',
232
+ '60' => '60 failed logins per hour',
233
+ '120' => '120 failed logins per hour',
234
+ '240' => '240 failed logins per hour',
235
+ '480' => '480 failed logins per hour',
236
+ );
237
+
238
+ $sucuriscan_verify_ssl_cert = array(
239
+ 'true' => 'Verify peer\'s cert',
240
+ 'false' => 'Stop peer\'s cert verification',
241
+ );
242
+
243
+ /**
244
+ * Remove the WordPress generator meta-tag from the source code.
245
+ */
246
+ remove_action( 'wp_head', 'wp_generator' );
247
+
248
+ /**
249
+ * Run a specific function defined in the plugin's code to locate every
250
+ * directory and file, collect their checksum and file size, and send this
251
+ * information to the Sucuri API service where a security and integrity scan
252
+ * will be performed against the hashes provided and the official versions.
253
+ */
254
+ add_action('sucuriscan_scheduled_scan', 'SucuriScanEvent::filesystem_scan');
255
+
256
+ /**
257
+ * Initialize the execute of the main plugin's functions.
258
+ *
259
+ * This will load the menu options in the WordPress administrator panel, and
260
+ * execute the bootstrap function of the plugin.
261
+ */
262
+ add_action( 'init', 'SucuriScanInterface::initialize', 1 );
263
+ add_action( 'admin_init', 'SucuriScanInterface::create_datastore_folder' );
264
+ add_action( 'admin_init', 'SucuriScanInterface::handle_old_plugins' );
265
+ add_action( 'admin_enqueue_scripts', 'SucuriScanInterface::enqueue_scripts', 1 );
266
+ add_action( 'admin_menu', 'SucuriScanInterface::add_interface_menu' );
267
+
268
+ /**
269
+ * Function call interceptors.
270
+ *
271
+ * Define the names for the hooks that will intercept specific function calls in
272
+ * the admin interface and parts of the external site, an event report will be
273
+ * sent to the API service and an email notification to the administrator of the
274
+ * site.
275
+ *
276
+ * @see Class SucuriScanHook
277
+ */
278
+ if( class_exists('SucuriScanHook') ){
279
+ $sucuriscan_hooks = array(
280
+ // Passes.
281
+ 'add_attachment',
282
+ 'add_link',
283
+ 'create_category',
284
+ 'delete_post',
285
+ 'delete_user',
286
+ 'login_form_resetpass',
287
+ 'private_to_published',
288
+ 'publish_page',
289
+ 'publish_post',
290
+ 'publish_phone',
291
+ 'xmlrpc_publish_post',
292
+ 'retrieve_password',
293
+ 'switch_theme',
294
+ 'user_register',
295
+ 'wp_login',
296
+ 'wp_login_failed',
297
+ );
298
+
299
+ foreach( $sucuriscan_hooks as $hook_name ){
300
+ $hook_func = 'SucuriScanHook::hook_' . $hook_name;
301
+ add_action( $hook_name, $hook_func, 50 );
302
+ }
303
+
304
+ add_action( 'admin_init', 'SucuriScanHook::hook_undefined_actions' );
305
+ add_action( 'login_form', 'SucuriScanHook::hook_undefined_actions' );
306
+ } else {
307
+ SucuriScanInterface::error( 'Function call interceptors are not working properly.' );
308
+ }
309
+
310
+ /**
311
+ * Display a message if the plugin is not activated.
312
+ *
313
+ * Display a message at the top of the administration panel with a button that
314
+ * once clicked will send the site's email and domain name to the Sucuri API
315
+ * service where an API key will be generated for the site, this key will allow
316
+ * the plugin to execute the filesystem scans, the project integrity, and the
317
+ * email notifications.
318
+ */
319
+ $sucuriscan_admin_notice_name = SucuriScan::is_multisite() ? 'network_admin_notices' : 'admin_notices';
320
+ add_action( $sucuriscan_admin_notice_name, 'SucuriScanInterface::setup_notice' );
321
+
322
+ }
323
+
324
  /**
325
  * Miscellaneous library.
326
  *
336
  public function __construct(){
337
  }
338
 
339
+ /**
340
+ * Return name of a variable with the plugin's prefix (if needed).
341
+ *
342
+ * To facilitate the development, you can prefix the name of the key in the
343
+ * request (when accessing it) with a single colon, this function will
344
+ * automatically replace that character with the unique identifier of the
345
+ * plugin.
346
+ *
347
+ * @param string $var_name Name of a variable with an optional colon at the beginning.
348
+ * @return string Full name of the variable with the extra characters (if needed).
349
+ */
350
+ public static function variable_prefix( $var_name='' ){
351
+ if( preg_match('/^:(.*)/', $var_name, $match) ){
352
+ $var_name = sprintf( '%s_%s', SUCURISCAN, $match[1] );
353
+ }
354
+
355
+ return $var_name;
356
+ }
357
+
358
+ /**
359
+ * Encodes the less-than, greater-than, ampersand, double quote and single quote
360
+ * characters, will never double encode entities.
361
+ *
362
+ * @param string $text The text which is to be encoded.
363
+ * @return string The encoded text with HTML entities.
364
+ */
365
+ public static function escape( $text='' ){
366
+ // Escape the value of the variable using a built-in function if possible.
367
+ if( function_exists('esc_attr') ){
368
+ $text = esc_attr($text);
369
+ } else {
370
+ $text = htmlspecialchars($text);
371
+ }
372
+
373
+ return $text;
374
+ }
375
+
376
  /**
377
  * Generates a lowercase random string with an specific length.
378
  *
421
  }
422
 
423
  /**
424
+ * Check whether the current site is working as a multi-site instance.
425
  *
426
+ * @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
427
  */
428
+ public static function is_multisite(){
 
429
  if(
430
+ function_exists('is_multisite')
431
+ && is_multisite()
432
  ){
433
+ return TRUE;
434
  }
435
 
436
+ return FALSE;
437
+ }
 
 
438
 
439
+ /**
440
+ * Find and retrieve the current version of Wordpress installed.
441
+ *
442
+ * @return string The version number of Wordpress installed.
443
+ */
444
+ public static function site_version(){
445
+ // Maybe the version number is in the database.
446
+ $version = get_option('version');
447
+ if( $version ){ return $version; }
448
+
449
+ // If not, then check for a specific file.
450
+ $wp_version_path = ABSPATH . WPINC . '/version.php';
451
+ if( file_exists($wp_version_path) ){
452
+ include($wp_version_path);
453
+ if( isset($wp_version) ){ return $wp_version; }
454
+ }
455
+
456
+ // At last, use the checksum of the main framework class.
457
+ return md5_file(ABSPATH . WPINC . '/class-wp.php');
458
+ }
459
+
460
+ /**
461
+ * Find and retrieve the absolute path of the WordPress configuration file.
462
+ *
463
+ * @return string Absolute path of the WordPress configuration file.
464
+ */
465
+ public static function get_wpconfig_path(){
466
+ if( defined('ABSPATH') ){
467
+ $file_path = ABSPATH . '/wp-config.php';
468
+
469
+ // if wp-config.php doesn't exist, or is not readable check one directory up.
470
+ if( !file_exists($file_path) ){
471
+ $file_path = ABSPATH . '/../wp-config.php';
472
  }
473
 
474
+ // Remove duplicated double slashes.
475
+ $file_path = realpath($file_path);
476
+
477
+ if( $file_path ){
478
+ return $file_path;
 
 
479
  }
480
  }
481
 
482
  return FALSE;
483
  }
484
 
485
+ /**
486
+ * Find and retrieve the absolute path of the main WordPress htaccess file.
487
+ *
488
+ * @return string Absolute path of the main WordPress htaccess file.
489
+ */
490
+ public static function get_htaccess_path(){
491
+ if( defined('ABSPATH') ){
492
+ $base_dirs = array(
493
+ rtrim(ABSPATH, '/'),
494
+ dirname(ABSPATH),
495
+ dirname(dirname(ABSPATH))
496
+ );
497
 
498
+ foreach( $base_dirs as $base_dir ){
499
+ $htaccess_path = sprintf('%s/.htaccess', $base_dir);
500
+
501
+ if( file_exists($htaccess_path) ){
502
+ return $htaccess_path;
503
+ }
504
+ }
505
+ }
506
+
507
+ return FALSE;
508
+ }
509
 
510
  /**
511
+ * Get the pattern of the definition related with a WordPress secret key.
 
 
512
  *
513
+ * @return string Secret key definition pattern.
514
  */
515
+ public static function secret_key_pattern(){
516
+ return '/define\(\'([A-Z_]+)\',([ ]+)\'(.*)\'\);/';
517
+ }
518
 
519
  /**
520
+ * Retrieve the real ip address of the user in the current request.
 
 
521
  *
522
+ * @return string The real ip address of the user in the current request.
523
  */
524
+ public static function get_remote_addr(){
525
+ $alternatives = array(
526
+ 'HTTP_X_REAL_IP',
527
+ 'HTTP_CLIENT_IP',
528
+ 'HTTP_X_FORWARDED_FOR',
529
+ 'HTTP_X_FORWARDED',
530
+ 'HTTP_FORWARDED_FOR',
531
+ 'HTTP_FORWARDED',
532
+ 'REMOTE_ADDR',
533
+ 'SUCURI_RIP',
534
+ );
535
+
536
+ foreach( $alternatives as $alternative ){
537
+ if( isset($_SERVER[$alternative]) ){
538
+ $remote_addr = preg_replace('/[^0-9a-z.,: ]/', '', $_SERVER[$alternative]);
539
+
540
+ if( $remote_addr ){ break; }
541
+ }
542
+ }
543
+
544
+ if( $remote_addr == '::1' ){
545
+ $remote_addr = '127.0.0.1';
546
+ }
547
+
548
+ return $remote_addr;
549
+ }
550
 
551
  /**
552
+ * Retrieve the user-agent from the current request.
553
  *
554
+ * @return string The user-agent from the current request.
555
  */
556
+ public static function get_user_agent(){
557
+ if( isset($_SERVER['HTTP_USER_AGENT']) ){
558
+ return self::escape($_SERVER['HTTP_USER_AGENT']);
559
+ }
560
+
561
+ return FALSE;
562
+ }
563
 
564
  /**
565
+ * Check whether the site is behing the Sucuri CloudProxy network.
566
+ *
567
+ * @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
568
  */
569
+ public static function is_behind_cloudproxy(){
570
+ if( isset($_SERVER['HTTP_HOST']) ){
571
+ $http_host = preg_replace('/^(.*):[0-9]+/', '$1', $_SERVER['HTTP_HOST']);
572
+ } else {
573
+ $http_host = 'localhost';
574
+ }
575
+
576
+ $host_by_name = gethostbyname($http_host);
577
+ $host_by_addr = gethostbyaddr($host_by_name);
578
+
579
+ return (bool) preg_match('/^cloudproxy[0-9]+\.sucuri\.net$/', $host_by_addr);
580
  }
581
 
582
  /**
583
+ * Return the time passed since the specified timestamp until now.
 
 
 
584
  *
585
+ * @param integer $timestamp The Unix time number of the date/time before now.
586
+ * @return string The time passed since the timestamp specified.
 
 
587
  */
588
+ public static function time_ago( $timestamp=0 ){
589
+ if( !is_numeric($timestamp) ){
590
+ $timestamp = strtotime($timestamp);
 
 
 
 
 
591
  }
592
 
593
+ $diff = abs( time() - intval($timestamp) );
 
 
594
 
595
+ if( $diff == 0 ){ return 'just now'; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
+ $intervals = array(
598
+ 1 => array('year', 31556926),
599
+ $diff < 31556926 => array('month', 2628000),
600
+ $diff < 2629744 => array('week', 604800),
601
+ $diff < 604800 => array('day', 86400),
602
+ $diff < 86400 => array('hour', 3600),
603
+ $diff < 3600 => array('minute', 60),
604
+ $diff < 60 => array('second', 1)
605
+ );
606
+
607
+ $value = floor($diff/$intervals[1][1]);
608
+ $time_ago = sprintf(
609
+ '%s %s%s ago',
610
+ $value,
611
+ $intervals[1][0],
612
+ ( $value > 1 ? 's' : '' )
613
+ );
614
+
615
+ return $time_ago;
616
  }
617
 
618
  /**
619
+ * Convert an string of characters into a valid variable name.
 
 
620
  *
621
+ * @see http://www.php.net/manual/en/language.variables.basics.php
622
+ *
623
+ * @param string $text A text containing alpha-numeric and special characters.
624
+ * @return string A valid variable name.
625
  */
626
+ public static function human2var( $text='' ){
627
+ $text = strtolower($text);
628
+ $pattern = '/[^a-z0-9_]/';
629
+ $var_name = preg_replace($pattern, '_', $text);
630
 
631
+ return $var_name;
632
+ }
 
 
 
 
 
 
633
 
634
+ /**
635
+ * Check whether an IP address has a valid format or not.
636
+ *
637
+ * @param string $remote_addr The host IP address.
638
+ * @return boolean Whether the IP address specified is valid or not.
639
+ */
640
+ public static function is_valid_ip( $remote_addr='' ){
641
+ // Check for IPv4 and IPv6.
642
+ if( function_exists('filter_var') ){
643
+ return (bool) filter_var( $remote_addr, FILTER_VALIDATE_IP );
644
+ }
645
+
646
+ // Assuming older version of PHP and server, so only will check for IPv4.
647
+ elseif( strlen($remote_addr) >= 7 ) {
648
+ $pattern = '/^([0-9]{1,3}\.){3}[0-9]{1,3}$/';
649
+
650
+ if( preg_match($pattern, $remote_addr, $match) ){
651
+ for( $i=0; $i<4; $i++ ){
652
+ if( $match[$i] > 255 ){ return FALSE; }
653
+ }
654
+
655
+ return TRUE;
656
+ }
657
+ }
658
+
659
+ return FALSE;
660
+ }
661
+
662
+ /**
663
+ * Validate email address.
664
+ *
665
+ * This use the native PHP function filter_var which is available in PHP >=
666
+ * 5.2.0 if it is not found in the interpreter this function will sue regular
667
+ * expressions to check whether the email address passed is valid or not.
668
+ *
669
+ * @see http://www.php.net/manual/en/function.filter-var.php
670
+ *
671
+ * @param string $email The string that will be validated as an email address.
672
+ * @return boolean TRUE if the email address passed to the function is valid, FALSE if not.
673
+ */
674
+ public static function is_valid_email( $email='' ){
675
+ if( function_exists('filter_var') ){
676
+ return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
677
+ } else {
678
+ $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix';
679
+ return (bool) preg_match($pattern, $email);
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Cut a long text to the length specified, and append suspensive points at the end.
685
+ *
686
+ * @param string $text String of characters that will be cut.
687
+ * @param integer $length Maximum length of the returned string, default is 10.
688
+ * @return string Short version of the text specified.
689
+ */
690
+ public static function excerpt( $text='', $length=10 ){
691
+ $text_length = strlen($text);
692
+
693
+ if( $text_length > $length ){
694
+ return substr( $text, 0, $length ) . '...';
695
+ }
696
+
697
+ return $text;
698
+ }
699
+
700
+ }
701
+
702
+ /**
703
+ * HTTP request handler.
704
+ *
705
+ * Function definitions to retrieve, validate, and clean the parameters during a
706
+ * HTTP request, generally after a form submission or while loading a URL. Use
707
+ * these methods at most instead of accessing an index in the global PHP
708
+ * variables _POST, _GET, _REQUEST since they may come with insecure data.
709
+ */
710
+ class SucuriScanRequest extends SucuriScan {
711
+
712
+ /**
713
+ * Returns the value stored in a specific index in the global _GET, _POST or
714
+ * _REQUEST variables, you can specify a pattern as the second argument to
715
+ * match allowed values.
716
+ *
717
+ * @param array $list The array where the specified key will be searched.
718
+ * @param string $key Name of the index where the requested variable is supposed to be.
719
+ * @param string $pattern Optional pattern to match allowed values in the requested key.
720
+ * @return string The value stored in the specified key inside the global _GET variable.
721
+ */
722
+ public static function request( $list=array(), $key='', $pattern='' ){
723
+ $key = self::variable_prefix($key);
724
+
725
+ if( is_array($list) && isset($list[$key]) ){
726
+ // Select the key from the list and escape its content.
727
+ $key_value = $list[$key];
728
+
729
+ // Define regular expressions for specific value types.
730
+ if( $pattern === '' ){
731
+ $pattern = '/.*/';
732
+ } else {
733
+ switch( $pattern ){
734
+ case '_nonce': $pattern = '/^[a-z0-9]{10}$/'; break;
735
+ case '_page': $pattern = '/^[a-z_]+$/'; break;
736
+ case '_array': $pattern = '_array'; break;
737
+ case '_yyyymmdd': $pattern = '/^[0-9]{4}\-[0-9]{2}\-[0-9]{2}$/'; break;
738
+ default: $pattern = '/^'.$pattern.'$/'; break;
739
+ }
740
+ }
741
+
742
+ // If the request data is an array, then only cast the value.
743
+ if( $pattern == '_array' && is_array($key_value) ){
744
+ return (array) $key_value;
745
+ }
746
+
747
+ // Check the format of the request data with a regex defined above.
748
+ if( preg_match($pattern, $key_value) ){
749
+ return self::escape($key_value);
750
+ }
751
+ }
752
+
753
+ return FALSE;
754
+ }
755
+
756
+ /**
757
+ * Returns the value stored in a specific index in the global _GET variable,
758
+ * you can specify a pattern as the second argument to match allowed values.
759
+ *
760
+ * @param string $key Name of the index where the requested variable is supposed to be.
761
+ * @param string $pattern Optional pattern to match allowed values in the requested key.
762
+ * @return string The value stored in the specified key inside the global _GET variable.
763
+ */
764
+ public static function get( $key='', $pattern='' ){
765
+ return self::request( $_GET, $key, $pattern );
766
+ }
767
+
768
+ /**
769
+ * Returns the value stored in a specific index in the global _POST variable,
770
+ * you can specify a pattern as the second argument to match allowed values.
771
+ *
772
+ * @param string $key Name of the index where the requested variable is supposed to be.
773
+ * @param string $pattern Optional pattern to match allowed values in the requested key.
774
+ * @return string The value stored in the specified key inside the global _POST variable.
775
+ */
776
+ public static function post( $key='', $pattern='' ){
777
+ return self::request( $_POST, $key, $pattern );
778
+ }
779
+
780
+ /**
781
+ * Returns the value stored in a specific index in the global _REQUEST variable,
782
+ * you can specify a pattern as the second argument to match allowed values.
783
+ *
784
+ * @param string $key Name of the index where the requested variable is supposed to be.
785
+ * @param string $pattern Optional pattern to match allowed values in the requested key.
786
+ * @return string The value stored in the specified key inside the global _POST variable.
787
+ */
788
+ public static function get_or_post( $key='', $pattern='' ){
789
+ return self::request( $_REQUEST, $key, $pattern );
790
+ }
791
+
792
+ }
793
+
794
+ /**
795
+ * Class to process files and folders.
796
+ *
797
+ * Here are implemented the functions needed to open, scan, read, create files
798
+ * and folders using the built-in PHP class SplFileInfo. The SplFileInfo class
799
+ * offers a high-level object oriented interface to information for an individual
800
+ * file.
801
+ */
802
+ class SucuriScanFileInfo extends SucuriScan {
803
+
804
+ /**
805
+ * Whether the list of files that can be ignored from the filesystem scan will
806
+ * be used to return the directory tree, this should be disabled when scanning a
807
+ * directory without the need to filter the items in the list.
808
+ *
809
+ * @var boolean
810
+ */
811
+ public $ignore_files = TRUE;
812
+
813
+ /**
814
+ * Whether the list of folders that can be ignored from the filesystem scan will
815
+ * be used to return the directory tree, this should be disabled when scanning a
816
+ * path without the need to filter the items in the list.
817
+ *
818
+ * @var boolean
819
+ */
820
+ public $ignore_directories = TRUE;
821
+
822
+ /**
823
+ * Whether the filesystem scanner should run recursively or not.
824
+ *
825
+ * @var boolean
826
+ */
827
+ public $run_recursively = TRUE;
828
+
829
+ /**
830
+ * Class constructor.
831
+ */
832
+ public function __construct(){
833
+ }
834
+
835
+ /**
836
+ * Retrieve a long text string with signatures of all the files contained
837
+ * in the main and subdirectories of the folder specified, also the filesize
838
+ * and md5sum of that file. Some folders and files will be ignored depending
839
+ * on some rules defined by the developer.
840
+ *
841
+ * @param string $directory Parent directory where the filesystem scan will start.
842
+ * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
843
+ * @param boolean $as_array Whether the result of the operation will be returned as an array or string.
844
+ * @return array List of files in the main and subdirectories of the folder specified.
845
+ */
846
+ public function get_directory_tree_md5( $directory='', $scan_with='spl', $as_array=FALSE ){
847
+ $project_signatures = '';
848
+ $abs_path = rtrim( ABSPATH, '/' );
849
+ $files = $this->get_directory_tree($directory, $scan_with);
850
+ sort($files);
851
+
852
+ if( $as_array ){
853
+ $project_signatures = array();
854
+ }
855
+
856
+ foreach( $files as $filepath){
857
+ $file_checksum = @md5_file($filepath);
858
+ $filesize = @filesize($filepath);
859
+
860
+ if( $as_array ){
861
+ $basename = str_replace( $abs_path . '/', '', $filepath );
862
+ $project_signatures[$basename] = array(
863
+ 'filepath' => $filepath,
864
+ 'checksum' => $file_checksum,
865
+ 'filesize' => $filesize,
866
+ 'created_at' => filectime($filepath),
867
+ 'modified_at' => filemtime($filepath),
868
+ );
869
+ } else {
870
+ $filepath = str_replace( $abs_path, $abs_path . '/', $filepath );
871
+ $project_signatures .= sprintf(
872
+ "%s%s%s%s\n",
873
+ $file_checksum,
874
+ $filesize,
875
+ chr(32),
876
+ $filepath
877
+ );
878
+ }
879
+ }
880
+
881
+ return $project_signatures;
882
+ }
883
+
884
+ /**
885
+ * Retrieve a list with all the files contained in the main and subdirectories
886
+ * of the folder specified. Some folders and files will be ignored depending
887
+ * on some rules defined by the developer.
888
+ *
889
+ * @param string $directory Parent directory where the filesystem scan will start.
890
+ * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
891
+ * @return array List of files in the main and subdirectories of the folder specified.
892
+ */
893
+ public function get_directory_tree($directory='', $scan_with='spl'){
894
+ if( file_exists($directory) && is_dir($directory) ){
895
+ $tree = array();
896
+
897
+ switch( $scan_with ){
898
+ case 'spl':
899
+ if( $this->is_spl_available() ){
900
+ $tree = $this->get_directory_tree_with_spl($directory);
901
+ } else {
902
+ $tree = $this->get_directory_tree($directory, 'opendir');
903
+ }
904
+ break;
905
+
906
+ case 'glob':
907
  $tree = $this->get_directory_tree_with_glob($directory);
908
  break;
909
 
1182
  return $all_removed;
1183
  }
1184
 
1185
+ /**
1186
+ * Return the lines of a file as an array, it will automatically remove the new
1187
+ * line characters from the end of each line, and skip empty lines from the
1188
+ * list.
1189
+ *
1190
+ * @param string $filepath Path to the file.
1191
+ * @return array An array where each element is a line in the file.
1192
+ */
1193
+ public static function file_lines( $filepath='' ){
1194
+ return @file( $filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
1195
+ }
1196
+
1197
  }
1198
 
1199
  /**
1200
+ * File-based cache library.
1201
+ *
1202
+ * WP_Object_Cache [1] is WordPress' class for caching data which may be
1203
+ * computationally expensive to regenerate, such as the result of complex
1204
+ * database queries. However the object cache is non-persistent. This means that
1205
+ * data stored in the cache resides in memory only and only for the duration of
1206
+ * the request. Cached data will not be stored persistently across page loads
1207
+ * unless of the installation of a 3party persistent caching plugin [2].
1208
  *
1209
+ * [1] http://codex.wordpress.org/Class_Reference/WP_Object_Cache
1210
+ * [2] http://codex.wordpress.org/Class_Reference/WP_Object_Cache#Persistent_Caching
 
1211
  */
1212
+ class SucuriScanCache extends SucuriScan {
1213
 
1214
  /**
1215
+ * The unique name (or identifier) of the file with the data.
1216
+ *
1217
+ * The file should be located in the same folder where the dynamic data
1218
+ * generated by the plugin is stored, and using the following format [1], it
1219
+ * most be a PHP file because it is expected to have an exit point in the first
1220
+ * line of the file causing it to stop the execution if a unauthorized user
1221
+ * tries to access it directly.
1222
+ *
1223
+ * [1] /public/data/sucuri-DATASTORE.php
1224
  *
1225
+ * @var null|string
1226
  */
1227
+ private $datastore = NULL;
 
 
 
 
 
 
 
 
1228
 
1229
+ /**
1230
+ * The full path of the datastore file.
1231
+ *
1232
+ * @var string
1233
+ */
1234
+ private $datastore_path = '';
1235
 
1236
  /**
1237
+ * Whether the datastore file is usable or not.
1238
  *
1239
+ * This variable will only be TRUE if the datastore file specified exists, is
1240
+ * writable and readable, in any other case it will always be FALSE.
1241
+ *
1242
+ * @var boolean
1243
  */
1244
+ private $usable_datastore = FALSE;
 
 
 
1245
 
1246
  /**
1247
+ * Class constructor.
1248
  *
1249
+ * @param string $datastore Unique name (or identifier) of the file with the data.
1250
  * @return void
1251
  */
1252
+ public function __construct( $datastore='' ){
1253
+ $this->datastore = $datastore;
1254
+ $this->datastore_path = $this->datastore_file_path();
1255
+ $this->usable_datastore = (bool) $this->datastore_path;
1256
  }
1257
 
1258
  /**
1259
+ * Default attributes for every datastore file.
1260
  *
1261
+ * @return string Default attributes for every datastore file.
 
1262
  */
1263
+ private function datastore_default_info(){
1264
+ $attrs = array(
1265
+ 'datastore' => $this->datastore,
1266
+ 'created_on' => time(),
1267
+ 'updated_on' => time(),
1268
+ );
 
 
 
 
 
 
 
 
1269
 
1270
+ return $attrs;
 
 
 
 
1271
  }
1272
 
1273
  /**
1274
+ * Default content of every datastore file.
1275
  *
1276
+ * @param array $finfo Rainbow table with the key names and decoded values.
1277
+ * @return string Default content of every datastore file.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1278
  */
1279
  private function datastore_info( $finfo=array() ){
1280
  $attrs = $this->datastore_default_info();
1387
  );
1388
 
1389
  if( $this->usable_datastore ){
1390
+ $data_lines = SucuriScanFileInfo::file_lines($this->datastore_path);
1391
 
1392
  if( !empty($data_lines) ){
1393
  foreach( $data_lines as $line ){
 
 
1394
  if( preg_match('/^\/\/ ([a-z_]+)=(.*);$/', $line, $match) ){
1395
  $data_object['info'][$match[1]] = $match[2];
1396
  }
1400
  $this->valid_key_name($match[1])
1401
  && !array_key_exists($match[1], $data_object)
1402
  ){
1403
+ $data_object['entries'][$match[1]] = @json_decode( $match[2], $assoc );
1404
  }
1405
  }
1406
  }
1575
  }
1576
 
1577
  /**
1578
+ * Plugin options handler.
1579
  *
1580
+ * Options are pieces of data that WordPress uses to store various preferences
1581
+ * and configuration settings. Listed below are the options, along with some of
1582
+ * the default values from the current WordPress install. By using the
1583
+ * appropriate function, options can be added, changed, removed, and retrieved,
1584
+ * from the wp_options table.
1585
+ *
1586
+ * The Options API is a simple and standardized way of storing data in the
1587
+ * database. The API makes it easy to create, access, update, and delete
1588
+ * options. All the data is stored in the wp_options table under a given custom
1589
+ * name. This page contains the technical documentation needed to use the
1590
+ * Options API. A list of default options can be found in the Option Reference.
1591
+ *
1592
+ * Note that the _site_ functions are essentially the same as their
1593
+ * counterparts. The only differences occur for WP Multisite, when the options
1594
+ * apply network-wide and the data is stored in the wp_sitemeta table under the
1595
+ * given custom name.
1596
  *
1597
+ * @see http://codex.wordpress.org/Option_Reference
1598
+ * @see http://codex.wordpress.org/Options_API
1599
  */
1600
+ class SucuriScanOption extends SucuriScanRequest {
1601
+
1602
+ /**
1603
+ * Retrieve specific options from the database.
1604
+ *
1605
+ * Considering the case in which this plugin is installed in a multisite instance
1606
+ * of Wordpress, the allowed values for the first parameter of this function will
1607
+ * be treated like this:
1608
+ *
1609
+ * <ul>
1610
+ * <li>all_plugin_options: Will retrieve all the option values created by this plugin in the main site (aka. network),</li>
1611
+ * <li>site_options: Will retrieve all the option values stored in the current site visited by the user (aka. sub-site) excluding the transient options,</li>
1612
+ * <li>plugin_option: Will retrieve one specific option from the network site only if the option starts with the prefix <i>sucuri_<i>.</li>
1613
+ * </ul>
1614
+ *
1615
+ * @param string $filter_by Criteria to filter the results, valid values: all_plugin_options, site_options, sucuri_option.
1616
+ * @param string $option_name Optional parameter with the name of the option that will be filtered.
1617
+ * @return array List of options retrieved from the query in the database.
1618
+ */
1619
+ public function get_options_from_db( $filter_by='', $option_name='' ){
1620
+ global $wpdb;
1621
+
1622
+ $output = FALSE;
1623
+ switch($filter_by){
1624
+ case 'all_plugin_options':
1625
+ $output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name LIKE 'sucuriscan%' ORDER BY option_id ASC");
1626
+ break;
1627
+ case 'site_options':
1628
+ $output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name NOT LIKE '%_transient_%' ORDER BY option_id ASC");
1629
+ break;
1630
+ case 'plugin_option':
1631
+ $row = $wpdb->get_row( $wpdb->prepare("SELECT option_value FROM {$wpdb->base_prefix}options WHERE option_name = %s LIMIT 1", $option_name) );
1632
+ if( $row ){ $output = $row->option_value; }
1633
+ break;
1634
  }
1635
 
1636
+ return $output;
1637
  }
1638
 
 
 
 
 
1639
  /**
1640
+ * Alias function for the method Common::SucuriScan_Get_Options()
1641
  *
1642
+ * This function search the specified option in the database, not only the options
1643
+ * set by the plugin but all the options set for the site. If the value retrieved
1644
+ * is FALSE the method tries to search for a default value.
 
1645
  *
1646
+ * To facilitate the development, you can prefix the name of the key in the
1647
+ * request (when accessing it) with a single colon, this function will
1648
+ * automatically replace that character with the unique identifier of the
1649
+ * plugin.
1650
+ *
1651
+ * @see http://codex.wordpress.org/Function_Reference/get_option
1652
+ *
1653
+ * @param string $option_name Optional parameter that you can use to filter the results to one option.
1654
+ * @return string The value (or default value) of the option specified.
1655
  */
1656
+ public static function get_option( $option_name='' ){
1657
+ $option_name = self::variable_prefix($option_name);
 
 
 
 
 
 
 
 
1658
 
1659
+ return self::get_options($option_name);
1660
+ }
1661
 
 
1662
  /**
1663
+ * Update the value of an database' option.
 
1664
  *
1665
+ * Use the function to update a named option/value pair to the options database
1666
+ * table. The option name value is escaped with a special database method before
1667
+ * the insert SQL statement but not the option value, this value should always
1668
+ * be properly sanitized.
1669
+ *
1670
+ * @see http://codex.wordpress.org/Function_Reference/update_option
1671
+ *
1672
+ * @param string $option_name Name of the option to update which must not exceed 64 characters.
1673
+ * @param string $option_value The new value for the option, can be an integer, string, array, or object.
1674
+ * @return boolean True if option value has changed, false if not or if update failed.
1675
  */
1676
+ public static function update_option( $option_name='', $option_value='' ){
1677
+ if( function_exists('update_option') ){
1678
+ $option_name = self::variable_prefix($option_name);
1679
 
1680
+ return update_option( $option_name, $option_value );
1681
+ }
 
 
1682
 
1683
+ return FALSE;
1684
+ }
 
 
 
 
1685
 
1686
+ /**
1687
+ * Remove an option from the database.
1688
+ *
1689
+ * A safe way of removing a named option/value pair from the options database table.
1690
+ *
1691
+ * @see http://codex.wordpress.org/Function_Reference/delete_option
1692
+ *
1693
+ * @param string $option_name Name of the option to be deleted.
1694
+ * @return boolean True, if option is successfully deleted. False on failure, or option does not exist.
1695
+ */
1696
+ public static function delete_option( $option_name='' ){
1697
+ if( function_exists('delete_option') ){
1698
+ $option_name = self::variable_prefix($option_name);
1699
+
1700
+ return delete_option( $option_name );
1701
  }
 
1702
 
1703
+ return FALSE;
1704
+ }
1705
 
 
1706
  /**
1707
+ * Retrieve all the options created by this Plugin from the Wordpress database.
1708
+ *
1709
+ * The function acts as an alias of WP::get_option() and if the returned value
1710
+ * is FALSE it tries to search for a default value to complement the information.
1711
+ *
1712
+ * @param string $option_name Optional parameter that you can use to filter the results to one option.
1713
+ * @return array Either FALSE or an Array containing all the sucuri options in the database.
1714
  */
1715
+ private static function get_options( $option_name='' ){
1716
+ if( !empty($option_name) ){
1717
+ return self::get_single_option($option_name);
 
 
1718
  }
1719
 
1720
+ $settings = array();
1721
+ $results = self::get_options_from_db('all_plugin_options');
1722
 
1723
+ foreach( $results as $row ){
1724
+ $settings[$row->option_name] = $row->option_value;
1725
+ }
1726
+
1727
+ return self::get_default_options($settings);
1728
  }
1729
 
1730
+ /**
1731
+ * Retrieve a single option from the database.
1732
+ *
1733
+ * @param string $option_name Name of the option that will be retrieved.
1734
+ * @return string Value of the option stored in the database, FALSE if not found.
1735
+ */
1736
+ private static function get_single_option( $option_name='' ){
1737
+ $option_value = FALSE;
1738
+ $is_plugin_option = preg_match('/^sucuriscan_/', $option_name) ? TRUE : FALSE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1739
 
1740
+ if( self::is_multisite() && $is_plugin_option ){
1741
+ $option_value = self::get_options_from_db('plugin_option', $option_name);
1742
+ }
 
 
 
 
 
 
 
 
 
 
 
 
1743
 
1744
+ // Use framework built-in function.
1745
+ elseif( function_exists('get_option') ){
1746
+ $option_value = get_option($option_name);
1747
+ }
1748
 
1749
+ if( $option_value === FALSE && $is_plugin_option ){
1750
+ $option_value = self::get_default_options($option_name);
1751
+ }
1752
 
1753
+ return $option_value;
 
 
 
 
 
 
 
1754
  }
 
1755
 
 
1756
  /**
1757
+ * Retrieve the default values for some specific options.
 
 
 
1758
  *
1759
+ * @param string|array $settings Either an array that will be complemented or a string with the name of the option.
1760
+ * @return string|array The default values for the specified options.
1761
  */
1762
+ private static function get_default_options( $settings='' ){
1763
+ $admin_email = '';
1764
+
1765
+ // Use framework built-in function.
1766
+ if( function_exists('get_option') ){
1767
+ $admin_email = get_option('admin_email');
1768
+ }
1769
+
1770
+ $default_options = array(
1771
+ 'sucuriscan_api_key' => FALSE,
1772
+ 'sucuriscan_account' => $admin_email,
1773
+ 'sucuriscan_fs_scanner' => 'enabled',
1774
+ 'sucuriscan_scan_frequency' => 'hourly',
1775
+ 'sucuriscan_scan_interface' => 'spl',
1776
+ 'sucuriscan_scan_modfiles' => 'enabled',
1777
+ 'sucuriscan_scan_checksums' => 'enabled',
1778
+ 'sucuriscan_runtime' => 0,
1779
+ 'sucuriscan_lastlogin_redirection' => 'enabled',
1780
+ 'sucuriscan_notify_to' => $admin_email,
1781
+ 'sucuriscan_emails_sent' => 0,
1782
+ 'sucuriscan_emails_per_hour' => 5,
1783
+ 'sucuriscan_last_email_at' => time(),
1784
+ 'sucuriscan_prettify_mails' => 'enabled',
1785
+ 'sucuriscan_notify_success_login' => 'enabled',
1786
+ 'sucuriscan_notify_failed_login' => 'enabled',
1787
+ 'sucuriscan_notify_post_publication' => 'enabled',
1788
+ 'sucuriscan_notify_theme_editor' => 'enabled',
1789
+ 'sucuriscan_maximum_failed_logins' => 30,
1790
+ 'sucuriscan_ignored_events' => '',
1791
+ 'sucuriscan_verify_ssl_cert' => 'true',
1792
  );
1793
 
1794
+ if( is_array($settings) ){
1795
+ foreach( $default_options as $option_name => $option_value ){
1796
+ if( !isset($settings[$option_name]) ){
1797
+ $settings[$option_name] = $option_value;
 
 
1798
  }
1799
+ }
1800
+
1801
+ return $settings;
1802
+ }
1803
 
1804
+ if( is_string($settings) ){
1805
+ if( isset($default_options[$settings]) ){
1806
+ return $default_options[$settings];
1807
  }
1808
  }
1809
+
1810
+ return FALSE;
1811
  }
1812
 
1813
+ /**
1814
+ * Retrieve all the options stored by Wordpress in the database. The options
1815
+ * containing the word "transient" are excluded from the results, this function
1816
+ * is compatible with multisite instances.
1817
+ *
1818
+ * @return array All the options stored by Wordpress in the database, except the transient options.
1819
+ */
1820
+ public static function get_site_options(){
1821
+ $settings = array();
1822
+ $results = self::get_options_from_db('site_options');
1823
 
1824
+ foreach( $results as $row ){
1825
+ $settings[$row->option_name] = $row->option_value;
1826
+ }
 
 
 
 
 
 
1827
 
1828
+ return $settings;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1829
  }
 
1830
 
1831
+ /**
1832
+ * Check what Wordpress options were changed comparing the values in the database
1833
+ * with the values sent through a simple request using a GET or POST method.
1834
+ *
1835
+ * @param array $request The content of the global variable GET or POST considering SERVER[REQUEST_METHOD].
1836
+ * @return array A list of all the options that were changes through this request.
1837
+ */
1838
+ public static function what_options_were_changed( $request=array() ){
1839
+ $options_changed = array(
1840
+ 'original' => array(),
1841
+ 'changed' => array()
1842
+ );
1843
+
1844
+ $site_options = self::get_site_options();
1845
+
1846
+ foreach( $request as $req_name => $req_value ){
1847
+ if(
1848
+ array_key_exists($req_name, $site_options)
1849
+ && $site_options[$req_name] != $req_value
1850
+ ){
1851
+ $options_changed['original'][$req_name] = $site_options[$req_name];
1852
+ $options_changed['changed'][$req_name] = $req_value;
1853
+ }
1854
+ }
1855
 
1856
+ return $options_changed;
 
1857
  }
1858
 
1859
+ /**
1860
+ * Check the nonce comming from any of the settings pages.
1861
+ *
1862
+ * @return boolean TRUE if the nonce is valid, FALSE otherwise.
1863
+ */
1864
+ public static function check_options_nonce(){
1865
+ // Create the option_page value if permalink submission.
1866
+ if(
1867
+ !isset($_POST['option_page'])
1868
+ && isset($_POST['permalink_structure'])
1869
+ ){
1870
+ $_POST['option_page'] = 'permalink';
1871
+ }
1872
 
1873
+ // Check if the option_page has an allowed value.
1874
+ if( $option_page = SucuriScanRequest::post('option_page') ){
1875
+ $nonce='_wpnonce';
1876
+ $action = '';
 
 
 
 
1877
 
1878
+ switch( $option_page ){
1879
+ case 'general': /* no_break */
1880
+ case 'writing': /* no_break */
1881
+ case 'reading': /* no_break */
1882
+ case 'discussion': /* no_break */
1883
+ case 'media': /* no_break */
1884
+ case 'options': /* no_break */
1885
+ $action = $option_page . '-options';
1886
+ break;
1887
+ case 'permalink':
1888
+ $action = 'update-permalink';
1889
+ break;
1890
+ }
1891
 
1892
+ // Check the nonce validity.
1893
+ if(
1894
+ !empty($action)
1895
+ && isset($_REQUEST[$nonce])
1896
+ && wp_verify_nonce($_REQUEST[$nonce], $action)
1897
+ ){
1898
+ return TRUE;
1899
+ }
1900
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1901
 
1902
+ return FALSE;
 
 
 
 
 
 
1903
  }
1904
 
1905
+ /**
1906
+ * Get the email address set by the administrator to receive the notifications
1907
+ * sent by the plugin, if the email is missing the WordPress email address is
1908
+ * chosen by default.
1909
+ *
1910
+ * @return string The administrator email address.
1911
+ */
1912
+ public static function get_site_email(){
1913
+ $email = self::get_option('admin_email');
1914
 
1915
+ if( self::is_valid_email($email) ){
1916
+ return $email;
1917
+ }
1918
 
1919
+ return FALSE;
1920
+ }
1921
 
1922
+ /**
1923
+ * Get the clean version of the current domain.
1924
+ *
1925
+ * @return string The domain of the current site.
1926
+ */
1927
+ public static function get_domain(){
1928
+ $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
1929
+ $domain_name = preg_replace( '/^www\./', '', $http_host );
1930
 
1931
+ return $domain_name;
1932
+ }
 
 
1933
 
1934
+ /**
1935
+ * Get a list of the post types ignored to receive email notifications when the
1936
+ * "new site content" hook is triggered.
1937
+ *
1938
+ * @return array List of ignored posts-types to send notifications.
1939
+ */
1940
+ public static function get_ignored_events(){
1941
+ $post_types = self::get_option(':ignored_events');
1942
+ $post_types_arr = @unserialize($post_types);
1943
+
1944
+ if( !is_array($post_types_arr) ){
1945
+ $post_types_arr = array();
1946
  }
 
 
 
1947
 
1948
+ return $post_types_arr;
1949
+ }
1950
 
1951
+ /**
1952
+ * Add a new post type to the list of ignored events to send notifications.
1953
+ *
1954
+ * @param string $event_name Unique post-type name.
1955
+ * @return boolean Whether the event was ignored or not.
1956
+ */
1957
+ public static function add_ignored_event( $event_name='' ){
1958
+ if( function_exists('get_post_types') ){
1959
+ $post_types = get_post_types();
1960
 
1961
+ // Check if the event is a registered post-type.
1962
+ if( array_key_exists($event_name, $post_types) ){
1963
+ $ignored_events = self::get_ignored_events();
 
 
1964
 
1965
+ // Check if the event is not ignored already.
1966
+ if( !array_key_exists($event_name, $ignored_events) ){
1967
+ $ignored_events[$event_name] = time();
1968
+ $saved = self::update_option( ':ignored_events', serialize($ignored_events) );
1969
 
1970
+ return $saved;
1971
+ }
1972
  }
 
 
 
1973
  }
1974
+
1975
+ return FALSE;
1976
  }
1977
 
1978
+ /**
1979
+ * Remove a post type from the list of ignored events to send notifications.
1980
+ *
1981
+ * @param string $event_name Unique post-type name.
1982
+ * @return boolean Whether the event was removed from the list or not.
1983
+ */
1984
+ public static function remove_ignored_event( $event_name='' ){
1985
+ $ignored_events = self::get_ignored_events();
1986
 
1987
+ if( array_key_exists($event_name, $ignored_events) ){
1988
+ unset( $ignored_events[$event_name] );
1989
+ $saved = self::update_option( ':ignored_events', serialize($ignored_events) );
 
 
 
 
 
 
 
 
 
 
 
1990
 
1991
+ return $saved;
1992
+ }
1993
+
1994
+ return FALSE;
 
 
1995
  }
1996
 
1997
+ /**
1998
+ * Check whether an event is being ignored to send notifications or not.
1999
+ *
2000
+ * @param string $event_name Unique post-type name.
2001
+ * @return boolean Whether an event is being ignored or not.
2002
+ */
2003
+ public static function is_ignored_event( $event_name='' ){
2004
+ $event_name = strtolower($event_name);
2005
+ $ignored_events = self::get_ignored_events();
2006
+
2007
+ if( array_key_exists($event_name, $ignored_events) ){
2008
+ return TRUE;
2009
+ }
2010
 
2011
+ return FALSE;
 
2012
  }
2013
 
2014
+ /**
2015
+ * Retrieve a list of basic security keys and check whether their values were
2016
+ * randomized correctly.
2017
+ *
2018
+ * @return array Array with three keys: good, missing, bad.
2019
+ */
2020
+ public static function get_security_keys(){
2021
+ $response = array(
2022
+ 'good' => array(),
2023
+ 'missing' => array(),
2024
+ 'bad' => array(),
2025
+ );
2026
+ $key_names = array(
2027
+ 'AUTH_KEY',
2028
+ 'AUTH_SALT',
2029
+ 'LOGGED_IN_KEY',
2030
+ 'LOGGED_IN_SALT',
2031
+ 'NONCE_KEY',
2032
+ 'NONCE_SALT',
2033
+ 'SECURE_AUTH_KEY',
2034
+ 'SECURE_AUTH_SALT',
2035
+ );
2036
 
2037
+ foreach( $key_names as $key_name ){
2038
+ if( defined($key_name) ){
2039
+ $key_value = constant($key_name);
 
 
 
 
 
 
 
 
 
 
 
 
 
2040
 
2041
+ if( stripos( $key_value, 'unique phrase' ) !== FALSE ){
2042
+ $response['bad'][$key_name] = $key_value;
2043
+ } else {
2044
+ $response['good'][$key_name] = $key_value;
2045
+ }
2046
+ } else {
2047
+ $response['missing'][$key_name] = FALSE;
2048
+ }
2049
+ }
2050
 
2051
+ return $response;
2052
+ }
 
 
 
 
 
 
 
2053
 
2054
+ /**
2055
+ * Retrieve the last time when the filesystem scan was ran.
2056
+ *
2057
+ * @param boolean $format Whether the timestamp must be formatted as date/time or not.
2058
+ * @return string The timestamp of the runtime, or an string with the date/time.
2059
+ */
2060
+ public static function get_filesystem_runtime( $format=FALSE ){
2061
+ $runtime = self::get_option(':runtime');
 
 
2062
 
2063
+ if( $runtime > 0 ){
2064
+ if( $format ){
2065
+ return date( 'd/M/Y H:i:s', $runtime );
2066
+ }
2067
 
2068
+ return $runtime;
2069
  }
 
2070
 
2071
+ if( $format ){
2072
+ return '<em>Unknown</em>';
 
 
 
 
 
 
 
 
 
 
 
 
 
2073
  }
2074
 
2075
+ return FALSE;
2076
  }
2077
 
 
2078
  }
2079
 
2080
  /**
2081
+ * System events, reports and actions.
2082
+ *
2083
+ * An event is an action or occurrence detected by the program that may be
2084
+ * handled by the program. Typically events are handled synchronously with the
2085
+ * program flow, that is, the program has one or more dedicated places where
2086
+ * events are handled, frequently an event loop. Typical sources of events
2087
+ * include the user; another source is a hardware device such as a timer. Any
2088
+ * program can trigger its own custom set of events as well, e.g. to communicate
2089
+ * the completion of a task. A computer program that changes its behavior in
2090
+ * response to events is said to be event-driven, often with the goal of being
2091
+ * interactive.
2092
  *
2093
+ * @see http://en.wikipedia.org/wiki/Event_(computing)
 
2094
  */
2095
+ class SucuriScanEvent extends SucuriScan {
 
 
2096
 
2097
+ /**
2098
+ * Schedule the task to run the first filesystem scan.
2099
+ *
2100
+ * @return void
2101
+ */
2102
+ public static function schedule_task(){
2103
+ $task_name = 'sucuriscan_scheduled_scan';
2104
 
2105
+ if( !wp_next_scheduled($task_name) ){
2106
+ wp_schedule_event( time() + 10, 'twicedaily', $task_name );
 
 
 
 
2107
  }
2108
 
2109
+ wp_schedule_single_event( time() + 300, $task_name );
2110
+ }
2111
 
2112
+ /**
2113
+ * Checks last time we ran to avoid running twice (or too often).
2114
+ *
2115
+ * @param integer $runtime When the filesystem scan must be scheduled to run.
2116
+ * @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
2117
+ * @return boolean Either TRUE or FALSE representing the success or fail of the operation respectively.
2118
+ */
2119
+ private function verify_run( $runtime=0, $force_scan=FALSE ){
2120
+ $option_name = ':runtime';
2121
+ $last_run = SucuriScanOption::get_option($option_name);
2122
+ $current_time = time();
2123
 
2124
+ // The filesystem scanner can be disabled from the settings page.
2125
+ if(
2126
+ SucuriScanOption::get_option(':fs_scanner') == 'disabled'
2127
+ && $force_scan === FALSE
2128
+ ){
2129
+ return FALSE;
2130
  }
2131
 
2132
+ // Check if the last runtime is too near the current time.
2133
+ if( $last_run && !$force_scan ){
2134
+ $runtime_diff = $current_time - $runtime;
 
 
 
 
2135
 
2136
+ if( $last_run >= $runtime_diff ){
2137
+ return FALSE;
2138
+ }
2139
+ }
2140
 
2141
+ SucuriScanOption::update_option( $option_name, $current_time );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2142
 
2143
+ return TRUE;
2144
+ }
 
2145
 
2146
+ /**
2147
+ * Check whether the current WordPress version must be reported to the API
2148
+ * service or not, this is to avoid duplicated information in the audit logs.
2149
+ *
2150
+ * @return boolean TRUE if the current WordPress version must be reported, FALSE otherwise.
2151
+ */
2152
+ private function report_site_version(){
2153
+ $option_name = ':site_version';
2154
+ $reported_version = SucuriScanOption::get_option($option_name);
2155
+ $wp_version = self::site_version();
2156
 
2157
+ if( $reported_version != $wp_version ){
2158
+ SucuriScanAPI::send_log( 'WordPress version: ' . $wp_version );
2159
+ SucuriScanOption::update_option( $option_name, $wp_version );
2160
 
2161
+ return TRUE;
 
 
 
 
 
 
2162
  }
2163
 
2164
+ return FALSE;
2165
  }
2166
 
2167
+ /**
2168
+ * Gather all the checksums (aka. file hashes) of this site, send them, and
2169
+ * analyze them using the Sucuri Monitoring service, this will generate the
2170
+ * audit logs for this site and be part of the integrity checks.
2171
+ *
2172
+ * @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
2173
+ * @return boolean TRUE if the filesystem scan was successful, FALSE otherwise.
2174
+ */
2175
+ public static function filesystem_scan( $force_scan=FALSE ){
2176
+ $minimum_runtime = SUCURISCAN_MINIMUM_RUNTIME;
2177
 
2178
+ if(
2179
+ self::verify_run( $minimum_runtime, $force_scan )
2180
+ && class_exists('SucuriScanFileInfo')
2181
+ && SucuriScanAPI::get_plugin_key()
2182
+ ){
2183
+ self::report_site_version();
2184
 
2185
+ $sucuri_fileinfo = new SucuriScanFileInfo();
2186
+ $scan_interface = SucuriScanOption::get_option(':scan_interface');
2187
+ $signatures = $sucuri_fileinfo->get_directory_tree_md5(ABSPATH, $scan_interface);
 
 
 
 
 
2188
 
2189
+ if( $signatures ){
2190
+ $hashes_sent = SucuriScanAPI::send_hashes( $signatures );
2191
 
2192
+ if( $hashes_sent ){
2193
+ SucuriScanInterface::info( 'Successful filesystem scan' );
2194
+ SucuriScanOption::update_option( ':runtime', time() );
2195
+ return TRUE;
2196
+ } else {
2197
+ SucuriScanInterface::error( 'The file hashes could not be stored.' );
2198
+ }
2199
+ } else {
2200
+ SucuriScanInterface::error( 'The file hashes could not be retrieved, the filesystem scan failed.' );
2201
+ }
2202
+ }
2203
 
2204
+ return FALSE;
2205
+ }
2206
 
2207
+ /**
2208
+ * Generates an audit event log (to be sent later).
2209
+ *
2210
+ * @param integer $severity Importance of the event that will be reported, values from one to five.
2211
+ * @param string $location In which part of the system was the event triggered.
2212
+ * @param string $message The explanation of the event.
2213
+ * @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
2214
+ */
2215
+ public static function report_event( $severity=0, $location='', $message='' ){
2216
+ $user = wp_get_current_user();
2217
+ $username = FALSE;
2218
+ $current_time = date( 'Y-m-d H:i:s' );
2219
+ $remote_ip = self::get_remote_addr();
2220
+
2221
+ // Fixing severity value.
2222
+ $severity = (int) $severity;
2223
+ if( $severity > 0 ){ $severity = 1; }
2224
+ elseif( $severity > 5 ){ $severity = 5; }
2225
+
2226
+ // Identify current user in session.
2227
+ if(
2228
+ $user instanceof WP_User
2229
+ && isset($user->user_login)
2230
+ && !empty($user->user_login)
2231
+ ){
2232
+ if( $user->user_login != $user->display_name ){
2233
+ $username = sprintf( ' %s (%s),', $user->display_name, $user->user_login );
2234
+ } else {
2235
+ $username = sprintf( ' %s,', $user->user_login );
2236
+ }
2237
+ }
2238
 
2239
+ // Convert the severity number into a readable string.
2240
+ switch( $severity ){
2241
+ case 0: $severity_name = 'Debug'; break;
2242
+ case 1: $severity_name = 'Notice'; break;
2243
+ case 2: $severity_name = 'Info'; break;
2244
+ case 3: $severity_name = 'Warning'; break;
2245
+ case 4: $severity_name = 'Error'; break;
2246
+ case 5: $severity_name = 'Critical'; break;
2247
+ default: $severity_name = 'Info'; break;
2248
+ }
2249
+
2250
+ $message = str_replace( array("\n", "\r"), array('', ''), $message );
2251
+ $event_message = sprintf(
2252
+ '%s:%s %s; %s',
2253
+ $severity_name,
2254
+ $username,
2255
+ $remote_ip,
2256
+ $message
2257
+ );
2258
 
2259
+ return SucuriScanAPI::send_log($event_message);
2260
+ }
2261
 
2262
+ /**
2263
+ * Send a notification to the administrator of the specified events, only if
2264
+ * the administrator accepted to receive alerts for this type of events.
2265
+ *
2266
+ * @param string $event The name of the event that was triggered.
2267
+ * @param string $content Body of the email that will be sent to the administrator.
2268
+ * @return void
2269
+ */
2270
+ public static function notify_event( $event='', $content='' ){
2271
+ $notify = SucuriScanOption::get_option(':notify_' . $event);
2272
+ $email = SucuriScanOption::get_option(':notify_to');
2273
+ $email_params = array();
2274
+
2275
+ if( $notify == 'enabled' ){
2276
+ if( $event == 'post_publication' ){
2277
+ $event = 'post_update';
2278
+ }
2279
 
2280
+ elseif( $event == 'failed_login' ){
2281
+ $content .= '<br><br><em>Explanation: Someone failed to login to your site. If you
2282
+ are getting too many of these messages, it is likely your site is under a brute
2283
+ force attack. You can disable the notifications for failed logins from
2284
+ <a href="' . SucuriScanTemplate::get_url('settings') . '" target="_blank">here</a>.
2285
+ More details at <a href="http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing"
2286
+ target="_blank">Password Guessing Brute Force Attacks</a>.</em>';
2287
+ }
2288
 
2289
+ // Send a notification even if the limit of emails per hour was reached.
2290
+ elseif( $event == 'bruteforce_attack' ){
2291
+ $email_params['Force'] = TRUE;
2292
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2293
 
2294
+ $title = sprintf( 'Sucuri notification (%s)', str_replace('_', chr(32), $event) );
2295
+ $mail_sent = SucuriScanMail::send_mail( $email, $title, $content, $email_params );
 
2296
 
2297
+ return $mail_sent;
 
 
2298
  }
 
2299
 
2300
+ return FALSE;
2301
+ }
 
 
2302
 
2303
+ /**
2304
+ * Generate and set a new password for a specific user not in session.
2305
+ *
2306
+ * @param integer $user_id The user identifier that will be changed, this must be different than the user in session.
2307
+ * @return boolean Either TRUE or FALSE in case of success or error respectively.
2308
+ */
2309
+ public static function set_new_password( $user_id=0 ){
2310
+ $user_id = intval($user_id);
 
 
 
 
2311
 
2312
+ if( $user_id > 0 && function_exists('wp_generate_password') ){
2313
+ $user = get_userdata($user_id);
 
 
 
 
 
 
 
2314
 
2315
+ if( $user instanceof WP_User ){
2316
+ $new_password = wp_generate_password(15, TRUE, FALSE);
 
2317
 
2318
+ $message .= 'The password for your user account <strong>"'. $user->display_name .'"</strong> '
2319
+ . 'in the website specified above was changed, this is the new password generated automatically '
2320
+ . 'by the system, please update as soon as possible.<br><div style="display:inline-block;'
2321
+ . 'background:#ddd;font-family:monaco,monospace,courier;font-size:30px;margin:0;padding:15px;'
2322
+ . 'border:1px solid #999">'. $new_password .'</div>';
2323
 
2324
+ $data_set = array( 'Force' => TRUE ); // Skip limit for emails per hour.
2325
+ SucuriScanMail::send_mail( $user->user_email, 'Password changed', $message, $data_set );
 
 
 
 
 
 
2326
 
2327
+ wp_set_password($new_password, $user_id);
 
 
2328
 
2329
+ return TRUE;
 
2330
  }
 
 
2331
  }
 
2332
 
2333
+ return FALSE;
2334
+ }
2335
 
2336
+ /**
2337
+ * Modify the WordPress configuration file and change the keys that were defined
2338
+ * by a new random-generated list of keys retrieved from the official WordPress
2339
+ * API. The result of the operation will be either FALSE in case of error, or an
2340
+ * array containing multiple indexes explaining the modification, among them you
2341
+ * will find the old and new keys.
2342
+ *
2343
+ * @return false|array Either FALSE in case of error, or an array with the old and new keys.
2344
+ */
2345
+ public static function set_new_config_keys(){
2346
+ $new_wpconfig = '';
2347
+ $config_path = self::get_wpconfig_path();
2348
+
2349
+ if( $config_path ){
2350
+ $pattern = self::secret_key_pattern();
2351
+ $define_tpl = "define('%s',%s'%s');";
2352
+ $config_lines = SucuriScanFileInfo::file_lines($config_path);
2353
+ $new_keys = SucuriScanAPI::get_new_secret_keys();
2354
+ $old_keys = array();
2355
+ $old_keys_string = '';
2356
+ $new_keys_string = '';
2357
+
2358
+ foreach( (array) $config_lines as $config_line ){
2359
+ $config_line = str_replace("\n", '', $config_line);
2360
+
2361
+ if( preg_match($pattern, $config_line, $match) ){
2362
+ $key_name = $match[1];
2363
+
2364
+ if( array_key_exists($key_name, $new_keys) ){
2365
+ $white_spaces = $match[2];
2366
+ $old_keys[$key_name] = $match[3];
2367
+ $config_line = sprintf( $define_tpl, $key_name, $white_spaces, $new_keys[$key_name] );
2368
+ $old_keys_string .= sprintf( $define_tpl, $key_name, $white_spaces, $old_keys[$key_name] ) . "\n";
2369
+ $new_keys_string .= $config_line . "\n";
2370
+ }
2371
  }
2372
+
2373
+ $new_wpconfig .= $config_line . "\n";
2374
  }
2375
 
2376
+ $response = array(
2377
+ 'updated' => is_writable($config_path),
2378
+ 'old_keys' => $old_keys,
2379
+ 'old_keys_string' => $old_keys_string,
2380
+ 'new_keys' => $new_keys,
2381
+ 'new_keys_string' => $new_keys_string,
2382
+ 'new_wpconfig' => $new_wpconfig,
2383
+ );
2384
 
2385
+ if( $response['updated'] ){
2386
+ file_put_contents($config_path, $new_wpconfig, LOCK_EX);
2387
+ }
 
 
 
 
 
2388
 
2389
+ return $response;
 
2390
  }
2391
+
2392
+ return FALSE;
2393
  }
2394
+
2395
  }
2396
 
2397
  /**
2398
+ * Function call interceptors.
2399
+ *
2400
+ * The term hooking covers a range of techniques used to alter or augment the
2401
+ * behavior of an operating system, of applications, or of other software
2402
+ * components by intercepting function calls or messages or events passed
2403
+ * between software components. Code that handles such intercepted function
2404
+ * calls, events or messages is called a "hook".
2405
  *
2406
+ * Hooking is used for many purposes, including debugging and extending
2407
+ * functionality. Examples might include intercepting keyboard or mouse event
2408
+ * messages before they reach an application, or intercepting operating system
2409
+ * calls in order to monitor behavior or modify the function of an application
2410
+ * or other component; it is also widely used in benchmarking programs.
2411
  */
2412
+ class SucuriScanHook extends SucuriScanEvent {
 
 
2413
 
2414
+ /**
2415
+ * Send to Sucuri servers an alert advising that an attachment was added to a post.
2416
+ *
2417
+ * @param integer $id The post identifier.
2418
+ * @return void
2419
+ */
2420
+ public static function hook_add_attachment( $id=0 ){
2421
+ $data = ( is_int($id) ? get_post($id) : FALSE );
2422
+ $title = ( $data ? $data->post_title : 'Unknown' );
2423
 
2424
+ $message = 'Media file added #'.$id.' ('.$title.')';
2425
+ self::report_event( 1, 'core', $message );
2426
+ self::notify_event( 'post_publication', $message );
2427
+ }
 
 
2428
 
2429
+ /**
2430
+ * Send an alert advising that a new link was added to the bookmarks.
2431
+ *
2432
+ * @param integer $id Identifier of the new link created;
2433
+ * @return void
2434
+ */
2435
+ public static function hook_add_link( $id=0 ){
2436
+ $data = ( is_int($id) ? get_bookmark($id) : FALSE );
2437
 
2438
+ if( $data ){
2439
+ $title = $data->link_name;
2440
+ $url = $data->link_url;
2441
+ } else {
2442
+ $title = 'Unknown';
2443
+ $url = 'undefined/url';
2444
+ }
2445
+
2446
+ $message = 'New link added #'.$id.' ('.$title.': '.$url.')';
2447
+ self::report_event( 2, 'core', $message );
2448
+ self::notify_event( 'post_publication', $message );
2449
  }
 
 
2450
 
2451
+ /**
2452
+ * Send an alert advising that a category was created.
2453
+ *
2454
+ * @param integer $id The identifier of the category created.
2455
+ * @return void
2456
+ */
2457
+ public static function hook_create_category( $id=0 ){
2458
+ $title = ( is_int($id) ? get_cat_name($id) : 'Unknown' );
 
 
 
 
 
 
 
 
 
 
2459
 
2460
+ $message = 'Category created #'.$id.' ('.$title.')';
2461
+ self::report_event( 1, 'core', $message );
2462
+ self::notify_event( 'post_publication', $message );
2463
  }
2464
 
2465
+ /**
2466
+ * Send an alert advising that a post was deleted.
2467
+ *
2468
+ * @param integer $id The identifier of the post deleted.
2469
+ * @return void
2470
+ */
2471
+ public static function hook_delete_post( $id=0 ){
2472
+ self::report_event( 3, 'core', 'Post deleted #'.$id );
2473
  }
2474
 
2475
+ /**
2476
+ * Send an alert advising that a user account was deleted.
2477
+ *
2478
+ * @param integer $id The identifier of the user account deleted.
2479
+ * @return void
2480
+ */
2481
+ public static function hook_delete_user( $id=0 ){
2482
+ self::report_event( 3, 'core', 'User account deleted #'.$id );
2483
+ }
2484
 
2485
+ /**
2486
+ * Send an alert advising that an attempt to reset the password
2487
+ * of an user account was executed.
2488
+ *
2489
+ * @return void
2490
+ */
2491
+ public static function hook_login_form_resetpass(){
2492
+ // Detecting WordPress 2.8.3 vulnerability - $key is array.
2493
+ if( isset($_GET['key']) && is_array($_GET['key']) ){
2494
+ self::report_event( 3, 'core', 'Attempt to reset password by attacking WP/2.8.3 bug' );
2495
+ }
2496
  }
2497
 
2498
+ /**
2499
+ * Send an alert advising that the state of a post was changed
2500
+ * from private to published. This will only applies for posts not pages.
2501
+ *
2502
+ * @param integer $id The identifier of the post changed.
2503
+ * @return void
2504
+ */
2505
+ public static function hook_private_to_published( $id=0 ){
2506
+ $data = ( is_int($id) ? get_post($id) : FALSE );
2507
 
2508
+ if( $data ){
2509
+ $title = $data->post_title;
2510
+ $p_type = ucwords($data->post_type);
2511
+ } else {
2512
+ $title = 'Unknown';
2513
+ $p_type = 'Publication';
2514
+ }
 
 
 
2515
 
2516
+ // Check whether the post-type is being ignored to send notifications.
2517
+ if( !SucuriScanOption::is_ignored_event($p_type) ){
2518
+ $message = $p_type.' changed from private to published #'.$id.' ('.$title.')';
2519
+ self::report_event( 2, 'core', $message );
2520
+ self::notify_event( 'post_publication', $message );
2521
+ }
2522
  }
2523
 
2524
+ /**
2525
+ * Send an alert advising that a post was published.
2526
+ *
2527
+ * @param integer $id The identifier of the post or page published.
2528
+ * @return void
2529
+ */
2530
+ public static function hook_publish( $id=0 ){
2531
+ $data = ( is_int($id) ? get_post($id) : FALSE );
2532
 
2533
+ if( $data ){
2534
+ $title = $data->post_title;
2535
+ $p_type = ucwords($data->post_type);
2536
+ $action = ( $data->post_date == $data->post_modified ? 'created' : 'updated' );
2537
+ } else {
2538
+ $title = 'Unknown';
2539
+ $p_type = 'Publication';
2540
+ $action = 'published';
2541
+ }
2542
 
2543
+ $message = $p_type.' was '.$action.' #'.$id.' ('.$title.')';
2544
+ self::report_event( 2, 'core', $message );
2545
+ self::notify_event( 'post_publication', $message );
 
2546
  }
2547
 
2548
+ /**
2549
+ * Alias function for hook_publish()
2550
+ *
2551
+ * @param integer $id The identifier of the post or page published.
2552
+ * @return void
2553
+ */
2554
+ public static function hook_publish_page( $id=0 ){
2555
+ self::hook_publish($id);
2556
+ }
2557
 
2558
+ /**
2559
+ * Alias function for hook_publish()
2560
+ *
2561
+ * @param integer $id The identifier of the post or page published.
2562
+ * @return void
2563
+ */
2564
+ public static function hook_publish_post( $id=0 ){
2565
+ self::hook_publish($id);
2566
+ }
2567
 
2568
+ /**
2569
+ * Alias function for hook_publish()
2570
+ *
2571
+ * @param integer $id The identifier of the post or page published.
2572
+ * @return void
2573
+ */
2574
+ public static function hook_publish_phone( $id=0 ){
2575
+ self::hook_publish($id);
2576
  }
2577
 
2578
+ /**
2579
+ * Alias function for hook_publish()
2580
+ *
2581
+ * @param integer $id The identifier of the post or page published.
2582
+ * @return void
2583
+ */
2584
+ public static function hook_xmlrpc_publish_post( $id=0 ){
2585
+ self::hook_publish($id);
2586
+ }
2587
 
2588
+ /**
2589
+ * Send an alert advising that an attempt to retrieve the password
2590
+ * of an user account was tried.
2591
+ *
2592
+ * @param string $title The name of the user account involved in the trasaction.
2593
+ * @return void
2594
+ */
2595
+ public static function hook_retrieve_password( $title='' ){
2596
+ if( empty($title) ){ $title = 'Unknown'; }
 
 
2597
 
2598
+ self::report_event( 3, 'core', 'Password retrieval attempt for user: '.$title );
 
 
 
 
2599
  }
2600
 
2601
+ /**
2602
+ * Send an alert advising that the theme of the site was changed.
2603
+ *
2604
+ * @param string $title The name of the new theme selected to used through out the site.
2605
+ * @return void
2606
+ */
2607
+ public static function hook_switch_theme( $title='' ){
2608
+ if( empty($title) ){ $title = 'Unknown'; }
 
 
 
 
2609
 
2610
+ $message = 'Theme switched to: '.$title;
2611
+ self::report_event( 3, 'core', $message );
2612
+ self::notify_event( 'theme_switched', $message );
2613
  }
2614
 
2615
+ /**
2616
+ * Send an alert advising that a new user account was created.
2617
+ *
2618
+ * @param integer $id The identifier of the new user account created.
2619
+ * @return void
2620
+ */
2621
+ public static function hook_user_register( $id=0 ){
2622
+ $data = ( is_int($id) ? get_userdata($id) : FALSE );
2623
+ $title = ( $data ? $data->display_name : 'Unknown' );
2624
 
2625
+ $message = 'New user account registered #'.$id.' ('.$title.')';
2626
+ self::report_event( 3, 'core', $message );
2627
+ self::notify_event( 'user_registration', $message );
2628
+ }
 
 
 
 
2629
 
2630
+ /**
2631
+ * Send an alert advising that an attempt to login into the
2632
+ * administration panel was successful.
2633
+ *
2634
+ * @param string $title The name of the user account involved in the transaction.
2635
+ * @return void
2636
+ */
2637
+ public static function hook_wp_login( $title='' ){
2638
+ if( empty($title) ){ $title = 'Unknown'; }
2639
 
2640
+ $message = 'User logged in: '.$title;
2641
+ self::report_event( 2, 'core', $message );
2642
+ self::notify_event( 'success_login', $message );
2643
+ }
 
 
 
2644
 
2645
+ /**
2646
+ * Send an alert advising that an attempt to login into the
2647
+ * administration panel failed.
2648
+ *
2649
+ * @param string $title The name of the user account involved in the transaction.
2650
+ * @return void
2651
+ */
2652
+ public static function hook_wp_login_failed( $title='' ){
2653
+ if( empty($title) ){ $title = 'Unknown'; }
2654
 
2655
+ $message = 'User authentication failed: '.$title;
2656
+ self::report_event( 2, 'core', $message );
2657
+ self::notify_event( 'failed_login', $message );
2658
 
2659
+ // Log the failed login in the internal datastore for future reports.
2660
+ $logged = sucuriscan_log_failed_login($title);
 
 
 
 
 
 
 
 
2661
 
2662
+ // Check if the quantity of failed logins will be considered as a brute-force attack.
2663
+ if( $logged ){
2664
+ $failed_logins = sucuriscan_get_failed_logins();
2665
 
2666
+ if( $failed_logins ){
2667
+ $max_time = 3600;
2668
+ $maximum_failed_logins = SucuriScanOption::get_option('sucuriscan_maximum_failed_logins');
2669
 
2670
+ /**
2671
+ * If the time passed is within the hour, and the quantity of failed logins
2672
+ * registered in the datastore file is bigger than the maximum quantity of
2673
+ * failed logins allowed per hour (value configured by the administrator in the
2674
+ * settings page), then send an email notification reporting the event and
2675
+ * specifying that it may be a brute-force attack against the login page.
2676
+ */
2677
+ if(
2678
+ $failed_logins['diff_time'] <= $max_time
2679
+ && $failed_logins['count'] >= $maximum_failed_logins
2680
+ ){
2681
+ sucuriscan_report_failed_logins($failed_logins);
2682
+ }
2683
 
2684
+ /**
2685
+ * If there time passed is superior to the hour, then reset the content of the
2686
+ * datastore file containing the failed logins so far, any entry in that file
2687
+ * will not be considered as part of a brute-force attack (if it exists) because
2688
+ * the time passed between the first and last login attempt is big enough to
2689
+ * mitigate the attack. We will consider the current failed login event as the
2690
+ * first entry of that file in case of future attempts during the next sixty
2691
+ * minutes.
2692
+ */
2693
+ elseif( $failed_logins['diff_time'] > $max_time ){
2694
+ sucuriscan_reset_failed_logins();
2695
+ sucuriscan_log_failed_login($title);
2696
+ }
2697
+ }
2698
+ }
2699
+ }
2700
 
2701
+ // TODO: Detect auto updates in core, themes, and plugin files.
 
2702
 
2703
+ /**
2704
+ * Send a notifications to the administrator of some specific events that are
2705
+ * not triggered through an hooked action, but through a simple request in the
2706
+ * admin interface.
2707
+ *
2708
+ * @return integer Either one or zero representing the success or fail of the operation.
2709
+ */
2710
+ public static function hook_undefined_actions(){
 
 
 
2711
 
2712
+ // Plugin activation and/or deactivation.
2713
+ if(
2714
+ current_user_can('activate_plugins')
2715
+ && (
2716
+ SucuriScanRequest::get('action', '(activate|deactivate)') ||
2717
+ SucuriScanRequest::post('action', '(activate|deactivate)-selected')
2718
+ )
2719
+ ){
2720
+ $plugin_list = array();
2721
 
2722
+ if(
2723
+ SucuriScanRequest::get('plugin', '.+')
2724
+ && strpos($_SERVER['REQUEST_URI'], 'plugins.php') !== FALSE
2725
+ ){
2726
+ $action_d = $_GET['action'] . 'd';
2727
+ $plugin_list[] = $_GET['plugin'];
2728
+ }
 
 
 
 
 
 
 
 
 
 
 
 
2729
 
2730
+ elseif(
2731
+ isset($_POST['checked'])
2732
+ && is_array($_POST['checked'])
2733
+ && !empty($_POST['checked'])
2734
+ ){
2735
+ $action_d = str_replace('-selected', 'd', $_POST['action']);
2736
+ $plugin_list = $_POST['checked'];
2737
+ }
 
 
 
 
 
2738
 
2739
+ foreach( $plugin_list as $plugin ){
2740
+ $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
2741
 
2742
+ if(
2743
+ !empty($plugin_info['Name'])
2744
+ && !empty($plugin_info['Version'])
2745
+ ){
2746
+ $message = sprintf(
2747
+ 'Plugin %s: %s (v%s; %s)',
2748
+ $action_d,
2749
+ self::escape($plugin_info['Name']),
2750
+ self::escape($plugin_info['Version']),
2751
+ self::escape($plugin)
2752
+ );
2753
+
2754
+ self::report_event( 3, 'core', $message );
2755
+ self::notify_event( 'plugin_' . $action_d, $message );
2756
+ }
2757
+ }
2758
+ }
2759
 
2760
+ // Plugin update request.
2761
+ elseif(
2762
+ current_user_can('update_plugins')
2763
+ && (
2764
+ SucuriScanRequest::get('action', '(upgrade-plugin|do-plugin-upgrade)')
2765
+ || SucuriScanRequest::post('action', 'update-selected')
2766
+ )
2767
+ ){
2768
+ $plugin_list = array();
 
 
 
 
2769
 
2770
+ if(
2771
+ SucuriScanRequest::get('plugin', '.+')
2772
+ && strpos($_SERVER['REQUEST_URI'], 'wp-admin/update.php') !== FALSE
2773
+ ){
2774
+ $plugin_list[] = SucuriScanRequest::get('plugin', '.+');
2775
+ }
2776
 
2777
+ elseif(
2778
+ isset($_POST['checked'])
2779
+ && is_array($_POST['checked'])
2780
+ && !empty($_POST['checked'])
2781
+ ){
2782
+ $plugin_list = $_POST['checked'];
2783
+ }
2784
 
2785
+ foreach( $plugin_list as $plugin ){
2786
+ $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
 
 
 
 
 
 
2787
 
2788
+ if(
2789
+ !empty($plugin_info['Name'])
2790
+ && !empty($plugin_info['Version'])
2791
+ ){
2792
+ $message = sprintf(
2793
+ 'Plugin request to be updated: %s (v%s; %s)',
2794
+ self::escape($plugin_info['Name']),
2795
+ self::escape($plugin_info['Version']),
2796
+ self::escape($plugin)
2797
+ );
2798
+
2799
+ self::report_event( 3, 'core', $message );
2800
+ self::notify_event( 'plugin_updated', $message );
2801
+ }
2802
+ }
2803
+ }
2804
 
2805
+ // Plugin installation request.
2806
+ elseif(
2807
+ current_user_can('install_plugins')
2808
+ && SucuriScanRequest::get('action', '(install|upload)-plugin')
2809
+ ){
2810
+ if( isset($_FILES['pluginzip']) ){
2811
+ $plugin = self::escape($_FILES['pluginzip']['name']);
2812
+ } else {
2813
+ $plugin = SucuriScanRequest::get('plugin', '.+');
2814
 
2815
+ if( !$plugin ){ $plugin = 'Unknown'; }
2816
+ }
2817
 
2818
+ $message = 'Plugin request to be installed: ' . self::escape($plugin);
2819
+ self::report_event( 3, 'core', $message );
2820
+ self::notify_event( 'plugin_installed', $message );
2821
+ }
2822
+
2823
+ // Plugin deletion request.
2824
+ elseif(
2825
+ current_user_can('delete_plugins')
2826
+ && SucuriScanRequest::post('action', 'delete-selected')
2827
+ && SucuriScanRequest::post('verify-delete', '1')
2828
+ ){
2829
+ $plugin_list = (array) $_POST['checked'];
2830
+
2831
+ foreach( $plugin_list as $plugin ){
2832
+ $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
 
 
 
 
 
 
 
 
 
 
 
 
 
2833
 
2834
+ if(
2835
+ !empty($plugin_info['Name'])
2836
+ && !empty($plugin_info['Version'])
2837
+ ){
2838
+ $message = sprintf(
2839
+ 'Plugin request to be deleted: %s (v%s; %s)',
2840
+ self::escape($plugin_info['Name']),
2841
+ self::escape($plugin_info['Version']),
2842
+ self::escape($plugin)
2843
+ );
2844
+
2845
+ self::report_event( 3, 'core', $message );
2846
+ self::notify_event( 'plugin_deleted', $message );
2847
+ }
2848
  }
2849
  }
 
 
2850
 
2851
+ // Plugin editor request.
2852
+ elseif(
2853
+ current_user_can('edit_plugins')
2854
+ && SucuriScanRequest::post('action', 'update')
2855
+ && SucuriScanRequest::post('plugin', '.+')
2856
+ && SucuriScanRequest::post('file', '.+')
2857
+ && strpos($_SERVER['REQUEST_URI'], 'plugin-editor.php') !== FALSE
2858
+ ){
2859
+ $message = 'Plugin editor used on: ' . SucuriScanRequest::post('file');
2860
+ self::report_event( 3, 'core', $message );
2861
+ self::notify_event( 'theme_editor', $message );
2862
  }
 
2863
 
2864
+ // Theme editor request.
2865
+ elseif(
2866
+ current_user_can('edit_themes')
2867
+ && SucuriScanRequest::post('action', 'update')
2868
+ && SucuriScanRequest::post('theme', '.+')
2869
+ && SucuriScanRequest::post('file', '.+')
2870
+ && strpos($_SERVER['REQUEST_URI'], 'theme-editor.php') !== FALSE
2871
+ ){
2872
+ $message = 'Theme editor used on: ' . SucuriScanRequest::post('theme') . '/' . SucuriScanRequest::post('file');
2873
+ self::report_event( 3, 'core', $message );
2874
+ self::notify_event( 'theme_editor', $message );
2875
+ }
2876
 
2877
+ // Theme activation and/or deactivation (same hook for switch_theme).
2878
+ // Theme installation request (hook not available).
2879
+ // Theme deletion request (hook not available).
 
 
 
 
 
 
2880
 
2881
+ // Theme update request.
2882
+ elseif(
2883
+ current_user_can('update_themes')
2884
+ && SucuriScanRequest::get('action', '(upgrade-theme|do-theme-upgrade)')
2885
+ && SucuriScanRequest::post('checked')
2886
+ ){
2887
+ foreach( (array) $_POST['checked'] as $theme ){
2888
+ $theme_info = wp_get_theme($theme);
2889
+ $theme_name = ucwords($theme);
2890
+ $theme_version = '0.0';
2891
 
2892
+ if( $theme_info->exists() ){
2893
+ $theme_name = $theme_info->get('Name');
2894
+ $theme_version = $theme_info->get('Version');
2895
+ }
2896
 
2897
+ $message = sprintf(
2898
+ 'Theme updated: %s (v%s; %s)',
2899
+ self::escape($theme_name),
2900
+ self::escape($theme_version),
2901
+ self::escape($theme)
2902
+ );
 
 
 
 
 
 
 
2903
 
2904
+ self::report_event( 3, 'core', $message );
2905
+ self::notify_event( 'theme_updated', $message );
2906
+ }
2907
+ }
2908
+
2909
+ // WordPress update request.
2910
+ elseif(
2911
+ current_user_can('update_core')
2912
+ && SucuriScanRequest::get('action', '(do-core-upgrade|do-core-reinstall)')
2913
+ && SucuriScanRequest::post('upgrade')
2914
  ){
2915
+ $message = 'WordPress updated to version: ' . SucuriScanRequest::post('version');
2916
+ self::report_event( 3, 'core', $message );
2917
+ self::notify_event( 'website_updated', $message );
2918
  }
 
 
 
2919
 
2920
+ // Widget addition or deletion.
2921
+ elseif(
2922
+ current_user_can('edit_theme_options')
2923
+ && SucuriScanRequest::post('action', 'save-widget')
2924
+ && SucuriScanRequest::post('id_base') !== FALSE
2925
+ && SucuriScanRequest::post('sidebar') !== FALSE
2926
+ ){
2927
+ if( SucuriScanRequest::post('delete_widget', '1') ){
2928
+ $action_d = 'deleted';
2929
+ $action_text = 'deleted from';
2930
+ } else {
2931
+ $action_d = 'added';
2932
+ $action_text = 'added to';
2933
+ }
2934
+
2935
+ $message = sprintf(
2936
+ 'Widget %s (%s) %s %s (#%d; size %dx%d)',
2937
+ SucuriScanRequest::post('id_base'),
2938
+ SucuriScanRequest::post('widget-id'),
2939
+ $action_text,
2940
+ SucuriScanRequest::post('sidebar'),
2941
+ SucuriScanRequest::post('widget_number'),
2942
+ SucuriScanRequest::post('widget-width'),
2943
+ SucuriScanRequest::post('widget-height')
2944
+ );
2945
 
2946
+ self::report_event( 3, 'core', $message );
2947
+ self::notify_event( 'widget_' . $action_d, $message );
2948
+ }
2949
 
2950
+ // Detect any Wordpress settings modification.
2951
+ elseif(
2952
+ current_user_can('manage_options')
2953
+ && SucuriScanOption::check_options_nonce()
2954
+ ){
2955
+ // Get the settings available in the database and compare them with the submission.
2956
+ $all_options = SucuriScanOption::get_site_options();
2957
+ $options_changed = SucuriScanOption::what_options_were_changed($_POST);
2958
+ $options_changed_str = '';
2959
+ $options_changed_count = 0;
2960
 
2961
+ // Generate the list of options changed.
2962
+ foreach( $options_changed['original'] as $option_name => $option_value ){
2963
+ $options_changed_count += 1;
2964
+ $options_changed_str .= sprintf(
2965
+ "The value of the option <b>%s</b> was changed from <b>'%s'</b> to <b>'%s'</b>.<br>\n",
2966
+ self::escape($option_name),
2967
+ self::escape($option_value),
2968
+ self::escape($options_changed['changed'][$option_name])
2969
+ );
2970
+ }
2971
 
2972
+ // Get the option group (name of the page where the request was originated).
2973
+ $option_page = isset($_POST['option_page']) ? $_POST['option_page'] : 'options';
2974
+ $page_referer = FALSE;
2975
 
2976
+ // Check which of these option groups where modified.
2977
+ switch( $option_page ){
2978
+ case 'options':
2979
+ $page_referer = 'Global';
2980
+ break;
2981
+ case 'general': /* no_break */
2982
+ case 'writing': /* no_break */
2983
+ case 'reading': /* no_break */
2984
+ case 'discussion': /* no_break */
2985
+ case 'media': /* no_break */
2986
+ case 'permalink':
2987
+ $page_referer = ucwords($option_page);
2988
+ break;
2989
+ default:
2990
+ $page_referer = 'Common';
2991
+ break;
2992
+ }
2993
 
2994
+ if( $page_referer && $options_changed_count > 0 ){
2995
+ $message = $page_referer.' settings changed';
2996
+ self::report_event( 3, 'core', $message );
2997
+ self::notify_event( 'settings_updated', $message . "<br>\n" . $options_changed_str );
2998
+ }
2999
  }
3000
+
3001
  }
3002
 
 
3003
  }
3004
 
3005
  /**
3006
+ * Plugin API library.
3007
+ *
3008
+ * When used in the context of web development, an API is typically defined as a
3009
+ * set of Hypertext Transfer Protocol (HTTP) request messages, along with a
3010
+ * definition of the structure of response messages, which is usually in an
3011
+ * Extensible Markup Language (XML) or JavaScript Object Notation (JSON) format.
3012
+ * While "web API" historically has been virtually synonymous for web service,
3013
+ * the recent trend (so-called Web 2.0) has been moving away from Simple Object
3014
+ * Access Protocol (SOAP) based web services and service-oriented architecture
3015
+ * (SOA) towards more direct representational state transfer (REST) style web
3016
+ * resources and resource-oriented architecture (ROA). Part of this trend is
3017
+ * related to the Semantic Web movement toward Resource Description Framework
3018
+ * (RDF), a concept to promote web-based ontology engineering technologies. Web
3019
+ * APIs allow the combination of multiple APIs into new applications known as
3020
+ * mashups.
3021
  *
3022
+ * @see http://en.wikipedia.org/wiki/Application_programming_interface#Web_APIs
 
3023
  */
3024
+ class SucuriScanAPI extends SucuriScanOption {
 
3025
 
3026
+ /**
3027
+ * Check whether the SSL certificates will be verified while executing a HTTP
3028
+ * request or not. This is only for customization of the administrator, in fact
3029
+ * not verifying the SSL certificates can lead to a "Man in the Middle" attack.
3030
+ *
3031
+ * @return boolean Whether the SSL certs will be verified while sending a request.
3032
+ */
3033
+ public static function verify_ssl_cert(){
3034
+ return ( self::get_option(':verify_ssl_cert') === 'true' );
3035
  }
3036
 
3037
+ /**
3038
+ * Generate an user-agent for the HTTP requests.
3039
+ *
3040
+ * @return string An user-agent for the HTTP requests.
3041
+ */
3042
+ private static function user_agent(){
3043
+ $user_agent = sprintf(
3044
+ 'WordPress/%s; %s',
3045
+ self::site_version(),
3046
+ self::get_domain()
3047
+ );
3048
 
3049
+ return $user_agent;
3050
+ }
 
 
 
 
 
 
 
3051
 
 
 
 
 
 
 
 
 
3052
  /**
3053
+ * Retrieves a URL using a changeable HTTP method, returning results in an
3054
+ * array. Results include HTTP headers and content.
 
3055
  *
3056
+ * @see http://codex.wordpress.org/Function_Reference/wp_remote_post
3057
+ * @see http://codex.wordpress.org/Function_Reference/wp_remote_get
3058
+ *
3059
+ * @param string $url The target URL where the request will be sent.
3060
+ * @param string $method HTTP method that will be used to send the request.
3061
+ * @param array $params Parameters for the request defined in an associative array of key-value.
3062
+ * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
3063
+ * @return array Array of results including HTTP headers or WP_Error if the request failed.
3064
  */
3065
+ private static function api_call( $url='', $method='GET', $params=array(), $args=array() ){
3066
+ if( !$url ){ return FALSE; }
3067
+
3068
+ $req_args = array(
3069
+ 'method' => $method,
3070
+ 'timeout' => 90,
3071
+ 'redirection' => 2,
3072
+ 'httpversion' => '1.0',
3073
+ 'user-agent' => self::user_agent(),
3074
+ 'blocking' => TRUE,
3075
+ 'headers' => array(),
3076
+ 'cookies' => array(),
3077
+ 'compress' => FALSE,
3078
+ 'decompress' => FALSE,
3079
+ 'sslverify' => self::verify_ssl_cert(),
3080
+ );
 
 
 
 
 
 
 
 
 
3081
 
3082
+ // Update the request arguments with the values passed tot he function.
3083
+ foreach( $args as $arg_name => $arg_value ){
3084
+ if( array_key_exists($arg_name, $req_args) ){
3085
+ $req_args[$arg_name] = $arg_value;
3086
+ }
3087
+ }
3088
 
3089
+ if( $method == 'GET' ){
3090
+ if( !empty($params) ){
3091
+ $url = sprintf( '%s?%s', $url, http_build_query($params) );
3092
+ }
3093
 
3094
+ $response = wp_remote_get( $url, $req_args );
3095
+ }
 
 
 
 
3096
 
3097
+ elseif( $method == 'POST' ){
3098
+ $req_args['body'] = $params;
3099
+ $response = wp_remote_post( $url, $req_args );
 
 
 
 
 
3100
  }
3101
 
3102
+ if( isset($response) ){
3103
+ if( is_wp_error($response) ){
3104
+ SucuriScanInterface::error(sprintf(
3105
+ 'Something went wrong with an API call (%s action): %s',
3106
+ ( isset($params['a']) ? $params['a'] : 'unknown' ),
3107
+ $response->get_error_message()
3108
+ ));
3109
  } else {
3110
+ $response['body_raw'] = $response['body'];
 
 
 
 
 
3111
 
3112
+ // Check if the response data is JSON-encoded, then decode it.
3113
  if(
3114
+ isset($response['headers']['content-type'])
3115
+ && $response['headers']['content-type'] == 'application/json'
3116
  ){
3117
+ $response['body'] = @json_decode($response['body_raw']);
3118
+ }
3119
+
3120
+ // Check if the response data is serialized, then unserialize it.
3121
+ elseif( preg_match('/^(a|O):[0-9]+:.+/', $response['body']) ){
3122
+ $response['body'] = @unserialize($response['body']);
3123
  }
3124
+
3125
+ return $response;
3126
  }
3127
+ } else {
3128
+ SucuriScanInterface::error( 'HTTP method not allowed: ' . $method );
3129
  }
3130
 
3131
+ return FALSE;
3132
+ }
3133
+
3134
+ /**
3135
+ * Store the API key locally.
3136
+ *
3137
+ * @param string $api_key An unique string of characters to identify this installation.
3138
+ * @param boolean $validate Whether the format of the key should be validated before store it.
3139
+ * @return boolean Either TRUE or FALSE if the key was saved successfully or not respectively.
3140
+ */
3141
+ public static function set_plugin_key( $api_key='', $validate=FALSE ){
3142
+ if( $validate ){
3143
+ if( !preg_match('/^([a-z0-9]{32})$/', $api_key) ){
3144
+ SucuriScanInterface::error( 'Invalid API key format' );
3145
+ return FALSE;
3146
+ }
3147
+ }
3148
 
3149
+ if( !empty($api_key) ){
3150
+ SucuriScanEvent::notify_event( 'plugin_change', 'API key updated successfully: ' . $api_key );
3151
  }
3152
+
3153
+ return self::update_option( ':api_key', $api_key );
3154
  }
3155
 
3156
+ /**
3157
+ * Retrieve the API key from the local storage.
3158
+ *
3159
+ * @return string|boolean The API key or FALSE if it does not exists.
3160
+ */
3161
+ public static function get_plugin_key(){
3162
+ $api_key = self::get_option(':api_key');
3163
+
3164
+ if( $api_key && strlen($api_key) > 10 ){
3165
+ return $api_key;
3166
+ }
3167
 
3168
+ return FALSE;
3169
+ }
3170
 
3171
+ /**
3172
+ * Check and return the API key for the plugin.
3173
+ *
3174
+ * In this plugin the key is a pair of two strings concatenated by a single
3175
+ * slash, the first part of it is in fact the key and the second part is the
3176
+ * unique identifier of the site in the remote server.
3177
+ *
3178
+ * @return array|boolean FALSE if the key is invalid or not present, an array otherwise.
3179
+ */
3180
+ public static function get_cloudproxy_key(){
3181
+ $option_name = ':cloudproxy_apikey';
3182
+ $api_key = self::get_option($option_name);
3183
+
3184
+ // Check if the cloudproxy-waf plugin was previously installed.
3185
+ if( !$api_key ){
3186
+ $api_key = self::get_option('sucuriwaf_apikey');
3187
+
3188
+ if( $api_key ){
3189
+ self::update_option( $option_name, $api_key );
3190
+ self::delete_option('sucuriwaf_apikey');
3191
+ }
3192
+ }
3193
 
3194
+ // Check the validity of the API key.
3195
+ $match = self::is_valid_cloudproxy_key( $api_key, TRUE );
3196
 
3197
+ if( $match ){
3198
+ return array(
3199
+ 'string' => $match[1].'/'.$match[2],
3200
+ 'k' => $match[1],
3201
+ 's' => $match[2]
3202
+ );
3203
  }
3204
+
3205
+ return FALSE;
3206
  }
3207
 
3208
+ /**
3209
+ * Check whether the CloudProxy API key is valid or not.
3210
+ *
3211
+ * @param string $api_key The CloudProxy API key.
3212
+ * @param boolean $return_match Whether the parts of the API key must be returned or not.
3213
+ * @return boolean TRUE if the API key specified is valid, FALSE otherwise.
3214
+ */
3215
+ public static function is_valid_cloudproxy_key( $api_key='', $return_match=FALSE ){
3216
+ $pattern = '/^([a-z0-9]{32})\/([a-z0-9]{32})$/';
3217
 
3218
+ if( $api_key && preg_match($pattern, $api_key, $match) ){
3219
+ if( $return_match ){ return $match; }
 
 
 
 
 
3220
 
3221
+ return TRUE;
3222
+ }
3223
+
3224
+ return FALSE;
 
 
 
3225
  }
3226
 
3227
+ /**
3228
+ * Call an action from the remote API interface of our WordPress service.
3229
+ *
3230
+ * @param string $method HTTP method that will be used to send the request.
3231
+ * @param array $params Parameters for the request defined in an associative array of key-value.
3232
+ * @param boolean $send_api_key Whether the API key should be added to the request parameters or not.
3233
+ * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
3234
+ * @return array Array of results including HTTP headers or WP_Error if the request failed.
3235
+ */
3236
+ public static function api_call_wordpress( $method='GET', $params=array(), $send_api_key=TRUE, $args=array() ){
3237
+ $url = SUCURISCAN_API;
3238
+ $params[SUCURISCAN_API_VERSION] = 1;
3239
+ $params['p'] = 'wordpress';
3240
 
3241
+ if( $send_api_key ){
3242
+ $api_key = self::get_plugin_key();
 
 
 
 
 
 
 
 
 
 
 
3243
 
3244
+ if( !$api_key ){ return FALSE; }
 
 
3245
 
3246
+ $params['k'] = $api_key;
 
3247
  }
3248
 
3249
+ $response = self::api_call( $url, $method, $params, $args );
3250
+
3251
+ return $response;
 
3252
  }
3253
 
3254
+ /**
3255
+ * Call an action from the remote API interface of our CloudProxy service.
3256
+ *
3257
+ * @param string $method HTTP method that will be used to send the request.
3258
+ * @param array $params Parameters for the request defined in an associative array of key-value.
3259
+ * @return array Array of results including HTTP headers or WP_Error if the request failed.
3260
+ */
3261
+ public static function api_call_cloudproxy( $method='GET', $params=array() ){
3262
+ $send_request = FALSE;
3263
 
3264
+ if( isset($params['k']) && isset($params['s']) ){
3265
+ $send_request = TRUE;
3266
+ } else {
3267
+ $api_key = self::get_cloudproxy_key();
 
 
 
 
 
 
 
 
 
 
 
 
 
3268
 
3269
+ if( $api_key ){
3270
+ $send_request = TRUE;
3271
+ $params['k'] = $api_key['k'];
3272
+ $params['s'] = $api_key['s'];
3273
+ }
3274
+ }
 
 
 
 
3275
 
3276
+ if( $send_request ){
3277
+ $url = SUCURISCAN_CLOUDPROXY_API;
3278
+ $params[SUCURISCAN_CLOUDPROXY_API_VERSION] = 1;
3279
 
3280
+ $response = self::api_call( $url, $method, $params );
 
 
3281
 
3282
+ return $response;
 
3283
  }
3284
 
3285
+ return FALSE;
3286
+ }
 
 
 
 
 
 
3287
 
3288
+ /**
3289
+ * Determine whether an API response was successful or not checking the expected
3290
+ * generic variables and types, in case of an error a notification will appears
3291
+ * in the administrator panel explaining the result of the operation.
3292
+ *
3293
+ * @param array $response Array of results including HTTP headers or WP_Error if the request failed.
3294
+ * @return boolean Either TRUE or FALSE in case of success or failure of the API response (respectively).
3295
+ */
3296
+ private static function handle_response( $response=array() ){
3297
+ if( $response ){
3298
+ if( $response['body'] instanceof stdClass ){
3299
+ if( isset($response['body']->status) ){
3300
+ if( $response['body']->status == 1 ){
3301
+ return TRUE;
3302
+ } else {
3303
+ SucuriScanInterface::error( ucwords($response['body']->action) . ': ' . $response['body']->messages[0] );
3304
+ }
3305
+ } else {
3306
+ SucuriScanInterface::error( 'Could not determine the status of an API call.' );
3307
  }
3308
+ } else {
3309
+ SucuriScanInterface::error( 'Unknown API content-type, it was not a JSON-encoded response.' );
3310
  }
3311
  }
3312
+
3313
+ return FALSE;
 
3314
  }
 
3315
 
3316
+ /**
3317
+ * Send a request to the API to register this site.
3318
+ *
3319
+ * @return boolean TRUE if the API key was generated, FALSE otherwise.
3320
+ */
3321
+ public static function register_site(){
3322
+ $response = self::api_call_wordpress( 'POST', array(
3323
+ 'e' => self::get_site_email(),
3324
+ 's' => self::get_domain(),
3325
+ 'a' => 'register_site',
3326
+ ), FALSE );
3327
+
3328
+ if( self::handle_response($response) ){
3329
+ self::set_plugin_key( $response['body']->output->api_key );
3330
+ SucuriScanEvent::schedule_task();
3331
+ SucuriScanEvent::notify_event( 'plugin_change', 'Site registered and API key generated' );
3332
+ SucuriScanInterface::info( 'The API key for your site was successfully generated and saved.');
3333
 
3334
+ return TRUE;
3335
+ }
3336
 
3337
+ return FALSE;
3338
+ }
3339
 
3340
+ /**
3341
+ * Send a request to recover a previously registered API key.
3342
+ *
3343
+ * @return boolean TRUE if the API key was sent to the administrator email, FALSE otherwise.
3344
+ */
3345
+ public static function recover_key(){
3346
+ $clean_domain = self::get_domain();
3347
 
3348
+ $response = self::api_call_wordpress( 'GET', array(
3349
+ 'e' => self::get_site_email(),
3350
+ 's' => $clean_domain,
3351
+ 'a' => 'recover_key',
3352
+ ), FALSE );
3353
 
3354
+ if( self::handle_response($response) ){
3355
+ SucuriScanEvent::notify_event( 'plugin_change', 'API key recovered for domain: ' . $clean_domain );
3356
+ SucuriScanInterface::info( $response['body']->output->message );
3357
+
3358
+ return TRUE;
3359
  }
3360
 
3361
+ return FALSE;
3362
+ }
 
 
 
 
 
3363
 
3364
+ /**
3365
+ * Send a request to the API to store and analyze the events of the site. An
3366
+ * event can be anything from a simple request, an internal modification of the
3367
+ * settings or files in the administrator panel, or a notification generated by
3368
+ * this plugin.
3369
+ *
3370
+ * @param string $event The information gathered through out the normal functioning of the site.
3371
+ * @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
3372
+ */
3373
+ public static function send_log( $event='' ){
3374
+ if( !empty($event) ){
3375
+ $response = self::api_call_wordpress( 'POST', array(
3376
+ 'a' => 'send_log',
3377
+ 'm' => $event,
3378
+ ), TRUE, array( 'timeout' => 20 ) );
3379
+
3380
+ if( self::handle_response($response) ){
3381
+ return TRUE;
3382
  }
3383
+ }
3384
 
3385
+ return FALSE;
3386
+ }
 
 
 
3387
 
3388
+ /**
3389
+ * Retrieve the event logs registered by the API service.
3390
+ *
3391
+ * @param integer $lines How many lines from the log file will be retrieved.
3392
+ * @return string The response of the API service.
3393
+ */
3394
+ public static function get_logs( $lines=50 ){
3395
+ $response = self::api_call_wordpress( 'GET', array(
3396
+ 'a' => 'get_logs',
3397
+ 'l' => $lines,
3398
+ ) );
3399
+
3400
+ if( self::handle_response($response) ){
3401
+ $response['body']->output_data = array();
3402
+ $log_pattern = '/^([0-9-: ]+) (.*) : (.*)/';
3403
+ $extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
3404
+
3405
+ foreach( $response['body']->output as $log ){
3406
+ if( preg_match($log_pattern, $log, $log_match) ){
3407
+ $log_data = array(
3408
+ 'datetime' => $log_match[1],
3409
+ 'timestamp' => strtotime($log_match[1]),
3410
+ 'account' => $log_match[2],
3411
+ 'message' => $log_match[3],
3412
+ 'extra' => FALSE,
3413
+ 'extra_total' => 0,
3414
+ );
3415
+
3416
+ $log_data['message'] = str_replace( ', new size', '; new size', $log_data['message'] );
3417
+
3418
+ if( preg_match($extra_pattern, $log_data['message'], $log_extra) ){
3419
+ $log_data['message'] = $log_extra[1];
3420
+ $log_data['extra'] = explode(',', $log_extra[2]);
3421
+ $log_data['extra_total'] = count($log_data['extra']);
3422
+ }
3423
+
3424
+ $response['body']->output_data[] = $log_data;
3425
+ }
3426
  }
3427
 
3428
+ return $response['body'];
3429
  }
 
3430
 
3431
+ return FALSE;
3432
+ }
 
3433
 
3434
+ /**
3435
+ * Send a request to the API to store and analyze the file's hashes of the site.
3436
+ * This will be the core of the monitoring tools and will enhance the
3437
+ * information of the audit logs alerting the administrator of suspicious
3438
+ * changes in the system.
3439
+ *
3440
+ * @param string $hashes The information gathered after the scanning of the site's files.
3441
+ * @return boolean TRUE if the hashes were stored, FALSE otherwise.
3442
+ */
3443
+ public static function send_hashes( $hashes='' ){
3444
+ if( !empty($hashes) ){
3445
+ $response = self::api_call_wordpress( 'POST', array(
3446
+ 'a' => 'send_hashes',
3447
+ 'h' => $hashes,
3448
+ ) );
3449
+
3450
+ if( self::handle_response($response) ){
3451
+ return TRUE;
3452
+ }
3453
+ }
3454
 
3455
+ return FALSE;
3456
+ }
3457
 
3458
+ /**
3459
+ * Retrieve the public settings of the account associated with the API keys
3460
+ * registered by the administrator of the site. This function will send a HTTP
3461
+ * request to the remote API service and process its response, when successful
3462
+ * it will return an array/object containing the public attributes of the site.
3463
+ *
3464
+ * @param boolean $api_key The CloudProxy API key.
3465
+ * @return array A hash with the settings of a CloudProxy account.
3466
+ */
3467
+ public static function get_cloudproxy_settings( $api_key=FALSE ){
3468
+ $params = array( 'a' => 'show_settings' );
3469
 
3470
+ if( $api_key ){
3471
+ $params = array_merge( $params, $api_key );
3472
+ }
3473
 
3474
+ $response = self::api_call_cloudproxy( 'GET', $params );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3475
 
3476
+ if( self::handle_response($response) ){
3477
+ return $response['body']->output;
3478
+ }
3479
 
3480
+ return FALSE;
3481
+ }
3482
 
3483
+ /**
3484
+ * Flush the cache of the site(s) associated with the API key.
3485
+ *
3486
+ * @param boolean $api_key The CloudProxy API key.
3487
+ * @return string Message explaining the result of the operation.
3488
+ */
3489
+ public static function clear_cloudproxy_cache( $api_key=FALSE ){
3490
+ $params = array( 'a' => 'clear_cache' );
3491
 
3492
+ if( $api_key ){
3493
+ $params = array_merge( $params, $api_key );
3494
+ }
 
 
 
 
 
 
 
3495
 
3496
+ $response = self::api_call_cloudproxy( 'GET', $params );
3497
 
3498
+ if( self::handle_response($response) ){
3499
+ return $response['body'];
3500
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3501
 
3502
+ return FALSE;
3503
+ }
 
 
 
 
3504
 
3505
+ /**
3506
+ * Retrieve the audit logs of the account associated with the API keys
3507
+ * registered b the administrator of the site. This function will send a HTTP
3508
+ * request to the remote API service and process its response, when successful
3509
+ * it will return an array/object containing a list of requests blocked by our
3510
+ * CloudProxy.
3511
+ *
3512
+ * By default the logs that will be retrieved are from today, if you need to see
3513
+ * the logs of previous days you will need to add a new parameter to the request
3514
+ * URL named "date" with format yyyy-mm-dd.
3515
+ *
3516
+ * @param boolean $api_key The CloudProxy API key.
3517
+ * @param string $date An optional date to filter the result to a specific timespan: yyyy-mm-dd.
3518
+ * @return array A list of objects with the detailed version of each request blocked by our service.
3519
+ */
3520
+ public static function get_cloudproxy_logs( $api_key=FALSE, $date='' ){
3521
+ $params = array(
3522
+ 'a' => 'audit_trails',
3523
+ 'date' => date('Y-m-d'),
3524
+ );
3525
 
3526
+ if( preg_match('/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/', $date) ){
3527
+ $params['date'] = $date;
3528
+ }
 
 
 
3529
 
3530
+ if( $api_key ){
3531
+ $params = array_merge( $params, $api_key );
3532
+ }
3533
 
3534
+ $response = self::api_call_cloudproxy( 'GET', $params );
 
 
 
 
 
 
 
3535
 
3536
+ if( self::handle_response($response) ){
3537
+ return $response['body']->output;
3538
+ }
 
3539
 
3540
+ return FALSE;
3541
+ }
3542
 
3543
+ /**
3544
+ * Scan a website through the public SiteCheck API [1] for known malware,
3545
+ * blacklisting status, website errors, and out-of-date software.
3546
+ *
3547
+ * [1] http://sitecheck.sucuri.net/
3548
+ *
3549
+ * @param string $domain The clean version of the website's domain.
3550
+ * @return object Serialized data of the scanning results for the site specified.
3551
+ */
3552
+ public static function get_sitecheck_results( $domain='' ){
3553
+ if( !empty($domain) ){
3554
+ $url = 'http://sitecheck.sucuri.net/scanner/';
3555
+ $response = self::api_call( $url, 'GET', array(
3556
+ 'serialized' => 1,
3557
+ 'clear' => 1,
3558
+ 'fromwp' => 2,
3559
+ 'scan' => $domain,
3560
+ ));
3561
 
3562
+ if( $response ){
3563
+ return $response['body'];
3564
+ }
3565
+ }
 
 
 
 
 
 
 
 
 
 
 
 
3566
 
3567
+ return FALSE;
3568
+ }
 
 
 
 
 
 
 
3569
 
3570
+ /**
3571
+ * Retrieve a new set of keys for the WordPress configuration file using the
3572
+ * official API provided by WordPress itself.
3573
+ *
3574
+ * @return array A list of the new set of keys generated by WordPress API.
3575
+ */
3576
+ public static function get_new_secret_keys(){
3577
+ $pattern = self::secret_key_pattern();
3578
+ $response = self::api_call( 'https://api.wordpress.org/secret-key/1.1/salt/', 'GET' );
3579
 
3580
+ if( $response && preg_match_all($pattern, $response['body'], $match) ){
3581
+ $new_keys = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3582
 
3583
+ foreach( $match[1] as $i => $value ){
3584
+ $new_keys[$value] = $match[3][$i];
3585
+ }
 
 
 
 
 
3586
 
3587
+ return $new_keys;
3588
+ }
 
 
 
 
3589
 
3590
+ return FALSE;
3591
+ }
 
 
 
 
 
 
 
 
 
 
 
 
3592
 
3593
+ /**
3594
+ * Retrieve a list with the checksums of the files in a specific version of WordPress.
3595
+ *
3596
+ * @see Release Archive http://wordpress.org/download/release-archive/
3597
+ *
3598
+ * @param integer $version Valid version number of the WordPress project.
3599
+ * @return object Associative object with the relative filepath and the checksums of the project files.
3600
+ */
3601
+ public static function get_official_checksums( $version=0 ){
3602
+ $url = 'http://api.wordpress.org/core/checksums/1.0/';
3603
+ $response = self::api_call( $url, 'GET', array(
3604
+ 'version' => $version,
3605
+ 'locale' => 'en_US',
3606
+ ));
 
 
 
 
 
 
 
3607
 
3608
+ if( $response ){
3609
+ if( $response['body'] instanceof stdClass ){
3610
+ $json_data = $response['body'];
3611
+ } else {
3612
+ $json_data = @json_decode($response['body']);
3613
+ }
3614
 
3615
+ if(
3616
+ isset($json_data->checksums)
3617
+ && !empty($json_data->checksums)
3618
+ ){
3619
+ $checksums = $json_data->checksums;
3620
 
3621
+ // Convert the object list to an array for better handle of the data.
3622
+ if( $checksums instanceof stdClass ){
3623
+ $checksums = (array) $checksums;
3624
+ }
 
 
 
 
 
 
3625
 
3626
+ return $checksums;
3627
+ }
3628
+ }
 
 
 
 
 
3629
 
3630
+ return FALSE;
3631
+ }
 
 
3632
 
3633
+ /**
3634
+ * Check the plugins directory and retrieve all plugin files with plugin data.
3635
+ * This function will also retrieve the URL and name of the repository/page
3636
+ * where it is being published at the WordPress plugins market.
3637
+ *
3638
+ * @return array Key is the plugin file path and the value is an array of the plugin data.
3639
+ */
3640
+ public static function get_plugins(){
3641
+ // Check if the cache library was loaded.
3642
+ $can_cache = class_exists('SucuriScanCache');
3643
 
3644
+ if( $can_cache ){
3645
+ $sucuri_cache = new SucuriScanCache('plugindata');
3646
+ $cached_data = $sucuri_cache->get( 'plugins', SUCURISCAN_GET_PLUGINS_LIFETIME, 'array' );
 
 
 
 
 
 
 
3647
 
3648
+ // Return the previously cached results of this function.
3649
+ if( $cached_data !== FALSE ){
3650
+ return $cached_data;
3651
+ }
3652
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3653
 
3654
+ // Get the plugin's basic information from WordPress transient data.
3655
+ $plugins = get_plugins();
3656
+ $pattern = '/^http:\/\/wordpress\.org\/plugins\/(.*)\/$/';
3657
+ $wp_market = 'http://wordpress.org/plugins/%s/';
3658
 
3659
+ // Loop through each plugin data and complement its information with more attributes.
3660
+ foreach( $plugins as $plugin_path => $plugin_data ){
3661
+ // Default values for the plugin extra attributes.
3662
+ $repository = '';
3663
+ $repository_name = '';
3664
+ $is_free_plugin = FALSE;
 
 
 
 
3665
 
3666
+ // If the plugin's info object has already a plugin_uri.
3667
+ if(
3668
+ isset($plugin_data['PluginURI'])
3669
+ && preg_match($pattern, $plugin_data['PluginURI'], $match)
3670
+ ){
3671
+ $repository = $match[0];
3672
+ $repository_name = $match[1];
3673
+ $is_free_plugin = TRUE;
3674
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3675
 
3676
+ // Retrieve the WordPress plugin page from the plugin's filename.
3677
+ else {
3678
+ if( strpos($plugin_path, '/') !== FALSE ){
3679
+ $plugin_path_parts = explode('/', $plugin_path, 2);
3680
+ } else {
3681
+ $plugin_path_parts = explode('.', $plugin_path, 2);
3682
+ }
3683
 
3684
+ if( isset($plugin_path_parts[0]) ){
3685
+ $possible_repository = sprintf($wp_market, $plugin_path_parts[0]);
3686
+ $resp = wp_remote_head($possible_repository);
 
3687
 
3688
+ if(
3689
+ !is_wp_error($resp)
3690
+ && $resp['response']['code'] == 200
3691
+ ){
3692
+ $repository = $possible_repository;
3693
+ $repository_name = $plugin_path_parts[0];
3694
+ $is_free_plugin = TRUE;
3695
+ }
3696
+ }
3697
+ }
3698
 
3699
+ // Complement the plugin's information with these attributes.
3700
+ $plugins[$plugin_path]['Repository'] = $repository;
3701
+ $plugins[$plugin_path]['RepositoryName'] = $repository_name;
3702
+ $plugins[$plugin_path]['IsFreePlugin'] = $is_free_plugin;
3703
+ $plugins[$plugin_path]['PluginType'] = ( $is_free_plugin ? 'free' : 'premium' );
3704
+ $plugins[$plugin_path]['IsPluginActive'] = FALSE;
 
 
 
3705
 
3706
+ if( is_plugin_active($plugin_path) ){
3707
+ $plugins[$plugin_path]['IsPluginActive'] = TRUE;
 
 
 
3708
  }
 
 
3709
  }
 
 
 
 
 
 
3710
 
3711
+ if( $can_cache ){
3712
+ // Add the information of the plugins to the file-based cache.
3713
+ $sucuri_cache->add( 'plugins', $plugins );
 
 
 
 
 
 
 
 
 
3714
  }
 
3715
 
3716
+ return $plugins;
 
3717
  }
3718
 
3719
+ /**
3720
+ * Retrieve plugin installer pages from WordPress Plugins API.
3721
+ *
3722
+ * It is possible for a plugin to override the Plugin API result with three
3723
+ * filters. Assume this is for plugins, which can extend on the Plugin Info to
3724
+ * offer more choices. This is very powerful and must be used with care, when
3725
+ * overriding the filters.
3726
+ *
3727
+ * The first filter, 'plugins_api_args', is for the args and gives the action as
3728
+ * the second parameter. The hook for 'plugins_api_args' must ensure that an
3729
+ * object is returned.
3730
+ *
3731
+ * The second filter, 'plugins_api', is the result that would be returned.
3732
+ *
3733
+ * @param string $plugin Frienly name of the plugin.
3734
+ * @return object Object on success, WP_Error on failure.
3735
+ */
3736
+ public static function get_remote_plugin_data( $plugin='' ){
3737
+ if( !empty($plugin) ){
3738
+ $url = sprintf( 'http://api.wordpress.org/plugins/info/1.0/%s/', $plugin );
3739
+ $response = self::api_call( $url, 'GET' );
3740
+
3741
+ if( $response ){
3742
+ if( $response['body'] instanceof stdClass ){
3743
+ return $response['body'];
3744
+ }
3745
+ }
3746
+ }
3747
 
3748
+ return FALSE;
 
3749
  }
3750
 
 
3751
  }
3752
 
3753
  /**
3754
+ * Process and send emails.
3755
  *
3756
+ * One of the core features of the plugin is the event alerts, a list of rules
3757
+ * will check if the site is being compromised, in which case a notification
3758
+ * will be sent to the site email address (an address that can be configured in
3759
+ * the settings page).
3760
  */
3761
+ class SucuriScanMail extends SucuriScanOption {
 
 
 
 
3762
 
3763
+ /**
3764
+ * Check whether the email notifications will be sent in HTML or Plain/Text.
3765
+ *
3766
+ * @return boolean Whether the emails will be in HTML or Plain/Text.
3767
+ */
3768
+ public static function prettify_mails(){
3769
+ return ( self::get_option(':prettify_mails') === 'enabled' );
3770
  }
3771
 
3772
+ /**
3773
+ * Send a message to a specific email address.
3774
+ *
3775
+ * @param string $email The email address of the recipient that will receive the message.
3776
+ * @param string $subject The reason of the message that will be sent.
3777
+ * @param string $message Body of the message that will be sent.
3778
+ * @param array $data_set Optional parameter to add more information to the notification.
3779
+ * @return boolean Whether the email contents were sent successfully.
3780
+ */
3781
+ public static function send_mail( $email='', $subject='', $message='', $data_set=array() ){
3782
+ $headers = array();
3783
+ $subject = ucwords(strtolower($subject));
3784
+ $wp_domain = SucuriScanOption::get_domain();
3785
+ $force = FALSE;
3786
+ $debug = FALSE;
3787
+
3788
+ // Check whether the mail will be printed in the site instead of sent.
3789
+ if(
3790
+ isset($data_set['Debug'])
3791
+ && $data_set['Debug'] == TRUE
3792
+ ){
3793
+ $debug = TRUE;
3794
+ unset($data_set['Debug']);
3795
+ }
3796
 
3797
+ // Check whether the mail will be even if the limit per hour was reached or not.
3798
+ if(
3799
+ isset($data_set['Force'])
3800
+ && $data_set['Force'] == TRUE
3801
+ ){
3802
+ $force = TRUE;
3803
+ unset($data_set['Force']);
3804
  }
 
3805
 
3806
+ // Check whether the email notifications will be sent in HTML or Plain/Text.
3807
+ if( self::prettify_mails() ){
3808
+ $headers = array( 'Content-type: text/html' );
3809
+ $data_set['PrettifyType'] = 'pretty';
3810
+ } else {
3811
+ $message = strip_tags($message);
3812
+ }
3813
 
3814
+ if( !self::emails_per_hour_reached() || $force || $debug ){
3815
+ $message = self::prettify_mail($subject, $message, $data_set);
 
 
 
 
 
3816
 
3817
+ if( $debug ){ die($message); }
 
3818
 
3819
+ $email_sent = wp_mail(
3820
+ $email,
3821
+ "Sucuri WP Notification: {$wp_domain} - {$subject}",
3822
+ $message,
3823
+ $headers
3824
+ );
 
 
 
 
 
 
 
3825
 
3826
+ if( $email_sent ){
3827
+ $emails_sent_num = (int) self::get_option(':emails_sent');
3828
+ self::update_option( ':emails_sent', $emails_sent_num + 1 );
3829
+ self::update_option( ':last_email_at', time() );
3830
 
3831
+ return TRUE;
3832
+ }
3833
+ }
3834
 
3835
+ return FALSE;
3836
  }
3837
 
3838
+ /**
3839
+ * Generate a HTML version of the message that will be sent through an email.
3840
+ *
3841
+ * @param string $subject The reason of the message that will be sent.
3842
+ * @param string $message Body of the message that will be sent.
3843
+ * @param array $data_set Optional parameter to add more information to the notification.
3844
+ * @return string The message formatted in a HTML template.
3845
+ */
3846
+ private static function prettify_mail( $subject='', $message='', $data_set=array() ){
3847
+ $prettify_type = isset($data_set['PrettifyType']) ? $data_set['PrettifyType'] : 'simple';
3848
+ $template_name = 'notification-' . $prettify_type;
3849
+ $remote_addr = self::get_remote_addr();
3850
+ $user = wp_get_current_user();
3851
+ $display_name = '';
3852
 
3853
+ if(
3854
+ $user instanceof WP_User
3855
+ && isset($user->user_login)
3856
+ && !empty($user->user_login)
3857
+ ){
3858
+ $display_name = sprintf( 'User: %s (%s)', $user->display_name, $user->user_login );
3859
+ }
 
 
3860
 
3861
+ $mail_variables = array(
3862
+ 'TemplateTitle' => 'Sucuri Alert',
3863
+ 'Subject' => $subject,
3864
+ 'Website' => self::get_option('siteurl'),
3865
+ 'RemoteAddress' => $remote_addr,
3866
+ 'Message' => $message,
3867
+ 'User' => $display_name,
3868
+ 'Time' => date('d/M/Y H:i:s'),
3869
+ );
3870
 
3871
+ foreach( $data_set as $var_key => $var_value ){
3872
+ $mail_variables[$var_key] = $var_value;
 
 
3873
  }
 
3874
 
3875
+ return SucuriScanTemplate::get_section( $template_name, $mail_variables );
3876
+ }
 
3877
 
3878
+ /**
3879
+ * Check whether the maximum quantity of emails per hour was reached.
3880
+ *
3881
+ * @return boolean Whether the quota emails per hour was reached.
3882
+ */
3883
+ private static function emails_per_hour_reached(){
3884
+ $max_per_hour = self::get_option(':emails_per_hour');
3885
 
3886
+ if( $max_per_hour != 'unlimited' ){
3887
+ // Check if we are still in that sixty minutes.
3888
+ $current_time = time();
3889
+ $last_email_at = self::get_option(':last_email_at');
3890
+ $diff_time = abs( $current_time - $last_email_at );
3891
 
3892
+ if( $diff_time <= 3600 ){
3893
+ // Check if the quantity of emails sent is bigger than the configured.
3894
+ $emails_sent = (int) self::get_option(':emails_sent');
3895
+ $max_per_hour = intval($max_per_hour);
3896
 
3897
+ if( $emails_sent >= $max_per_hour ){
 
 
 
 
 
 
 
 
 
 
 
 
3898
  return TRUE;
 
 
3899
  }
3900
  } else {
3901
+ // Reset the counter of emails sent.
3902
+ self::update_option( ':emails_sent', 0 );
3903
  }
 
 
3904
  }
3905
+
3906
+ return FALSE;
3907
  }
3908
 
 
3909
  }
3910
 
3911
  /**
3912
+ * Read, parse and handle everything related with the templates.
3913
  *
3914
+ * A web template system uses a template processor to combine web templates to
3915
+ * form finished web pages, possibly using some data source to customize the
3916
+ * pages or present a large amount of content on similar-looking pages. It is a
3917
+ * web publishing tool present in content management systems, web application
3918
+ * frameworks, and HTML editors.
3919
+ *
3920
+ * Web templates can be used like the template of a form letter to either
3921
+ * generate a large number of "static" (unchanging) web pages in advance, or to
3922
+ * produce "dynamic" web pages on demand.
3923
  */
3924
+ class SucuriScanTemplate extends SucuriScanRequest {
 
 
 
 
 
 
 
 
 
 
 
3925
 
3926
+ /**
3927
+ * Replace all pseudo-variables from a string of characters.
3928
+ *
3929
+ * @param string $content The content of a template file which contains pseudo-variables.
3930
+ * @param array $params List of pseudo-variables that will be replaced in the template.
3931
+ * @return string The content of the template with the pseudo-variables replated.
3932
+ */
3933
+ private static function replace_pseudovars( $content='', $params=array() ){
3934
+ if( is_array($params) ){
3935
+ foreach( $params as $tpl_key => $tpl_value ){
3936
+ $tpl_key = '%%SUCURI.' . $tpl_key . '%%';
3937
+ $content = str_replace( $tpl_key, $tpl_value, $content );
3938
+ }
3939
+
3940
+ return $content;
3941
+ }
3942
+
3943
+ return FALSE;
3944
  }
3945
 
3946
+ /**
3947
+ * Gather and generate the information required globally by all the template files.
3948
+ *
3949
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
3950
+ * @return array A complementary list of pseudo-variables for the template files.
3951
+ */
3952
+ private static function shared_params( $params=array() ){
3953
+ $params = is_array($params) ? $params : array();
3954
 
3955
+ // Base parameters, required to render all the pages.
3956
+ $params = self::links_and_navbar($params);
 
 
 
 
 
3957
 
3958
+ // Global parameters, used through out all the pages.
3959
+ $params['PageTitle'] = isset($params['PageTitle']) ? '('.$params['PageTitle'].')' : '';
3960
+ $params['PageNonce'] = wp_create_nonce('sucuriscan_page_nonce');
3961
+ $params['PageStyleClass'] = isset($params['PageStyleClass']) ? $params['PageStyleClass'] : 'base';
3962
+ $params['CleanDomain'] = SucuriScanOption::get_domain();
3963
+ $params['AdminEmail'] = SucuriScanOption::get_site_email();
3964
 
3965
+ return $params;
3966
+ }
 
3967
 
3968
+ /**
3969
+ * Return a string indicating the visibility of a HTML component.
3970
+ *
3971
+ * @param boolean $visible Whether the condition executed returned a positive value or not.
3972
+ * @return string A string indicating the visibility of a HTML component.
3973
+ */
3974
+ public static function visibility( $visible=FALSE ){
3975
+ return ( $visible === TRUE ? 'visible' : 'hidden' );
3976
  }
3977
 
3978
+ /**
3979
+ * Generate an URL pointing to the page indicated in the function and that must
3980
+ * be loaded through the administrator panel.
3981
+ *
3982
+ * @param string $page Short name of the page that will be generated.
3983
+ * @return string Full string containing the link of the page.
3984
+ */
3985
+ public static function get_url( $page='' ){
3986
+ $url_path = admin_url('admin.php?page=sucuriscan');
3987
 
3988
+ if( !empty($page) ){
3989
+ $url_path .= '_' . strtolower($page);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3990
  }
3991
+
3992
+ return $url_path;
3993
  }
3994
 
3995
+ /**
3996
+ * Complement the list of pseudo-variables that will be used in the base
3997
+ * template files, this will also generate the navigation bar and detect which
3998
+ * items in it are selected by the current page.
3999
+ *
4000
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
4001
+ * @return array A complementary list of pseudo-variables for the template files.
4002
+ */
4003
+ private static function links_and_navbar( $params=array() ){
4004
+ global $sucuriscan_pages;
4005
 
4006
+ $params = is_array($params) ? $params : array();
4007
+ $sub_pages = is_array($sucuriscan_pages) ? $sucuriscan_pages : array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4008
 
4009
+ $params['Navbar'] = '';
4010
+ $params['CurrentPageFunc'] = '';
4011
 
4012
+ if( $_page = self::get('page', '_page') ){
4013
+ $params['CurrentPageFunc'] = $_page;
4014
+ }
 
 
4015
 
4016
+ foreach( $sub_pages as $sub_page_func => $sub_page_title ){
4017
+ $func_parts = explode( '_', $sub_page_func, 2 );
4018
+
4019
+ if( isset($func_parts[1]) ){
4020
+ $unique_name = $func_parts[1];
4021
+ $pseudo_var = 'URL.' . ucwords($unique_name);
4022
+ } else {
4023
+ $unique_name = '';
4024
+ $pseudo_var = 'URL.Home';
4025
  }
 
4026
 
4027
+ $params[$pseudo_var] = self::get_url($unique_name);
 
4028
 
4029
+ $navbar_item_css_class = 'nav-tab';
 
4030
 
4031
+ if( $params['CurrentPageFunc'] == $sub_page_func ){
4032
+ $navbar_item_css_class .= chr(32) . 'nav-tab-active';
4033
+ }
 
 
 
 
 
 
 
 
 
 
 
 
4034
 
4035
+ $params['Navbar'] .= sprintf(
4036
+ '<a class="%s" href="%s">%s</a>' . "\n",
4037
+ $navbar_item_css_class,
4038
+ $params[$pseudo_var],
4039
+ $sub_page_title
4040
+ );
4041
  }
4042
+
4043
+ return $params;
4044
  }
4045
 
4046
+ /**
4047
+ * Generate a HTML code using a template and replacing all the pseudo-variables
4048
+ * by the dynamic variables provided by the developer through one of the parameters
4049
+ * of the function.
4050
+ *
4051
+ * @param string $html The HTML content of a template file with its pseudo-variables parsed.
4052
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
4053
+ * @return string The formatted HTML content of the base template.
4054
+ */
4055
+ public static function get_base_template( $html='', $params=array() ){
4056
+ $params = is_array($params) ? $params : array();
4057
 
4058
+ $params = self::shared_params($params);
4059
+ $params['PageContent'] = $html;
 
 
 
 
 
 
 
 
 
4060
 
4061
+ return self::get_template( 'base', $params );
4062
+ }
4063
 
4064
+ /**
4065
+ * Generate a HTML code using a template and replacing all the pseudo-variables
4066
+ * by the dynamic variables provided by the developer through one of the parameters
4067
+ * of the function.
4068
+ *
4069
+ * @param string $template Filename of the template that will be used to generate the page.
4070
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
4071
+ * @param boolean $type Either page, section or snippet indicating the type of template that will be retrieved.
4072
+ * @return string The formatted HTML page after replace all the pseudo-variables.
4073
+ */
4074
+ public static function get_template( $template='', $params=array(), $type='page' ){
4075
+ switch( $type ){
4076
+ case 'page': /* no_break */
4077
+ case 'section':
4078
+ $template_path_pattern = '%s/%s/inc/tpl/%s.html.tpl';
4079
+ break;
4080
+ case 'snippet':
4081
+ $template_path_pattern = '%s/%s/inc/tpl/%s.snippet.tpl';
4082
+ break;
4083
  }
 
4084
 
4085
+ $template_content = '';
4086
+ $template_path = sprintf( $template_path_pattern, WP_PLUGIN_DIR, SUCURISCAN_PLUGIN_FOLDER, $template );
4087
+ $params = is_array($params) ? $params : array();
4088
 
4089
+ if( file_exists($template_path) && is_readable($template_path) ){
4090
+ $template_content = @file_get_contents($template_path);
 
 
 
 
 
 
 
 
4091
 
4092
+ $params['SucuriURL'] = SUCURISCAN_URL;
 
 
4093
 
4094
+ // Detect the current page URL.
4095
+ if( $_page = self::get('page', '_page') ){
4096
+ $params['CurrentURL'] = admin_url('admin.php?page=' . $_page);
4097
+ } else {
4098
+ $params['CurrentURL'] = admin_url();
4099
+ }
4100
 
4101
+ // Replace the global pseudo-variables in the section/snippets templates.
4102
+ if(
4103
+ $template == 'base'
4104
+ && isset($params['PageContent'])
4105
+ && preg_match('/%%SUCURI\.(.+)%%/', $params['PageContent'])
4106
+ ){
4107
+ $params['PageContent'] = self::replace_pseudovars( $params['PageContent'], $params );
4108
+ }
4109
 
4110
+ $template_content = self::replace_pseudovars( $template_content, $params );
4111
+ }
 
 
 
 
 
4112
 
4113
+ if( $template == 'base' || $type != 'page' ){
4114
+ return $template_content;
4115
+ }
4116
 
4117
+ return self::get_base_template( $template_content, $params );
4118
+ }
 
4119
 
4120
+ /**
4121
+ * Generate a HTML code using a template and replacing all the pseudo-variables
4122
+ * by the dynamic variables provided by the developer through one of the parameters
4123
+ * of the function.
4124
+ *
4125
+ * @param string $template Filename of the template that will be used to generate the page.
4126
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
4127
+ * @return string The formatted HTML page after replace all the pseudo-variables.
4128
+ */
4129
+ public static function get_section($template='', $params=array()){
4130
+ $params = self::shared_params($params);
4131
 
4132
+ return self::get_template( $template, $params, 'section' );
4133
+ }
 
 
 
 
4134
 
4135
+ /**
4136
+ * Generate a HTML code using a template and replacing all the pseudo-variables
4137
+ * by the dynamic variables provided by the developer through one of the parameters
4138
+ * of the function.
4139
+ *
4140
+ * @param string $template Filename of the template that will be used to generate the page.
4141
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
4142
+ * @return string The formatted HTML page after replace all the pseudo-variables.
4143
+ */
4144
+ public static function get_modal($template='', $params=array()){
4145
+ $required = array(
4146
+ 'Title' => 'Lorem ipsum dolor sit amet',
4147
+ 'CssClass' => '',
4148
+ 'Content' => '<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
4149
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
4150
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
4151
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
4152
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
4153
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>',
4154
+ );
4155
 
4156
+ if( !empty($template) && $template != 'none' ){
4157
+ $params['Content'] = self::get_section($template);
4158
+ }
4159
 
4160
+ foreach( $required as $param_name => $param_value ){
4161
+ if( !isset($params[$param_name]) ){
4162
+ $params[$param_name] = $param_value;
 
 
4163
  }
 
 
4164
  }
 
4165
 
4166
+ $params = self::shared_params($params);
 
4167
 
4168
+ return self::get_template( 'modalwindow', $params, 'section' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4169
  }
4170
 
4171
+ /**
4172
+ * Generate a HTML code using a template and replacing all the pseudo-variables
4173
+ * by the dynamic variables provided by the developer through one of the parameters
4174
+ * of the function.
4175
+ *
4176
+ * @param string $template Filename of the template that will be used to generate the page.
4177
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
4178
+ * @return string The formatted HTML page after replace all the pseudo-variables.
4179
+ */
4180
+ public static function get_snippet($template='', $params=array()){
4181
+ return self::get_template( $template, $params, 'snippet' );
4182
  }
4183
 
4184
+ /**
4185
+ * Generate the HTML code necessary to render a list of options in a form.
4186
+ *
4187
+ * @param array $allowed_values List with keys and values allowed for the options.
4188
+ * @param string $selected_val Value of the option that will be selected by default.
4189
+ * @return string Option list for a select form field.
4190
+ */
4191
+ public static function get_select_options( $allowed_values=array(), $selected_val='' ){
4192
+ $options = '';
4193
 
4194
+ foreach( $allowed_values as $option_name => $option_label ){
4195
+ $selected_str = '';
4196
 
4197
+ if( $option_name == $selected_val ){
4198
+ $selected_str = 'selected="selected"';
4199
+ }
 
 
 
 
 
 
 
 
 
 
4200
 
4201
+ $options .= sprintf(
4202
+ '<option value="%s" %s>%s</option>',
4203
+ $option_name, $selected_str, $option_label
4204
+ );
4205
  }
4206
 
4207
+ return $options;
4208
+ }
 
 
 
 
 
 
4209
 
4210
+ /**
4211
+ * Detect which number in a pagination was clicked.
4212
+ *
4213
+ * @return integer Page number of the link clicked in a pagination.
4214
+ */
4215
+ public static function get_page_number(){
4216
+ $num = self::get( 'num', '[0-9]{1,2}' );
4217
+
4218
+ return ( $num ? intval($num) : 1 );
4219
+ }
4220
+
4221
+ /**
4222
+ * Generate the HTML code to display a pagination.
4223
+ *
4224
+ * @param string $base_url Base URL for the links before the page number.
4225
+ * @param integer $total_items Total quantity of items retrieved from a query.
4226
+ * @param integer $max_per_page Maximum number of items that will be shown per page.
4227
+ * @return string HTML code for a pagination generated using the provided data.
4228
+ */
4229
+ public static function get_pagination( $base_url='', $total_items=0, $max_per_page=1 ){
4230
+ // Calculate the number of links for the pagination.
4231
+ $html_links = '';
4232
+ $page_number = self::get_page_number();
4233
+ $max_pages = ceil($total_items / $max_per_page);
4234
+
4235
+ // Generate the HTML links for the pagination.
4236
+ for( $j=1; $j<=$max_pages; $j++ ){
4237
+ $link_class = 'sucuriscan-pagination-link';
4238
+
4239
+ if( $page_number == $j ){
4240
+ $link_class .= chr(32) . 'sucuriscan-pagination-active';
4241
+ }
4242
 
4243
+ $html_links .= sprintf(
4244
+ '<li><a href="%s&num=%d" class="%s">%s</a></li>',
4245
+ $base_url, $j, $link_class, $j
4246
+ );
4247
+ }
4248
 
4249
+ return $html_links;
4250
  }
4251
 
 
4252
  }
4253
 
4254
  /**
4255
+ * Plugin initializer.
 
 
 
4256
  *
4257
+ * Define all the required variables, script, styles, and basic functions needed
4258
+ * when the site is loaded, not even the administrator panel but also the front
4259
+ * page, some bug-fixes will/are applied here for sites behind a proxy, and
4260
+ * sites with old versions of the premium plugin (that was deprecated at
4261
+ * July/2014).
4262
  */
4263
+ class SucuriScanInterface {
 
4264
 
4265
+ /**
4266
+ * Initialization code for the plugin.
4267
+ *
4268
+ * The initial variables and information needed by the plugin during the
4269
+ * execution of other functions will be generated. Things like the real IP
4270
+ * address of the client when it has been forwarded or it's behind an external
4271
+ * service like a Proxy.
4272
+ *
4273
+ * @return void
4274
+ */
4275
+ public static function initialize(){
4276
+ if(
4277
+ isset($_SERVER['HTTP_X_FORWARDED_FOR'])
4278
+ && SucuriScan::is_valid_ip($_SERVER['HTTP_X_FORWARDED_FOR'])
4279
+ && SucuriScan::is_behind_cloudproxy()
4280
+ ){
4281
+ $_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
4282
+ $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
4283
+ }
4284
  }
4285
 
4286
+ /**
4287
+ * Define which javascript and css files will be loaded in the header of the
4288
+ * plugin pages, only when the administrator panel is accessed.
4289
+ *
4290
+ * @return void
4291
+ */
4292
+ public static function enqueue_scripts(){
4293
+ $asset_version = '';
4294
+
4295
+ if( strlen(SUCURISCAN_PLUGIN_CHECKSUM) >= 7 ){
4296
+ $asset_version = substr(SUCURISCAN_PLUGIN_CHECKSUM, 0, 7);
4297
+ }
4298
+
4299
+ wp_register_style( 'sucuriscan', SUCURISCAN_URL . '/inc/css/sucuriscan-default-css.css', array(), $asset_version );
4300
+ wp_register_script( 'sucuriscan', SUCURISCAN_URL . '/inc/js/sucuriscan-scripts.js', array(), $asset_version );
4301
 
4302
+ wp_enqueue_style( 'sucuriscan' );
4303
+ wp_enqueue_script( 'sucuriscan' );
4304
  }
4305
 
4306
+ /**
4307
+ * Generate the menu and submenus for the plugin in the admin interface.
4308
+ *
4309
+ * @return void
4310
+ */
4311
+ public static function add_interface_menu(){
4312
+ global $sucuriscan_pages;
4313
 
4314
+ if(
4315
+ function_exists('add_menu_page')
4316
+ && $sucuriscan_pages
4317
+ ){
4318
+ // Add main menu link.
4319
+ add_menu_page(
4320
+ 'Sucuri Security',
4321
+ 'Sucuri Security',
4322
+ 'manage_options',
4323
+ 'sucuriscan',
4324
+ 'sucuriscan_page',
4325
+ SUCURISCAN_URL . '/inc/images/menu-icon.png'
4326
+ );
4327
 
4328
+ $sub_pages = is_array($sucuriscan_pages) ? $sucuriscan_pages : array();
 
 
4329
 
4330
+ foreach( $sub_pages as $sub_page_func => $sub_page_title ){
4331
+ $page_func = $sub_page_func . '_page';
4332
 
4333
+ add_submenu_page(
4334
+ 'sucuriscan',
4335
+ $sub_page_title,
4336
+ $sub_page_title,
4337
+ 'manage_options',
4338
+ $sub_page_func,
4339
+ $page_func
4340
+ );
4341
+ }
4342
+ }
4343
  }
4344
 
4345
+ /**
4346
+ * Remove the old Sucuri plugins considering that with the new version (after
4347
+ * 1.6.0) all the functionality of the others will be merged here, this will
4348
+ * remove duplicated functionality, duplicated bugs and/or duplicated
4349
+ * maintenance reports allowing us to focus in one unique project.
4350
+ *
4351
+ * @return void
4352
+ */
4353
+ public static function handle_old_plugins(){
4354
+ if( class_exists('SucuriScanFileInfo') ){
4355
+ $sucuri_fileinfo = new SucuriScanFileInfo();
4356
+ $sucuri_fileinfo->ignore_files = FALSE;
4357
+ $sucuri_fileinfo->ignore_directories = FALSE;
4358
 
4359
+ $plugins = array(
4360
+ 'sucuri-wp-plugin/sucuri.php',
4361
+ 'sucuri-cloudproxy-waf/cloudproxy.php',
4362
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4363
 
4364
+ foreach( $plugins as $plugin ){
4365
+ $plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin );
 
4366
 
4367
+ if( file_exists($plugin_directory) ){
4368
+ if( is_plugin_active($plugin) ){
4369
+ deactivate_plugins($plugin);
4370
+ }
4371
+
4372
+ $plugin_removed = $sucuri_fileinfo->remove_directory_tree($plugin_directory);
4373
+ }
4374
+ }
4375
+ }
4376
  }
4377
 
4378
+ /**
4379
+ * Create a folder in the WordPress upload directory where the plugin will
4380
+ * store all the temporal or dynamic information.
4381
+ *
4382
+ * @return void
4383
+ */
4384
+ public static function create_datastore_folder(){
4385
+ $plugin_upload_folder = SucuriScan::datastore_folder_path();
4386
+
4387
+ if( !file_exists($plugin_upload_folder) ){
4388
+ if( @mkdir($plugin_upload_folder) ){
4389
+ // Create last-logins datastore file.
4390
+ sucuriscan_lastlogins_datastore_exists();
4391
+
4392
+ // Create a htaccess file to deny access from all.
4393
+ @file_put_contents(
4394
+ $plugin_upload_folder . '/.htaccess',
4395
+ "Order Deny,Allow\nDeny from all\n",
4396
+ LOCK_EX
4397
+ );
4398
 
4399
+ // Create an index.html to avoid directory listing.
4400
+ @file_put_contents(
4401
+ $plugin_upload_folder . '/index.html',
4402
+ '<!-- Attemp to prevent the directory listing. -->',
4403
+ LOCK_EX
4404
+ );
4405
+ } else {
4406
+ SucuriScanInterface::error(
4407
+ 'Data folder does not exists and could not be created. You will need to
4408
+ create this folder manually and give it write permissions:<br><br><code>'
4409
+ . $plugin_upload_folder . '</code>'
4410
+ );
4411
+ }
4412
+ }
4413
  }
4414
 
4415
+ /**
4416
+ * Check whether a user has the permissions to see a page from the plugin.
4417
+ *
4418
+ * @return void
4419
+ */
4420
+ public static function check_permissions(){
4421
+ global $sucuriscan_pages;
4422
 
4423
+ if(
4424
+ !function_exists('current_user_can')
4425
+ || !current_user_can('manage_options')
4426
+ ){
4427
+ $page = SucuriScanRequest::get('page', '_page');
4428
+ wp_die(__('Access denied by <b>Sucuri</b> to see <code>' . $page . '</code>') );
4429
+ }
4430
+ }
 
 
 
 
 
 
 
 
 
 
4431
 
4432
+ /**
4433
+ * Verify the nonce of the previous page after a form submission. If the
4434
+ * validation fails the execution of the script will be stopped and a dead page
4435
+ * will be printed to the client using the official WordPress method.
4436
+ *
4437
+ * @return boolean Either TRUE or FALSE if the nonce is valid or not respectively.
4438
+ */
4439
+ public static function check_nonce(){
4440
+ if( !empty($_POST) ){
4441
+ $nonce_name = 'sucuriscan_page_nonce';
4442
+ $nonce_value = SucuriScanRequest::post($nonce_name, '_nonce');
4443
 
4444
+ if( !$nonce_value || !wp_verify_nonce($nonce_value, $nonce_name) ){
4445
+ wp_die(__('WordPress Nonce verification failed, try again going back and checking the form.') );
 
 
4446
 
4447
+ return FALSE;
4448
+ }
4449
+ }
 
 
 
 
 
4450
 
4451
+ return TRUE;
4452
+ }
 
 
4453
 
4454
+ /**
4455
+ * Prints a HTML alert in the WordPress admin interface.
4456
+ *
4457
+ * @param string $type The type of alert, it can be either Updated or Error.
4458
+ * @param string $message The message that will be printed in the alert.
4459
+ * @return void
4460
+ */
4461
+ private static function admin_notice( $type='updated', $message='' ){
4462
+ $alert_id = rand(100, 999);
4463
+ if( !empty($message) ): ?>
4464
+ <div id="sucuriscan-alert-<?php echo $alert_id; ?>" class="<?php echo $type; ?> sucuriscan-alert sucuriscan-alert-<?php echo $type; ?>">
4465
+ <a href="javascript:void(0)" class="close" onclick="sucuriscan_alert_close('<?php echo $alert_id; ?>')">&times;</a>
4466
+ <p><?php _e($message); ?></p>
4467
+ </div>
4468
+ <?php endif;
4469
+ }
4470
 
4471
+ /**
4472
+ * Prints a HTML alert of type ERROR in the WordPress admin interface.
4473
+ *
4474
+ * @param string $error_msg The message that will be printed in the alert.
4475
+ * @return void
4476
+ */
4477
+ public static function error( $error_msg='' ){
4478
+ self::admin_notice( 'error', '<b>Sucuri:</b> ' . $error_msg );
4479
+ }
4480
 
4481
+ /**
4482
+ * Prints a HTML alert of type INFO in the WordPress admin interface.
4483
+ *
4484
+ * @param string $info_msg The message that will be printed in the alert.
4485
+ * @return void
4486
+ */
4487
+ public static function info( $info_msg='' ){
4488
+ self::admin_notice( 'updated', '<b>Sucuri:</b> ' . $info_msg );
4489
  }
4490
 
4491
+ /**
4492
+ * Display a notice message with instructions to continue the setup of the
4493
+ * plugin, this includes the generation of the API key and other steps that need
4494
+ * to be done to fully activate this plugin.
4495
+ *
4496
+ * @return void
4497
+ */
4498
+ public static function setup_notice(){
4499
+ if(
4500
+ current_user_can('manage_options')
4501
+ && !SucuriScanAPI::get_plugin_key()
4502
+ && SucuriScanRequest::post(':plugin_api_key') === FALSE
4503
+ && SucuriScanRequest::post(':recover_key') === FALSE
4504
+ && !SucuriScanRequest::post(':manual_api_key')
4505
+ ){
4506
+ echo SucuriScanTemplate::get_section('setup-notice');
4507
+ }
4508
  }
4509
+
4510
  }
4511
 
4512
  /**
4513
+ * Display the page with a temporary message explaining the action that will be
4514
+ * performed once the hidden form is submitted to retrieve the scanning results
4515
+ * from the public SiteCheck API.
4516
  *
 
4517
  * @return void
4518
  */
4519
+ function sucuriscan_scanner_page(){
4520
+ SucuriScanInterface::check_permissions();
4521
 
4522
+ // Check if the information is already cached.
4523
+ $sucuri_cache = new SucuriScanCache('sitecheck');
4524
+ $scan_results = $sucuri_cache->get( 'scan_results', SUCURISCAN_SITECHECK_LIFETIME, 'array' );
4525
+
4526
+ if(
4527
+ (
4528
+ $scan_results
4529
+ && !empty($scan_results)
4530
+ ) || (
4531
+ SucuriScanInterface::check_nonce()
4532
+ && SucuriScanRequest::post(':malware_scan', '1')
4533
+ )
4534
+ ){
4535
+ sucuriscan_sitecheck_info($scan_results);
4536
  } else {
4537
+ echo SucuriScanTemplate::get_template('malwarescan', array(
4538
+ 'PageTitle' => 'Malware Scan',
4539
+ 'PageStyleClass' => 'scanner-loading',
4540
+ ));
4541
  }
 
 
 
 
4542
  }
4543
 
4544
  /**
4545
+ * Display the result of site scan made through SiteCheck.
4546
  *
4547
+ * @param array $res Array with information of the scanning.
4548
  * @return void
4549
  */
4550
+ function sucuriscan_sitecheck_info( $res=array() ){
4551
+ // Will be TRUE only if the scanning results were retrieved from the cache.
4552
+ $display_results = (bool) $res;
4553
+ $clean_domain = SucuriScanOption::get_domain();
4554
 
4555
+ // If the results are not cached, then request a new scanning.
4556
+ if( $res === FALSE ){
4557
+ $res = SucuriScanAPI::get_sitecheck_results($clean_domain);
 
 
 
 
4558
 
4559
+ // Check for error messages in the request's response.
4560
+ if( is_string($res) && preg_match('/^ERROR:(.*)/', $res, $error_m) ){
4561
+ SucuriScanInterface::error( 'The site <code>' . $clean_domain . '</code> was not scanned: ' . $error_m[1] );
4562
+ }
 
 
 
4563
 
4564
+ else {
4565
+ $sucuri_cache = new SucuriScanCache('sitecheck');
4566
+ $display_results = TRUE;
 
 
 
 
 
 
 
 
 
 
 
 
 
4567
 
4568
+ // Cache the scanning results to reduce memory lose.
4569
+ if( !$sucuri_cache->add( 'scan_results', $res ) ){
4570
+ SucuriScanInterface::error( 'Could not cache the results of the SiteCheck scanning.' );
4571
+ }
4572
+ }
 
4573
  }
4574
 
4575
+ ob_start();
4576
+ ?>
 
 
 
 
 
 
 
 
 
 
 
4577
 
 
 
 
 
4578
 
4579
+ <?php if( $display_results ): ?>
 
 
 
 
 
 
 
 
4580
 
4581
+ <?php
4582
+ // Check for general warnings, and return the information for Infected/Clean site.
4583
+ $malware_warns_exist = isset($res['MALWARE']['WARN']) ? TRUE : FALSE;
4584
+ $blacklist_warns_exist = isset($res['BLACKLIST']['WARN']) ? TRUE : FALSE;
4585
+ $outdated_warns_exist = isset($res['OUTDATEDSCAN']) ? TRUE : FALSE;
4586
+ $recommendations_exist = isset($res['RECOMMENDATIONS']) ? TRUE : FALSE;
 
 
 
4587
 
4588
+ // Check whether this WordPress installation needs an update.
4589
+ global $wp_version;
4590
+ $wordpress_updated = FALSE;
4591
+ $updates = function_exists('get_core_updates') ? get_core_updates() : array();
4592
 
4593
+ if( !is_array($updates) || empty($updates) || $updates[0]->response=='latest' ){
4594
+ $wordpress_updated = TRUE;
4595
+ }
 
 
 
 
 
 
4596
 
4597
+ if( TRUE ){
4598
+ // Initialize the CSS classes with default values.
4599
+ $sucuriscan_css_blacklist = 'sucuriscan-border-good';
4600
+ $sucuriscan_css_malware = 'sucuriscan-border-good';
4601
+ $sitecheck_results_tab = '';
4602
+ $blacklist_status_tab = '';
4603
+ $website_details_tab = '';
4604
 
4605
+ // Generate the CSS classes for the blacklist status.
4606
+ if( $blacklist_warns_exist ){
4607
+ $sucuriscan_css_blacklist = 'sucuriscan-border-bad';
4608
+ $blacklist_status_tab = 'sucuriscan-red-tab';
4609
+ }
 
 
 
 
4610
 
4611
+ // Generate the CSS classes for the SiteCheck scanning results.
4612
+ if( $malware_warns_exist ){
4613
+ $sucuriscan_css_malware = 'sucuriscan-border-bad';
4614
+ $sitecheck_results_tab = 'sucuriscan-red-tab';
4615
+ }
4616
 
4617
+ // Generate the CSS classes for the outdated/recommendations panel.
4618
+ if( $outdated_warns_exist || $recommendations_exist ){
4619
+ $website_details_tab = 'sucuriscan-red-tab';
4620
+ }
 
 
 
 
 
4621
 
4622
+ $sucuriscan_css_wpupdate = $wordpress_updated ? 'sucuriscan-border-good' : 'sucuriscan-border-bad';
4623
+ }
4624
+ ?>
4625
 
4626
+ <div id="poststuff">
4627
+ <div class="postbox sucuriscan-border sucuriscan-border-info sucuriscan-malwarescan-message">
4628
+ <h3>SiteCheck Scanner</h3>
4629
 
4630
+ <div class="inside">
4631
+ <p>
4632
+ If your site was recently hacked, you can see which files were modified to
4633
+ assist with any investigation.
4634
+ </p>
4635
+ </div>
4636
+ </div>
4637
+ </div>
4638
 
 
 
 
4639
 
4640
+ <div class="sucuriscan-tabs">
 
 
 
 
 
 
 
 
 
 
 
 
4641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4642
 
4643
+ <ul>
4644
+ <li class="<?php _e($sitecheck_results_tab) ?>">
4645
+ <a href="#" data-tabname="sitecheck-results">Remote Scanner Results</a>
4646
+ </li>
4647
+ <li class="<?php _e($website_details_tab) ?>">
4648
+ <a href="#" data-tabname="website-details">Website Details</a>
4649
+ </li>
4650
+ <li>
4651
+ <a href="#" data-tabname="website-links">IFrames / Links / Scripts</a>
4652
+ </li>
4653
+ <li class="<?php _e($blacklist_status_tab) ?>">
4654
+ <a href="#" data-tabname="blacklist-status">Blacklist Status</a>
4655
+ </li>
4656
+ <li>
4657
+ <a href="#" data-tabname="modified-files">Modified Files</a>
4658
+ </li>
4659
+ </ul>
4660
 
 
 
 
 
4661
 
4662
+ <div class="sucuriscan-tab-containers">
 
 
 
 
4663
 
 
4664
 
4665
+ <div id="sucuriscan-sitecheck-results">
4666
+ <div id="poststuff">
4667
+ <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_malware) ?>">
4668
+ <h3>
4669
+ <?php if( $malware_warns_exist ): ?>
4670
+ Site compromised (malware was identified)
4671
+ <?php else: ?>
4672
+ Site clean (no malware was identified)
4673
+ <?php endif; ?>
4674
+ </h3>
4675
 
4676
+ <div class="inside">
 
 
 
 
 
 
 
 
4677
 
4678
+ <?php if( !$malware_warns_exist ): ?>
4679
+ <p>
4680
+ <span><strong>Malware:</strong> Clean.</span><br>
4681
+ <span><strong>Malicious javascript:</strong> Clean.</span><br>
4682
+ <span><strong>Malicious iframes:</strong> Clean.</span><br>
4683
+ <span><strong>Suspicious redirections (htaccess):</strong> Clean.</span><br>
4684
+ <span><strong>Blackhat SEO Spam:</strong> Clean.</span><br>
4685
+ <span><strong>Anomaly detection:</strong> Clean.</span>
4686
+ </p>
4687
+ <?php else: ?>
4688
+ <ul>
4689
+ <?php
4690
+ foreach( $res['MALWARE']['WARN'] as $malres ){
4691
+ if( !is_array($malres) ){
4692
+ echo '<li>' . htmlspecialchars($malres) . '</li>';
4693
+ } else {
4694
+ $mwdetails = explode("\n", htmlspecialchars($malres[1]));
4695
+ $mw_name_link = isset($mwdetails[0]) ? substr($mwdetails[0], 1) : '';
4696
 
4697
+ if( preg_match('/(.*)\. Details: (.*)/', $mw_name_link, $mw_match) ){
4698
+ $mw_name_link = sprintf(
4699
+ '%s. Details: <a href="%s" target="_blank">%s</a>',
4700
+ $mw_match[1], $mw_match[2], $mw_match[2]
4701
+ );
4702
+ }
4703
 
4704
+ echo '<li>'. htmlspecialchars($malres[0]) . "\n<br>" . $mw_name_link . "</li>\n";
4705
+ }
4706
+ }
4707
+ ?>
4708
+ </ul>
4709
+ <?php endif; ?>
 
 
 
4710
 
4711
+ <p>
4712
+ <i>
4713
+ More details here: <a href="http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?>"
4714
+ target="_blank">http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?></a>
4715
+ </i>
4716
+ </p>
4717
 
4718
+ <hr />
 
 
 
 
 
 
 
 
4719
 
4720
+ <p>
4721
+ <i>
4722
+ If our free scanner did not detect any issue, you may have a more complicated
4723
+ and hidden problem. You can <a href="http://sucuri.net/signup" target="_blank">
4724
+ sign up</a> with Sucuri for a complete and in depth scan+cleanup (not included
4725
+ in the free checks).
4726
+ </i>
4727
+ </p>
4728
 
4729
+ </div>
4730
+ </div>
4731
+ </div>
4732
+ </div>
4733
 
 
 
 
 
 
 
 
 
4734
 
4735
+ <div id="sucuriscan-website-details">
4736
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-details">
4737
+ <thead>
4738
+ <tr>
4739
+ <th colspan="2" class="thead-with-button">
4740
+ <span>System Information</span>
4741
+ <?php if( !$wordpress_updated ): ?>
4742
+ <a href="<?php echo admin_url('update-core.php'); ?>" class="button button-primary thead-topright-action">
4743
+ Update to <?php _e($updates[0]->version) ?>
4744
+ </a>
4745
+ <?php endif; ?>
4746
+ </th>
4747
+ </tr>
4748
+ </thead>
4749
 
4750
+ <tbody>
4751
+ <!-- List of generic information from the site. -->
4752
+ <?php
4753
+ $possible_keys = array(
4754
+ 'DOMAIN' => 'Domain Scanned',
4755
+ 'IP' => 'Site IP Address',
4756
+ 'HOSTING' => 'Hosting Company',
4757
+ 'CMS' => 'CMS Found',
4758
+ );
4759
+ $possible_url_keys = array(
4760
+ 'IFRAME' => 'List of iframes found',
4761
+ 'JSEXTERNAL' => 'List of external scripts included',
4762
+ 'JSLOCAL' => 'List of scripts included',
4763
+ 'URL' => 'List of links found',
4764
+ );
4765
+ ?>
4766
 
4767
+ <?php foreach( $possible_keys as $result_key=>$result_title ): ?>
4768
+ <?php if( isset($res['SCAN'][$result_key]) ): ?>
4769
+ <?php $result_value = implode(', ', $res['SCAN'][$result_key]); ?>
4770
+ <tr>
4771
+ <td><?php _e($result_title) ?></td>
4772
+ <td><span class="sucuriscan-monospace"><?php _e($result_value) ?></span></td>
4773
+ </tr>
4774
+ <?php endif; ?>
4775
+ <?php endforeach; ?>
4776
 
4777
+ <tr>
4778
+ <td>WordPress Version</td>
4779
+ <td><span class="sucuriscan-monospace"><?php _e($wp_version) ?></span></td>
4780
+ </tr>
4781
+ <tr>
4782
+ <td>PHP Version</td>
4783
+ <td><span class="sucuriscan-monospace"><?php _e(phpversion()) ?></span></td>
4784
+ </tr>
 
4785
 
4786
+ <!-- List of application details from the site. -->
4787
+ <tr>
4788
+ <th colspan="2">Web application details</th>
4789
+ </tr>
4790
+ <?php foreach( $res['WEBAPP'] as $webapp_key=>$webapp_details ): ?>
4791
+ <?php if( is_array($webapp_details) ): ?>
4792
+ <?php foreach( $webapp_details as $i=>$details ): ?>
4793
+ <?php if( is_array($details) ){ $details = isset($details[0]) ? $details[0] : ''; } ?>
4794
+ <tr>
4795
+ <td colspan="2">
4796
+ <span class="sucuriscan-monospace"><?php _e($details) ?></span>
4797
+ </td>
4798
+ </tr>
4799
+ <?php endforeach; ?>
4800
+ <?php endif; ?>
4801
+ <?php endforeach; ?>
4802
 
4803
+ <?php foreach( $res['SYSTEM']['NOTICE'] as $j=>$notice ): ?>
4804
+ <?php if( is_array($notice) ){ $notice = implode(', ', $notice); } ?>
4805
+ <tr>
4806
+ <td colspan="2">
4807
+ <span class="sucuriscan-monospace"><?php _e($notice) ?></span>
4808
+ </td>
4809
+ </tr>
4810
+ <?php endforeach; ?>
4811
 
4812
+ <!-- Possible recommendations or outdated software on the site. -->
4813
+ <?php if( $outdated_warns_exist || $recommendations_exist ): ?>
4814
+ <tr>
4815
+ <th colspan="2">Recommendations for the site</th>
4816
+ </tr>
4817
+ <?php endif; ?>
 
 
 
 
 
 
 
4818
 
4819
+ <!-- Possible outdated software on the site. -->
4820
+ <?php if( $outdated_warns_exist ): ?>
4821
+ <?php foreach( $res['OUTDATEDSCAN'] as $outdated ): ?>
4822
+ <?php if( count($outdated) >= 3 ): ?>
4823
+ <tr>
4824
+ <td colspan="2" class="sucuriscan-border-bad">
4825
+ <strong><?php _e($outdated[0]) ?></strong>
4826
+ <em>(<?php _e($outdated[2]) ?>)</em>
4827
+ <span><?php _e($outdated[1]) ?></span>
4828
+ </td>
4829
+ </tr>
4830
+ <?php endif; ?>
4831
+ <?php endforeach; ?>
4832
+ <?php endif; ?>
4833
 
4834
+ <!-- Possible recommendations for the site. -->
4835
+ <?php if( $recommendations_exist ): ?>
4836
+ <?php foreach( $res['RECOMMENDATIONS'] as $recommendation ): ?>
4837
+ <?php if( count($recommendation) >= 3 ): ?>
4838
+ <tr>
4839
+ <td colspan="2" class="sucuriscan-border-bad">
4840
+ <?php printf(
4841
+ '<strong>%s</strong><br><span>%s</span><br><a href="%s" target="_blank">%s</a>',
4842
+ SucuriScan::escape($recommendation[0]),
4843
+ SucuriScan::escape($recommendation[1]),
4844
+ SucuriScan::escape($recommendation[2]),
4845
+ SucuriScan::escape($recommendation[2])
4846
+ ); ?>
4847
+ </td>
4848
+ </tr>
4849
+ <?php endif; ?>
4850
+ <?php endforeach; ?>
4851
+ <?php endif; ?>
4852
+ </tbody>
4853
+ </table>
4854
+ </div>
4855
 
 
 
 
 
 
 
 
 
4856
 
4857
+ <div id="sucuriscan-website-links">
4858
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-links">
4859
+ <tbody>
4860
+ <?php foreach( $possible_url_keys as $result_url_key=>$result_url_title ): ?>
4861
 
4862
+ <?php if( isset($res['LINKS'][$result_url_key]) ): ?>
4863
+ <tr>
4864
+ <th colspan="2">
4865
+ <?php printf(
4866
+ '%s (%d found)',
4867
+ __($result_url_title),
4868
+ count($res['LINKS'][$result_url_key])
4869
+ ) ?>
4870
+ </th>
4871
+ </tr>
4872
 
4873
+ <?php foreach( $res['LINKS'][$result_url_key] as $url_path ): ?>
4874
+ <tr>
4875
+ <td colspan="2">
4876
+ <span class="sucuriscan-monospace sucuriscan-wraptext"><?php _e($url_path) ?></span>
4877
+ </td>
4878
+ </tr>
4879
+ <?php endforeach; ?>
4880
+ <?php endif; ?>
4881
 
4882
+ <?php endforeach; ?>
4883
+ </tbody>
4884
+ </table>
4885
+ </div>
4886
 
 
 
 
 
 
 
 
 
 
 
 
 
4887
 
4888
+ <div id="sucuriscan-blacklist-status">
4889
+ <div id="poststuff">
4890
+ <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_blacklist) ?>">
4891
+ <h3>
4892
+ <?php if( $blacklist_warns_exist ): ?>
4893
+ Site blacklisted
4894
+ <?php else: ?>
4895
+ Site blacklist-free
4896
+ <?php endif; ?>
4897
+ </h3>
 
 
 
 
 
 
 
 
4898
 
4899
+ <div class="inside">
4900
+ <ul>
4901
+ <?php
4902
+ foreach(array(
4903
+ 'INFO' => 'CLEAN',
4904
+ 'WARN' => 'WARNING'
4905
+ ) as $type => $group_title){
4906
+ if( isset($res['BLACKLIST'][$type]) ){
4907
+ foreach( $res['BLACKLIST'][$type] as $blres ){
4908
+ $report_site = SucuriScan::escape($blres[0]);
4909
+ $report_url = SucuriScan::escape($blres[1]);
4910
+ printf(
4911
+ '<li><b>%s:</b> %s.<br>Details at <a href="%s" target="_blank">%s</a></li>',
4912
+ $group_title, $report_site, $report_url, $report_url
4913
+ );
4914
+ }
4915
+ }
4916
+ }
4917
+ ?>
4918
+ </ul>
4919
+ </div>
4920
+ </div>
4921
+ </div>
4922
+ </div>
4923
 
 
 
 
4924
 
4925
+ <div id="sucuriscan-modified-files">
4926
+ <?php echo sucuriscan_modified_files(); ?>
4927
+ </div>
 
 
 
 
 
 
 
 
4928
 
 
 
 
 
 
 
 
 
4929
 
4930
+ </div>
4931
+ </div>
 
4932
 
4933
+ <?php if( $malware_warns_exist || $blacklist_warns_exist ): ?>
4934
+ <a href="http://sucuri.net/signup/" target="_blank" class="button button-primary button-hero sucuriscan-cleanup-btn">
4935
+ Get your site protected with Sucuri
4936
+ </a>
4937
+ <?php endif; ?>
 
 
 
 
 
 
 
 
 
 
 
 
4938
 
4939
+ <?php endif; ?>
 
 
 
 
 
4940
 
 
4941
 
4942
+ <?php
4943
+ $_html = ob_get_contents();
4944
+ ob_end_clean();
4945
+ echo SucuriScanTemplate::get_base_template($_html, array(
4946
+ 'PageTitle' => 'Malware Scan',
4947
+ 'PageContent' => $_html,
4948
+ 'PageStyleClass' => 'scanner-results',
4949
+ ));
4950
+ return;
4951
  }
4952
 
4953
  /**
4960
  * @return void
4961
  */
4962
  function sucuriscan_monitoring_page(){
4963
+ SucuriScanInterface::check_permissions();
 
 
4964
 
4965
  // Process all form submissions.
4966
  sucuriscan_monitoring_form_submissions();
4967
 
4968
  // Get the dynamic values for the template variables.
4969
+ $api_key = SucuriScanAPI::get_cloudproxy_key();
4970
 
4971
  // Page pseudo-variables initialization.
4972
  $template_variables = array(
4988
  $template_variables['Monitoring.InstructionsVisibility'] = 'hidden';
4989
  }
4990
 
4991
+ echo SucuriScanTemplate::get_template('monitoring', $template_variables);
4992
  }
4993
 
4994
  /**
4995
  * Process the requests sent by the form submissions originated in the monitoring
4996
+ * page, all forms must have a nonce field that will be checked against the one
4997
  * generated in the template render function.
4998
  *
4999
  * @return void
5000
  */
5001
  function sucuriscan_monitoring_form_submissions(){
5002
 
5003
+ if( SucuriScanInterface::check_nonce() ){
5004
 
5005
  // Add and/or Update the Sucuri WAF API Key (do it before anything else).
5006
+ $option_name = ':cloudproxy_apikey';
5007
+ $api_key = SucuriScanRequest::post($option_name);
 
 
5008
 
5009
+ if( $api_key !== FALSE ){
5010
+ if( SucuriScanAPI::is_valid_cloudproxy_key($api_key) ){
5011
+ SucuriScanOption::update_option($option_name, $api_key);
5012
+ SucuriScanInterface::info( 'CloudProxy API key saved successfully' );
5013
  } elseif( empty($api_key) ){
5014
+ SucuriScanOption::delete_option($option_name);
5015
+ SucuriScanInterface::info( 'CloudProxy API key removed successfully' );
5016
  } else {
5017
+ SucuriScanInterface::error( 'Invalid CloudProxy API key, check your settings and try again.' );
5018
  }
5019
  }
5020
 
5021
  // Flush the cache of the site(s) associated with the API key.
5022
+ if( SucuriScanRequest::post(':clear_cache', '1') ){
5023
+ $clear_cache_resp = SucuriScanAPI::clear_cloudproxy_cache();
5024
 
5025
  if( $clear_cache_resp ){
5026
  if( isset($clear_cache_resp->messages[0]) ){
5027
+ SucuriScanInterface::info($clear_cache_resp->messages[0]);
5028
  } else {
5029
+ SucuriScanInterface::error('Could not clear the cache of your site, try later again.');
5030
  }
5031
  } else {
5032
+ SucuriScanInterface::error( 'CloudProxy is not enabled on your site, or your API key is invalid.' );
5033
  }
5034
  }
5035
 
5051
  );
5052
 
5053
  if( $api_key ){
5054
+ $settings = SucuriScanAPI::get_cloudproxy_settings($api_key);
5055
 
5056
  $template_variables['Monitoring.APIKey'] = $api_key['string'];
5057
 
5083
  }
5084
 
5085
  // Parse the snippet template and replace the pseudo-variables.
5086
+ $template_variables['Monitoring.SettingOptions'] .= SucuriScanTemplate::get_snippet('monitoring-settings', array(
5087
  'Monitoring.OptionCssClass' => $css_class,
5088
  'Monitoring.OptionName' => $option_title,
5089
  'Monitoring.OptionValue' => $option_value,
5093
  }
5094
  }
5095
 
5096
+ return SucuriScanTemplate::get_section( 'monitoring-settings', $template_variables );
5097
  }
5098
 
5099
  /**
5131
  }
5132
 
5133
  /**
5134
+ * Get an explanation of the meaning of the value set for the account's attribute cache_mode.
5135
  *
5136
  * @param string $mode The value set for the cache settings of the site.
5137
+ * @return string Explanation of the meaning of the cache_mode value.
5138
  */
5139
  function sucuriscan_cache_mode_title( $mode='' ){
5140
  $title = '';
5142
  switch( $mode ){
5143
  case 'docache': $title = 'Enabled (recommended)'; break;
5144
  case 'sitecache': $title = 'Site caching (using your site headers)'; break;
5145
+ case 'nocache': $title = 'Minimal (only for a few minutes)'; break;
5146
+ case 'nocacheatall': $title = 'Caching disabled (use with caution)'; break;
5147
  default: $title = 'Unknown'; break;
5148
  }
5149
 
5165
  'AuditLogs.PaginationVisibility' => 'hidden',
5166
  'AuditLogs.AuditPagination' => '',
5167
  'AuditLogs.TargetDate' => '',
5168
+ 'AuditLogs.DateYears' => '',
5169
+ 'AuditLogs.DateMonths' => '',
5170
+ 'AuditLogs.DateDays' => '',
5171
  );
5172
 
5173
  $date = date('Y-m-d');
5174
 
5175
  if( $api_key ){
5176
+ // Retrieve the date filter from the GET request (if any).
5177
+ if( $date_by_get = SucuriScanRequest::get('date', '_yyyymmdd') ){
5178
+ $date = $date_by_get;
5179
  }
5180
 
5181
+ // Retrieve the date filter from the POST request (if any).
5182
+ $year = SucuriScanRequest::post(':year');
5183
+ $month = SucuriScanRequest::post(':month');
5184
+ $day = SucuriScanRequest::post(':day');
5185
+
5186
+ if( $year && $month && $day ){
5187
+ $date = sprintf( '%s-%s-%s', $year, $month, $day );
 
 
 
 
5188
  }
5189
 
5190
+ $logs_data = SucuriScanAPI::get_cloudproxy_logs( $api_key, $date );
5191
 
5192
  if( $logs_data ){
5193
  add_thickbox(); /* Include the Thickbox library. */
5199
  }
5200
 
5201
  $template_variables['AuditLogs.TargetDate'] = htmlentities($date);
5202
+ $template_variables['AuditLogs.DateYears'] = sucuriscan_monitoring_dates('years', $date);
5203
+ $template_variables['AuditLogs.DateMonths'] = sucuriscan_monitoring_dates('months', $date);
5204
+ $template_variables['AuditLogs.DateDays'] = sucuriscan_monitoring_dates('days', $date);
5205
 
5206
+ return SucuriScanTemplate::get_section( 'monitoring-logs', $template_variables );
5207
  }
5208
 
5209
  /**
5237
  $filter_by_keyword = FALSE;
5238
  $filter_query = FALSE;
5239
 
5240
+ if( $q = SucuriScanRequest::post(':monitoring_denial_type') ){
5241
  $filter_by_denial_type = TRUE;
5242
+ $filter_query = $q;
5243
  }
5244
 
5245
+ if( $q = SucuriScanRequest::post(':monitoring_log_filter') ){
5246
  $filter_by_keyword = TRUE;
5247
+ $filter_query = $q;
5248
  }
5249
 
5250
  foreach( $access_logs as $access_log ){
5257
  // If there is a filter, check the access_log data and break the operation if needed.
5258
  if( $filter_query ){
5259
  if( $filter_by_denial_type ){
5260
+ $denial_type_slug = SucuriScan::human2var($access_log->sucuri_block_reason);
5261
 
5262
  if( $denial_type_slug != $filter_query ){ continue; }
5263
  }
5287
  $audit_log_snippet[$attr_title] = $attr_value;
5288
  }
5289
 
5290
+ $logs_html .= SucuriScanTemplate::get_snippet('monitoring-logs', $audit_log_snippet);
5291
  $counter += 1;
5292
  }
5293
  }
5306
  */
5307
  function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=TRUE ){
5308
  $types = array();
 
5309
 
5310
  if( $access_logs && !empty($access_logs) ){
5311
  foreach( $access_logs as $access_log ){
5312
  if( !array_key_exists($access_log->sucuri_block_reason, $types) ){
5313
+ $denial_type_k = SucuriScan::human2var($access_log->sucuri_block_reason);
5314
  $types[$denial_type_k] = $access_log->sucuri_block_reason;
5315
  }
5316
  }
5318
 
5319
  if( $in_html ){
5320
  $html_types = '<option value="">Filter</option>';
5321
+ $selected = SucuriScanRequest::post(':monitoring_denial_type', '.+');
 
 
 
5322
 
5323
  foreach( $types as $type_key => $type_value ){
5324
+ $selected_tag = ( $type_key === $selected ) ? 'selected="selected"' : '';
5325
+ $html_types .= sprintf(
5326
+ '<option value="%s" %s>%s</option>',
5327
+ SucuriScan::escape($type_key),
5328
+ $selected_tag,
5329
+ SucuriScan::escape($type_value)
5330
+ );
5331
  }
5332
 
5333
  return $html_types;
5340
  * Get a list of years, months or days depending of the type specified.
5341
  *
5342
  * @param string $type Either years, months or days.
5343
+ * @param string $date Year, month and day selected from the request.
5344
  * @param boolean $in_html Whether the list should be converted to a HTML select options or not.
5345
  * @return array Either an array with the expected values, or a HTML code.
5346
  */
5347
+ function sucuriscan_monitoring_dates( $type='', $date='', $in_html=TRUE ){
5348
  $options = array();
5349
  $selected = '';
5350
 
5351
+ if( preg_match('/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/', $date, $date_m) ){
5352
+ $s_year = $date_m[1];
5353
+ $s_month = $date_m[2];
5354
+ $s_day = $date_m[3];
5355
+ } else {
5356
+ $s_year = '';
5357
+ $s_month = '';
5358
+ $s_day = '';
5359
+ }
5360
+
5361
  switch( $type ){
5362
  case 'years':
5363
+ $selected = $s_year;
5364
  $current_year = (int) date('Y');
5365
  $max_years = 5; /* Maximum number of years to keep the logs. */
5366
  $options = range( ($current_year - $max_years), $current_year );
 
 
 
 
5367
  break;
5368
  case 'months':
5369
+ $selected = $s_month;
5370
  $options = array(
5371
  '01' => 'January',
5372
  '02' => 'February',
5381
  '11' => 'November',
5382
  '12' => 'December'
5383
  );
 
 
 
 
5384
  break;
5385
  case 'days':
5386
  $options = range(1, 31);
5387
+ $selected = $s_day;
 
 
 
5388
  break;
5389
  }
5390
 
5415
  * @return void
5416
  */
5417
  function sucuriscan_hardening_page(){
5418
+ SucuriScanInterface::check_permissions();
5419
 
5420
+ if(
5421
+ SucuriScanRequest::post(':run_hardening')
5422
+ && !SucuriScanInterface::check_nonce()
5423
+ ){
5424
+ unset($_POST['sucuriscan_run_hardening']);
 
 
 
5425
  }
5426
 
5427
  ob_start();
5429
 
5430
  <div id="poststuff">
5431
  <form method="post">
5432
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
5433
+ <input type="hidden" name="sucuriscan_run_hardening" value="1" />
5434
 
5435
  <?php
5436
  sucuriscan_harden_version();
5452
  <?php
5453
  $_html = ob_get_contents();
5454
  ob_end_clean();
5455
+ echo SucuriScanTemplate::get_base_template($_html, array(
5456
  'PageTitle' => 'Hardening',
5457
  'PageContent' => $_html,
5458
  'PageStyleClass' => 'hardening'
5516
  * @return void
5517
  */
5518
  function sucuriscan_harden_version(){
5519
+ $site_version = SucuriScan::site_version();
 
5520
  $updates = get_core_updates();
5521
  $cp = ( !is_array($updates) || empty($updates) ? 1 : 0 );
5522
 
5529
  }
5530
  }
5531
 
5532
+ if( strcmp($site_version, '3.7') < 0 ){
5533
  $cp = 0;
5534
  }
5535
 
 
5536
  $initial_msg = 'Why keep your site updated? WordPress is an open-source
5537
  project which means that with every update the details of the changes made
5538
  to the source code are made public, if there were security fixes then
5539
  someone with malicious intent can use this information to attack any site
5540
  that has not been upgraded.';
5541
+ $messageok = sprintf('Your WordPress installation (%s) is current.', $site_version);
5542
  $messagewarn = sprintf(
5543
  'Your current version (%s) is not current.<br>
5544
  <a href="update-core.php" class="button-primary">Update now!</a>',
5545
+ $site_version
5546
  );
5547
 
5548
  sucuriscan_harden_status( 'Verify WordPress version', $cp, NULL, $messageok, $messagewarn, $initial_msg );
5579
  function sucuriscan_harden_upload(){
5580
  $cp = 1;
5581
  $upmsg = NULL;
5582
+ $datastore_path = SucuriScan::datastore_folder_path();
5583
+ $htaccess_upload = dirname($datastore_path) . '/.htaccess';
5584
 
5585
  if( !is_readable($htaccess_upload) ){
5586
  $cp = 0;
5587
  } else {
5588
  $cp = 0;
5589
+ $fcontent = SucuriScanFileInfo::file_lines($htaccess_upload);
5590
 
5591
  foreach( $fcontent as $fline ){
5592
+ if( stripos($fline, 'deny from all') !== FALSE ){
5593
  $cp = 1;
5594
  break;
5595
  }
5596
  }
5597
  }
5598
 
5599
+ if( SucuriScanRequest::post(':run_hardening') ){
5600
+ if( SucuriScanRequest::post(':harden_upload') && $cp == 0 ){
5601
  if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
5602
+ $upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5603
  } else {
5604
+ $upmsg = SucuriScanInterface::info('Hardening applied successfully to upload directory');
5605
  $cp = 1;
5606
  }
5607
  }
5608
 
5609
+ elseif( SucuriScanRequest::post(':harden_upload_unharden') ){
5610
  $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
5611
  $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
5612
 
5618
  @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5619
  }
5620
 
5621
+ SucuriScanInterface::info('Hardening reverted for upload directory <code>/wp-content/uploads/</code>');
5622
  } else {
5623
+ SucuriScanInterface::error(
5624
+ 'File <code>/wp-content/uploads/.htaccess</code> does not exists or
5625
+ is not writable, you will need to remove the following code (manually):
5626
  <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5627
  );
5628
  }
5658
  $cp = 0;
5659
  } else {
5660
  $cp = 0;
5661
+ $fcontent = SucuriScanFileInfo::file_lines($htaccess_upload);
5662
 
5663
  foreach( $fcontent as $fline ){
5664
+ if( stripos($fline, 'deny from all') !== FALSE ){
5665
  $cp = 1;
5666
  break;
5667
  }
5668
  }
5669
  }
5670
 
5671
+ if( SucuriScanRequest::post(':run_hardening') ){
5672
+ if( SucuriScanRequest::post(':harden_wpcontent') && $cp == 0 ){
5673
  if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
5674
+ $upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5675
  } else {
5676
+ $upmsg = SucuriScanInterface::info('Hardening applied successfully to content directory <code>/wp-content/</code>');
5677
  $cp = 1;
5678
  }
5679
  }
5680
 
5681
+ elseif( SucuriScanRequest::post(':harden_wpcontent_unharden') ){
5682
  $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
5683
  $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
5684
 
5690
  @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5691
  }
5692
 
5693
+ SucuriScanInterface::info('Hardening reverted for content directory <code>/wp-content/</code>');
5694
  } else {
5695
+ SucuriScanInterface::info(
5696
+ 'File <code>/wp-content/.htaccess</code> does not exists or is not writable,
5697
+ you will need to remove the following code manually from there:
5698
  <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5699
  );
5700
  }
5733
  $cp = 0;
5734
  } else {
5735
  $cp = 0;
5736
+ $fcontent = SucuriScanFileInfo::file_lines($htaccess_upload);
5737
 
5738
  foreach( $fcontent as $fline ){
5739
+ if( stripos($fline, 'deny from all') !== FALSE ){
5740
  $cp = 1;
5741
  break;
5742
  }
5743
  }
5744
  }
5745
 
5746
+ if( SucuriScanRequest::post(':run_hardening') ){
5747
+ if( SucuriScanRequest::post(':harden_wpincludes') && $cp == 0 ){
5748
  if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>\n<Files wp-tinymce.php>\nallow from all\n</Files>\n")===FALSE ){
5749
+ $upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5750
  } else {
5751
+ $upmsg = SucuriScanInterface::info('Hardening applied successfully to library\'s directory <code>/wp-includes/</code>');
5752
  $cp = 1;
5753
  }
5754
  }
5755
 
5756
+ elseif( SucuriScanRequest::post(':harden_wpincludes_unharden') ){
5757
  $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
5758
  $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
5759
 
5766
 
5767
  @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5768
  }
5769
+ SucuriScanInterface::info('Hardening reverted for library\'s directory <code>/wp-includes/</code>');
5770
  } else {
5771
+ SucuriScanInterface::error(
5772
+ 'File <code>wp-includes/.htaccess</code> does not exists or is not
5773
+ writable, you will need to remove the following code manually from
5774
  there: <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5775
  );
5776
  }
5816
  */
5817
  function sucuriscan_cloudproxy_enabled(){
5818
  $btn_string = '';
5819
+ $enabled = SucuriScan::is_behind_cloudproxy();
5820
  $status = 1;
5821
 
5822
  if( $enabled !== TRUE ){
5848
  * @return void
5849
  */
5850
  function sucuriscan_harden_secretkeys(){
5851
+ $wp_config_path = SucuriScan::get_wpconfig_path();
5852
+ $current_keys = SucuriScanOption::get_security_keys();
5853
 
5854
  if( $wp_config_path ){
5855
  $cp = 1;
5856
  $message = 'The main configuration file was found at: <code>'.$wp_config_path.'</code><br>';
5857
 
5858
+ if(
5859
+ !empty($current_keys['bad'])
5860
+ || !empty($current_keys['missing'])
5861
+ ){
5862
+ $cp = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5863
  }
5864
  }else{
5865
  $cp = 0;
5866
+ $message = 'The <code>wp-config.php</code> file was not found.<br>';
5867
  }
5868
 
5869
  $message .= '<br>It checks whether you have proper random keys/salts created for WordPress. A
5872
  random elements to the password. In simple terms, a secret key is a password with
5873
  elements that make it harder to generate enough options to break through your
5874
  security barriers.';
5875
+ $messageok = 'Security keys and salts not set, we recommend creating them for security reasons'
5876
+ . '<a href="' . SucuriScanTemplate::get_url('posthack') . '" class="button button-primary">'
5877
+ . 'Harden</a>';
5878
 
5879
  sucuriscan_harden_status(
5880
+ 'Security keys',
5881
  $cp,
5882
  NULL,
5883
+ 'Security keys and salts properly created',
5884
+ $messageok,
5885
  $message,
5886
  NULL
5887
  );
5898
  $upmsg = NULL;
5899
  $cp = is_readable(ABSPATH.'/readme.html') ? 0 : 1;
5900
 
5901
+ // TODO: After hardening create an option to automatically remove this after WP upgrade.
5902
+ if( SucuriScanRequest::post(':run_hardening') ){
5903
+ if( SucuriScanRequest::post(':harden_readme') && $cp == 0 ){
5904
  if( @unlink(ABSPATH.'/readme.html') === FALSE ){
5905
+ $upmsg = SucuriScanInterface::error('Unable to remove <code>readme.html</code> file.');
5906
  } else {
5907
  $cp = 1;
5908
+ $upmsg = SucuriScanInterface::info('<code>readme.html</code> file removed successfully.');
5909
  }
5910
  }
5911
 
5912
+ elseif( SucuriScanRequest::post(':harden_readme_unharden') ){
5913
+ SucuriScanInterface::error('We can not revert this action, you must create the <code>readme.html</code> file by yourself.');
5914
  }
5915
  }
5916
 
5935
  global $wpdb;
5936
 
5937
  $upmsg = NULL;
5938
+ $user_query = new WP_User_Query(array(
5939
+ 'search' => 'admin',
5940
+ 'fields' => array( 'ID', 'user_login' ),
5941
+ 'search_columns' => array( 'user_login' ),
5942
+ ));
5943
+ $results = $user_query->get_results();
5944
+ $account_removed = ( count($results) === 0 ? 1 : 0 );
5945
 
5946
+ if( $account_removed === 0 ){
5947
+ $upmsg = '<i><strong>Notice.</strong> We do not offer an option to automatically change the user name.
5948
  Go to the <a href="'.admin_url('users.php').'" target="_blank">user list</a> and create a new
5949
+ administrator user. Once created, log in as that user and remove the default <code>admin</code>
5950
+ (make sure to assign all the admin posts to the new user too).</i>';
5951
  }
5952
 
5953
  sucuriscan_harden_status(
5969
  function sucuriscan_harden_fileeditor(){
5970
  $file_editor_disabled = defined('DISALLOW_FILE_EDIT') ? DISALLOW_FILE_EDIT : FALSE;
5971
 
5972
+ if( SucuriScanRequest::post(':run_hardening') ){
5973
  $current_time = date('r');
5974
+ $wp_config_path = SucuriScan::get_wpconfig_path();
5975
 
5976
  $wp_config_writable = ( file_exists($wp_config_path) && is_writable($wp_config_path) ) ? TRUE : FALSE;
5977
  $new_wpconfig = $wp_config_writable ? file_get_contents($wp_config_path) : '';
5978
 
5979
+ if( SucuriScanRequest::post(':harden_fileeditor') ){
5980
  if( $wp_config_writable ){
5981
  if( preg_match('/(.*define\(.DB_COLLATE..*)/', $new_wpconfig, $match) ){
5982
  $disallow_fileedit_definition = "\n\ndefine('DISALLOW_FILE_EDIT', TRUE); // Sucuri Security: {$current_time}\n";
5984
  }
5985
 
5986
  @file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
5987
+ SucuriScanInterface::info( 'Configuration file updated successfully, the plugin and theme editor were disabled.' );
5988
  $file_editor_disabled = TRUE;
5989
  } else {
5990
+ SucuriScanInterface::error( 'The <code>wp-config.php</code> file is not in the default location
5991
  or is not writable, you will need to put the following code manually there:
5992
  <code>define("DISALLOW_FILE_EDIT", TRUE);</code>' );
5993
  }
5994
  }
5995
 
5996
+ elseif( SucuriScanRequest::post(':harden_fileeditor_unharden') ){
5997
  if( preg_match("/(.*define\('DISALLOW_FILE_EDIT', TRUE\);.*)/", $new_wpconfig, $match) ){
5998
  if( $wp_config_writable ){
5999
  $new_wpconfig = str_replace("\n{$match[1]}", '', $new_wpconfig);
6000
  file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
6001
+ SucuriScanInterface::info( 'Configuration file updated successfully, the plugin and theme editor were enabled.' );
6002
  $file_editor_disabled = FALSE;
6003
  } else {
6004
+ SucuriScanInterface::error( 'The <code>wp-config.php</code> file is not in the default location
6005
  or is not writable, you will need to remove the following code manually from there:
6006
  <code>define("DISALLOW_FILE_EDIT", TRUE);</code>' );
6007
  }
6008
  } else {
6009
+ SucuriScanInterface::error( 'The theme and plugin editor are not disabled from the configuration file.' );
6010
  }
6011
  }
6012
  }
6013
 
6014
+ $message = 'Occasionally you may wish to disable the plugin or theme editor to prevent overzealous
6015
+ users from being able to edit sensitive files and potentially crash the site. Disabling these
6016
+ also provides an additional layer of security if a hacker gains access to a well-privileged
6017
+ user account.';
6018
 
6019
  sucuriscan_harden_status(
6020
  'Plugin &amp; Theme editor',
6060
  * @return void
6061
  */
6062
  function sucuriscan_page(){
6063
+ SucuriScanInterface::check_permissions();
 
 
6064
 
6065
+ if(
6066
+ SucuriScanInterface::check_nonce()
6067
+ && SucuriScanRequest::post(':force_scan')
6068
+ ){
6069
+ SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scan forced at: ' . date('r') );
6070
+ SucuriScanEvent::filesystem_scan(TRUE);
6071
+ }
6072
 
6073
  $template_variables = array(
6074
  'WordpressVersion' => sucuriscan_wordpress_outdated(),
6076
  'CoreFiles' => sucuriscan_core_files(),
6077
  );
6078
 
6079
+ echo SucuriScanTemplate::get_template('integrity', $template_variables);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6080
  }
6081
 
6082
  /**
6113
 
6114
  // Initialize the values for the pagination.
6115
  $max_per_page = SUCURISCAN_AUDITLOGS_PER_PAGE;
6116
+ $page_number = SucuriScanTemplate::get_page_number();
6117
  $logs_limit = $page_number * $max_per_page;
6118
+ $audit_logs = SucuriScanAPI::get_logs($logs_limit);
6119
 
6120
  $show_all = TRUE;
6121
 
6164
  $snippet_data['AuditLog.Extra'] .= '<small>For Mac users, this is a scrollable container</small>';
6165
  }
6166
 
6167
+ $template_variables['AuditLogs.List'] .= SucuriScanTemplate::get_snippet('integrity-auditlogs', $snippet_data);
6168
  $counter_i += 1;
6169
  }
6170
  }
6174
 
6175
  if( $total_items > 0 ){
6176
  $template_variables['AuditLogs.PaginationVisibility'] = 'visible';
6177
+ $template_variables['AuditLogs.PaginationLinks'] = SucuriScanTemplate::get_pagination(
6178
  '%%SUCURI.URL.Home%%',
6179
+ $max_per_page * 5, /* TODO: Temporary value while we get the total logs. */
6180
  $max_per_page
6181
  );
6182
  }
6183
  }
6184
 
6185
+ return SucuriScanTemplate::get_section('integrity-auditlogs', $template_variables);
6186
  }
6187
 
6188
  /**
6191
  * @return string Panel with a warning advising that WordPress is outdated.
6192
  */
6193
  function sucuriscan_wordpress_outdated(){
6194
+ $site_version = SucuriScan::site_version();
 
6195
  $updates = get_core_updates();
6196
  $cp = ( !is_array($updates) || empty($updates) ? 1 : 0 );
6197
 
6198
  $template_variables = array(
6199
+ 'WordPress.Version' => $site_version,
6200
  'WordPress.UpgradeURL' => admin_url('update-core.php'),
6201
  'WordPress.UpdateVisibility' => 'hidden',
6202
  'WordPressBeta.Visibility' => 'hidden',
6218
  }
6219
  }
6220
 
6221
+ if( strcmp($site_version, '3.7') < 0 ){
6222
  $cp = 0;
6223
  }
6224
 
6226
  $template_variables['WordPress.UpdateVisibility'] = 'visible';
6227
  }
6228
 
6229
+ return SucuriScanTemplate::get_section('integrity-wpoutdate', $template_variables);
6230
  }
6231
 
6232
  /**
6237
  * @return void
6238
  */
6239
  function sucuriscan_core_files(){
6240
+ $site_version = SucuriScan::site_version();
6241
 
6242
  $template_variables = array(
6243
  'CoreFiles.List' => '',
6246
  'CoreFiles.BadVisibility' => 'hidden',
6247
  );
6248
 
6249
+ if( $site_version && SucuriScanOption::get_option(':scan_checksums') == 'enabled' ){
6250
+ $latest_hashes = sucuriscan_check_wp_integrity($site_version);
6251
 
6252
  if( $latest_hashes ){
6253
  $counter = 0;
6280
  }
6281
 
6282
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6283
+ $template_variables['CoreFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-corefiles', array(
6284
  'CoreFiles.CssClass' => $css_class,
6285
  'CoreFiles.StatusType' => $list_type,
6286
  'CoreFiles.StatusAbbr' => substr($list_type, 0, 1),
6296
  $template_variables['CoreFiles.BadVisibility'] = 'visible';
6297
  }
6298
  } else {
6299
+ SucuriScanInterface::error( 'Error retrieving the WordPress core hashes, try again.' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6300
  }
6301
  }
6302
 
6303
+ return SucuriScanTemplate::get_section('integrity-corefiles', $template_variables);
6304
  }
6305
 
6306
  /**
6319
  * @return array Associative array with these keys: modified, stable, removed, added.
6320
  */
6321
  function sucuriscan_check_wp_integrity( $version=0 ){
6322
+ $latest_hashes = SucuriScanAPI::get_official_checksums($version);
6323
 
6324
  if( !$latest_hashes ){ return FALSE; }
6325
 
6378
  function sucuriscan_ignore_integrity_filepath( $filepath='' ){
6379
  // List of files that will be ignored from the integrity checking.
6380
  $ignore_files = array(
6381
+ '^sucuri-[0-9a-z]+\.php$',
6382
+ '^favicon\.ico$',
6383
+ '^php\.ini$',
6384
+ '^\.htaccess$',
6385
+ '^wp-includes\/\.htaccess$',
6386
+ '^wp-admin\/setup-config\.php$',
6387
+ '^wp-(config|pass|rss|feed|register|atom|commentsrss2|rss2|rdf)\.php$',
6388
+ '^wp-content\/(themes|plugins)\/.+', // TODO: Add the popular themes/plugins integrity checks.
6389
+ '^sitemap\.xml($|\.gz)$',
6390
+ '^readme\.html$',
6391
+ '^(503|404)\.php$',
6392
+ '^500\.(shtml|php)$',
6393
+ '^40[0-9]\.shtml$',
6394
+ '^([^\/]*)\.(pdf|css)$',
6395
+ '^google[0-9a-z]{16}\.html$',
6396
+ '^pinterest-[0-9a-z]{5}\.html$',
6397
+ '(^|\/)error_log$',
6398
  );
6399
 
6400
+ foreach( $ignore_files as $ignore_pattern ){
6401
+ if( preg_match('/'.$ignore_pattern.'/', $filepath) ){
6402
+ return TRUE;
6403
+ }
 
 
6404
  }
6405
 
6406
  return FALSE;
6423
  // Find files modified in the last days.
6424
  $back_days = 7;
6425
 
6426
+ // Set the ranges of the search to be between one and sixty days.
6427
+ if( SucuriScanInterface::check_nonce() ){
6428
+ $back_days = (int) SucuriScanRequest::post(':last_days', '[0-9]+');
6429
  if ( $back_days <= 0 ){ $back_days = 1; }
6430
  elseif( $back_days >= 60 ){ $back_days = 60; }
6431
  }
6439
  );
6440
  }
6441
 
6442
+ // The scanner for modified files can be disabled from the settings page.
6443
+ if( SucuriScanOption::get_option(':scan_modfiles') == 'enabled' ){
6444
+ // Search modified files among the project's files.
6445
+ $content_hashes = sucuriscan_get_integrity_tree( ABSPATH.'wp-content', true );
 
6446
 
6447
+ if( $content_hashes ){
6448
+ $template_variables['ModifiedFiles.Days'] = $back_days;
6449
+ $back_days = current_time('timestamp') - ( $back_days * 86400);
6450
+ $counter = 0;
6451
 
6452
+ foreach( $content_hashes as $file_path => $file_info ){
6453
+ if(
6454
+ isset($file_info['modified_at'])
6455
+ && $file_info['modified_at'] >= $back_days
6456
+ ){
6457
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6458
+ $mod_date = date('d/M/Y H:i:s', $file_info['modified_at']);
6459
+
6460
+ $template_variables['ModifiedFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-modifiedfiles', array(
6461
+ 'ModifiedFiles.CssClass' => $css_class,
6462
+ 'ModifiedFiles.CheckSum' => $file_info['checksum'],
6463
+ 'ModifiedFiles.FilePath' => $file_path,
6464
+ 'ModifiedFiles.DateTime' => $mod_date
6465
+ ));
6466
+ $counter += 1;
6467
+ }
6468
+ }
6469
 
6470
+ if( $counter > 0 ){
6471
+ $template_variables['ModifiedFiles.NoFilesVisibility'] = 'hidden';
6472
+ }
6473
+ }
6474
  }
6475
 
6476
+ return SucuriScanTemplate::get_section('integrity-modifiedfiles', $template_variables);
6477
  }
6478
 
6479
  /**
6482
  * @return void
6483
  */
6484
  function sucuriscan_posthack_page(){
6485
+ SucuriScanInterface::check_permissions();
 
 
6486
 
6487
  $process_form = sucuriscan_posthack_process_form();
6488
 
6494
  'ResetPlugins' => sucuriscan_posthack_plugins($process_form),
6495
  );
6496
 
6497
+ echo SucuriScanTemplate::get_template('posthack', $template_variables);
6498
  }
6499
 
6500
  /**
6503
  * @return boolean TRUE if a form submission should be processed, FALSE otherwise.
6504
  */
6505
  function sucuriscan_posthack_process_form(){
6506
+ $process_form = SucuriScanRequest::post(':process_form', '(0|1)');
6507
+
6508
+ if(
6509
+ SucuriScanInterface::check_nonce()
6510
+ && $process_form !== FALSE
6511
+ ){
6512
+ if( $process_form === '1' ){
6513
  return TRUE;
6514
  } else {
6515
+ SucuriScanInterface::error('You need to confirm that you understand the risk of this operation.');
6516
  }
6517
  }
6518
 
6529
  $template_variables = array(
6530
  'WPConfigUpdate.Visibility' => 'hidden',
6531
  'WPConfigUpdate.NewConfig' => '',
6532
+ 'SecurityKeys.List' => '',
6533
  );
6534
 
6535
  // Update all WordPress secret keys.
6536
+ if( $process_form && SucuriScanRequest::post(':update_wpconfig', '1') ){
6537
+ $wpconfig_process = SucuriScanEvent::set_new_config_keys();
6538
 
6539
  if( $wpconfig_process ){
6540
  $template_variables['WPConfigUpdate.Visibility'] = 'visible';
6541
 
6542
  if( $wpconfig_process['updated'] === TRUE ){
6543
+ SucuriScanInterface::info( 'Secret keys updated successfully (summary of the operation bellow).' );
6544
  $template_variables['WPConfigUpdate.NewConfig'] .= "// Old Keys\n";
6545
  $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['old_keys_string'];
6546
  $template_variables['WPConfigUpdate.NewConfig'] .= "//\n";
6547
  $template_variables['WPConfigUpdate.NewConfig'] .= "// New Keys\n";
6548
  $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['new_keys_string'];
6549
  } else {
6550
+ SucuriScanInterface::error(
6551
+ '<code>wp-config.php</code> file is not writable, replace the '
6552
+ . 'old configuration file with the new values shown bellow.'
6553
+ );
6554
  $template_variables['WPConfigUpdate.NewConfig'] = $wpconfig_process['new_wpconfig'];
6555
  }
6556
  } else {
6557
+ SucuriScanInterface::error('<code>wp-config.php</code> file was not found in the default location.' );
6558
+ }
6559
+ }
6560
+
6561
+ // Display the current status of the security keys.
6562
+ $current_keys = SucuriScanOption::get_security_keys();
6563
+ $counter = 0;
6564
+
6565
+ foreach( $current_keys as $key_status => $key_list ){
6566
+ foreach( $key_list as $key_name => $key_value ){
6567
+ $css_class = ( $counter %2 == 0 ) ? '' : 'alternate';
6568
+ $key_value = SucuriScan::excerpt( $key_value, 50 );
6569
+
6570
+ switch( $key_status ){
6571
+ case 'good':
6572
+ $key_status_text = 'good';
6573
+ $key_status_css_class = 'success';
6574
+ break;
6575
+ case 'bad':
6576
+ $key_status_text = 'not randomized';
6577
+ $key_status_css_class = 'warning';
6578
+ break;
6579
+ case 'missing':
6580
+ $key_value = '';
6581
+ $key_status_text = 'not set';
6582
+ $key_status_css_class = 'danger';
6583
+ break;
6584
+ }
6585
+
6586
+ if( isset($key_status_text) ){
6587
+ $template_variables['SecurityKeys.List'] .= SucuriScanTemplate::get_snippet('posthack-updatesecretkeys', array(
6588
+ 'SecurityKey.CssClass' => $css_class,
6589
+ 'SecurityKey.KeyName' => SucuriScan::escape($key_name),
6590
+ 'SecurityKey.KeyValue' => SucuriScan::escape($key_value),
6591
+ 'SecurityKey.KeyStatusText' => $key_status_text,
6592
+ 'SecurityKey.KeyStatusCssClass' => $key_status_css_class,
6593
+ ));
6594
+ $counter += 1;
6595
+ }
6596
  }
6597
  }
6598
 
6599
+ return SucuriScanTemplate::get_section('posthack-updatesecretkeys', $template_variables);
6600
  }
6601
 
6602
  /**
6625
  $user->user_registered_formatted = date('D, M/Y H:i', $user->user_registered_timestamp);
6626
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6627
 
6628
+ $template_variables['ResetPassword.UserList'] .= SucuriScanTemplate::get_snippet('posthack-resetpassword', array(
6629
  'ResetPassword.UserId' => $user->ID,
6630
+ 'ResetPassword.Username' => SucuriScan::escape($user->user_login),
6631
+ 'ResetPassword.Displayname' => SucuriScan::escape($user->display_name),
6632
+ 'ResetPassword.Email' => SucuriScan::escape($user->user_email),
6633
  'ResetPassword.Registered' => $user->user_registered_formatted,
6634
  'ResetPassword.Roles' => implode(', ', $user->roles),
6635
+ 'ResetPassword.CssClass' => $css_class,
6636
  ));
6637
 
 
6638
  $counter += 1;
6639
  }
6640
  }
6641
 
6642
+ return SucuriScanTemplate::get_section('posthack-resetpassword', $template_variables);
6643
  }
6644
 
6645
  /**
6649
  * @return void
6650
  */
6651
  function sucuriscan_reset_user_password( $process_form=FALSE ){
6652
+ if( $process_form && SucuriScanRequest::post(':reset_password') ){
6653
+ $user_identifiers = SucuriScanRequest::post('user_ids', '_array');
6654
+ $pwd_changed = array();
6655
+ $pwd_not_changed = array();
6656
 
6657
  if( is_array($user_identifiers) && !empty($user_identifiers) ){
6658
  arsort($user_identifiers);
6659
 
6660
  foreach( $user_identifiers as $user_id ){
6661
+ if( SucuriScanEvent::set_new_password($user_id) ){
6662
  $pwd_changed[] = $user_id;
6663
  } else {
6664
  $pwd_not_changed[] = $user_id;
6666
  }
6667
 
6668
  if( !empty($pwd_changed) ){
6669
+ SucuriScanInterface::info( 'Password changed successfully for users: ' . implode(', ',$pwd_changed) );
6670
  }
6671
 
6672
  if( !empty($pwd_not_changed) ){
6673
+ SucuriScanInterface::error( 'Password change failed for users: ' . implode(', ',$pwd_not_changed) );
6674
  }
6675
  } else {
6676
+ SucuriScanInterface::error( 'You did not select a user from the list.' );
6677
  }
6678
  }
6679
  }
6690
  );
6691
 
6692
  sucuriscan_posthack_reinstall_plugins($process_form);
6693
+ $all_plugins = SucuriScanAPI::get_plugins();
6694
  $counter = 0;
6695
 
6696
  foreach( $all_plugins as $plugin_path => $plugin_data ){
6700
  $plugin_status = $plugin_data['IsPluginActive'] ? 'active' : 'not active';
6701
  $plugin_status_class = $plugin_data['IsPluginActive'] ? 'success' : 'default';
6702
 
6703
+ $template_variables['ResetPlugin.PluginList'] .= SucuriScanTemplate::get_snippet('posthack-resetplugins', array(
6704
  'ResetPlugin.CssClass' => $css_class,
6705
  'ResetPlugin.Disabled' => $input_disabled,
6706
+ 'ResetPlugin.PluginPath' => SucuriScan::escape($plugin_path),
6707
+ 'ResetPlugin.Plugin' => SucuriScan::excerpt($plugin_data['Name'], 35),
6708
  'ResetPlugin.Version' => $plugin_data['Version'],
6709
  'ResetPlugin.Type' => $plugin_data['PluginType'],
6710
  'ResetPlugin.TypeClass' => $plugin_type_class,
6715
  $counter += 1;
6716
  }
6717
 
6718
+ return SucuriScanTemplate::get_section('posthack-resetplugins', $template_variables);
6719
  }
6720
 
6721
  /**
6732
  include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
6733
  include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); // For plugins_api.
6734
 
6735
+ if( $plugin_list = SucuriScanRequest::post('plugin_path', '_array') ){
 
 
 
6736
  // Create an instance of the FileInfo interface.
6737
  $sucuri_fileinfo = new SucuriScanFileInfo();
6738
  $sucuri_fileinfo->ignore_files = FALSE;
6739
  $sucuri_fileinfo->ignore_directories = FALSE;
6740
 
6741
  // Get (possible) cached information from the installed plugins.
6742
+ $all_plugins = SucuriScanAPI::get_plugins();
6743
 
6744
  // Loop through all the installed plugins.
6745
  foreach( $_POST['plugin_path'] as $plugin_path ){
6748
 
6749
  // Check if the plugin can be downloaded from the free market.
6750
  if( $plugin_data['IsFreePlugin'] === TRUE ){
6751
+ $plugin_info = SucuriScanAPI::get_remote_plugin_data($plugin_data['RepositoryName']);
6752
 
6753
  if( $plugin_info ){
6754
  // First, remove all files/sub-folders from the plugin's directory.
6760
  $upgrader = new Plugin_Upgrader($upgrader_skin);
6761
  $upgrader->install($plugin_info->download_link);
6762
  } else {
6763
+ SucuriScanInterface::error( 'Could not establish a stable connection with the WordPress plugins market.' );
6764
  }
6765
  }
6766
  }
6767
  }
6768
  } else {
6769
+ SucuriScanInterface::error( 'You did not select a free plugin to reinstall.' );
6770
  }
6771
  }
6772
  }
6779
  * @return string Last-logings for the administrator accounts.
6780
  */
6781
  function sucuriscan_lastlogins_page(){
6782
+ SucuriScanInterface::check_permissions();
 
 
6783
 
6784
  // Page pseudo-variables initialization.
6785
  $template_variables = array(
6790
  'FailedLogins' => sucuriscan_failed_logins_panel(),
6791
  );
6792
 
6793
+ echo SucuriScanTemplate::get_template('lastlogins', $template_variables);
6794
  }
6795
 
6796
  /**
6814
  $admin->lastlogins = $last_logins['entries'];
6815
 
6816
  $user_snippet = array(
6817
+ 'AdminUsers.Username' => SucuriScan::escape($admin->user_login),
6818
+ 'AdminUsers.Email' => SucuriScan::escape($admin->user_email),
6819
  'AdminUsers.LastLogins' => '',
6820
  'AdminUsers.RegisteredAt' => 'Undefined',
6821
  'AdminUsers.UserURL' => admin_url('user-edit.php?user_id='.$admin->ID),
6831
 
6832
  foreach( $admin->lastlogins as $lastlogin ){
6833
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6834
+ $user_snippet['AdminUsers.LastLogins'] .= SucuriScanTemplate::get_snippet('lastlogins-admins-lastlogin', array(
6835
+ 'AdminUsers.RemoteAddr' => SucuriScan::escape($lastlogin->user_remoteaddr),
6836
+ 'AdminUsers.Datetime' => SucuriScan::escape($lastlogin->user_lastlogin),
6837
  'AdminUsers.CssClass' => $css_class,
6838
  ));
6839
  $counter += 1;
6840
  }
6841
  }
6842
 
6843
+ $template_variables['AdminUsers.List'] .= SucuriScanTemplate::get_snippet('lastlogins-admins', $user_snippet);
6844
  }
6845
 
6846
+ return SucuriScanTemplate::get_section('lastlogins-admins', $template_variables);
6847
  }
6848
 
6849
  /**
6855
  */
6856
  function sucuriscan_lastlogins_all(){
6857
  $max_per_page = SUCURISCAN_LASTLOGINS_USERSLIMIT;
6858
+ $page_number = SucuriScanTemplate::get_page_number();
6859
  $offset = ($max_per_page * $page_number) - $max_per_page;
6860
 
6861
  $template_variables = array(
6868
  );
6869
 
6870
  if( !sucuriscan_lastlogins_datastore_is_writable() ){
6871
+ SucuriScanInterface::error( 'Last-logins datastore file is not writable: <code>'.sucuriscan_lastlogins_datastore_filepath().'</code>' );
6872
  }
6873
 
6874
  $counter = 0;
6877
 
6878
  if( $last_logins['total'] > $max_per_page ){
6879
  $template_variables['UserList.PaginationVisibility'] = 'visible';
6880
+ }
6881
+
6882
+ if( $last_logins['total'] > 0 ){
6883
  $template_variables['UserList.NoItemsVisibility'] = 'hidden';
6884
  }
6885
 
6894
  'UserList.Displayname' => '',
6895
  'UserList.Email' => '',
6896
  'UserList.Registered' => '',
6897
+ 'UserList.RemoteAddr' => SucuriScan::escape($user->user_remoteaddr),
6898
+ 'UserList.Hostname' => SucuriScan::escape($user->user_hostname),
6899
+ 'UserList.Datetime' => SucuriScan::escape($user->user_lastlogin),
6900
+ 'UserList.TimeAgo' => SucuriScan::time_ago($user->user_lastlogin),
6901
  'UserList.UserURL' => admin_url('user-edit.php?user_id='.$user->user_id),
6902
  'UserList.CssClass' => $css_class,
6903
  );
6904
 
6905
  if( $user->user_exists ){
6906
+ $user_dataset['UserList.Username'] = SucuriScan::escape($user->user_login);
6907
+ $user_dataset['UserList.Displayname'] = SucuriScan::escape($user->display_name);
6908
+ $user_dataset['UserList.Email'] = SucuriScan::escape($user->user_email);
6909
+ $user_dataset['UserList.Registered'] = SucuriScan::escape($user->user_registered);
6910
  }
6911
 
6912
+ $template_variables['UserList'] .= SucuriScanTemplate::get_snippet('lastlogins-all', $user_dataset);
6913
  }
6914
 
6915
  // Generate the pagination for the list.
6916
+ $template_variables['UserList.Pagination'] = SucuriScanTemplate::get_pagination(
6917
  '%%SUCURI.URL.Lastlogins%%',
6918
  $last_logins['total'],
6919
  $max_per_page
6920
  );
6921
 
6922
+ return SucuriScanTemplate::get_section('lastlogins-all', $template_variables);
6923
  }
6924
 
6925
  /**
6928
  * @return string Absolute filepath where the user's last login information is stored.
6929
  */
6930
  function sucuriscan_lastlogins_datastore_filepath(){
6931
+ return SucuriScan::datastore_folder_path( 'sucuri-lastlogins.php' );
6932
  }
6933
 
6934
  /**
6957
  */
6958
  function sucuriscan_lastlogins_datastore_is_writable(){
6959
  $datastore_filepath = sucuriscan_lastlogins_datastore_exists();
6960
+
6961
  if($datastore_filepath){
6962
  if( !is_writable($datastore_filepath) ){
6963
  @chmod($datastore_filepath, 0644);
6964
  }
6965
+
6966
+ if( is_writable($datastore_filepath) ){
6967
+ return $datastore_filepath;
6968
+ }
6969
  }
6970
+
6971
  return FALSE;
6972
  }
6973
 
6979
  */
6980
  function sucuriscan_lastlogins_datastore_is_readable(){
6981
  $datastore_filepath = sucuriscan_lastlogins_datastore_exists();
6982
+
6983
  if( $datastore_filepath && is_readable($datastore_filepath) ){
6984
  return $datastore_filepath;
6985
  }
6986
+
6987
  return FALSE;
6988
  }
6989
 
6999
 
7000
  if($datastore_filepath){
7001
  $current_user = get_user_by('login', $user_login);
7002
+ $remote_addr = SucuriScan::get_remote_addr();
7003
 
7004
  $login_info = array(
7005
  'user_id' => $current_user->ID,
7035
 
7036
  if( $datastore_filepath ){
7037
  $parsed_lines = 0;
7038
+ $data_lines = SucuriScanFileInfo::file_lines($datastore_filepath);
7039
 
7040
  if( $data_lines ){
7041
  /**
7064
  for( $i=$offset; $i<$total_lines; $i++ ){
7065
  $line = $reversed_lines[$i] ? trim($reversed_lines[$i]) : '';
7066
 
7067
+ if( preg_match('/^a:[0-9]+:.+/', $line) ){
7068
  $last_login = @unserialize($line);
7069
 
7070
  // Only administrators can see all login stats.
7094
  $parsed_lines += 1;
7095
  }
7096
 
7097
+ if( preg_match('/^[0-9]+$/', $limit) && $limit>0 ){
7098
  if( $parsed_lines >= $limit ){ break; }
7099
  }
7100
  }
7124
  return $login_url;
7125
  }
7126
 
7127
+ if( SucuriScanOption::get_option(':lastlogin_redirection') == 'enabled' ){
 
7128
  add_filter('login_redirect', 'sucuriscan_login_redirect', 10, 3);
7129
  }
7130
  }
7136
  * @return void
7137
  */
7138
  function sucuriscan_get_user_lastlogin(){
7139
+ if(
7140
+ current_user_can('manage_options')
7141
+ && SucuriScanRequest::get(':lastlogin', '1')
7142
+ ){
7143
  $current_user = wp_get_current_user();
7144
 
7145
  // Select the penultimate entry, not the last one.
7148
  if( isset($last_logins['entries'][1]) ){
7149
  $row = $last_logins['entries'][1];
7150
 
7151
+ $message_tpl = 'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>';
7152
  $lastlogin_message = sprintf( $message_tpl, date('d/M/Y H:i'), $row->user_remoteaddr, $row->user_hostname );
7153
+ $lastlogin_message .= chr(32).'(<a href="'.SucuriScanTemplate::get_url('lastlogins').'">view all logs</a>)';
7154
+ SucuriScanInterface::info( $lastlogin_message );
7155
  }
7156
  }
7157
  }
7172
  );
7173
 
7174
  $logged_in_users = sucuriscan_get_online_users(TRUE);
7175
+
7176
  if( is_array($logged_in_users) && !empty($logged_in_users) ){
7177
  $template_variables['LoggedInUsers.Total'] = count($logged_in_users);
7178
  $counter = 0;
7182
  $logged_in_user['last_activity_datetime'] = date('d/M/Y H:i', $logged_in_user['last_activity']);
7183
  $logged_in_user['user_registered_datetime'] = date('d/M/Y H:i', strtotime($logged_in_user['user_registered']));
7184
 
7185
+ $template_variables['LoggedInUsers.List'] .= SucuriScanTemplate::get_snippet('lastlogins-loggedin', array(
7186
+ 'LoggedInUsers.Id' => SucuriScan::escape($logged_in_user['user_id']),
7187
  'LoggedInUsers.UserURL' => admin_url('user-edit.php?user_id='.$logged_in_user['user_id']),
7188
+ 'LoggedInUsers.UserLogin' => SucuriScan::escape($logged_in_user['user_login']),
7189
+ 'LoggedInUsers.UserEmail' => SucuriScan::escape($logged_in_user['user_email']),
7190
+ 'LoggedInUsers.LastActivity' => SucuriScan::escape($logged_in_user['last_activity_datetime']),
7191
+ 'LoggedInUsers.Registered' => SucuriScan::escape($logged_in_user['user_registered_datetime']),
7192
+ 'LoggedInUsers.RemoveAddr' => SucuriScan::escape($logged_in_user['remote_addr']),
7193
  'LoggedInUsers.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate'
7194
  ));
7195
  }
7196
  }
7197
 
7198
+ return SucuriScanTemplate::get_section('lastlogins-loggedin', $template_variables);
7199
  }
7200
 
7201
  /**
7207
  function sucuriscan_get_online_users( $add_current_user=FALSE ){
7208
  $users = array();
7209
 
7210
+ if( SucuriScan::is_multisite() ){
7211
  $users = get_site_transient('online_users');
7212
  } else {
7213
  $users = get_transient('online_users');
7238
  function sucuriscan_save_online_users( $logged_in_users=array() ){
7239
  $expiration = 30 * 60;
7240
 
7241
+ if( SucuriScan::is_multisite() ){
7242
  return set_site_transient('online_users', $logged_in_users, $expiration);
7243
  } else {
7244
  return set_transient('online_users', $logged_in_users, $expiration);
7253
  * @return void
7254
  */
7255
  function sucuriscan_unset_online_user_on_logout(){
7256
+ $remote_addr = SucuriScan::get_remote_addr();
7257
  $current_user = wp_get_current_user();
7258
  $user_id = $current_user->ID;
7259
 
7303
  // Get logged in user information.
7304
  $current_user = ($user instanceof WP_User) ? $user : wp_get_current_user();
7305
  $current_user_id = $current_user->ID;
7306
+ $remote_addr = SucuriScan::get_remote_addr();
7307
  $current_time = current_time('timestamp');
7308
  $logged_in_users = sucuriscan_get_online_users();
7309
 
7314
  'user_email' => $current_user->user_email,
7315
  'user_registered' => $current_user->user_registered,
7316
  'last_activity' => $current_time,
7317
+ 'remote_addr' => $remote_addr,
7318
  );
7319
 
7320
  if( !is_array($logged_in_users) || empty($logged_in_users) ){
7372
  'FailedLogins.WarningVisibility' => 'visible',
7373
  );
7374
 
7375
+ $max_failed_logins = SucuriScanOption::get_option(':maximum_failed_logins');
7376
+ $notify_bruteforce_attack = SucuriScanOption::get_option(':notify_bruteforce_attack');
7377
  $failed_logins = sucuriscan_get_failed_logins();
7378
 
7379
  if( $failed_logins ){
7382
  foreach( $failed_logins['entries'] as $login_data ){
7383
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
7384
 
7385
+ $template_variables['FailedLogins.List'] .= SucuriScanTemplate::get_snippet('lastlogins-failedlogins', array(
7386
  'FailedLogins.CssClass' => $css_class,
7387
  'FailedLogins.Num' => ($counter + 1),
7388
+ 'FailedLogins.Username' => SucuriScan::escape($login_data['user_login']),
7389
+ 'FailedLogins.RemoteAddr' => SucuriScan::escape($login_data['remote_addr']),
7390
  'FailedLogins.Datetime' => date('d/M/Y H:i', $login_data['attempt_time']),
7391
+ 'FailedLogins.UserAgent' => SucuriScan::escape($login_data['user_agent']),
7392
  ));
7393
 
7394
  $counter += 1;
7405
  $template_variables['FailedLogins.WarningVisibility'] = 'hidden';
7406
  }
7407
 
7408
+ return SucuriScanTemplate::get_section('lastlogins-failedlogins', $template_variables);
7409
  }
7410
 
7411
  /**
7420
  * @return string The full (relative) path where the file is located.
7421
  */
7422
  function sucuriscan_failed_logins_datastore_path( $reset=FALSE ){
7423
+ $datastore_path = SucuriScan::datastore_folder_path('sucuri-failedlogins.php');
7424
  $default_content = sucuriscan_failed_logins_default_content();
7425
 
7426
  // Create the file if it does not exists.
7466
  $default_content_n = substr_count($default_content, "\n");
7467
 
7468
  if( $datastore_path ){
7469
+ $lines = SucuriScanFileInfo::file_lines($datastore_path);
7470
 
7471
  if( $lines ){
7472
  $failed_logins = array(
7480
  // Read and parse all the entries found in the datastore file.
7481
  foreach( $lines as $i => $line ){
7482
  if( $i >= $default_content_n ){
7483
+ $login_data = @json_decode( trim($line), TRUE );
7484
  $login_data['attempt_date'] = date('r', $login_data['attempt_time']);
7485
 
7486
  if( !$login_data['user_agent'] ){
7523
  $login_data = json_encode(array(
7524
  'user_login' => $user_login,
7525
  'attempt_time' => time(),
7526
+ 'remote_addr' => SucuriScan::get_remote_addr(),
7527
+ 'user_agent' => SucuriScan::get_user_agent(),
7528
  ));
7529
 
7530
  $logged = @file_put_contents( $datastore_path, $login_data . "\n", FILE_APPEND );
7546
  */
7547
  function sucuriscan_report_failed_logins( $failed_logins=array() ){
7548
  if( $failed_logins && $failed_logins['count'] > 0 ){
7549
+ $prettify_mails = SucuriScanMail::prettify_mails();
7550
  $mail_content = '';
7551
 
7552
  if( $prettify_mails ){
7582
  }
7583
  }
7584
 
7585
+ if( $prettify_mails ){
7586
+ $table_html .= '</tbody>';
7587
+ $table_html .= '</table>';
7588
+ $mail_content = $table_html;
7589
+ }
7590
+
7591
+ if( SucuriScanEvent::notify_event( 'bruteforce_attack', $mail_content ) ){
7592
+ sucuriscan_reset_failed_logins();
7593
+
7594
+ return TRUE;
7595
+ }
7596
+ }
7597
+
7598
+ return FALSE;
7599
+ }
7600
+
7601
+ /**
7602
+ * Remove all the entries in the datastore file where the failed logins are
7603
+ * being kept. The execution of this function will not delete the file (which is
7604
+ * likely the best move) but rather will clean its content and append the
7605
+ * default code defined by another function above.
7606
+ *
7607
+ * @return boolean Whether the datastore file was resetted or not.
7608
+ */
7609
+ function sucuriscan_reset_failed_logins(){
7610
+ return (bool) sucuriscan_failed_logins_datastore_path(TRUE);
7611
+ }
7612
+
7613
+ /**
7614
+ * Print a HTML code with the settings of the plugin.
7615
+ *
7616
+ * @return void
7617
+ */
7618
+ function sucuriscan_settings_page(){
7619
+ SucuriScanInterface::check_permissions();
7620
+
7621
+ $template_variables = array(
7622
+ 'PageTitle' => 'Settings',
7623
+ 'Settings.General' => sucuriscan_settings_general(),
7624
+ 'Settings.Notifications' => sucuriscan_settings_notifications(),
7625
+ 'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
7626
+ );
7627
+
7628
+ echo SucuriScanTemplate::get_template('settings', $template_variables);
7629
+ }
7630
+
7631
+ /**
7632
+ * Process the requests sent by the form submissions originated in the settings
7633
+ * page, all forms must have a nonce field that will be checked against the one
7634
+ * generated in the template render function.
7635
+ *
7636
+ * @param boolean $page_nonce True if the nonce is valid, False otherwise.
7637
+ * @return void
7638
+ */
7639
+ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
7640
+ global $sucuriscan_schedule_allowed,
7641
+ $sucuriscan_interface_allowed,
7642
+ $sucuriscan_notify_options,
7643
+ $sucuriscan_emails_per_hour,
7644
+ $sucuriscan_maximum_failed_logins,
7645
+ $sucuriscan_verify_ssl_cert;
7646
+
7647
+ // Use this conditional to avoid double checking.
7648
+ if( is_null($page_nonce) ){
7649
+ $page_nonce = SucuriScanInterface::check_nonce();
7650
+ }
7651
+
7652
+ if( $page_nonce ){
7653
+
7654
+ // Recover API key through the email registered previously.
7655
+ if( SucuriScanRequest::post(':recover_key') !== FALSE ){
7656
+ SucuriScanAPI::recover_key();
7657
+ }
7658
+
7659
+ // Save API key after it was recovered by the administrator.
7660
+ if( $api_key = SucuriScanRequest::post(':manual_api_key') ){
7661
+ SucuriScanAPI::set_plugin_key( $api_key, TRUE );
7662
+ SucuriScanEvent::schedule_task();
7663
+ }
7664
+
7665
+ // Remove API key from the local storage.
7666
+ if( SucuriScanRequest::post(':remove_api_key') !== FALSE ){
7667
+ SucuriScanAPI::set_plugin_key('');
7668
+ wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7669
+ SucuriScanEvent::notify_event( 'plugin_change', 'Sucuri API key removed' );
7670
+ }
7671
+
7672
+ // Enable or disable the filesystem scanner.
7673
+ if( $fs_scanner = SucuriScanRequest::post(':fs_scanner', '(en|dis)able') ){
7674
+ $action_d = $fs_scanner . 'd';
7675
+ SucuriScanOption::update_option(':fs_scanner', $action_d);
7676
+ SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner was: ' . $action_d );
7677
+ SucuriScanInterface::info( 'Filesystem scanner was <code>' . $action_d . '</code>' );
7678
+ }
7679
+
7680
+ // Enable or disable the filesystem scanner for modified files.
7681
+ if( $scan_modfiles = SucuriScanRequest::post(':scan_modfiles', '(en|dis)able') ){
7682
+ $action_d = $scan_modfiles . 'd';
7683
+ SucuriScanOption::update_option(':scan_modfiles', $action_d);
7684
+ SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner for modified files was: ' . $action_d );
7685
+ SucuriScanInterface::info( 'Filesystem scanner for modified files was <code>' . $action_d . '</code>' );
7686
+ }
7687
+
7688
+ // Enable or disable the filesystem scanner for file integrity.
7689
+ if( $scan_checksums = SucuriScanRequest::post(':scan_checksums', '(en|dis)able') ){
7690
+ $action_d = $scan_checksums . 'd';
7691
+ SucuriScanOption::update_option(':scan_checksums', $action_d);
7692
+ SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner for file integrity was: ' . $action_d );
7693
+ SucuriScanInterface::info( 'Filesystem scanner for file integrity was <code>' . $action_d . '</code>' );
7694
+ }
7695
+
7696
+ // Modify the schedule of the filesystem scanner.
7697
+ if( $frequency = SucuriScanRequest::post(':scan_frequency') ){
7698
+ $allowed_frequency = array_keys($sucuriscan_schedule_allowed);
7699
+
7700
+ if( in_array($frequency, $allowed_frequency) ){
7701
+ SucuriScanOption::update_option(':scan_frequency', $frequency);
7702
+ wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7703
+
7704
+ if( $frequency != '_oneoff' ){
7705
+ wp_schedule_event( time()+10, $frequency, 'sucuriscan_scheduled_scan' );
7706
+ }
7707
+
7708
+ SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanning frequency changed to: ' . $frequency );
7709
+ SucuriScanInterface::info( 'Filesystem scan scheduled to run <code>'.$frequency.'</code>' );
7710
+ }
7711
+ }
7712
+
7713
+ // Set the method (aka. interface) that will be used to scan the site.
7714
+ if( $interface = SucuriScanRequest::post(':scan_interface') ){
7715
+ $allowed_values = array_keys($sucuriscan_interface_allowed);
7716
+
7717
+ if( in_array($interface, $allowed_values) ){
7718
+ SucuriScanOption::update_option(':scan_interface', $interface);
7719
+ SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanning interface changed to: ' . $interface );
7720
+ SucuriScanInterface::info( 'Filesystem scan interface set to <code>'.$interface.'</code>' );
7721
+ }
7722
+ }
7723
+
7724
+ // Update the value for the maximum emails per hour.
7725
+ if( $per_hour = SucuriScanRequest::post(':emails_per_hour') ){
7726
+ if( array_key_exists($per_hour, $sucuriscan_emails_per_hour) ){
7727
+ $per_hour_label = $sucuriscan_emails_per_hour[$per_hour];
7728
+ SucuriScanOption::update_option( ':emails_per_hour', $per_hour );
7729
+ SucuriScanEvent::notify_event( 'plugin_change', 'Maximum email notifications per hour changed' );
7730
+ SucuriScanInterface::info( 'E-mail notifications: <code>' . $per_hour_label . '</code>' );
7731
+ } else {
7732
+ SucuriScanInterface::error( 'Invalid value for the maximum emails per hour.' );
7733
+ }
7734
+ }
7735
+
7736
+ // Update the email where the event notifications will be sent.
7737
+ if( $new_email = SucuriScanRequest::post(':notify_to') ){
7738
+ if( SucuriScan::is_valid_email($new_email) ){
7739
+ SucuriScanOption::update_option( ':notify_to', $new_email );
7740
+ SucuriScanEvent::notify_event( 'plugin_change', 'Email address to get the event notifications was changed' );
7741
+ SucuriScanInterface::info( 'All the event notifications will be sent to the email specified.' );
7742
+ } else {
7743
+ SucuriScanInterface::error( 'Email format not supported.' );
7744
+ }
7745
+ }
7746
+
7747
+ // Update the maximum failed logins per hour before consider it a brute-force attack.
7748
+ if( $failed_logins = SucuriScanRequest::post(':maximum_failed_logins') ){
7749
+ if( array_key_exists($failed_logins, $sucuriscan_maximum_failed_logins) ){
7750
+ SucuriScanOption::update_option( ':maximum_failed_logins', $failed_logins );
7751
+ $message = 'You will receive an email with a report containing information of the failed
7752
+ logins occurred during the last hour if its quantity is bigger than the value you just
7753
+ selected, which is <code>' . $failed_logins . '</code>. The information collected per
7754
+ hour will be resetted if the quantity of failed logins is lower.';
7755
+ SucuriScanEvent::notify_event( 'plugin_change', $message );
7756
+ SucuriScanInterface::info($message);
7757
+ } else {
7758
+ SucuriScanInterface::error( 'Invalid value for the maximum failed logins per hour before consider it a brute-force attack.' );
7759
+ }
7760
+ }
7761
+
7762
+ // Update the configuration for the SSL certificate verification.
7763
+ if( $verify_ssl_cert = SucuriScanRequest::post(':verify_ssl_cert') ){
7764
+ if( array_key_exists($verify_ssl_cert, $sucuriscan_verify_ssl_cert) ){
7765
+ SucuriScanOption::update_option( ':verify_ssl_cert', $verify_ssl_cert );
7766
+ $message = 'SSL certificates will not be verified when executing a HTTP request '
7767
+ . 'while communicating with the Sucuri API service, nor the official '
7768
+ . 'WordPress API.';
7769
+ SucuriScanEvent::notify_event( 'plugin_change', $message );
7770
+ SucuriScanInterface::info( $message );
7771
+ } else {
7772
+ SucuriScanInterface::error( 'Invalid value for the SSL certificate verification.' );
7773
+ }
7774
+ }
7775
+
7776
+ // Update the notification settings.
7777
+ if( SucuriScanRequest::post(':save_notification_settings') !== FALSE ){
7778
+ $options_updated_counter = 0;
7779
+
7780
+ foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
7781
+ if( $option_value = SucuriScanRequest::post($alert_type, '(1|0)') ){
7782
+ $option_value = ( $option_value == '1' ) ? 'enabled' : 'disabled';
7783
+ SucuriScanOption::update_option( $alert_type, $option_value );
7784
+ $options_updated_counter += 1;
7785
+ }
7786
+ }
7787
+
7788
+ if( $options_updated_counter > 0 ){
7789
+ SucuriScanEvent::notify_event( 'plugin_change', 'Email notification settings changed' );
7790
+ SucuriScanInterface::info( 'Notification settings updated.' );
7791
+ }
7792
+ }
7793
+
7794
+ // Reset all the plugin's options.
7795
+ if( SucuriScanRequest::post(':reset_options') !== FALSE ){
7796
+ // Notify the event before the API key is removed.
7797
+ $event_msg = 'All plugin\'s options were resetted.';
7798
+ SucuriScanEvent::report_event( 1, 'core', $event_msg );
7799
+ SucuriScanEvent::notify_event( 'plugin_change', $event_msg );
7800
+
7801
+ // Remove all plugin's options from the database.
7802
+ $options = SucuriScanOption::get_options_from_db('all_plugin_options');
7803
+
7804
+ foreach( $options as $option ){
7805
+ SucuriScanOption::delete_option( $option->option_name );
7806
+ }
7807
+
7808
+ // Remove the scheduled tasks.
7809
+ wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7810
+
7811
+ SucuriScanInterface::info( 'All plugin options were resetted successfully' );
7812
+ }
7813
+
7814
+ // Ignore a new event for email notifications.
7815
+ if( $action = SucuriScanRequest::post(':ignorerule_action', '(add|remove)') ){
7816
+ $ignore_rule = SucuriScanRequest::post(':ignorerule');
7817
+
7818
+ if( $action == 'add' ){
7819
+ if( SucuriScanOption::add_ignored_event($ignore_rule) ){
7820
+ SucuriScanInterface::info( 'Post-type ignored successfully.' );
7821
+ } else {
7822
+ SucuriScanInterface::error( 'The post-type is invalid or it may be already ignored.' );
7823
+ }
7824
+ }
7825
+
7826
+ elseif( $action == 'remove' ) {
7827
+ SucuriScanOption::remove_ignored_event($ignore_rule);
7828
+ SucuriScanInterface::info( 'Post-type removed from the list successfully.' );
7829
+ }
7830
+ }
7831
+
7832
+ }
7833
+ }
7834
+
7835
+ /**
7836
+ * Read and parse the content of the general settings template.
7837
+ *
7838
+ * @return string Parsed HTML code for the general settings panel.
7839
+ */
7840
+ function sucuriscan_settings_general(){
7841
+
7842
+ global $sucuriscan_schedule_allowed,
7843
+ $sucuriscan_interface_allowed,
7844
+ $sucuriscan_emails_per_hour,
7845
+ $sucuriscan_maximum_failed_logins,
7846
+ $sucuriscan_verify_ssl_cert;
7847
+
7848
+ // Check the nonce here to populate the value through other functions.
7849
+ $page_nonce = SucuriScanInterface::check_nonce();
7850
+
7851
+ // Process all form submissions.
7852
+ sucuriscan_settings_form_submissions($page_nonce);
7853
+
7854
+ // Register the site, get its API key, and store it locally for future usage.
7855
+ $api_registered_modal = '';
7856
+
7857
+ // Whether the form to manually add the API key should be shown or not.
7858
+ $display_manual_key_form = (bool) ( SucuriScanRequest::post(':recover_key') !== FALSE );
7859
+
7860
+ if( $page_nonce && SucuriScanRequest::post(':plugin_api_key') !== FALSE ){
7861
+ $registered = SucuriScanAPI::register_site();
7862
+
7863
+ if( $registered ){
7864
+ $api_registered_modal = SucuriScanTemplate::get_modal('settings-apiregistered', array(
7865
+ 'Title' => 'Site registered successfully',
7866
+ 'CssClass' => 'sucuriscan-apikey-registered',
7867
+ ));
7868
+ } else {
7869
+ $display_manual_key_form = TRUE;
7870
+ }
7871
+ }
7872
+
7873
+ // Get initial variables to decide some things bellow.
7874
+ $api_key = SucuriScanAPI::get_plugin_key();
7875
+ $fs_scanner = SucuriScanOption::get_option(':fs_scanner');
7876
+ $scan_freq = SucuriScanOption::get_option(':scan_frequency');
7877
+ $scan_interface = SucuriScanOption::get_option(':scan_interface');
7878
+ $scan_modfiles = SucuriScanOption::get_option(':scan_modfiles');
7879
+ $scan_checksums = SucuriScanOption::get_option(':scan_checksums');
7880
+ $emails_per_hour = SucuriScanOption::get_option(':emails_per_hour');
7881
+ $maximum_failed_logins = SucuriScanOption::get_option(':maximum_failed_logins');
7882
+ $verify_ssl_cert = SucuriScanOption::get_option(':verify_ssl_cert');
7883
+ $runtime_scan_human = SucuriScanOption::get_filesystem_runtime(TRUE);
7884
+
7885
+ // Generate the HTML code for the option list in the form select fields.
7886
+ $scan_freq_options = SucuriScanTemplate::get_select_options( $sucuriscan_schedule_allowed, $scan_freq );
7887
+ $scan_interface_options = SucuriScanTemplate::get_select_options( $sucuriscan_interface_allowed, $scan_interface );
7888
+ $emails_per_hour_options = SucuriScanTemplate::get_select_options( $sucuriscan_emails_per_hour, $emails_per_hour );
7889
+ $maximum_failed_logins_options = SucuriScanTemplate::get_select_options( $sucuriscan_maximum_failed_logins, $maximum_failed_logins );
7890
+ $verify_ssl_cert_options = SucuriScanTemplate::get_select_options( $sucuriscan_verify_ssl_cert, $verify_ssl_cert );
7891
+
7892
+ $template_variables = array(
7893
+ 'APIKey' => $api_key,
7894
+ 'APIKey.RecoverVisibility' => SucuriScanTemplate::visibility( !$api_key && !$display_manual_key_form ),
7895
+ 'APIKey.ManualKeyFormVisibility' => SucuriScanTemplate::visibility($display_manual_key_form),
7896
+ 'APIKey.RemoveVisibility' => SucuriScanTemplate::visibility($api_key),
7897
+ /* Filesystem scanner */
7898
+ 'FsScannerStatus' => 'Enabled',
7899
+ 'FsScannerSwitchText' => 'Disable',
7900
+ 'FsScannerSwitchValue' => 'disable',
7901
+ 'FsScannerSwitchCssClass' => 'button-danger',
7902
+ /* Scan modified files. */
7903
+ 'ScanModfilesStatus' => 'Enabled',
7904
+ 'ScanModfilesSwitchText' => 'Disable',
7905
+ 'ScanModfilesSwitchValue' => 'disable',
7906
+ 'ScanModfilesSwitchCssClass' => 'button-danger',
7907
+ /* Scan files checksum. */
7908
+ 'ScanChecksumsStatus' => 'Enabled',
7909
+ 'ScanChecksumsSwitchText' => 'Disable',
7910
+ 'ScanChecksumsSwitchValue' => 'disable',
7911
+ 'ScanChecksumsSwitchCssClass' => 'button-danger',
7912
+ /* Filsystem scanning frequency. */
7913
+ 'ScanningFrequency' => 'Undefined',
7914
+ 'ScanningFrequencyOptions' => $scan_freq_options,
7915
+ 'ScanningInterface' => ( $scan_interface ? $sucuriscan_interface_allowed[$scan_interface] : 'Undefined' ),
7916
+ 'ScanningInterfaceOptions' => $scan_interface_options,
7917
+ 'ScanningInterfaceVisibility' => SucuriScanTemplate::visibility( !SucuriScanFileInfo::is_spl_available() ),
7918
+ /* Filesystem scanning runtime. */
7919
+ 'ScanningRuntimeHuman' => $runtime_scan_human,
7920
+ 'ModalWhenAPIRegistered' => $api_registered_modal,
7921
+ 'NotifyTo' => SucuriScanOption::get_option(':notify_to'),
7922
+ 'EmailsPerHour' => 'Undefined',
7923
+ 'EmailsPerHourOptions' => $emails_per_hour_options,
7924
+ 'MaximumFailedLogins' => 'Undefined',
7925
+ 'MaximumFailedLoginsOptions' => $maximum_failed_logins_options,
7926
+ 'VerifySSLCert' => 'Undefined',
7927
+ 'VerifySSLCertOptions' => $verify_ssl_cert_options,
7928
+ 'ModalWhenAPIRegistered' => $api_registered_modal,
7929
+ );
7930
+
7931
+ if( $fs_scanner == 'disabled' ){
7932
+ $template_variables['FsScannerStatus'] = 'Disabled';
7933
+ $template_variables['FsScannerSwitchText'] = 'Enable';
7934
+ $template_variables['FsScannerSwitchValue'] = 'enable';
7935
+ $template_variables['FsScannerSwitchCssClass'] = 'button-success';
7936
+ }
7937
+
7938
+ if( $scan_modfiles == 'disabled' ){
7939
+ $template_variables['ScanModfilesStatus'] = 'Disabled';
7940
+ $template_variables['ScanModfilesSwitchText'] = 'Enable';
7941
+ $template_variables['ScanModfilesSwitchValue'] = 'enable';
7942
+ $template_variables['ScanModfilesSwitchCssClass'] = 'button-success';
7943
+ }
7944
+
7945
+ if( $scan_checksums == 'disabled' ){
7946
+ $template_variables['ScanChecksumsStatus'] = 'Disabled';
7947
+ $template_variables['ScanChecksumsSwitchText'] = 'Enable';
7948
+ $template_variables['ScanChecksumsSwitchValue'] = 'enable';
7949
+ $template_variables['ScanChecksumsSwitchCssClass'] = 'button-success';
7950
+ }
7951
+
7952
+ if( array_key_exists($scan_freq, $sucuriscan_schedule_allowed) ){
7953
+ $template_variables['ScanningFrequency'] = $sucuriscan_schedule_allowed[$scan_freq];
7954
+ }
7955
+
7956
+ if( array_key_exists($emails_per_hour, $sucuriscan_emails_per_hour) ){
7957
+ $template_variables['EmailsPerHour'] = $sucuriscan_emails_per_hour[$emails_per_hour];
7958
+ }
7959
+
7960
+ if( array_key_exists($maximum_failed_logins, $sucuriscan_maximum_failed_logins) ){
7961
+ $template_variables['MaximumFailedLogins'] = $sucuriscan_maximum_failed_logins[$maximum_failed_logins];
7962
+ }
7963
+
7964
+ if( array_key_exists($verify_ssl_cert, $sucuriscan_verify_ssl_cert) ){
7965
+ $template_variables['VerifySSLCert'] = $sucuriscan_verify_ssl_cert[$verify_ssl_cert];
7966
+ }
7967
+
7968
+ return SucuriScanTemplate::get_section('settings-general', $template_variables);
7969
+ }
7970
+
7971
+ /**
7972
+ * Read and parse the content of the notification settings template.
7973
+ *
7974
+ * @return string Parsed HTML code for the notification settings panel.
7975
+ */
7976
+ function sucuriscan_settings_notifications(){
7977
+ global $sucuriscan_notify_options;
7978
+
7979
+ $template_variables = array(
7980
+ 'NotificationOptions' => '',
7981
+ );
7982
+
7983
+ $counter = 0;
7984
 
7985
+ foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
7986
+ $alert_value = SucuriScanOption::get_option($alert_type);
7987
+ $checked = ( $alert_value == 'enabled' ? 'checked="checked"' : '' );
7988
+ $css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
7989
 
7990
+ $template_variables['NotificationOptions'] .= SucuriScanTemplate::get_snippet('settings-notifications', array(
7991
+ 'Notification.CssClass' => $css_class,
7992
+ 'Notification.Name' => $alert_type,
7993
+ 'Notification.Checked' => $checked,
7994
+ 'Notification.Label' => $alert_label,
7995
+ ));
7996
+ $counter += 1;
7997
  }
7998
 
7999
+ return SucuriScanTemplate::get_section('settings-notifications', $template_variables);
8000
  }
8001
 
8002
  /**
8003
+ * Read and parse the content of the ignored-rules settings template.
 
 
 
8004
  *
8005
+ * @return string Parsed HTML code for the ignored-rules settings panel.
8006
  */
8007
+ function sucuriscan_settings_ignore_rules(){
8008
+ $notify_new_site_content = SucuriScanOption::get_option(':notify_post_publication');
8009
+
8010
+ $template_variables = array(
8011
+ 'IgnoreRules.MessageVisibility' => 'visible',
8012
+ 'IgnoreRules.TableVisibility' => 'hidden',
8013
+ 'IgnoreRules.PostTypes' => '',
8014
+ );
8015
+
8016
+ if( $notify_new_site_content == 'enabled' ){
8017
+ $post_types = get_post_types();
8018
+ $ignored_events = SucuriScanOption::get_ignored_events();
8019
+
8020
+ $template_variables['IgnoreRules.MessageVisibility'] = 'hidden';
8021
+ $template_variables['IgnoreRules.TableVisibility'] = 'visible';
8022
+ $counter = 0;
8023
+
8024
+ foreach( $post_types as $post_type => $post_type_object ){
8025
+ $counter += 1;
8026
+ $css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
8027
+ $post_type_title = ucwords( str_replace('_', chr(32), $post_type) );
8028
+
8029
+ if( array_key_exists($post_type, $ignored_events) ){
8030
+ $is_ignored_text = 'YES';
8031
+ $was_ignored_at = @date('d/M/Y - H:i:s', $ignored_events[$post_type]);
8032
+ $is_ignored_class = 'danger';
8033
+ $button_action = 'remove';
8034
+ $button_class = 'button-primary';
8035
+ $button_text = 'Allow';
8036
+ } else {
8037
+ $is_ignored_text = 'NO';
8038
+ $was_ignored_at = 'Not ignored';
8039
+ $is_ignored_class = 'success';
8040
+ $button_action = 'add';
8041
+ $button_class = 'button-primary button-danger';
8042
+ $button_text = 'Ignore';
8043
+ }
8044
+
8045
+ $template_variables['IgnoreRules.PostTypes'] .= SucuriScanTemplate::get_snippet('settings-ignorerules', array(
8046
+ 'IgnoreRules.CssClass' => $css_class,
8047
+ 'IgnoreRules.Num' => $counter,
8048
+ 'IgnoreRules.PostTypeTitle' => $post_type_title,
8049
+ 'IgnoreRules.IsIgnored' => $is_ignored_text,
8050
+ 'IgnoreRules.WasIgnoredAt' => $was_ignored_at,
8051
+ 'IgnoreRules.IsIgnoredClass' => $is_ignored_class,
8052
+ 'IgnoreRules.PostType' => $post_type,
8053
+ 'IgnoreRules.Action' => $button_action,
8054
+ 'IgnoreRules.ButtonClass' => 'button ' . $button_class,
8055
+ 'IgnoreRules.ButtonText' => $button_text,
8056
+ ));
8057
+ }
8058
+ }
8059
+
8060
+ return SucuriScanTemplate::get_section('settings-ignorerules', $template_variables);
8061
  }
8062
 
8063
  /**
8070
  * @return void
8071
  */
8072
  function sucuriscan_infosys_page(){
8073
+ SucuriScanInterface::check_permissions();
8074
+
8075
+ // Process all form submissions.
8076
+ sucuriscan_infosys_form_submissions();
8077
 
8078
  // Page pseudo-variables initialization.
8079
  $template_variables = array(
8084
  'WordpressConfig' => sucuriscan_infosys_wpconfig(),
8085
  );
8086
 
8087
+ echo SucuriScanTemplate::get_template('infosys', $template_variables);
8088
  }
8089
 
8090
  /**
8094
  * @return string The HTML code displaying the information about the HTAccess rules.
8095
  */
8096
  function sucuriscan_infosys_htaccess(){
8097
+ $htaccess_path = SucuriScan::get_htaccess_path();
8098
 
8099
  $template_variables = array(
8100
  'HTAccess.Content' => '',
8129
  $template_variables['HTAccess.MessageVisible'] = 'visible';
8130
  }
8131
 
8132
+ return SucuriScanTemplate::get_section('infosys-htaccess', $template_variables);
8133
  }
8134
 
8135
  /**
8141
  */
8142
  function sucuriscan_htaccess_is_standard($rules=FALSE){
8143
  if( $rules===FALSE ){
8144
+ $htaccess_path = SucuriScan::get_htaccess_path();
8145
  $rules = $htaccess_path ? file_get_contents($htaccess_path) : '';
8146
  }
8147
 
8188
  $template_variables = array(
8189
  'WordpressConfig.Rules' => '',
8190
  'WordpressConfig.Total' => 0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8191
  );
8192
 
8193
+ $ignore_wp_rules = array( 'DB_PASSWORD' );
8194
+ $wp_config_path = SucuriScan::get_wpconfig_path();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8195
 
8196
+ if( $wp_config_path ){
8197
+ $wp_config_rules = array();
8198
+ $wp_config_content = SucuriScanFileInfo::file_lines($wp_config_path);
8199
 
8200
+ // Parse the main configuration file and look for constants and global variables.
8201
+ foreach( (array) $wp_config_content as $line ){
8202
+ // Detect PHP constants even if the line if indented.
8203
+ if( preg_match('/define\(/', $line) ){
8204
+ $line = preg_replace('*define\((.+)\);*', '$1', $line);
8205
+ $line_parts = explode(',', $line, 2);
 
8206
  }
 
 
 
 
 
8207
 
8208
+ // Detect global variables like the database table prefix.
8209
+ else if( preg_match('/^\$[a-zA-Z_]+/', $line) ){
8210
+ $line_parts = explode('=', $line, 2);
 
 
 
8211
  }
 
8212
 
8213
+ // Ignore other lines.
8214
+ else{ continue; }
 
8215
 
8216
+ // Clean and append the rule to the wp_config_rules variable.
8217
+ if( isset($line_parts) && count($line_parts)==2 ){
8218
+ $key_name = '';
8219
+ $key_value = '';
 
 
 
 
 
 
 
8220
 
8221
+ // TODO: A foreach loop is not really necessary, find a better way.
8222
+ foreach( $line_parts as $i => $line_part ){
8223
+ $line_part = trim($line_part);
8224
+ $line_part = ltrim($line_part, '$');
8225
+ $line_part = rtrim($line_part, ';');
8226
 
8227
+ // Remove single/double quotes at the beginning and end of the string.
8228
+ $line_part = ltrim($line_part, "'");
8229
+ $line_part = rtrim($line_part, "'");
8230
+ $line_part = ltrim($line_part, '"');
8231
+ $line_part = rtrim($line_part, '"');
 
 
 
 
 
 
8232
 
8233
+ // Assign the clean strings to specific variables.
8234
+ if( $i==0 ){ $key_name = $line_part; }
8235
+ if( $i==1 ){
8236
+ if( defined($key_name) ){
8237
+ $key_value = constant($key_name);
8238
+
8239
+ if( is_bool($key_value) ){
8240
+ $key_value = ( $key_value === TRUE ) ? 'TRUE' : 'FALSE';
8241
+ }
8242
+ } else {
8243
+ $key_value = $line_part;
8244
+ }
8245
+ }
8246
+ }
8247
 
8248
+ // Remove the value of sensitive variables like the database password.
8249
+ if( in_array($key_name, $ignore_wp_rules) ){
8250
+ $key_value = 'hidden';
 
 
8251
  }
 
8252
 
8253
+ // Append the value to the configuration rules.
8254
+ $wp_config_rules[$key_name] = $key_value;
 
8255
  }
8256
  }
8257
 
8258
+ // Pass the WordPress configuration rules to the template and show them.
8259
+ $counter = 0;
8260
+ foreach( $wp_config_rules as $var_name => $var_value ){
8261
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
8262
+ $label_css = 'sucuriscan-monospace';
 
 
 
 
8263
 
8264
+ if( empty($var_value) ){
8265
+ $var_value = 'empty';
8266
+ $label_css = 'sucuriscan-label-default';
8267
  }
8268
 
8269
+ elseif( $var_value == 'hidden' ){
8270
+ $label_css = 'sucuriscan-label-info';
8271
+ }
8272
 
8273
+ $template_variables['WordpressConfig.Total'] += 1;
8274
+ $template_variables['WordpressConfig.Rules'] .= SucuriScanTemplate::get_snippet('infosys-wpconfig', array(
8275
+ 'WordpressConfig.VariableName' => SucuriScan::escape($var_name),
8276
+ 'WordpressConfig.VariableValue' => SucuriScan::escape($var_value),
8277
+ 'WordpressConfig.VariableCssClass' => $label_css,
8278
+ 'WordpressConfig.CssClass' => $css_class,
8279
+ ));
8280
+ $counter += 1;
8281
  }
8282
+ }
8283
 
8284
+ return SucuriScanTemplate::get_section('infosys-wpconfig', $template_variables);
8285
+ }
 
 
 
 
 
8286
 
8287
+ /**
8288
+ * Retrieve a list with the scheduled tasks configured for the site.
8289
+ *
8290
+ * @return array A list of pseudo-variables and values that will replace them in the HTML template.
8291
+ */
8292
+ function sucuriscan_show_cronjobs(){
8293
+ $template_variables = array(
8294
+ 'Cronjobs.List' => '',
8295
+ 'Cronjobs.Total' => 0,
8296
+ );
8297
+
8298
+ $cronjobs = _get_cron_array();
8299
+ $schedules = wp_get_schedules();
8300
+ $date_format = _x('M j, Y - H:i', 'Publish box date format', 'cron-view' );
8301
+ $counter = 0;
8302
+
8303
+ foreach( $cronjobs as $timestamp => $cronhooks ){
8304
+ foreach( (array) $cronhooks as $hook => $events ){
8305
+ foreach( (array) $events as $key => $event ){
8306
+ $template_variables['Cronjobs.Total'] += 1;
8307
+ $template_variables['Cronjobs.List'] .= SucuriScanTemplate::get_snippet('infosys-cronjobs', array(
8308
+ 'Cronjob.Hook' => $hook,
8309
+ 'Cronjob.Schedule' => $event['schedule'],
8310
+ 'Cronjob.NextTime' => date_i18n($date_format, $timestamp),
8311
+ 'Cronjob.Arguments' => !empty($event['args']) ? implode(', ', $event['args']) : '<em>empty</em>',
8312
+ 'Cronjob.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate',
8313
+ ));
8314
+ $counter += 1;
8315
  }
8316
  }
 
8317
  }
8318
 
8319
+ return SucuriScanTemplate::get_section('infosys-cronjobs', $template_variables);
8320
  }
8321
 
8322
  /**
8323
+ * Process the requests sent by the form submissions originated in the infosys
8324
+ * page, all forms must have a nonce field that will be checked against the one
8325
+ * generated in the template render function.
8326
  *
8327
+ * @param boolean $page_nonce True if the nonce is valid, False otherwise.
8328
+ * @return void
8329
  */
8330
+ function sucuriscan_infosys_form_submissions(){
8331
+ if( SucuriScanInterface::check_nonce() ){
 
 
 
 
 
 
 
 
 
 
 
8332
 
8333
+ // Modify the scheduled tasks (run now, remove, re-schedule).
8334
+ $allowed_actions = '(runnow|hourly|twicedaily|daily|remove)';
8335
 
8336
+ if( $cronjob_action = SucuriScanRequest::post( ':cronjob_action', $allowed_actions ) ){
8337
+ $cronjobs = SucuriScanRequest::post(':cronjobs', '_array');
8338
 
8339
+ if( !empty($cronjobs) ){
8340
+ $total_tasks = count($cronjobs);
8341
 
8342
+ switch( $cronjob_action ){
8343
+ case 'runnow':
8344
+ SucuriScanInterface::info( $total_tasks . ' tasks were scheduled to run in the next ten seconds.' );
8345
+ foreach( $cronjobs as $task_name ){
8346
+ wp_schedule_single_event( time() + 600, $task_name );
8347
+ }
8348
+ break;
8349
+ case 'remove':
8350
+ SucuriScanInterface::info( $total_tasks . ' scheduled tasks were removed.' );
8351
+ foreach( $cronjobs as $task_name ){
8352
+ wp_clear_scheduled_hook($task_name);
8353
+ }
8354
+ break;
8355
+ default:
8356
+ SucuriScanInterface::info( $total_tasks . ' tasks were re-scheduled to run <code>' . $cronjob_action . '</code>.' );
8357
+ foreach( $cronjobs as $task_name ){
8358
+ wp_clear_scheduled_hook($task_name);
8359
+ $next_due = wp_next_scheduled($task_name);
8360
+ wp_schedule_event( $next_due, $cronjob_action, $task_name );
8361
+ }
8362
+ break;
8363
+ }
8364
+ } else {
8365
+ SucuriScanInterface::error( 'No scheduled tasks were selected from the list.' );
8366
+ }
8367
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8369
  }
8370
+ }
8371
 
8372
+ /**
8373
+ * Gather information from the server, database engine, and PHP interpreter.
8374
+ *
8375
+ * @return array A list of pseudo-variables and values that will replace them in the HTML template.
8376
+ */
8377
+ function sucuriscan_server_info(){
8378
+ global $wpdb;
 
 
 
 
8379
 
8380
  $template_variables = array(
8381
+ 'ServerInfo.Variables' => '',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8382
  );
8383
 
8384
+ $info_vars = array(
8385
+ 'Plugin_version' => SUCURISCAN_VERSION,
8386
+ 'Plugin_checksum' => SUCURISCAN_PLUGIN_CHECKSUM,
8387
+ 'Last_filesystem_scan' => SucuriScanOption::get_filesystem_runtime(TRUE),
8388
+ 'Using_CloudProxy' => 'No',
8389
+ 'Operating_system' => sprintf('%s (%d Bit)', PHP_OS, PHP_INT_SIZE*8),
8390
+ 'Server' => 'Unknown',
8391
+ 'Developer_mode' => 'OFF',
8392
+ 'Memory_usage' => 'N/A',
8393
+ 'MySQL_version' => '0.0',
8394
+ 'SQL_mode' => 'Not set',
8395
+ 'PHP_version' => PHP_VERSION,
8396
+ );
8397
+
8398
+ if( SucuriScan::is_behind_cloudproxy() ){
8399
+ $info_vars['Using_CloudProxy'] = 'Yes';
8400
  }
8401
 
8402
+ if( defined('WP_DEBUG') && WP_DEBUG ){
8403
+ $info_vars['Developer_mode'] = 'ON';
8404
  }
8405
 
8406
+ if( function_exists('memory_get_usage') ){
8407
+ $info_vars['Memory_usage'] = round(memory_get_usage() / 1024 / 1024, 2).' MB';
8408
  }
8409
 
8410
+ if( isset($_SERVER['SERVER_SOFTWARE']) ){
8411
+ $info_vars['Server'] = SucuriScan::escape($_SERVER['SERVER_SOFTWARE']);
8412
  }
8413
 
8414
+ if( $wpdb ){
8415
+ $info_vars['MySQL_version'] = $wpdb->get_var('SELECT VERSION() AS version');
8416
 
8417
+ $mysql_info = $wpdb->get_results('SHOW VARIABLES LIKE "sql_mode"');
8418
+ if( is_array($mysql_info) && !empty($mysql_info[0]->Value) ){
8419
+ $info_vars['SQL_mode'] = $mysql_info[0]->Value;
8420
+ }
8421
+ }
 
 
8422
 
8423
+ $field_names = array(
8424
+ 'safe_mode',
8425
+ 'allow_url_fopen',
8426
+ 'memory_limit',
8427
+ 'upload_max_filesize',
8428
+ 'post_max_size',
8429
+ 'max_execution_time',
8430
+ 'max_input_time',
8431
  );
8432
 
8433
+ foreach( $field_names as $php_flag ){
8434
+ $php_flag_value = ini_get($php_flag);
8435
+ $php_flag_name = 'PHP_' . $php_flag;
8436
+ $info_vars[$php_flag_name] = $php_flag_value ? $php_flag_value : 'N/A';
8437
+ }
8438
+
8439
  $counter = 0;
8440
 
8441
+ foreach( $info_vars as $var_name => $var_value ){
8442
+ $css_class = ( $counter %2 == 0 ) ? '' : 'alternate';
8443
+ $var_name = str_replace('_', chr(32), $var_name);
 
8444
 
8445
+ $template_variables['ServerInfo.Variables'] .= SucuriScanTemplate::get_snippet('infosys-serverinfo', array(
8446
+ 'ServerInfo.CssClass' => $css_class,
8447
+ 'ServerInfo.Title' => $var_name,
8448
+ 'ServerInfo.Value' => $var_value,
 
8449
  ));
8450
  $counter += 1;
8451
  }
8452
 
8453
+ return SucuriScanTemplate::get_section('infosys-serverinfo', $template_variables);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8454
  }
8455