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$/', $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})$/', $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})$/', $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]+$/', $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$/', $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}$/', $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}$/', $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]+$/', $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;