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

Version Description

  • Multiple bug fixes (as reported on the support forums).
  • Added heartbeat for the file scans.
  • Code cleanup.
Download this release

Release Info

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

Code changes from version 1.6.8 to 1.6.9

inc/css/sucuriscan-default-css.css CHANGED
@@ -181,12 +181,13 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
181
.sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form select + select{width:112px}
182
.sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form select + select + select{width:60px}
183
.sucuriscan-monitoring-logs .sucuriscan-target-date{font-size:12px;color:#999;margin-right:5px}
184
/* Monitoring AccessLog Styles */
185
.sucuriscan-request-summary{margin:-15px;margin-top:-3px}
186
- .sucuriscan-request-summary ul{margin:0}
187
- .sucuriscan-request-summary label, .sucuriscan-request-summary span{display:inline-block;font-size:14px}
188
- .sucuriscan-request-summary label{width:200px;font-weight:bold}
189
- .sucuriscan-request-summary span{max-width:395px;font-family:monospace;vertical-align:top;word-break:break-all}
190
/* Hardening Status */
191
.sucuriscan-hstatus{position:relative;margin:0 -12px;padding:10px 12px;border:1px solid transparent}
192
.sucuriscan-hstatus-1{background-color:#dff0d8;color:#3c763d;border-color:#d6e9c6}
@@ -219,8 +220,9 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
219
.sucuriscan-maincontent .sucuriscan-full-textarea{width:100%;height:400px;line-height:normal;resize:vertical;padding:10px}
220
.sucuriscan-maincontent .sucuriscan-settings{margin-top:0}
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) {
181
.sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form select + select{width:112px}
182
.sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form select + select + select{width:60px}
183
.sucuriscan-monitoring-logs .sucuriscan-target-date{font-size:12px;color:#999;margin-right:5px}
184
+ .sucuriscan-monitoring-logs .sucuriscan-denial-type{font-size:14px}
185
+ .sucuriscan-monitoring-logs .sucuriscan-denial-type-date{font-style:italic;color:#999}
186
/* Monitoring AccessLog Styles */
187
.sucuriscan-request-summary{margin:-15px;margin-top:-3px}
188
+ .sucuriscan-request-summary td{font-size:14px}
189
+ .sucuriscan-request-summary tr td:first-child{font-weight:bold}
190
+ .sucuriscan-request-summary td+td{font-family:monospace;word-break:break-all}
191
/* Hardening Status */
192
.sucuriscan-hstatus{position:relative;margin:0 -12px;padding:10px 12px;border:1px solid transparent}
193
.sucuriscan-hstatus-1{background-color:#dff0d8;color:#3c763d;border-color:#d6e9c6}
220
.sucuriscan-maincontent .sucuriscan-full-textarea{width:100%;height:400px;line-height:normal;resize:vertical;padding:10px}
221
.sucuriscan-maincontent .sucuriscan-settings{margin-top:0}
222
.sucuriscan-maincontent .sucuriscan-settings form{display:inline-block}
223
+ .sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{width:220px}
224
.sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:0}
225
+ .sucuriscan-maincontent .sucuriscan-settings-heartbeat{}
226
.sucuriscan-maincontent .sucuriscan-wpcron-list{margin-top:0}
227
/* Responsive Styles */
228
@media (max-width: 620px) {
inc/images/menu-icon.png CHANGED
Binary file
inc/images/ok.png DELETED
Binary file
inc/images/warn.png DELETED
Binary file
inc/tpl/integrity-modifiedfiles.html.tpl CHANGED
@@ -22,9 +22,9 @@
22
</tr>
23
24
<tr>
25
- <th width="540">Filepath</th>
26
- <th>CheckSum</th>
27
- <th width="160">Modification</th>
28
</tr>
29
</thead>
30
22
</tr>
23
24
<tr>
25
+ <th>Filepath</th>
26
+ <th width="130">CheckSum</th>
27
+ <th width="200">Modification</th>
28
</tr>
29
</thead>
30
inc/tpl/lastlogins-all.html.tpl CHANGED
@@ -3,9 +3,12 @@
3
<thead>
4
<tr>
5
<th colspan="6" class="thead-with-button">
6
- <span>User last logins</span>
7
- <span class="thead-topright-action sucuriscan-lastlogin-outof">
8
- %%SUCURI.UserList.Limit%% per page out of %%SUCURI.UserList.Total%%
9
</span>
10
</th>
11
</tr>
3
<thead>
4
<tr>
5
<th colspan="6" class="thead-with-button">
6
+ <span>User last logins (%%SUCURI.UserList.Total%%)</span>
7
+ <span class="thead-topright-action">
8
+ <form action="%%SUCURI.URL.Lastlogins%%" method="post">
9
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
10
+ <button type="submit" name="sucuriscan_reset_lastlogins" class="button button-primary">Reset logs</button>
11
+ </form>
12
</span>
13
</th>
14
</tr>
inc/tpl/lastlogins-loggedin.html.tpl CHANGED
@@ -7,8 +7,8 @@
7
<tr>
8
<th>ID</th>
9
<th>Username</th>
10
- <th>Last Activity (GMT/UTC)</th>
11
- <th>Registered (GMT/UTC)</th>
12
<th>IP Address</th>
13
<th>&nbsp;</th>
14
</tr>
7
<tr>
8
<th>ID</th>
9
<th>Username</th>
10
+ <th>Last Activity</th>
11
+ <th>Registered</th>
12
<th>IP Address</th>
13
<th>&nbsp;</th>
14
</tr>
inc/tpl/monitoring-logs.html.tpl CHANGED
@@ -45,9 +45,8 @@
45
</tr>
46
47
<tr>
48
- <th>Denial Type</th>
49
- <th>Time</th>
50
- <th>Remote Address</th>
51
<th>Request Path</th>
52
</tr>
53
</thead>
45
</tr>
46
47
<tr>
48
+ <th width="250">Denial Type</th>
49
+ <th width="120">Remote Address</th>
50
<th>Request Path</th>
51
</tr>
52
</thead>
inc/tpl/monitoring-logs.snippet.tpl CHANGED
@@ -1,10 +1,8 @@
1
2
<tr class="%%SUCURI.AuditLog.CssClass%%">
3
- <td>%%SUCURI.AuditLog.SucuriBlockReason%%</td>
4
<td>
5
- <span class="sucuriscan-monospace" title="%%SUCURI.AuditLog.RequestDate%% %%SUCURI.AuditLog.RequestTime%% %%SUCURI.AuditLog.RequestTimezone%%">
6
- %%SUCURI.AuditLog.RequestTime%% %%SUCURI.AuditLog.RequestTimezone%%
7
- </span>
8
</td>
9
<td><span class="sucuriscan-monospace">%%SUCURI.AuditLog.RemoteAddr%%</span></td>
10
<td>
@@ -16,48 +14,57 @@
16
17
<div id="sucuriscan-reqsummary-%%SUCURI.AuditLog.Id%%" style="display:none">
18
<div class="sucuriscan-request-summary">
19
- <ul class="sucuriscan-list-as-table">
20
- <li>
21
- <label>Blocked Reason:</label>
22
- <span>%%SUCURI.AuditLog.SucuriBlockReason%%</span>
23
- </li>
24
- <li>
25
- <label>Remote Address:</label>
26
- <span>%%SUCURI.AuditLog.RemoteAddr%%</span>
27
- </li>
28
- <li>
29
- <label>Date/Time (Timezone)</label>
30
- <span>%%SUCURI.AuditLog.RequestDate%% %%SUCURI.AuditLog.RequestTime%% (%%SUCURI.AuditLog.RequestTimezone%%)</span>
31
- </li>
32
- <li>
33
- <label>Resource Path:</label>
34
- <span>%%SUCURI.AuditLog.ResourcePath%%</span>
35
- </li>
36
- <li>
37
- <label>Request Method:</label>
38
- <span>%%SUCURI.AuditLog.RequestMethod%%</span>
39
- </li>
40
- <li>
41
- <label>HTTP Protocol:</label>
42
- <span>%%SUCURI.AuditLog.HttpProtocol%%</span>
43
- </li>
44
- <li>
45
- <label>HTTP Status:</label>
46
- <span>%%SUCURI.AuditLog.HttpStatus%% %%SUCURI.AuditLog.HttpStatusTitle%%</span>
47
- </li>
48
- <li>
49
- <label>HTTP Bytes Sent:</label>
50
- <span>%%SUCURI.AuditLog.HttpBytesSent%%</span>
51
- </li>
52
- <li>
53
- <label>HTTP Referer:</label>
54
- <span>%%SUCURI.AuditLog.HttpReferer%%</span>
55
- </li>
56
- <li>
57
- <label>HTTP User Agent:</label>
58
- <span>%%SUCURI.AuditLog.HttpUserAgent%%</span>
59
- </li>
60
- </ul>
61
</div>
62
</div>
63
</td>
1
2
<tr class="%%SUCURI.AuditLog.CssClass%%">
3
<td>
4
+ <span class="sucuriscan-denial-type">%%SUCURI.AuditLog.SucuriBlockReason%%</span><br>
5
+ <span class="sucuriscan-denial-type-date">Date/Time: %%SUCURI.AuditLog.LocalRequestTime%%</span>
6
</td>
7
<td><span class="sucuriscan-monospace">%%SUCURI.AuditLog.RemoteAddr%%</span></td>
8
<td>
14
15
<div id="sucuriscan-reqsummary-%%SUCURI.AuditLog.Id%%" style="display:none">
16
<div class="sucuriscan-request-summary">
17
+ <table class="wp-list-table widefat">
18
+ <thead>
19
+ <tr>
20
+ <th width="200">Information</th>
21
+ <th>&nbsp;</th>
22
+ </tr>
23
+ </thead>
24
+
25
+ <tbody>
26
+ <tr class="alternate">
27
+ <td>Blocked Reason</td>
28
+ <td>%%SUCURI.AuditLog.SucuriBlockReason%%</td>
29
+ </tr>
30
+ <tr>
31
+ <td>Remote Address</td>
32
+ <td>%%SUCURI.AuditLog.RemoteAddr%%</td>
33
+ </tr>
34
+ <tr class="alternate">
35
+ <td>Date &amp; Time (Local Time)</td>
36
+ <td>%%SUCURI.AuditLog.LocalRequestTime%%</td>
37
+ </tr>
38
+ <tr>
39
+ <td>Resource Path</td>
40
+ <td>%%SUCURI.AuditLog.ResourcePath%%</td>
41
+ </tr>
42
+ <tr class="alternate">
43
+ <td>Request Method</td>
44
+ <td>%%SUCURI.AuditLog.RequestMethod%%</td>
45
+ </tr>
46
+ <tr>
47
+ <td>HTTP Protocol</td>
48
+ <td>%%SUCURI.AuditLog.HttpProtocol%%</td>
49
+ </tr>
50
+ <tr class="alternate">
51
+ <td>HTTP Status</td>
52
+ <td>%%SUCURI.AuditLog.HttpStatus%% %%SUCURI.AuditLog.HttpStatusTitle%%</td>
53
+ </tr>
54
+ <tr>
55
+ <td>HTTP Bytes Sent</td>
56
+ <td>%%SUCURI.AuditLog.HttpBytesSent%%</td>
57
+ </tr>
58
+ <tr class="alternate">
59
+ <td>HTTP Referer</td>
60
+ <td>%%SUCURI.AuditLog.HttpReferer%%</td>
61
+ </tr>
62
+ <tr>
63
+ <td>HTTP User Agent</td>
64
+ <td>%%SUCURI.AuditLog.HttpUserAgent%%</td>
65
+ </tr>
66
+ </tbody>
67
+ </table>
68
</div>
69
</div>
70
</td>
inc/tpl/settings-general.html.tpl CHANGED
@@ -5,7 +5,7 @@
5
<thead>
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>
@@ -25,6 +25,15 @@
25
the domain of this site, this will allow you to have access to our free
26
monitoring tool forever even if you remove the API key and generate it again.
27
</p>
28
</td>
29
</tr>
30
@@ -107,6 +116,18 @@
107
</tr>
108
109
<tr>
110
<td>Filesystem scanner</td>
111
<td>%%SUCURI.FsScannerStatus%%</td>
112
<td class="td-with-button">
@@ -118,7 +139,7 @@
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">
@@ -130,7 +151,7 @@
130
</td>
131
</tr>
132
133
- <tr>
134
<td>Integrity checking</td>
135
<td>%%SUCURI.ScanChecksumsStatus%%</td>
136
<td class="td-with-button">
@@ -142,7 +163,7 @@
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">
@@ -153,7 +174,7 @@
153
</td>
154
</tr>
155
156
- <tr>
157
<td>Scanning frequency</td>
158
<td>%%SUCURI.ScanningFrequency%%</td>
159
<td class="td-with-button">
@@ -167,7 +188,7 @@
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">
5
<thead>
6
<tr>
7
<th colspan="3" class="thead-with-button">
8
+ <span>General 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>
25
the domain of this site, this will allow you to have access to our free
26
monitoring tool forever even if you remove the API key and generate it again.
27
</p>
28
+
29
+ <div class="sucuriscan-inline-alert-warning sucuriscan-%%SUCURI.InvalidDomainVisibility%%">
30
+ <p>
31
+ Your domain <code>%%SUCURI.CleanDomain%%</code> does not seems to have a DNS
32
+ <code>A</code> record so it will be considered as <em>invalid</em> by the API
33
+ interface when you request the generation of a new key. Adding <code>www</code>
34
+ at the beginning of the domain name may fix this issue.
35
+ </p>
36
+ </div>
37
</td>
38
</tr>
39
116
</tr>
117
118
<tr>
119
+ <td>API request timeout</td>
120
+ <td>%%SUCURI.RequestTimeout%%</td>
121
+ <td class="td-with-button">
122
+ <form action="%%SUCURI.URL.Settings%%" method="post">
123
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
124
+ <input type="text" name="sucuriscan_request_timeout" class="input-text" placeholder="Timeout in seconds..." />
125
+ <button type="submit" class="button-primary">Change</button>
126
+ </form>
127
+ </td>
128
+ </tr>
129
+
130
+ <tr class="alternate">
131
<td>Filesystem scanner</td>
132
<td>%%SUCURI.FsScannerStatus%%</td>
133
<td class="td-with-button">
139
</td>
140
</tr>
141
142
+ <tr>
143
<td>Scan modified files</td>
144
<td>%%SUCURI.ScanModfilesStatus%%</td>
145
<td class="td-with-button">
151
</td>
152
</tr>
153
154
+ <tr class="alternate">
155
<td>Integrity checking</td>
156
<td>%%SUCURI.ScanChecksumsStatus%%</td>
157
<td class="td-with-button">
163
</td>
164
</tr>
165
166
+ <tr>
167
<td>Last Scanning</td>
168
<td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
169
<td class="td-with-button">
174
</td>
175
</tr>
176
177
+ <tr class="alternate">
178
<td>Scanning frequency</td>
179
<td>%%SUCURI.ScanningFrequency%%</td>
180
<td class="td-with-button">
188
</td>
189
</tr>
190
191
+ <tr class="sucuriscan-%%SUCURI.ScanningInterfaceVisibility%%">
192
<td>Scanning interface</td>
193
<td>%%SUCURI.ScanningInterface%%</td>
194
<td class="td-with-button">
inc/tpl/settings-heartbeat.html.tpl ADDED
@@ -0,0 +1,84 @@
1
+
2
+ <div id="poststuff">
3
+ <div class="postbox sucuriscan-border sucuriscan-table-description">
4
+ <h3>Heartbeat</h3>
5
+
6
+ <div class="inside">
7
+ <p>
8
+ The purpose of the <a href="https://core.trac.wordpress.org/ticket/23216"
9
+ target="_blank">Heartbeat API</a> is to simulate bidirectional connection
10
+ between the browser and the server. Initially it was used for autosave, post
11
+ locking and log-in expiration warning while a user is writing or editing. The
12
+ idea was to have an API that sends XHR <em>(XML HTTP Request)</em> requests to
13
+ the server every fifteen seconds and triggers events <em>(or callbacks)</em> on
14
+ receiving data.
15
+ </p>
16
+ </div>
17
+ </div>
18
+ </div>
19
+
20
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-settings sucuriscan-settings-heartbeat">
21
+ <thead>
22
+ <tr>
23
+ <th>Option</th>
24
+ <th>Value</th>
25
+ <th>&nbsp;</th>
26
+ </tr>
27
+ </thead>
28
+
29
+ <tbody>
30
+ <tr>
31
+ <td>Heartbeat status</td>
32
+ <td>%%SUCURI.HeartbeatStatus%%</td>
33
+ <td class="td-with-button">
34
+ <form action="%%SUCURI.URL.Settings%%#settings-heartbeat" method="post">
35
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
36
+ <select name="sucuriscan_heartbeat_status">
37
+ %%SUCURI.HeartbeatStatusOptions%%
38
+ </select>
39
+ <button type="submit" class="button-primary">Change</button>
40
+ </form>
41
+ </td>
42
+ </tr>
43
+
44
+ <tr class="alternate">
45
+ <td>Pulse interval</td>
46
+ <td>%%SUCURI.HeartbeatPulse%%</td>
47
+ <td class="td-with-button">
48
+ <form action="%%SUCURI.URL.Settings%%#settings-heartbeat" method="post">
49
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
50
+ <select name="sucuriscan_heartbeat_pulse">
51
+ %%SUCURI.HeartbeatPulseOptions%%
52
+ </select>
53
+ <button type="submit" class="button-primary">Change</button>
54
+ </form>
55
+ </td>
56
+ </tr>
57
+
58
+ <tr>
59
+ <td>Interval speed</td>
60
+ <td>%%SUCURI.HeartbeatInterval%%</td>
61
+ <td class="td-with-button">
62
+ <form action="%%SUCURI.URL.Settings%%#settings-heartbeat" method="post">
63
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
64
+ <select name="sucuriscan_heartbeat_interval">
65
+ %%SUCURI.HeartbeatIntervalOptions%%
66
+ </select>
67
+ <button type="submit" class="button-primary">Change</button>
68
+ </form>
69
+ </td>
70
+ </tr>
71
+
72
+ <tr class="alternate">
73
+ <td>Auto-start</td>
74
+ <td>%%SUCURI.HeartbeatAutostart%%</td>
75
+ <td class="td-with-button">
76
+ <form action="%%SUCURI.URL.Settings%%#settings-heartbeat" method="post">
77
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
78
+ <input type="hidden" name="sucuriscan_heartbeat_autostart" value="%%SUCURI.HeartbeatAutostartSwitchValue%%" />
79
+ <button type="submit" class="button-primary %%SUCURI.HeartbeatAutostartSwitchCssClass%%">%%SUCURI.HeartbeatAutostartSwitchText%%</button>
80
+ </form>
81
+ </td>
82
+ </tr>
83
+ </tbody>
84
+ </table>
inc/tpl/settings.html.tpl CHANGED
@@ -10,6 +10,9 @@
10
<li>
11
<a href="#" data-tabname="settings-ignorerules">Ignore Notifications</a>
12
</li>
13
</ul>
14
15
<div class="sucuriscan-tab-containers">
@@ -24,5 +27,9 @@
24
<div id="sucuriscan-settings-ignorerules">
25
%%SUCURI.Settings.IgnoreRules%%
26
</div>
27
</div>
28
</div>
10
<li>
11
<a href="#" data-tabname="settings-ignorerules">Ignore Notifications</a>
12
</li>
13
+ <li>
14
+ <a href="#" data-tabname="settings-heartbeat">Heartbeat</a>
15
+ </li>
16
</ul>
17
18
<div class="sucuriscan-tab-containers">
27
<div id="sucuriscan-settings-ignorerules">
28
%%SUCURI.Settings.IgnoreRules%%
29
</div>
30
+
31
+ <div id="sucuriscan-settings-heartbeat">
32
+ %%SUCURI.Settings.Heartbeat%%
33
+ </div>
34
</div>
35
</div>
readme.txt CHANGED
@@ -3,10 +3,10 @@ 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.8
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
11
== Description ==
12
@@ -66,6 +66,11 @@ the compromise on your site).
66
67
== Changelog ==
68
69
= 1.6.8 =
70
* Fixing interface.
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.9
7
+ Tested up to: 4.0
8
9
+ The Sucuri WordPress Security plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features.
10
11
== Description ==
12
66
67
== Changelog ==
68
69
+ = 1.6.9 =
70
+ * Multiple bug fixes (as reported on the support forums).
71
+ * Added heartbeat for the file scans.
72
+ * Code cleanup.
73
+
74
= 1.6.8 =
75
* Fixing interface.
76
sucuri.php CHANGED
@@ -2,9 +2,9 @@
2
/*
3
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.8
8
Author URI: http://sucuri.net
9
*/
10
@@ -66,7 +66,7 @@ define('SUCURISCAN', 'sucuriscan');
66
/**
67
* Current version of the plugin's code.
68
*/
69
- define('SUCURISCAN_VERSION', '1.6.8');
70
71
/**
72
* The name of the Sucuri plugin main file.
@@ -319,6 +319,22 @@ if( defined('SUCURISCAN') ){
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
/**
@@ -571,6 +587,28 @@ class SucuriScan {
571
return FALSE;
572
}
573
574
/**
575
* Check whether the site is behing the Sucuri CloudProxy network.
576
*
@@ -578,14 +616,9 @@ class SucuriScan {
578
* @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
579
*/
580
public static function is_behind_cloudproxy( $verbose=FALSE ){
581
- if( isset($_SERVER['SERVER_NAME']) ){
582
- $http_host = preg_replace('/^(.*):[0-9]+/', '$1', $_SERVER['SERVER_NAME']);
583
- } else {
584
- $http_host = 'localhost';
585
- }
586
-
587
- $host_by_name = gethostbyname($http_host);
588
- $host_by_addr = gethostbyaddr($host_by_name);
589
$status = (bool) preg_match('/^cloudproxy[0-9]+\.sucuri\.net#x2F;', $host_by_addr);
590
591
if( $verbose ){
@@ -600,6 +633,45 @@ class SucuriScan {
600
return $status;
601
}
602
603
/**
604
* Return the time passed since the specified timestamp until now.
605
*
@@ -718,6 +790,47 @@ class SucuriScan {
718
return $text;
719
}
720
721
}
722
723
/**
@@ -1676,6 +1789,79 @@ class SucuriScanCache extends SucuriScan {
1676
*/
1677
class SucuriScanOption extends SucuriScanRequest {
1678
1679
/**
1680
* Retrieve specific options from the database.
1681
*
@@ -1831,63 +2017,6 @@ class SucuriScanOption extends SucuriScanRequest {
1831
return $option_value;
1832
}
1833
1834
- /**
1835
- * Retrieve the default values for some specific options.
1836
- *
1837
- * @param string|array $settings Either an array that will be complemented or a string with the name of the option.
1838
- * @return string|array The default values for the specified options.
1839
- */
1840
- private static function get_default_options( $settings='' ){
1841
- $admin_email = '';
1842
-
1843
- // Use framework built-in function.
1844
- if( function_exists('get_option') ){
1845
- $admin_email = get_option('admin_email');
1846
- }
1847
-
1848
- $default_options = array(
1849
- 'sucuriscan_api_key' => FALSE,
1850
- 'sucuriscan_account' => $admin_email,
1851
- 'sucuriscan_fs_scanner' => 'enabled',
1852
- 'sucuriscan_scan_frequency' => 'hourly',
1853
- 'sucuriscan_scan_interface' => 'spl',
1854
- 'sucuriscan_scan_modfiles' => 'enabled',
1855
- 'sucuriscan_scan_checksums' => 'enabled',
1856
- 'sucuriscan_runtime' => 0,
1857
- 'sucuriscan_lastlogin_redirection' => 'enabled',
1858
- 'sucuriscan_notify_to' => $admin_email,
1859
- 'sucuriscan_emails_sent' => 0,
1860
- 'sucuriscan_emails_per_hour' => 5,
1861
- 'sucuriscan_last_email_at' => time(),
1862
- 'sucuriscan_prettify_mails' => 'enabled',
1863
- 'sucuriscan_notify_success_login' => 'enabled',
1864
- 'sucuriscan_notify_failed_login' => 'enabled',
1865
- 'sucuriscan_notify_post_publication' => 'enabled',
1866
- 'sucuriscan_notify_theme_editor' => 'enabled',
1867
- 'sucuriscan_maximum_failed_logins' => 30,
1868
- 'sucuriscan_ignored_events' => '',
1869
- 'sucuriscan_verify_ssl_cert' => 'true',
1870
- );
1871
-
1872
- if( is_array($settings) ){
1873
- foreach( $default_options as $option_name => $option_value ){
1874
- if( !isset($settings[$option_name]) ){
1875
- $settings[$option_name] = $option_value;
1876
- }
1877
- }
1878
-
1879
- return $settings;
1880
- }
1881
-
1882
- if( is_string($settings) ){
1883
- if( isset($default_options[$settings]) ){
1884
- return $default_options[$settings];
1885
- }
1886
- }
1887
-
1888
- return FALSE;
1889
- }
1890
-
1891
/**
1892
* Retrieve all the options stored by Wordpress in the database. The options
1893
* containing the word "transient" are excluded from the results, this function
@@ -1980,35 +2109,6 @@ class SucuriScanOption extends SucuriScanRequest {
1980
return FALSE;
1981
}
1982
1983
- /**
1984
- * Get the email address set by the administrator to receive the notifications
1985
- * sent by the plugin, if the email is missing the WordPress email address is
1986
- * chosen by default.
1987
- *
1988
- * @return string The administrator email address.
1989
- */
1990
- public static function get_site_email(){
1991
- $email = self::get_option('admin_email');
1992
-
1993
- if( self::is_valid_email($email) ){
1994
- return $email;
1995
- }
1996
-
1997
- return FALSE;
1998
- }
1999
-
2000
- /**
2001
- * Get the clean version of the current domain.
2002
- *
2003
- * @return string The domain of the current site.
2004
- */
2005
- public static function get_domain(){
2006
- $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
2007
- $domain_name = preg_replace( '/^www\./', '', $http_host );
2008
-
2009
- return $domain_name;
2010
- }
2011
-
2012
/**
2013
* Get a list of the post types ignored to receive email notifications when the
2014
* "new site content" hook is triggered.
@@ -2140,7 +2240,7 @@ class SucuriScanOption extends SucuriScanRequest {
2140
2141
if( $runtime > 0 ){
2142
if( $format ){
2143
- return date( 'd/M/Y H:i:s', $runtime );
2144
}
2145
2146
return $runtime;
@@ -2369,7 +2469,7 @@ class SucuriScanEvent extends SucuriScan {
2369
$email_params['Force'] = TRUE;
2370
}
2371
2372
- $title = sprintf( 'Sucuri notification (%s)', str_replace('_', chr(32), $event) );
2373
$mail_sent = SucuriScanMail::send_mail( $email, $title, $content, $email_params );
2374
2375
return $mail_sent;
@@ -3112,6 +3212,15 @@ class SucuriScanAPI extends SucuriScanOption {
3112
return ( self::get_option(':verify_ssl_cert') === 'true' );
3113
}
3114
3115
/**
3116
* Generate an user-agent for the HTTP requests.
3117
*
@@ -3145,7 +3254,7 @@ class SucuriScanAPI extends SucuriScanOption {
3145
3146
$req_args = array(
3147
'method' => $method,
3148
- 'timeout' => 90,
3149
'redirection' => 2,
3150
'httpversion' => '1.0',
3151
'user-agent' => self::user_agent(),
@@ -3218,7 +3327,7 @@ class SucuriScanAPI extends SucuriScanOption {
3218
*/
3219
public static function set_plugin_key( $api_key='', $validate=FALSE ){
3220
if( $validate ){
3221
- if( !preg_match('/^([a-z0-9]{32})#x2F;', $api_key) ){
3222
SucuriScanInterface::error( 'Invalid API key format' );
3223
return FALSE;
3224
}
@@ -3601,7 +3710,7 @@ class SucuriScanAPI extends SucuriScanOption {
3601
'date' => date('Y-m-d'),
3602
);
3603
3604
- if( preg_match('/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})#x2F;', $date) ){
3605
$params['date'] = $date;
3606
}
3607
@@ -3895,7 +4004,7 @@ class SucuriScanMail extends SucuriScanOption {
3895
public static function send_mail( $email='', $subject='', $message='', $data_set=array() ){
3896
$headers = array();
3897
$subject = ucwords(strtolower($subject));
3898
- $wp_domain = SucuriScanOption::get_domain();
3899
$force = FALSE;
3900
$debug = FALSE;
3901
@@ -3930,9 +4039,11 @@ class SucuriScanMail extends SucuriScanOption {
3930
3931
if( $debug ){ die($message); }
3932
3933
$email_sent = wp_mail(
3934
$email,
3935
- "Sucuri WP Notification: {$wp_domain} - {$subject}",
3936
$message,
3937
$headers
3938
);
@@ -4073,8 +4184,8 @@ class SucuriScanTemplate extends SucuriScanRequest {
4073
$params['PageTitle'] = isset($params['PageTitle']) ? '('.$params['PageTitle'].')' : '';
4074
$params['PageNonce'] = wp_create_nonce('sucuriscan_page_nonce');
4075
$params['PageStyleClass'] = isset($params['PageStyleClass']) ? $params['PageStyleClass'] : 'base';
4076
- $params['CleanDomain'] = SucuriScanOption::get_domain();
4077
- $params['AdminEmail'] = SucuriScanOption::get_site_email();
4078
4079
return $params;
4080
}
@@ -4365,6 +4476,160 @@ class SucuriScanTemplate extends SucuriScanRequest {
4365
4366
}
4367
4368
/**
4369
* Plugin initializer.
4370
*
@@ -4664,7 +4929,7 @@ function sucuriscan_scanner_page(){
4664
function sucuriscan_sitecheck_info( $res=array() ){
4665
// Will be TRUE only if the scanning results were retrieved from the cache.
4666
$display_results = (bool) $res;
4667
- $clean_domain = SucuriScanOption::get_domain();
4668
4669
// If the results are not cached, then request a new scanning.
4670
if( $res === FALSE ){
@@ -5140,6 +5405,9 @@ function sucuriscan_monitoring_form_submissions(){
5140
5141
if( $clear_cache_resp ){
5142
if( isset($clear_cache_resp->messages[0]) ){
5143
SucuriScanInterface::info($clear_cache_resp->messages[0]);
5144
} else {
5145
SucuriScanInterface::error('Could not clear the cache of your site, try later again.');
@@ -5314,7 +5582,7 @@ function sucuriscan_monitoring_logs( $api_key='' ){
5314
}
5315
}
5316
5317
- $template_variables['AuditLogs.TargetDate'] = htmlentities($date);
5318
$template_variables['AuditLogs.DateYears'] = sucuriscan_monitoring_dates('years', $date);
5319
$template_variables['AuditLogs.DateMonths'] = sucuriscan_monitoring_dates('months', $date);
5320
$template_variables['AuditLogs.DateDays'] = sucuriscan_monitoring_dates('days', $date);
@@ -5337,6 +5605,8 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
5337
'request_date',
5338
'request_time',
5339
'request_timezone',
5340
'remote_addr',
5341
'sucuri_block_reason',
5342
'resource_path',
@@ -5398,9 +5668,20 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
5398
5399
if( isset($access_log->{$attr_name}) ){
5400
$attr_value = $access_log->{$attr_name};
5401
}
5402
5403
- $audit_log_snippet[$attr_title] = $attr_value;
5404
}
5405
5406
$logs_html .= SucuriScanTemplate::get_snippet('monitoring-logs', $audit_log_snippet);
@@ -5427,7 +5708,9 @@ function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=TRUE
5427
foreach( $access_logs as $access_log ){
5428
if( !array_key_exists($access_log->sucuri_block_reason, $types) ){
5429
$denial_type_k = SucuriScan::human2var($access_log->sucuri_block_reason);
5430
- $types[$denial_type_k] = $access_log->sucuri_block_reason;
5431
}
5432
}
5433
}
@@ -5734,7 +6017,7 @@ function sucuriscan_harden_upload(){
5734
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5735
}
5736
5737
- SucuriScanInterface::info('Hardening reverted for upload directory <code>/wp-content/uploads/</code>');
5738
} else {
5739
SucuriScanInterface::error(
5740
'File <code>/wp-content/uploads/.htaccess</code> does not exists or
@@ -5768,7 +6051,7 @@ function sucuriscan_harden_upload(){
5768
function sucuriscan_harden_wpcontent(){
5769
$cp = 1;
5770
$upmsg = NULL;
5771
- $htaccess_upload = ABSPATH . '/wp-content/.htaccess';
5772
5773
if( !is_readable($htaccess_upload) ){
5774
$cp = 0;
@@ -5789,7 +6072,7 @@ function sucuriscan_harden_wpcontent(){
5789
if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
5790
$upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5791
} else {
5792
- $upmsg = SucuriScanInterface::info('Hardening applied successfully to content directory <code>/wp-content/</code>');
5793
$cp = 1;
5794
}
5795
}
@@ -5806,11 +6089,11 @@ function sucuriscan_harden_wpcontent(){
5806
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5807
}
5808
5809
- SucuriScanInterface::info('Hardening reverted for content directory <code>/wp-content/</code>');
5810
} else {
5811
SucuriScanInterface::info(
5812
- 'File <code>/wp-content/.htaccess</code> does not exists or is not writable,
5813
- you will need to remove the following code manually from there:
5814
<code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5815
);
5816
}
@@ -5819,7 +6102,7 @@ function sucuriscan_harden_wpcontent(){
5819
5820
$description = 'This option blocks direct PHP access to any file inside wp-content. If you experience '
5821
. 'any issue after this with a theme or plugin in your site, like for example images not displaying, '
5822
- . 'remove the <code>.htaccess</code> file located at the <code>/wp-content/</code> directory.'
5823
. '</p><p><b>Note:</b> Many <em>(insecure)</em> themes and plugins use a PHP file in this directory '
5824
. 'to generate images like thumbnails and captcha codes, this is intentional so it is recommended '
5825
. 'to check your site once this option is enabled.';
@@ -5869,7 +6152,7 @@ function sucuriscan_harden_wpincludes(){
5869
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 ){
5870
$upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5871
} else {
5872
- $upmsg = SucuriScanInterface::info('Hardening applied successfully to library\'s directory <code>/wp-includes/</code>');
5873
$cp = 1;
5874
}
5875
}
@@ -5887,7 +6170,7 @@ function sucuriscan_harden_wpincludes(){
5887
5888
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5889
}
5890
- SucuriScanInterface::info('Hardening reverted for library\'s directory <code>/wp-includes/</code>');
5891
} else {
5892
SucuriScanInterface::error(
5893
'File <code>wp-includes/.htaccess</code> does not exists or is not
@@ -6337,9 +6620,9 @@ function sucuriscan_auditlogs(){
6337
$css_class = ( $counter_i % 2 == 0 ) ? '' : 'alternate';
6338
$snippet_data = array(
6339
'AuditLog.CssClass' => $css_class,
6340
- 'AuditLog.DateTime' => date( 'd/M/Y H:i:s', $audit_log['timestamp'] ),
6341
- 'AuditLog.Account' => $audit_log['account'],
6342
- 'AuditLog.Message' => $audit_log['message'],
6343
'AuditLog.Extra' => '',
6344
);
6345
@@ -6348,7 +6631,7 @@ function sucuriscan_auditlogs(){
6348
$css_scrollable = $audit_log['extra_total'] > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
6349
$snippet_data['AuditLog.Extra'] .= '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
6350
foreach( $audit_log['extra'] as $log_extra ){
6351
- $snippet_data['AuditLog.Extra'] .= '<li>' . $log_extra . '</li>';
6352
}
6353
$snippet_data['AuditLog.Extra'] .= '</ul>';
6354
$snippet_data['AuditLog.Extra'] .= '<small>For Mac users, this is a scrollable container</small>';
@@ -6652,13 +6935,13 @@ function sucuriscan_modified_files(){
6652
&& $file_info['modified_at'] >= $back_days
6653
){
6654
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6655
- $mod_date = date('d/M/Y H:i:s', $file_info['modified_at']);
6656
6657
$template_variables['ModifiedFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-modifiedfiles', array(
6658
'ModifiedFiles.CssClass' => $css_class,
6659
'ModifiedFiles.CheckSum' => $file_info['checksum'],
6660
'ModifiedFiles.FilePath' => $file_path,
6661
- 'ModifiedFiles.DateTime' => $mod_date
6662
));
6663
$counter += 1;
6664
}
@@ -6819,7 +7102,7 @@ function sucuriscan_posthack_users( $process_form=FALSE ){
6819
6820
foreach( $user_list as $user ){
6821
$user->user_registered_timestamp = strtotime($user->user_registered);
6822
- $user->user_registered_formatted = date('D, M/Y H:i', $user->user_registered_timestamp);
6823
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6824
6825
$template_variables['ResetPassword.UserList'] .= SucuriScanTemplate::get_snippet('posthack-resetpassword', array(
@@ -6978,6 +7261,21 @@ function sucuriscan_posthack_reinstall_plugins( $process_form=FALSE ){
6978
function sucuriscan_lastlogins_page(){
6979
SucuriScanInterface::check_permissions();
6980
6981
// Page pseudo-variables initialization.
6982
$template_variables = array(
6983
'PageTitle' => 'Last Logins',
@@ -7023,14 +7321,18 @@ function sucuriscan_lastlogins_admins(){
7023
if( !empty($admin->lastlogins) ){
7024
$user_snippet['AdminUsers.NoLastLogins'] = 'hidden';
7025
$user_snippet['AdminUsers.NoLastLoginsTable'] = 'visible';
7026
- $user_snippet['AdminUsers.RegisteredAt'] = $admin->user_registered;
7027
$counter = 0;
7028
7029
- foreach( $admin->lastlogins as $lastlogin ){
7030
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
7031
$user_snippet['AdminUsers.LastLogins'] .= SucuriScanTemplate::get_snippet('lastlogins-admins-lastlogin', array(
7032
'AdminUsers.RemoteAddr' => SucuriScan::escape($lastlogin->user_remoteaddr),
7033
- 'AdminUsers.Datetime' => SucuriScan::escape($lastlogin->user_lastlogin),
7034
'AdminUsers.CssClass' => $css_class,
7035
));
7036
$counter += 1;
@@ -7263,6 +7565,8 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
7263
7264
if( preg_match('/^a:[0-9]+:.+/', $line) ){
7265
$last_login = @unserialize($line);
7266
7267
// Only administrators can see all login stats.
7268
if( !$is_admin_user && $current_user->user_login != $last_login['user_login'] ){
@@ -7283,6 +7587,10 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
7283
7284
foreach( $user_account->data as $var_name=>$var_value ){
7285
$last_login[$var_name] = $var_value;
7286
}
7287
}
7288
@@ -7291,6 +7599,10 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
7291
$parsed_lines += 1;
7292
}
7293
7294
if( preg_match('/^[0-9]+#x2F;', $limit) && $limit>0 ){
7295
if( $parsed_lines >= $limit ){ break; }
7296
}
@@ -7345,8 +7657,10 @@ if( !function_exists('sucuri_get_user_lastlogin') ){
7345
if( isset($last_logins['entries'][1]) ){
7346
$row = $last_logins['entries'][1];
7347
7348
- $message_tpl = 'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>';
7349
- $lastlogin_message = sprintf( $message_tpl, date('d/M/Y H:i'), $row->user_remoteaddr, $row->user_hostname );
7350
$lastlogin_message .= chr(32).'(<a href="'.SucuriScanTemplate::get_url('lastlogins').'">view all logs</a>)';
7351
SucuriScanInterface::info( $lastlogin_message );
7352
}
@@ -7376,8 +7690,8 @@ function sucuriscan_loggedin_users_panel(){
7376
7377
foreach( (array) $logged_in_users as $logged_in_user ){
7378
$counter += 1;
7379
- $logged_in_user['last_activity_datetime'] = date('d/M/Y H:i', $logged_in_user['last_activity']);
7380
- $logged_in_user['user_registered_datetime'] = date('d/M/Y H:i', strtotime($logged_in_user['user_registered']));
7381
7382
$template_variables['LoggedInUsers.List'] .= SucuriScanTemplate::get_snippet('lastlogins-loggedin', array(
7383
'LoggedInUsers.Id' => SucuriScan::escape($logged_in_user['user_id']),
@@ -7584,7 +7898,7 @@ function sucuriscan_failed_logins_panel(){
7584
'FailedLogins.Num' => ($counter + 1),
7585
'FailedLogins.Username' => SucuriScan::escape($login_data['user_login']),
7586
'FailedLogins.RemoteAddr' => SucuriScan::escape($login_data['remote_addr']),
7587
- 'FailedLogins.Datetime' => date('d/M/Y H:i', $login_data['attempt_time']),
7588
'FailedLogins.UserAgent' => SucuriScan::escape($login_data['user_agent']),
7589
));
7590
@@ -7820,6 +8134,7 @@ function sucuriscan_settings_page(){
7820
'Settings.General' => sucuriscan_settings_general(),
7821
'Settings.Notifications' => sucuriscan_settings_notifications(),
7822
'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
7823
);
7824
7825
echo SucuriScanTemplate::get_template('settings', $template_variables);
@@ -7970,6 +8285,12 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
7970
}
7971
}
7972
7973
// Update the notification settings.
7974
if( SucuriScanRequest::post(':save_notification_settings') !== FALSE ){
7975
$options_updated_counter = 0;
@@ -8028,6 +8349,48 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
8028
}
8029
}
8030
8031
}
8032
}
8033
@@ -8081,6 +8444,13 @@ function sucuriscan_settings_general(){
8081
$verify_ssl_cert = SucuriScanOption::get_option(':verify_ssl_cert');
8082
$runtime_scan_human = SucuriScanOption::get_filesystem_runtime(TRUE);
8083
8084
// Generate the HTML code for the option list in the form select fields.
8085
$scan_freq_options = SucuriScanTemplate::get_select_options( $sucuriscan_schedule_allowed, $scan_freq );
8086
$scan_interface_options = SucuriScanTemplate::get_select_options( $sucuriscan_interface_allowed, $scan_interface );
@@ -8093,6 +8463,7 @@ function sucuriscan_settings_general(){
8093
'APIKey.RecoverVisibility' => SucuriScanTemplate::visibility( !$api_key && !$display_manual_key_form ),
8094
'APIKey.ManualKeyFormVisibility' => SucuriScanTemplate::visibility($display_manual_key_form),
8095
'APIKey.RemoveVisibility' => SucuriScanTemplate::visibility($api_key),
8096
/* Filesystem scanner */
8097
'FsScannerStatus' => 'Enabled',
8098
'FsScannerSwitchText' => 'Disable',
@@ -8124,6 +8495,7 @@ function sucuriscan_settings_general(){
8124
'MaximumFailedLoginsOptions' => $maximum_failed_logins_options,
8125
'VerifySSLCert' => 'Undefined',
8126
'VerifySSLCertOptions' => $verify_ssl_cert_options,
8127
'ModalWhenAPIRegistered' => $api_registered_modal,
8128
);
8129
@@ -8227,7 +8599,7 @@ function sucuriscan_settings_ignore_rules(){
8227
8228
if( array_key_exists($post_type, $ignored_events) ){
8229
$is_ignored_text = 'YES';
8230
- $was_ignored_at = @date('d/M/Y - H:i:s', $ignored_events[$post_type]);
8231
$is_ignored_class = 'danger';
8232
$button_action = 'remove';
8233
$button_class = 'button-primary';
@@ -8259,6 +8631,65 @@ function sucuriscan_settings_ignore_rules(){
8259
return SucuriScanTemplate::get_section('settings-ignorerules', $template_variables);
8260
}
8261
8262
/**
8263
* Generate and print the HTML code for the InfoSys page.
8264
*
@@ -8496,18 +8927,21 @@ function sucuriscan_show_cronjobs(){
8496
8497
$cronjobs = _get_cron_array();
8498
$schedules = wp_get_schedules();
8499
- $date_format = _x('M j, Y - H:i', 'Publish box date format', 'cron-view' );
8500
$counter = 0;
8501
8502
foreach( $cronjobs as $timestamp => $cronhooks ){
8503
foreach( (array) $cronhooks as $hook => $events ){
8504
foreach( (array) $events as $key => $event ){
8505
$template_variables['Cronjobs.Total'] += 1;
8506
$template_variables['Cronjobs.List'] .= SucuriScanTemplate::get_snippet('infosys-cronjobs', array(
8507
'Cronjob.Hook' => $hook,
8508
'Cronjob.Schedule' => $event['schedule'],
8509
- 'Cronjob.NextTime' => date_i18n($date_format, $timestamp),
8510
- 'Cronjob.Arguments' => !empty($event['args']) ? implode(', ', $event['args']) : '<em>empty</em>',
8511
'Cronjob.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate',
8512
));
8513
$counter += 1;
2
/*
3
Plugin Name: Sucuri Security - Auditing, Malware Scanner and Hardening
4
Plugin URI: http://wordpress.sucuri.net/
5
+ Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
6
Author: Sucuri, INC
7
+ Version: 1.6.9
8
Author URI: http://sucuri.net
9
*/
10
66
/**
67
* Current version of the plugin's code.
68
*/
69
+ define('SUCURISCAN_VERSION', '1.6.9');
70
71
/**
72
* The name of the Sucuri plugin main file.
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
+ * Heartbeat API
324
+ *
325
+ * Update the settings of the Heartbeat API according to the values set by an
326
+ * administrator. This tool may cause an increase in the CPU usage, a bad
327
+ * configuration may cause low account to run out of resources, but in better
328
+ * cases it may improve the performance of the site by reducing the quantity of
329
+ * requests sent to the server per session.
330
+ */
331
+ add_filter( 'init', 'SucuriScanHeartbeat::register_script', 1 );
332
+ add_filter( 'heartbeat_settings', 'SucuriScanHeartbeat::update_settings' );
333
+ add_filter( 'heartbeat_send', 'SucuriScanHeartbeat::respond_to_send', 10, 3 );
334
+ add_filter( 'heartbeat_received', 'SucuriScanHeartbeat::respond_to_received', 10, 3 );
335
+ add_filter( 'heartbeat_nopriv_send', 'SucuriScanHeartbeat::respond_to_send', 10, 3 );
336
+ add_filter( 'heartbeat_nopriv_received', 'SucuriScanHeartbeat::respond_to_received', 10, 3 );
337
+
338
}
339
340
/**
587
return FALSE;
588
}
589
590
+ /**
591
+ * Get the clean version of the current domain.
592
+ *
593
+ * @return string The domain of the current site.
594
+ */
595
+ public static function get_domain(){
596
+ if( function_exists('get_site_url') ){
597
+ $site_url = get_site_url();
598
+ } else {
599
+ if( !isset($_SERVER['HTTP_HOST']) ){
600
+ $_SERVER['HTTP_HOST'] = 'localhost';
601
+ }
602
+
603
+ $site_url = $_SERVER['HTTP_HOST'];
604
+ }
605
+
606
+ $pattern = '/([fhtps]+:\/\/)?([^:\/]+)(:[0-9:]+)?(\/.*)?/';
607
+ $domain_name = preg_replace( $pattern, '$2', $site_url );
608
+
609
+ return $domain_name;
610
+ }
611
+
612
/**
613
* Check whether the site is behing the Sucuri CloudProxy network.
614
*
616
* @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
617
*/
618
public static function is_behind_cloudproxy( $verbose=FALSE ){
619
+ $http_host = self::get_domain();
620
+ $host_by_name = @gethostbyname($http_host);
621
+ $host_by_addr = @gethostbyaddr($host_by_name);
622
$status = (bool) preg_match('/^cloudproxy[0-9]+\.sucuri\.net#x2F;', $host_by_addr);
623
624
if( $verbose ){
633
return $status;
634
}
635
636
+ /**
637
+ * Get the email address set by the administrator to receive the notifications
638
+ * sent by the plugin, if the email is missing the WordPress email address is
639
+ * chosen by default.
640
+ *
641
+ * @return string The administrator email address.
642
+ */
643
+ public static function get_site_email(){
644
+ $email = get_option('admin_email');
645
+
646
+ if( self::is_valid_email($email) ){
647
+ return $email;
648
+ }
649
+
650
+ return FALSE;
651
+ }
652
+
653
+ /**
654
+ * Retrieve the date in localized format, based on timestamp.
655
+ *
656
+ * If the locale specifies the locale month and weekday, then the locale will
657
+ * take over the format for the date. If it isn't, then the date format string
658
+ * will be used instead.
659
+ *
660
+ * @param integer $timestamp Unix timestamp.
661
+ * @return string The date, translated if locale specifies it.
662
+ */
663
+ public static function datetime( $timestamp=0 ){
664
+ if( is_numeric($timestamp) && $timestamp > 0 ){
665
+ $date_format = get_option('date_format');
666
+ $time_format = get_option('time_format');
667
+ $timezone_format = sprintf( '%s %s', $date_format, $time_format );
668
+
669
+ return date_i18n( $timezone_format, $timestamp );
670
+ }
671
+
672
+ return NULL;
673
+ }
674
+
675
/**
676
* Return the time passed since the specified timestamp until now.
677
*
790
return $text;
791
}
792
793
+ /**
794
+ * Check whether an list is a multidimensional array or not.
795
+ *
796
+ * @param array $list An array or multidimensional array of different values.
797
+ * @return boolean TRUE if the list is multidimensional, FALSE otherwise.
798
+ */
799
+ public static function is_multi_list( $list=array() ){
800
+ if( !empty($list) ){
801
+ foreach( $list as $item ){
802
+ if( is_array($item) ){
803
+ return TRUE;
804
+ }
805
+ }
806
+ }
807
+
808
+ return FALSE;
809
+ }
810
+
811
+ /**
812
+ * Join array elements with a string no matter if it is multidimensional.
813
+ *
814
+ * @param string $separator Character that will act as a separator, default to an empty string.
815
+ * @param array $list The array of strings to implode.
816
+ * @return string String of all the items in the list, with the separator between them.
817
+ */
818
+ public static function implode( $separator='', $list=array() ){
819
+ if( self::is_multi_list($list) ){
820
+ $pieces = array();
821
+
822
+ foreach( $list as $items ){
823
+ $pieces[] = @implode( $separator, $items );
824
+ }
825
+
826
+ $joined_pieces = '(' . implode( '), (', $pieces ) . ')';
827
+
828
+ return $joined_pieces;
829
+ } else {
830
+ return implode( $separator, $list );
831
+ }
832
+ }
833
+
834
}
835
836
/**
1789
*/
1790
class SucuriScanOption extends SucuriScanRequest {
1791
1792
+ /**
1793
+ * Default values for the plugin options.
1794
+ *
1795
+ * @return array Default plugin option values.
1796
+ */
1797
+ private static function get_default_option_values(){
1798
+ $defaults = array(
1799
+ 'sucuriscan_api_key' => FALSE,
1800
+ 'sucuriscan_account' => '',
1801
+ 'sucuriscan_fs_scanner' => 'enabled',
1802
+ 'sucuriscan_scan_frequency' => 'hourly',
1803
+ 'sucuriscan_scan_interface' => 'spl',
1804
+ 'sucuriscan_scan_modfiles' => 'enabled',
1805
+ 'sucuriscan_scan_checksums' => 'enabled',
1806
+ 'sucuriscan_runtime' => 0,
1807
+ 'sucuriscan_lastlogin_redirection' => 'enabled',
1808
+ 'sucuriscan_notify_to' => '',
1809
+ 'sucuriscan_emails_sent' => 0,
1810
+ 'sucuriscan_emails_per_hour' => 5,
1811
+ 'sucuriscan_last_email_at' => time(),
1812
+ 'sucuriscan_prettify_mails' => 'enabled',
1813
+ 'sucuriscan_notify_success_login' => 'enabled',
1814
+ 'sucuriscan_notify_failed_login' => 'enabled',
1815
+ 'sucuriscan_notify_post_publication' => 'enabled',
1816
+ 'sucuriscan_notify_theme_editor' => 'enabled',
1817
+ 'sucuriscan_maximum_failed_logins' => 30,
1818
+ 'sucuriscan_ignored_events' => '',
1819
+ 'sucuriscan_verify_ssl_cert' => 'true',
1820
+ 'sucuriscan_request_timeout' => 90,
1821
+ 'sucuriscan_heartbeat' => 'enabled',
1822
+ 'sucuriscan_heartbeat_pulse' => 15,
1823
+ 'sucuriscan_heartbeat_interval' => 'standard',
1824
+ 'sucuriscan_heartbeat_autostart' => 'enabled',
1825
+ );
1826
+
1827
+ return $defaults;
1828
+ }
1829
+
1830
+ /**
1831
+ * Retrieve the default values for some specific options.
1832
+ *
1833
+ * @param string|array $settings Either an array that will be complemented or a string with the name of the option.
1834
+ * @return string|array The default values for the specified options.
1835
+ */
1836
+ private static function get_default_options( $settings='' ){
1837
+ $default_options = self::get_default_option_values();
1838
+
1839
+ // Use framework built-in function.
1840
+ if( function_exists('get_option') ){
1841
+ $admin_email = get_option('admin_email');
1842
+ $default_options['sucuriscan_account'] = $admin_email;
1843
+ $default_options['sucuriscan_notify_to'] = $admin_email;
1844
+ }
1845
+
1846
+ if( is_array($settings) ){
1847
+ foreach( $default_options as $option_name => $option_value ){
1848
+ if( !isset($settings[$option_name]) ){
1849
+ $settings[$option_name] = $option_value;
1850
+ }
1851
+ }
1852
+
1853
+ return $settings;
1854
+ }
1855
+
1856
+ if( is_string($settings) ){
1857
+ if( isset($default_options[$settings]) ){
1858
+ return $default_options[$settings];
1859
+ }
1860
+ }
1861
+
1862
+ return FALSE;
1863
+ }
1864
+
1865
/**
1866
* Retrieve specific options from the database.
1867
*
2017
return $option_value;
2018
}
2019
2020
/**
2021
* Retrieve all the options stored by Wordpress in the database. The options
2022
* containing the word "transient" are excluded from the results, this function
2109
return FALSE;
2110
}
2111
2112
/**
2113
* Get a list of the post types ignored to receive email notifications when the
2114
* "new site content" hook is triggered.
2240
2241
if( $runtime > 0 ){
2242
if( $format ){
2243
+ return SucuriScan::datetime($runtime);
2244
}
2245
2246
return $runtime;
2469
$email_params['Force'] = TRUE;
2470
}
2471
2472
+ $title = str_replace('_', chr(32), $event);
2473
$mail_sent = SucuriScanMail::send_mail( $email, $title, $content, $email_params );
2474
2475
return $mail_sent;
3212
return ( self::get_option(':verify_ssl_cert') === 'true' );
3213
}
3214
3215
+ /**
3216
+ * Seconds before consider a HTTP request as timeout.
3217
+ *
3218
+ * @return integer Seconds to consider a HTTP request timeout.
3219
+ */
3220
+ public static function request_timeout(){
3221
+ return intval( self::get_option(':request_timeout') );
3222
+ }
3223
+
3224
/**
3225
* Generate an user-agent for the HTTP requests.
3226
*
3254
3255
$req_args = array(
3256
'method' => $method,
3257
+ 'timeout' => self::request_timeout(),
3258
'redirection' => 2,
3259
'httpversion' => '1.0',
3260
'user-agent' => self::user_agent(),
3327
*/
3328
public static function set_plugin_key( $api_key='', $validate=FALSE ){
3329
if( $validate ){
3330
+ if( !preg_match('/^[a-z0-9]{32}#x2F;', $api_key) ){
3331
SucuriScanInterface::error( 'Invalid API key format' );
3332
return FALSE;
3333
}
3710
'date' => date('Y-m-d'),
3711
);
3712
3713
+ if( preg_match('/^[0-9]{4}(\-[0-9]{2}){2}#x2F;', $date) ){
3714
$params['date'] = $date;
3715
}
3716
4004
public static function send_mail( $email='', $subject='', $message='', $data_set=array() ){
4005
$headers = array();
4006
$subject = ucwords(strtolower($subject));
4007
+ $wp_domain = self::get_domain();
4008
$force = FALSE;
4009
$debug = FALSE;
4010
4039
4040
if( $debug ){ die($message); }
4041
4042
+ $subject = sprintf( 'Sucuri Alert, %s, %s', $wp_domain, $subject );
4043
+
4044
$email_sent = wp_mail(
4045
$email,
4046
+ $subject,
4047
$message,
4048
$headers
4049
);
4184
$params['PageTitle'] = isset($params['PageTitle']) ? '('.$params['PageTitle'].')' : '';
4185
$params['PageNonce'] = wp_create_nonce('sucuriscan_page_nonce');
4186
$params['PageStyleClass'] = isset($params['PageStyleClass']) ? $params['PageStyleClass'] : 'base';
4187
+ $params['CleanDomain'] = self::get_domain();
4188
+ $params['AdminEmail'] = self::get_site_email();
4189
4190
return $params;
4191
}
4476
4477
}
4478
4479
+ /**
4480
+ * Heartbeat library.
4481
+ *
4482
+ * The purpose of the Heartbeat API is to simulate bidirectional connection
4483
+ * between the browser and the server. Initially it was used for autosave, post
4484
+ * locking and log-in expiration warning while a user is writing or editing. The
4485
+ * idea was to have an API that sends XHR (XML HTTP Request) requests to the
4486
+ * server every fifteen seconds and triggers events (or callbacks) on receiving
4487
+ * data.
4488
+ *
4489
+ * @see https://core.trac.wordpress.org/ticket/23216
4490
+ */
4491
+ class SucuriScanHeartbeat extends SucuriScanOption {
4492
+
4493
+ /**
4494
+ * Stop execution of the heartbeat API in certain parts of the site.
4495
+ *
4496
+ * @return void
4497
+ */
4498
+ public static function register_script(){
4499
+ global $pagenow;
4500
+
4501
+ $status = SucuriScanOption::get_option(':heartbeat');
4502
+
4503
+ // Enable heartbeat everywhere.
4504
+ if( $status == 'enabled' ){ /* do_nothing */ }
4505
+
4506
+ // Disable heartbeat everywhere.
4507
+ elseif( $status == 'disabled' ){
4508
+ wp_deregister_script('heartbeat');
4509
+ }
4510
+
4511
+ // Disable heartbeat only on the dashboard and home pages.
4512
+ elseif(
4513
+ $status == 'dashboard'
4514
+ && $pagenow == 'index.php'
4515
+ ){
4516
+ wp_deregister_script('heartbeat');
4517
+ }
4518
+
4519
+ // Disable heartbeat everywhere except in post edition.
4520
+ elseif(
4521
+ $status == 'addpost'
4522
+ && $pagenow != 'post.php'
4523
+ && $pagenow != 'post-new.php'
4524
+ ){
4525
+ wp_deregister_script('heartbeat');
4526
+ }
4527
+ }
4528
+
4529
+ /**
4530
+ * Update the settings of the Heartbeat API according to the values set by an
4531
+ * administrator. This tool may cause an increase in the CPU usage, a bad
4532
+ * configuration may cause low account to run out of resources, but in better
4533
+ * cases it may improve the performance of the site by reducing the quantity of
4534
+ * requests sent to the server per session.
4535
+ *
4536
+ * @param array $settings Heartbeat settings.
4537
+ * @return array Updated version of the heartbeat settings.
4538
+ */
4539
+ public static function update_settings( $settings=array() ){
4540
+ $pulse = SucuriScanOption::get_option(':heartbeat_pulse');
4541
+ $autostart = SucuriScanOption::get_option(':heartbeat_autostart');
4542
+
4543
+ if( $pulse < 15 || $pulse > 60 ){
4544
+ SucuriScanOption::delete_option(':heartbeat_pulse');
4545
+ $pulse = 15;
4546
+ }
4547
+
4548
+ $settings['interval'] = $pulse;
4549
+ $settings['autostart'] = ( $autostart == 'disabled' ? FALSE : TRUE );
4550
+
4551
+ return $settings;
4552
+ }
4553
+
4554
+ /**
4555
+ * Respond to the browser according to the data received.
4556
+ *
4557
+ * @param array $response Response received.
4558
+ * @param array $data Data received from the beat.
4559
+ * @param string $screen_id Identifier of the screen the heartbeat occurred on.
4560
+ * @return array Response with new data.
4561
+ */
4562
+ public static function respond_to_received( $response=array(), $data=array(), $screen_id='' ) {
4563
+ $interval = SucuriScanOption::get_option(':heartbeat_interval');
4564
+
4565
+ if(
4566
+ $interval == 'slow'
4567
+ || $interval == 'fast'
4568
+ || $interval == 'standard'
4569
+ ){
4570
+ $response['heartbeat_interval'] = $interval;
4571
+ } else {
4572
+ SucuriScanOption::delete_option(':heartbeat_interval');
4573
+ }
4574
+
4575
+ return $response;
4576
+ }
4577
+
4578
+ /**
4579
+ * Respond to the browser according to the data sent.
4580
+ *
4581
+ * @param array $response Response sent.
4582
+ * @param string $screen_id Identifier of the screen the heartbeat occurred on.
4583
+ * @return array Response with new data.
4584
+ */
4585
+ public static function respond_to_send( $response=array(), $screen_id='' ) {
4586
+ return $response;
4587
+ }
4588
+
4589
+ /**
4590
+ * Allowed values for the heartbeat status.
4591
+ *
4592
+ * @return array Allowed values for the heartbeat status.
4593
+ */
4594
+ public static function statuses_allowed(){
4595
+ return array(
4596
+ 'enabled' => 'Enable everywhere',
4597
+ 'disabled' => 'Disable everywhere',
4598
+ 'dashboard' => 'Disable on dashboard page',
4599
+ 'addpost' => 'Everywhere except post addition',
4600
+ );
4601
+ }
4602
+
4603
+ /**
4604
+ * Allowed values for the heartbeat intervals.
4605
+ *
4606
+ * @return array Allowed values for the heartbeat intervals.
4607
+ */
4608
+ public static function intervals_allowed(){
4609
+ return array(
4610
+ 'slow' => 'Slow interval',
4611
+ 'fast' => 'Fast interval',
4612
+ 'standard' => 'Standard interval',
4613
+ );
4614
+ }
4615
+
4616
+ /**
4617
+ * Allowed values for the heartbeat pulses.
4618
+ *
4619
+ * @return array Allowed values for the heartbeat pulses.
4620
+ */
4621
+ public static function pulses_allowed(){
4622
+ $pulses = array();
4623
+
4624
+ for( $i=15; $i<=60; $i++ ){
4625
+ $pulses[$i] = sprintf( 'Run every %d seconds', $i );
4626
+ }
4627
+
4628
+ return $pulses;
4629
+ }
4630
+
4631
+ }
4632
+
4633
/**
4634
* Plugin initializer.
4635
*
4929
function sucuriscan_sitecheck_info( $res=array() ){
4930
// Will be TRUE only if the scanning results were retrieved from the cache.
4931
$display_results = (bool) $res;
4932
+ $clean_domain = SucuriScan::get_domain();
4933
4934
// If the results are not cached, then request a new scanning.
4935
if( $res === FALSE ){
5405
5406
if( $clear_cache_resp ){
5407
if( isset($clear_cache_resp->messages[0]) ){
5408
+ // Clear W3 Total Cache if it is installed.
5409
+ if( function_exists('w3tc_flush_all') ){ w3tc_flush_all(); }
5410
+
5411
SucuriScanInterface::info($clear_cache_resp->messages[0]);
5412
} else {
5413
SucuriScanInterface::error('Could not clear the cache of your site, try later again.');
5582
}
5583
}
5584
5585
+ $template_variables['AuditLogs.TargetDate'] = SucuriScan::escape($date);
5586
$template_variables['AuditLogs.DateYears'] = sucuriscan_monitoring_dates('years', $date);
5587
$template_variables['AuditLogs.DateMonths'] = sucuriscan_monitoring_dates('months', $date);
5588
$template_variables['AuditLogs.DateDays'] = sucuriscan_monitoring_dates('days', $date);
5605
'request_date',
5606
'request_time',
5607
'request_timezone',
5608
+ 'request_timestamp',
5609
+ 'local_request_time',
5610
'remote_addr',
5611
'sucuri_block_reason',
5612
'resource_path',
5668
5669
if( isset($access_log->{$attr_name}) ){
5670
$attr_value = $access_log->{$attr_name};
5671
+
5672
+ if(
5673
+ empty($attr_value)
5674
+ && $attr_name == 'sucuri_block_reason'
5675
+ ){
5676
+ $attr_value = 'Unknown';
5677
+ }
5678
+ }
5679
+
5680
+ elseif( $attr_name == 'local_request_time' ){
5681
+ $attr_value = SucuriScan::datetime($access_log->request_timestamp);
5682
}
5683
5684
+ $audit_log_snippet[$attr_title] = SucuriScan::escape($attr_value);
5685
}
5686
5687
$logs_html .= SucuriScanTemplate::get_snippet('monitoring-logs', $audit_log_snippet);
5708
foreach( $access_logs as $access_log ){
5709
if( !array_key_exists($access_log->sucuri_block_reason, $types) ){
5710
$denial_type_k = SucuriScan::human2var($access_log->sucuri_block_reason);
5711
+ $denial_type_v = $access_log->sucuri_block_reason;
5712
+ if( empty($denial_type_v) ){ $denial_type_v = 'Unknown'; }
5713
+ $types[$denial_type_k] = $denial_type_v;
5714
}
5715
}
5716
}
6017
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
6018
}
6019
6020
+ SucuriScanInterface::info('Hardening reverted for upload directory.');
6021
} else {
6022
SucuriScanInterface::error(
6023
'File <code>/wp-content/uploads/.htaccess</code> does not exists or
6051
function sucuriscan_harden_wpcontent(){
6052
$cp = 1;
6053
$upmsg = NULL;
6054
+ $htaccess_upload = WP_CONTENT_DIR . '/.htaccess';
6055
6056
if( !is_readable($htaccess_upload) ){
6057
$cp = 0;
6072
if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
6073
$upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
6074
} else {
6075
+ $upmsg = SucuriScanInterface::info('Hardening applied successfully to content directory.');
6076
$cp = 1;
6077
}
6078
}
6089
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
6090
}
6091
6092
+ SucuriScanInterface::info('Hardening reverted for content directory.');
6093
} else {
6094
SucuriScanInterface::info(
6095
+ 'File <code>' . WP_CONTENT_DIR . '/.htaccess</code> does not exists or is not
6096
+ writable, you will need to remove the following code manually from there:
6097
<code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
6098
);
6099
}
6102
6103
$description = 'This option blocks direct PHP access to any file inside wp-content. If you experience '
6104
. 'any issue after this with a theme or plugin in your site, like for example images not displaying, '
6105
+ . 'remove the <code>.htaccess</code> file located in the content directory.'
6106
. '</p><p><b>Note:</b> Many <em>(insecure)</em> themes and plugins use a PHP file in this directory '
6107
. 'to generate images like thumbnails and captcha codes, this is intentional so it is recommended '
6108
. 'to check your site once this option is enabled.';
6152
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 ){
6153
$upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
6154
} else {
6155
+ $upmsg = SucuriScanInterface::info('Hardening applied successfully to library\'s directory.');
6156
$cp = 1;
6157
}
6158
}
6170
6171
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
6172
}
6173
+ SucuriScanInterface::info('Hardening reverted for library\'s directory.');
6174
} else {
6175
SucuriScanInterface::error(
6176
'File <code>wp-includes/.htaccess</code> does not exists or is not
6620
$css_class = ( $counter_i % 2 == 0 ) ? '' : 'alternate';
6621
$snippet_data = array(
6622
'AuditLog.CssClass' => $css_class,
6623
+ 'AuditLog.DateTime' => SucuriScan::datetime($audit_log['timestamp']),
6624
+ 'AuditLog.Account' => SucuriScan::escape($audit_log['account']),
6625
+ 'AuditLog.Message' => SucuriScan::escape($audit_log['message']),
6626
'AuditLog.Extra' => '',
6627
);
6628
6631
$css_scrollable = $audit_log['extra_total'] > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
6632
$snippet_data['AuditLog.Extra'] .= '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
6633
foreach( $audit_log['extra'] as $log_extra ){
6634
+ $snippet_data['AuditLog.Extra'] .= '<li>' . SucuriScan::escape($log_extra) . '</li>';
6635
}
6636
$snippet_data['AuditLog.Extra'] .= '</ul>';
6637
$snippet_data['AuditLog.Extra'] .= '<small>For Mac users, this is a scrollable container</small>';
6935
&& $file_info['modified_at'] >= $back_days
6936
){
6937
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6938
+ $mod_date = SucuriScan::datetime($file_info['modified_at']);
6939
6940
$template_variables['ModifiedFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-modifiedfiles', array(
6941
'ModifiedFiles.CssClass' => $css_class,
6942
'ModifiedFiles.CheckSum' => $file_info['checksum'],
6943
'ModifiedFiles.FilePath' => $file_path,
6944
+ 'ModifiedFiles.DateTime' => $mod_date,
6945
));
6946
$counter += 1;
6947
}
7102
7103
foreach( $user_list as $user ){
7104
$user->user_registered_timestamp = strtotime($user->user_registered);
7105
+ $user->user_registered_formatted = SucuriScan::datetime($user->user_registered_timestamp);
7106
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
7107
7108
$template_variables['ResetPassword.UserList'] .= SucuriScanTemplate::get_snippet('posthack-resetpassword', array(
7261
function sucuriscan_lastlogins_page(){
7262
SucuriScanInterface::check_permissions();
7263
7264
+ // Reset the file with the last-logins logs.
7265
+ if(
7266
+ SucuriScanInterface::check_nonce()
7267
+ && SucuriScanRequest::post(':reset_lastlogins') !== FALSE
7268
+ ){
7269
+ $file_path = sucuriscan_lastlogins_datastore_filepath();
7270
+
7271
+ if( unlink($file_path) ){
7272
+ sucuriscan_lastlogins_datastore_exists();
7273
+ SucuriScanInterface::info( 'Last-Logins logs were reset successfully.' );
7274
+ } else {
7275
+ SucuriScanInterface::error( 'Could not reset the last-logins logs.' );
7276
+ }
7277
+ }
7278
+
7279
// Page pseudo-variables initialization.
7280
$template_variables = array(
7281
'PageTitle' => 'Last Logins',
7321
if( !empty($admin->lastlogins) ){
7322
$user_snippet['AdminUsers.NoLastLogins'] = 'hidden';
7323
$user_snippet['AdminUsers.NoLastLoginsTable'] = 'visible';
7324
+ $user_snippet['AdminUsers.RegisteredAt'] = 'Unknown';
7325
$counter = 0;
7326
7327
+ foreach( $admin->lastlogins as $i => $lastlogin ){
7328
+ if( $i == 0 ){
7329
+ $user_snippet['AdminUsers.RegisteredAt'] = SucuriScan::datetime($lastlogin->user_registered_timestamp);
7330
+ }
7331
+
7332
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
7333
$user_snippet['AdminUsers.LastLogins'] .= SucuriScanTemplate::get_snippet('lastlogins-admins-lastlogin', array(
7334
'AdminUsers.RemoteAddr' => SucuriScan::escape($lastlogin->user_remoteaddr),
7335
+ 'AdminUsers.Datetime' => SucuriScan::datetime($lastlogin->user_lastlogin_timestamp),
7336
'AdminUsers.CssClass' => $css_class,
7337
));
7338
$counter += 1;
7565
7566
if( preg_match('/^a:[0-9]+:.+/', $line) ){
7567
$last_login = @unserialize($line);
7568
+ $last_login['user_lastlogin_timestamp'] = strtotime($last_login['user_lastlogin']);
7569
+ $last_login['user_registered_timestamp'] = 0;
7570
7571
// Only administrators can see all login stats.
7572
if( !$is_admin_user && $current_user->user_login != $last_login['user_login'] ){
7587
7588
foreach( $user_account->data as $var_name=>$var_value ){
7589
$last_login[$var_name] = $var_value;
7590
+
7591
+ if( $var_name == 'user_registered' ){
7592
+ $last_login['user_registered_timestamp'] = strtotime($var_value);
7593
+ }
7594
}
7595
}
7596
7599
$parsed_lines += 1;
7600
}
7601
7602
+ else {
7603
+ $last_logins['total'] -= 1;
7604
+ }
7605
+
7606
if( preg_match('/^[0-9]+#x2F;', $limit) && $limit>0 ){
7607
if( $parsed_lines >= $limit ){ break; }
7608
}
7657
if( isset($last_logins['entries'][1]) ){
7658
$row = $last_logins['entries'][1];
7659
7660
+ $lastlogin_message = sprintf(
7661
+ 'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>',
7662
+ date('d/M/Y H:i'), $row->user_remoteaddr, $row->user_hostname
7663
+ );
7664
$lastlogin_message .= chr(32).'(<a href="'.SucuriScanTemplate::get_url('lastlogins').'">view all logs</a>)';
7665
SucuriScanInterface::info( $lastlogin_message );
7666
}
7690
7691
foreach( (array) $logged_in_users as $logged_in_user ){
7692
$counter += 1;
7693
+ $logged_in_user['last_activity_datetime'] = SucuriScan::datetime($logged_in_user['last_activity']);
7694
+ $logged_in_user['user_registered_datetime'] = SucuriScan::datetime( strtotime($logged_in_user['user_registered']) );
7695
7696
$template_variables['LoggedInUsers.List'] .= SucuriScanTemplate::get_snippet('lastlogins-loggedin', array(
7697
'LoggedInUsers.Id' => SucuriScan::escape($logged_in_user['user_id']),
7898
'FailedLogins.Num' => ($counter + 1),
7899
'FailedLogins.Username' => SucuriScan::escape($login_data['user_login']),
7900
'FailedLogins.RemoteAddr' => SucuriScan::escape($login_data['remote_addr']),
7901
+ 'FailedLogins.Datetime' => SucuriScan::datetime($login_data['attempt_time']),
7902
'FailedLogins.UserAgent' => SucuriScan::escape($login_data['user_agent']),
7903
));
7904
8134
'Settings.General' => sucuriscan_settings_general(),
8135
'Settings.Notifications' => sucuriscan_settings_notifications(),
8136
'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
8137
+ 'Settings.Heartbeat' => sucuriscan_settings_heartbeat(),
8138
);
8139
8140
echo SucuriScanTemplate::get_template('settings', $template_variables);
8285
}
8286
}
8287
8288
+ // Update the API request timeout.
8289
+ if( $request_timeout = SucuriScanRequest::post(':request_timeout', '[0-9]+') ){
8290
+ SucuriScanOption::update_option(':request_timeout', $request_timeout);
8291
+ SucuriScanInterface::info( 'API request timeout set to <code>' . $request_timeout . '</code> seconds.' );
8292
+ }
8293
+
8294
// Update the notification settings.
8295
if( SucuriScanRequest::post(':save_notification_settings') !== FALSE ){
8296
$options_updated_counter = 0;
8349
}
8350
}
8351
8352
+ // Update the settings for the heartbeat API.
8353
+ if( $heartbeat_status = SucuriScanRequest::post(':heartbeat_status') ){
8354
+ $statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
8355
+
8356
+ if( array_key_exists($heartbeat_status, $statuses_allowed) ){
8357
+ SucuriScanOption::update_option(':heartbeat', $heartbeat_status);
8358
+ SucuriScanInterface::info( 'Heartbeat status set to <code>' . $heartbeat_status . '</code>' );
8359
+ } else {
8360
+ SucuriScanInterface::error( 'Heartbeat status not allowed.' );
8361
+ }
8362
+ }
8363
+
8364
+ // Update the value of the heartbeat pulse.
8365
+ if( $heartbeat_pulse = SucuriScanRequest::post(':heartbeat_pulse') ){
8366
+ $pulses_allowed = SucuriScanHeartbeat::pulses_allowed();
8367
+
8368
+ if( array_key_exists($heartbeat_pulse, $pulses_allowed) ){
8369
+ SucuriScanOption::update_option(':heartbeat_pulse', $heartbeat_pulse);
8370
+ SucuriScanInterface::info( 'Heartbeat pulse set to <code>' . $heartbeat_pulse . '</code> seconds.' );
8371
+ } else {
8372
+ SucuriScanInterface::error( 'Heartbeat pulse not allowed.' );
8373
+ }
8374
+ }
8375
+
8376
+ // Update the value of the heartbeat interval.
8377
+ if( $heartbeat_interval = SucuriScanRequest::post(':heartbeat_interval') ){
8378
+ $intervals_allowed = SucuriScanHeartbeat::intervals_allowed();
8379
+
8380
+ if( array_key_exists($heartbeat_interval, $intervals_allowed) ){
8381
+ SucuriScanOption::update_option(':heartbeat_interval', $heartbeat_interval);
8382
+ SucuriScanInterface::info( 'Heartbeat interval set to <code>' . $heartbeat_interval . '</code>' );
8383
+ } else {
8384
+ SucuriScanInterface::error( 'Heartbeat interval not allowed.' );
8385
+ }
8386
+ }
8387
+
8388
+ // Enable or disable the auto-start execution of heartbeat.
8389
+ if( $heartbeat_autostart = SucuriScanRequest::post(':heartbeat_autostart', '(en|dis)able') ){
8390
+ $action_d = $heartbeat_autostart . 'd';
8391
+ SucuriScanOption::update_option(':heartbeat_autostart', $action_d);
8392
+ SucuriScanInterface::info( 'Heartbeat auto-start was <code>' . $action_d . '</code>' );
8393
+ }
8394
}
8395
}
8396
8444
$verify_ssl_cert = SucuriScanOption::get_option(':verify_ssl_cert');
8445
$runtime_scan_human = SucuriScanOption::get_filesystem_runtime(TRUE);
8446
8447
+ // Check whether the domain name is valid or not.
8448
+ if( !$api_key ){
8449
+ $clean_domain = SucuriScan::get_domain();
8450
+ $domain_address = @gethostbyname($clean_domain);
8451
+ $invalid_domain = ( $domain_address == $clean_domain ) ? TRUE : FALSE;
8452
+ }
8453
+
8454
// Generate the HTML code for the option list in the form select fields.
8455
$scan_freq_options = SucuriScanTemplate::get_select_options( $sucuriscan_schedule_allowed, $scan_freq );
8456
$scan_interface_options = SucuriScanTemplate::get_select_options( $sucuriscan_interface_allowed, $scan_interface );
8463
'APIKey.RecoverVisibility' => SucuriScanTemplate::visibility( !$api_key && !$display_manual_key_form ),
8464
'APIKey.ManualKeyFormVisibility' => SucuriScanTemplate::visibility($display_manual_key_form),
8465
'APIKey.RemoveVisibility' => SucuriScanTemplate::visibility($api_key),
8466
+ 'InvalidDomainVisibility' => SucuriScanTemplate::visibility($invalid_domain),
8467
/* Filesystem scanner */
8468
'FsScannerStatus' => 'Enabled',
8469
'FsScannerSwitchText' => 'Disable',
8495
'MaximumFailedLoginsOptions' => $maximum_failed_logins_options,
8496
'VerifySSLCert' => 'Undefined',
8497
'VerifySSLCertOptions' => $verify_ssl_cert_options,
8498
+ 'RequestTimeout' => SucuriScanOption::get_option(':request_timeout') . ' seconds',
8499
'ModalWhenAPIRegistered' => $api_registered_modal,
8500
);
8501
8599
8600
if( array_key_exists($post_type, $ignored_events) ){
8601
$is_ignored_text = 'YES';
8602
+ $was_ignored_at = SucuriScan::datetime($ignored_events[$post_type]);
8603
$is_ignored_class = 'danger';
8604
$button_action = 'remove';
8605
$button_class = 'button-primary';
8631
return SucuriScanTemplate::get_section('settings-ignorerules', $template_variables);
8632
}
8633
8634
+ /**
8635
+ * Read and parse the content of the heartbeat settings template.
8636
+ *
8637
+ * @return string Parsed HTML code for the ignored-rules settings panel.
8638
+ */
8639
+ function sucuriscan_settings_heartbeat(){
8640
+ // Current values set in the options table.
8641
+ $heartbeat_status = SucuriScanOption::get_option(':heartbeat');
8642
+ $heartbeat_pulse = SucuriScanOption::get_option(':heartbeat_pulse');
8643
+ $heartbeat_interval = SucuriScanOption::get_option(':heartbeat_interval');
8644
+ $heartbeat_autostart = SucuriScanOption::get_option(':heartbeat_autostart');
8645
+
8646
+ // Allowed values for each setting.
8647
+ $statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
8648
+ $pulses_allowed = SucuriScanHeartbeat::pulses_allowed();
8649
+ $intervals_allowed = SucuriScanHeartbeat::intervals_allowed();
8650
+
8651
+ // HTML select form fields.
8652
+ $heartbeat_options = SucuriScanTemplate::get_select_options( $statuses_allowed, $heartbeat_status );
8653
+ $heartbeat_pulse_options = SucuriScanTemplate::get_select_options( $pulses_allowed, $heartbeat_pulse );
8654
+ $heartbeat_interval_options = SucuriScanTemplate::get_select_options( $intervals_allowed, $heartbeat_interval );
8655
+
8656
+ $template_variables = array(
8657
+ 'HeartbeatStatus' => 'Undefined',
8658
+ 'HeartbeatPulse' => 'Undefined',
8659
+ 'HeartbeatInterval' => 'Undefined',
8660
+ /* Heartbeat Options. */
8661
+ 'HeartbeatStatusOptions' => $heartbeat_options,
8662
+ 'HeartbeatPulseOptions' => $heartbeat_pulse_options,
8663
+ 'HeartbeatIntervalOptions' => $heartbeat_interval_options,
8664
+ /* Heartbeat Auto-Start. */
8665
+ 'HeartbeatAutostart' => 'Enabled',
8666
+ 'HeartbeatAutostartSwitchText' => 'Disable',
8667
+ 'HeartbeatAutostartSwitchValue' => 'disable',
8668
+ 'HeartbeatAutostartSwitchCssClass' => 'button-danger',
8669
+ );
8670
+
8671
+ if( array_key_exists($heartbeat_status, $statuses_allowed) ){
8672
+ $template_variables['HeartbeatStatus'] = $statuses_allowed[$heartbeat_status];
8673
+ }
8674
+
8675
+ if( array_key_exists($heartbeat_pulse, $pulses_allowed) ){
8676
+ $template_variables['HeartbeatPulse'] = $pulses_allowed[$heartbeat_pulse];
8677
+ }
8678
+
8679
+ if( array_key_exists($heartbeat_interval, $intervals_allowed) ){
8680
+ $template_variables['HeartbeatInterval'] = $intervals_allowed[$heartbeat_interval];
8681
+ }
8682
+
8683
+ if( $heartbeat_autostart == 'disabled' ){
8684
+ $template_variables['HeartbeatAutostart'] = 'Disabled';
8685
+ $template_variables['HeartbeatAutostartSwitchText'] = 'Enable';
8686
+ $template_variables['HeartbeatAutostartSwitchValue'] = 'enable';
8687
+ $template_variables['HeartbeatAutostartSwitchCssClass'] = 'button-success';
8688
+ }
8689
+
8690
+ return SucuriScanTemplate::get_section('settings-heartbeat', $template_variables);
8691
+ }
8692
+
8693
/**
8694
* Generate and print the HTML code for the InfoSys page.
8695
*
8927
8928
$cronjobs = _get_cron_array();
8929
$schedules = wp_get_schedules();
8930
$counter = 0;
8931
8932
foreach( $cronjobs as $timestamp => $cronhooks ){
8933
foreach( (array) $cronhooks as $hook => $events ){
8934
foreach( (array) $events as $key => $event ){
8935
+ if( empty($event['args']) ){
8936
+ $event['args'] = array( '<em>empty</em>' );
8937
+ }
8938
+
8939
$template_variables['Cronjobs.Total'] += 1;
8940
$template_variables['Cronjobs.List'] .= SucuriScanTemplate::get_snippet('infosys-cronjobs', array(
8941
'Cronjob.Hook' => $hook,
8942
'Cronjob.Schedule' => $event['schedule'],
8943
+ 'Cronjob.NextTime' => SucuriScan::datetime($timestamp),
8944
+ 'Cronjob.Arguments' => SucuriScan::implode(', ', $event['args']),
8945
'Cronjob.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate',
8946
));
8947
$counter += 1;