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

Version Description

Download this release

Release Info

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

Code changes from version 1.7.2 to 1.7.3

inc/css/sucuriscan-default-css.css CHANGED
@@ -39,13 +39,13 @@
39
.sucuriscan-modal-inside p:first-child{margin-top:0}
40
.sucuriscan-modal-inside p:last-child{margin-bottom:0}
41
/* Label and Tags */
42
- .sucuriscan-label, .sucuriscan-label-default, .sucuriscan-label-primary, .sucuriscan-label-success, .sucuriscan-label-info, .sucuriscan-label-warning, .sucuriscan-label-danger{display:inline;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;padding:0.2em 0.6em 0.3em;border-radius:0.25em}
43
- .sucuriscan-label-default{background:#777}
44
- .sucuriscan-label-primary{background:#428bca}
45
- .sucuriscan-label-success{background:#5cb85c}
46
- .sucuriscan-label-info{background:#5bc0de}
47
.sucuriscan-label-warning{background:#f0ad4e}
48
- .sucuriscan-label-danger{background:#d9534f}
49
/* Interface Wrapper */
50
.sucuriscan-wrap{margin-top:20px}
51
.sucuriscan-wrap .sucuriscan-maincontent{margin:20px 0}
@@ -105,6 +105,7 @@ div.sucuriscan-alert{position:relative;margin:0 0 20px 0}
105
div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:18px;font-weight:bold;text-decoration:none}
106
.sucuriscan-inline-alert, .sucuriscan-inline-alert-updated, .sucuriscan-inline-alert-error, .sucuriscan-inline-alert-warning, .sucuriscan-inline-alert-info{background:#fff;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);padding:0;border-left:4px solid #ddd}
107
.sucuriscan-inline-alert > p, .sucuriscan-inline-alert-updated > p, .sucuriscan-inline-alert-error > p, .sucuriscan-inline-alert-warning > p, .sucuriscan-inline-alert-info > p{margin:0;padding:8px 12px;border:1px solid #ddd;border-left:0}
108
.sucuriscan-inline-alert-updated{border-left-color:#7ad03a}
109
.sucuriscan-inline-alert-warning{border-left-color:#ffba00}
110
.sucuriscan-inline-alert-error{border-left-color:#dd3d36}
@@ -224,8 +225,19 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
224
.sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{width:220px}
225
.sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:0}
226
.sucuriscan-maincontent .sucuriscan-settings-ignorescanning{margin-top:0}
227
.sucuriscan-maincontent .sucuriscan-settings-heartbeat{}
228
.sucuriscan-maincontent .sucuriscan-wpcron-list{margin-top:0}
229
/* Responsive Styles */
230
@media (max-width: 620px) {
231
.sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:block}
39
.sucuriscan-modal-inside p:first-child{margin-top:0}
40
.sucuriscan-modal-inside p:last-child{margin-bottom:0}
41
/* Label and Tags */
42
+ .sucuriscan-label, .sucuriscan-label-default, .sucuriscan-label-unknown, .sucuriscan-label-primary, .sucuriscan-label-success, .sucuriscan-label-info, .sucuriscan-label-notice, .sucuriscan-label-warning, .sucuriscan-label-danger, .sucuriscan-label-error{display:inline;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;padding:0.2em 0.6em 0.3em;border-radius:0.25em}
43
+ .sucuriscan-label-default, .sucuriscan-label-unknown{background:#777}
44
+ .sucuriscan-label-danger, .sucuriscan-label-error{background:#d9534f}
45
+ .sucuriscan-label-info, .sucuriscan-label-notice{background:#5bc0de}
46
.sucuriscan-label-warning{background:#f0ad4e}
47
+ .sucuriscan-label-success{background:#5cb85c}
48
+ .sucuriscan-label-primary{background:#428bca}
49
/* Interface Wrapper */
50
.sucuriscan-wrap{margin-top:20px}
51
.sucuriscan-wrap .sucuriscan-maincontent{margin:20px 0}
105
div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:18px;font-weight:bold;text-decoration:none}
106
.sucuriscan-inline-alert, .sucuriscan-inline-alert-updated, .sucuriscan-inline-alert-error, .sucuriscan-inline-alert-warning, .sucuriscan-inline-alert-info{background:#fff;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);padding:0;border-left:4px solid #ddd}
107
.sucuriscan-inline-alert > p, .sucuriscan-inline-alert-updated > p, .sucuriscan-inline-alert-error > p, .sucuriscan-inline-alert-warning > p, .sucuriscan-inline-alert-info > p{margin:0;padding:8px 12px;border:1px solid #ddd;border-left:0}
108
+ .sucuriscan-inline-alert-updated + div, .sucuriscan-inline-alert-warning + div, .sucuriscan-inline-alert-error + div, .sucuriscan-inline-alert-info + div{margin-top:10px}
109
.sucuriscan-inline-alert-updated{border-left-color:#7ad03a}
110
.sucuriscan-inline-alert-warning{border-left-color:#ffba00}
111
.sucuriscan-inline-alert-error{border-left-color:#dd3d36}
225
.sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{width:220px}
226
.sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:0}
227
.sucuriscan-maincontent .sucuriscan-settings-ignorescanning{margin-top:0}
228
+ .sucuriscan-maincontent .sucuriscan-settings-trustip{margin-top:0}
229
.sucuriscan-maincontent .sucuriscan-settings-heartbeat{}
230
.sucuriscan-maincontent .sucuriscan-wpcron-list{margin-top:0}
231
+ .sucuriscan-maincontent .sucuriscan-infosys-htaccess .inside .sucuriscan-inline-alert-updated{margin-bottom:10px}
232
+ .sucuriscan-maincontent .sucuriscan-errorlogs .inside .sucuriscan-inline-alert-error{margin-top:10px}
233
+ .sucuriscan-maincontent .sucuriscan-errorlogs-list{}
234
+ /* Parent Resetter: Midnight */
235
+ .admin-color-blue .wrap div.sucuriscan-setup-notice, .admin-color-blue .sucuriscan-ad:nth-child(odd){background:#e5d1ae;border-color:#d39323}
236
+ .admin-color-coffee .wrap div.sucuriscan-setup-notice, .admin-color-coffee .sucuriscan-ad:nth-child(odd){background:#e4cfbe;border-color:#b78a66}
237
+ .admin-color-ectoplasm .wrap div.sucuriscan-setup-notice, .admin-color-ectoplasm .sucuriscan-ad:nth-child(odd){background:#ccd894;border-color:#a3b745}
238
+ .admin-color-midnight .wrap div.sucuriscan-setup-notice, .admin-color-midnight .sucuriscan-ad:nth-child(odd){background:#f1b8b4;border-color:#d02a21}
239
+ .admin-color-ocean .wrap div.sucuriscan-setup-notice, .admin-color-ocean .sucuriscan-ad:nth-child(odd){background:#c6e7c8;border-color:#719a74}
240
+ .admin-color-sunrise .wrap div.sucuriscan-setup-notice, .admin-color-sunrise .sucuriscan-ad:nth-child(odd){background:#ecc2a2;border-color:#c36822}
241
/* Responsive Styles */
242
@media (max-width: 620px) {
243
.sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:block}
inc/tpl/infosys-errorlogs.html.tpl ADDED
@@ -0,0 +1,69 @@
1
+
2
+ <div id="poststuff">
3
+ <div class="postbox sucuriscan-border sucuriscan-table-description sucuriscan-errorlogs">
4
+ <h3>Error Logs</h3>
5
+
6
+ <div class="inside">
7
+ <p>
8
+ Web servers like Apache, Nginx and others use files to record errors encountered
9
+ during the execution of a dynamic language or the server processes. Depending on
10
+ the configuration of the server, these files may be accessible from the web
11
+ opening a hole in your site to allow an attacker to gather sensitive information
12
+ of your project, so it is highly recommended to delete them.
13
+ </p>
14
+
15
+ <div class="sucuriscan-inline-alert-info">
16
+ <p>
17
+ If you are a developer, you may want to check the latest errors encountered by
18
+ the server before delete the log file, that way you can see where the
19
+ application is failing and fix the errors. Note that many error log files may
20
+ have thousand of lines, so you will only see the latest entries to prevent PHP
21
+ interpreter to stop the execution of the parser when the maximum execution time
22
+ is reached.
23
+ </p>
24
+ </div>
25
+
26
+ <div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.ErrorLog.DisabledVisibility%%">
27
+ <p>
28
+ The analysis of error logs is disabled, go to the <em>Scanner Settings</em>
29
+ panel in the <em>Settings</em> page to enable it.
30
+ </p>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-errorlogs-list">
37
+ <thead>
38
+ <tr>
39
+ <th colspan="5" class="thead-with-button">
40
+ <span>Error Logs (%%SUCURI.ErrorLog.FileSize%%)</span>
41
+
42
+ <form action="%%SUCURI.URL.Hardening%%#error-logs" method="post" class="thead-topright-action">
43
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
44
+ <input type="hidden" name="sucuriscan_run_hardening" value="1" />
45
+ <input type="hidden" name="sucuriscan_harden_errorlog" value="Harden" />
46
+ <button type="submit" class="button-primary">Delete logs</button>
47
+ </form>
48
+ </th>
49
+ </tr>
50
+
51
+ <tr>
52
+ <th width="100">Date Time</th>
53
+ <th width="50">Type</th>
54
+ <th>Error Message</th>
55
+ <th width="300">File</th>
56
+ <th width="50">Line</th>
57
+ </tr>
58
+ </thead>
59
+
60
+ <tbody>
61
+ %%SUCURI.ErrorLog.List%%
62
+
63
+ <tr class="sucuriscan-%%SUCURI.ErrorLog.NoItemsVisibility%%">
64
+ <td colspan="5">
65
+ <em>No logs so far.</em>
66
+ </td>
67
+ </tr>
68
+ </tbody>
69
+ </table>
inc/tpl/infosys-errorlogs.snippet.tpl ADDED
@@ -0,0 +1,8 @@
1
+
2
+ <tr class="%%SUCURI.ErrorLog.CssClass%%">
3
+ <td>%%SUCURI.ErrorLog.DateTime%%</td>
4
+ <td><a href="#" title="%%SUCURI.ErrorLog.ErrorType%%" class="sucuriscan-label-%%SUCURI.ErrorLog.ErrorCode%%">%%SUCURI.ErrorLog.ErrorAbbr%%</a></td>
5
+ <td>%%SUCURI.ErrorLog.ErrorMessage%%</td>
6
+ <td><span class="sucuriscan-monospace sucuriscan-wraptext">%%SUCURI.ErrorLog.FilePath%%</span></td>
7
+ <td><span class="sucuriscan-monospace">%%SUCURI.ErrorLog.LineNumber%%</span></td>
8
+ </tr>
inc/tpl/infosys.html.tpl CHANGED
@@ -13,6 +13,9 @@
13
<li>
14
<a href="#" data-tabname="wpconfig-vars">Config. Variables</a>
15
</li>
16
</ul>
17
18
<div class="sucuriscan-tab-containers">
@@ -31,5 +34,9 @@
31
<div id="sucuriscan-wpconfig-vars">
32
%%SUCURI.WordpressConfig%%
33
</div>
34
</div>
35
</div>
13
<li>
14
<a href="#" data-tabname="wpconfig-vars">Config. Variables</a>
15
</li>
16
+ <li>
17
+ <a href="#" data-tabname="error-logs">Error Logs</a>
18
+ </li>
19
</ul>
20
21
<div class="sucuriscan-tab-containers">
34
<div id="sucuriscan-wpconfig-vars">
35
%%SUCURI.WordpressConfig%%
36
</div>
37
+
38
+ <div id="sucuriscan-error-logs">
39
+ %%SUCURI.ErrorLogs%%
40
+ </div>
41
</div>
42
</div>
inc/tpl/lastlogins-failedlogins.html.tpl CHANGED
@@ -21,6 +21,17 @@
21
settings</a> to enable the brute-force attack alerts.
22
</p>
23
</div>
24
</div>
25
</div>
26
</div>
@@ -30,9 +41,10 @@
30
<tr>
31
<th width="20">No.</th>
32
<th>User</th>
33
<th>IP Address</th>
34
<th>Date/Time</th>
35
- <th width="400">User-Agent</th>
36
</tr>
37
</thead>
38
@@ -40,7 +52,7 @@
40
%%SUCURI.FailedLogins.List%%
41
42
<tr class="sucuriscan-%%SUCURI.FailedLogins.NoItemsVisibility%%">
43
- <td colspan="5">
44
<em>No logs so far.</em>
45
</td>
46
</tr>
21
settings</a> to enable the brute-force attack alerts.
22
</p>
23
</div>
24
+
25
+ <div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.FailedLogins.CollectPasswordsVisibility%%">
26
+ <p>
27
+ If you type a wrong password by mistake your password, the plugin will log the
28
+ username and password in the security logs <em>(which are text/plain
29
+ files)</em>. If someone get access to your API key, or your server fails to
30
+ process the PHP files <em>(which is not usual but may happen)</em> then an
31
+ attacker may get your credentials and invade your site. Change this from the <a
32
+ href="%%SUCURI.URL.Settings%%#settings-general">general settings</a>
33
+ </p>
34
+ </div>
35
</div>
36
</div>
37
</div>
41
<tr>
42
<th width="20">No.</th>
43
<th>User</th>
44
+ <th>Password</th>
45
<th>IP Address</th>
46
<th>Date/Time</th>
47
+ <th width="300">User-Agent</th>
48
</tr>
49
</thead>
50
52
%%SUCURI.FailedLogins.List%%
53
54
<tr class="sucuriscan-%%SUCURI.FailedLogins.NoItemsVisibility%%">
55
+ <td colspan="6">
56
<em>No logs so far.</em>
57
</td>
58
</tr>
inc/tpl/lastlogins-failedlogins.snippet.tpl CHANGED
@@ -2,6 +2,7 @@
2
<tr class="%%SUCURI.FailedLogins.CssClass%%">
3
<td>%%SUCURI.FailedLogins.Num%%</td>
4
<td>%%SUCURI.FailedLogins.Username%%</td>
5
<td><span class="sucuriscan-monospace">%%SUCURI.FailedLogins.RemoteAddr%%</span></td>
6
<td><em>%%SUCURI.FailedLogins.Datetime%%</em></td>
7
<td><div class="sucuriscan-wraptext">%%SUCURI.FailedLogins.UserAgent%%</div></td>
2
<tr class="%%SUCURI.FailedLogins.CssClass%%">
3
<td>%%SUCURI.FailedLogins.Num%%</td>
4
<td>%%SUCURI.FailedLogins.Username%%</td>
5
+ <td>%%SUCURI.FailedLogins.Password%%</td>
6
<td><span class="sucuriscan-monospace">%%SUCURI.FailedLogins.RemoteAddr%%</span></td>
7
<td><em>%%SUCURI.FailedLogins.Datetime%%</em></td>
8
<td><div class="sucuriscan-wraptext">%%SUCURI.FailedLogins.UserAgent%%</div></td>
inc/tpl/settings-general.html.tpl CHANGED
@@ -127,5 +127,29 @@
127
</td>
128
</tr>
129
130
</tbody>
131
</table>
127
</td>
128
</tr>
129
130
+ <tr>
131
+ <td>Collect failed passwords</td>
132
+ <td>%%SUCURI.CollectWrongPasswords%%</td>
133
+ <td class="td-with-button">
134
+ <form action="%%SUCURI.URL.Settings%%" method="post">
135
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
136
+ <input type="text" name="sucuriscan_collect_wrong_passwords" class="input-text" placeholder="Type: YES or NO" />
137
+ <button type="submit" class="button-primary">Change</button>
138
+ </form>
139
+ </td>
140
+ </tr>
141
+
142
+ <tr>
143
+ <td>Log storage path</td>
144
+ <td><span class="sucuriscan-monospace" title="%%SUCURI.DatastorePath%%">%%SUCURI.DatastorePathShort%%</span></td>
145
+ <td class="td-with-button">
146
+ <form action="%%SUCURI.URL.Settings%%" method="post">
147
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
148
+ <input type="text" name="sucuriscan_datastore_path" class="input-text" placeholder="Directory to save logs..." />
149
+ <button type="submit" class="button-primary">Change</button>
150
+ </form>
151
+ </td>
152
+ </tr>
153
+
154
</tbody>
155
</table>
inc/tpl/settings-ignorerules.html.tpl CHANGED
@@ -45,15 +45,4 @@
45
<tbody>
46
%%SUCURI.IgnoreRules.PostTypes%%
47
</tbody>
48
-
49
- <tfoot>
50
- <tr>
51
- <td colspan="5">
52
- <em>
53
- <strong>Notifications example:</strong>
54
- <code>Post_Type</code> changed from private to published <code>#ID</code> (<code>Title</code>)
55
- </em>
56
- </td>
57
- </tr>
58
- </tfoot>
59
</table>
45
<tbody>
46
%%SUCURI.IgnoreRules.PostTypes%%
47
</tbody>
48
</table>
inc/tpl/settings-ignorescanning.html.tpl CHANGED
@@ -13,6 +13,13 @@
13
directories, this will force the plugin to ignore the files inside these
14
folders.
15
</p>
16
</div>
17
</div>
18
</div>
@@ -33,6 +40,12 @@
33
34
<tbody>
35
%%SUCURI.IgnoreScanning.ResourceList%%
36
</tbody>
37
38
<tfoot>
13
directories, this will force the plugin to ignore the files inside these
14
folders.
15
</p>
16
+
17
+ <div class="sucuriscan-inline-alert-warning sucuriscan-%%SUCURI.IgnoreScanning.DisabledVisibility%%">
18
+ <p>
19
+ The feature to ignore directories during the file system scans is disabled, go
20
+ to the <em>Scanner Settings</em> panel to enable it.
21
+ </p>
22
+ </div>
23
</div>
24
</div>
25
</div>
40
41
<tbody>
42
%%SUCURI.IgnoreScanning.ResourceList%%
43
+
44
+ <tr class="sucuriscan-%%SUCURI.IgnoreScanning.NoItemsVisibility%%">
45
+ <td colspan="4">
46
+ <em>List is empty.</em>
47
+ </td>
48
+ </tr>
49
</tbody>
50
51
<tfoot>
inc/tpl/settings-scanner.html.tpl CHANGED
@@ -74,6 +74,18 @@
74
</tr>
75
76
<tr>
77
<td>Scan error log files</td>
78
<td>%%SUCURI.ScanErrorlogsStatus%%</td>
79
<td class="td-with-button">
@@ -85,6 +97,18 @@
85
</td>
86
</tr>
87
88
<tr class="alternate">
89
<td>Last Scanning</td>
90
<td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
@@ -124,5 +148,17 @@
124
</td>
125
</tr>
126
127
</tbody>
128
</table>
74
</tr>
75
76
<tr>
77
+ <td>Ignore some files</td>
78
+ <td>%%SUCURI.IgnoreScanningStatus%%</td>
79
+ <td class="td-with-button">
80
+ <form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
81
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
82
+ <input type="hidden" name="sucuriscan_ignore_scanning" value="%%SUCURI.IgnoreScanningSwitchValue%%" />
83
+ <button type="submit" class="button-primary %%SUCURI.IgnoreScanningSwitchCssClass%%">%%SUCURI.IgnoreScanningSwitchText%%</button>
84
+ </form>
85
+ </td>
86
+ </tr>
87
+
88
+ <tr class="alternate">
89
<td>Scan error log files</td>
90
<td>%%SUCURI.ScanErrorlogsStatus%%</td>
91
<td class="td-with-button">
97
</td>
98
</tr>
99
100
+ <tr>
101
+ <td>Parse error logs</td>
102
+ <td>%%SUCURI.ParseErrorLogsStatus%%</td>
103
+ <td class="td-with-button">
104
+ <form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
105
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
106
+ <input type="hidden" name="sucuriscan_parse_errorlogs" value="%%SUCURI.ParseErrorLogsSwitchValue%%" />
107
+ <button type="submit" class="button-primary %%SUCURI.ParseErrorLogsSwitchCssClass%%">%%SUCURI.ParseErrorLogsSwitchText%%</button>
108
+ </form>
109
+ </td>
110
+ </tr>
111
+
112
<tr class="alternate">
113
<td>Last Scanning</td>
114
<td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
148
</td>
149
</tr>
150
151
+ <tr>
152
+ <td>Error logs limit</td>
153
+ <td>%%SUCURI.ErrorLogsLimit%% last lines</td>
154
+ <td class="td-with-button">
155
+ <form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
156
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
157
+ <input type="text" name="sucuriscan_errorlogs_limit" placeholder="Number of lines to analyze" class="input-text" />
158
+ <button type="submit" class="button-primary">Change</button>
159
+ </form>
160
+ </td>
161
+ </tr>
162
+
163
</tbody>
164
</table>
inc/tpl/settings-trustip.html.tpl ADDED
@@ -0,0 +1,58 @@
1
+
2
+ <div id="poststuff">
3
+ <div class="postbox sucuriscan-border sucuriscan-table-description sucuriscan-trustip-form">
4
+ <h3>Trust IP Address</h3>
5
+
6
+ <div class="inside">
7
+ <p>
8
+ If you are working in a LAN <em>(Local Area Network)</em> you may want to
9
+ include the IP addresses of all the nodes in the subnet, this will force the
10
+ plugin to stop sending email notifications about actions executed from trusted
11
+ IP addresses. Use the CIDR <em>(Classless Inter Domain Routing)</em> format to
12
+ specify ranges of IP addresses <em>(only 8, 16, and 24)</em>.
13
+ </p>
14
+
15
+ <form action="%%SUCURI.URL.Settings%%#settings-trustip" method="POST">
16
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
17
+ <input type="text" name="sucuriscan_trust_ip" placeholder="e.g. 182.120.56.0/24" />
18
+ <input type="submit" value="Add Entry" class="button button-primary" />
19
+ </form>
20
+ </div>
21
+ </div>
22
+ </div>
23
+
24
+ <form action="%%SUCURI.URL.Settings%%#settings-trustip" method="post">
25
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
26
+
27
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-settings-trustip">
28
+ <thead>
29
+ <tr>
30
+ <th class="manage-column column-cb check-column">
31
+ <label class="screen-reader-text" for="cb-select-all-1">Select All</label>
32
+ <input id="cb-select-all-1" type="checkbox">
33
+ </th>
34
+ <th class="manage-column">IP Address</th>
35
+ <th class="manage-column">CIDR Format</th>
36
+ <th class="manage-column">Added At</th>
37
+ </tr>
38
+ </thead>
39
+
40
+ <tbody>
41
+ %%SUCURI.TrustedIPs.List%%
42
+
43
+ <tr class="sucuriscan-%%SUCURI.TrustedIPs.NoItems.Visibility%%">
44
+ <td colspan="4">
45
+ <em>List is empty.</em>
46
+ </td>
47
+ </tr>
48
+ </tbody>
49
+
50
+ <tfoot>
51
+ <tr>
52
+ <td colspan="4">
53
+ <button type="submit" class="button button-primary">Removed selected</button>
54
+ </td>
55
+ </tr>
56
+ </tfoot>
57
+ </table>
58
+ </form>
inc/tpl/settings-trustip.snippet.tpl ADDED
@@ -0,0 +1,9 @@
1
+
2
+ <tr class="%%SUCURI.TrustIP.CssClass%%">
3
+ <td class="check-column">
4
+ <input type="checkbox" name="sucuriscan_del_trust_ip[]" value="%%SUCURI.TrustIP.CacheKey%%" />
5
+ </td>
6
+ <td><span class="sucuriscan-monospace">%%SUCURI.TrustIP.RemoteAddr%%</span></td>
7
+ <td><span class="sucuriscan-monospace">%%SUCURI.TrustIP.CIDRFormat%%</span></td>
8
+ <td><span class="sucuriscan-monospace">%%SUCURI.TrustIP.AddedAt%%</span></td>
9
+ </tr>
inc/tpl/settings.html.tpl CHANGED
@@ -16,6 +16,9 @@
16
<li>
17
<a href="#" data-tabname="settings-ignorerules">Ignore Alerts</a>
18
</li>
19
<li>
20
<a href="#" data-tabname="settings-heartbeat">Heartbeat</a>
21
</li>
@@ -42,6 +45,10 @@
42
%%SUCURI.Settings.IgnoreRules%%
43
</div>
44
45
<div id="sucuriscan-settings-heartbeat">
46
%%SUCURI.Settings.Heartbeat%%
47
</div>
16
<li>
17
<a href="#" data-tabname="settings-ignorerules">Ignore Alerts</a>
18
</li>
19
+ <li>
20
+ <a href="#" data-tabname="settings-trustip">Trust IP</a>
21
+ </li>
22
<li>
23
<a href="#" data-tabname="settings-heartbeat">Heartbeat</a>
24
</li>
45
%%SUCURI.Settings.IgnoreRules%%
46
</div>
47
48
+ <div id="sucuriscan-settings-trustip">
49
+ %%SUCURI.Settings.TrustIP%%
50
+ </div>
51
+
52
<div id="sucuriscan-settings-heartbeat">
53
%%SUCURI.Settings.Heartbeat%%
54
</div>
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: dd@sucuri.net
3
Donate Link: http://sucuri.net/
4
Tags: malware, security, firewall, scan, spam, virus, sucuri, protection,WordPress Security, Login Security,Security Auditing,File Integrity,htaccess,phishing,backdoors,SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
5
Requires at least:3.2
6
- Stable tag:1.7.2
7
- Tested up to: 4.0
8
9
The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
10
3
Donate Link: http://sucuri.net/
4
Tags: malware, security, firewall, scan, spam, virus, sucuri, protection,WordPress Security, Login Security,Security Auditing,File Integrity,htaccess,phishing,backdoors,SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
5
Requires at least:3.2
6
+ Stable tag:1.7.3
7
+ Tested up to: 4.0.1
8
9
The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
10
sucuri.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Sucuri Security - Auditing, Malware Scanner and Hardening
4
Plugin URI: http://wordpress.sucuri.net/
5
Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
6
Author: Sucuri, INC
7
- Version: 1.7.2
8
Author URI: http://sucuri.net
9
*/
10
@@ -66,7 +66,7 @@ define('SUCURISCAN', 'sucuriscan');
66
/**
67
* Current version of the plugin's code.
68
*/
69
- define('SUCURISCAN_VERSION', '1.7.2');
70
71
/**
72
* The name of the Sucuri plugin main file.
@@ -128,6 +128,11 @@ define('SUCURISCAN_LASTLOGINS_USERSLIMIT', 25);
128
*/
129
define('SUCURISCAN_AUDITLOGS_PER_PAGE', 50);
130
131
/**
132
* The minimum quantity of seconds to wait before each filesystem scan.
133
*/
@@ -240,6 +245,9 @@ if( defined('SUCURISCAN') ){
240
'false' => 'Stop peer\'s cert verification',
241
);
242
243
/**
244
* Remove the WordPress generator meta-tag from the source code.
245
*/
@@ -454,9 +462,24 @@ class SucuriScan {
454
* @return string The full filesystem path including the directory specified.
455
*/
456
public static function datastore_folder_path( $path='' ){
457
- $wp_dir_array = wp_upload_dir();
458
- $wp_dir_array['basedir'] = untrailingslashit($wp_dir_array['basedir']);
459
- $wp_filepath = $wp_dir_array['basedir'] . '/sucuri/' . $path;
460
461
return $wp_filepath;
462
}
@@ -569,13 +592,13 @@ class SucuriScan {
569
570
if( self::is_behind_cloudproxy() ){
571
$alternatives = array(
572
'HTTP_X_REAL_IP',
573
'HTTP_CLIENT_IP',
574
'HTTP_X_FORWARDED_FOR',
575
'HTTP_X_FORWARDED',
576
'HTTP_FORWARDED_FOR',
577
'HTTP_FORWARDED',
578
- 'HTTP_X_SUCURI_CLIENTIP',
579
'SUCURI_RIP',
580
'REMOTE_ADDR',
581
);
@@ -664,6 +687,18 @@ class SucuriScan {
664
$host_by_name = @gethostbyaddr($host_by_addr);
665
$status = (bool) preg_match('/^cloudproxy[0-9]+\.sucuri\.net#x2F;', $host_by_name);
666
667
if( $verbose ){
668
return array(
669
'http_host' => $http_host,
@@ -830,6 +865,44 @@ class SucuriScan {
830
return FALSE;
831
}
832
833
/**
834
* Validate email address.
835
*
@@ -961,6 +1034,30 @@ class SucuriScan {
961
}
962
}
963
964
}
965
966
/**
@@ -1069,6 +1166,15 @@ class SucuriScanRequest extends SucuriScan {
1069
*/
1070
class SucuriScanFileInfo extends SucuriScan {
1071
1072
/**
1073
* Whether the list of files that can be ignored from the filesystem scan will
1074
* be used to return the directory tree, this should be disabled when scanning a
@@ -1117,14 +1223,13 @@ class SucuriScanFileInfo extends SucuriScan {
1117
* on some rules defined by the developer.
1118
*
1119
* @param string $directory Parent directory where the filesystem scan will start.
1120
- * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
1121
* @param boolean $as_array Whether the result of the operation will be returned as an array or string.
1122
* @return array List of files in the main and subdirectories of the folder specified.
1123
*/
1124
- public function get_directory_tree_md5( $directory='', $scan_with='spl', $as_array=FALSE ){
1125
$project_signatures = '';
1126
$abs_path = rtrim( ABSPATH, '/' );
1127
- $files = $this->get_directory_tree($directory, $scan_with);
1128
1129
if( $as_array ){
1130
$project_signatures = array();
@@ -1168,20 +1273,24 @@ class SucuriScanFileInfo extends SucuriScan {
1168
* on some rules defined by the developer.
1169
*
1170
* @param string $directory Parent directory where the filesystem scan will start.
1171
- * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
1172
* @return array List of files in the main and subdirectories of the folder specified.
1173
*/
1174
- public function get_directory_tree($directory='', $scan_with='spl'){
1175
if( file_exists($directory) && is_dir($directory) ){
1176
$tree = array();
1177
- $this->ignored_directories = SucuriScanFSScanner::get_ignored_directories();
1178
1179
- switch( $scan_with ){
1180
case 'spl':
1181
if( $this->is_spl_available() ){
1182
$tree = $this->get_directory_tree_with_spl($directory);
1183
} else {
1184
- $tree = $this->get_directory_tree($directory, 'opendir');
1185
}
1186
break;
1187
@@ -1194,7 +1303,8 @@ class SucuriScanFileInfo extends SucuriScan {
1194
break;
1195
1196
default:
1197
- $tree = $this->get_directory_tree($directory, 'spl');
1198
break;
1199
}
1200
@@ -1321,14 +1431,18 @@ class SucuriScanFileInfo extends SucuriScan {
1321
foreach( $files_found as $filepath ){
1322
$filepath = realpath($filepath);
1323
$directory = dirname($filepath);
1324
- $filename = array_pop(explode('/', $filepath));
1325
1326
if( is_dir($filepath) ){
1327
if( $this->ignore_folderpath($directory, $filename) ){ continue; }
1328
1329
if( $this->run_recursively ){
1330
- $sub_files = $this->get_directory_tree_with_opendir($filepath);
1331
- $files = array_merge($files, $sub_files);
1332
}
1333
} else {
1334
if( $this->ignore_filepath($filename) ){ continue; }
@@ -1361,7 +1475,10 @@ class SucuriScanFileInfo extends SucuriScan {
1361
1362
if( $this->run_recursively ){
1363
$sub_files = $this->get_directory_tree_with_opendir($filepath);
1364
- $files = array_merge($files, $sub_files);
1365
}
1366
} else {
1367
if( $this->ignore_filepath($filename) ){ continue; }
@@ -1443,11 +1560,16 @@ class SucuriScanFileInfo extends SucuriScan {
1443
$dir_tree = $this->get_directory_tree($dir_tree);
1444
}
1445
1446
- foreach( $dir_tree as $filepath ){
1447
- $dir_path = dirname($filepath);
1448
1449
- if( !in_array($dir_path, $dirs) ){
1450
- $dirs[] = $dir_path;
1451
}
1452
}
1453
@@ -1516,6 +1638,53 @@ class SucuriScanFileInfo extends SucuriScan {
1516
return @file( $filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
1517
}
1518
1519
}
1520
1521
/**
@@ -1986,12 +2155,16 @@ class SucuriScanOption extends SucuriScanRequest {
1986
$defaults = array(
1987
'sucuriscan_api_key' => FALSE,
1988
'sucuriscan_account' => '',
1989
'sucuriscan_fs_scanner' => 'enabled',
1990
'sucuriscan_scan_frequency' => 'hourly',
1991
'sucuriscan_scan_interface' => 'spl',
1992
'sucuriscan_scan_modfiles' => 'enabled',
1993
'sucuriscan_scan_checksums' => 'enabled',
1994
'sucuriscan_scan_errorlogs' => 'enabled',
1995
'sucuriscan_runtime' => 0,
1996
'sucuriscan_lastlogin_redirection' => 'enabled',
1997
'sucuriscan_notify_to' => '',
@@ -2004,6 +2177,7 @@ class SucuriScanOption extends SucuriScanRequest {
2004
'sucuriscan_notify_post_publication' => 'enabled',
2005
'sucuriscan_notify_theme_editor' => 'enabled',
2006
'sucuriscan_maximum_failed_logins' => 30,
2007
'sucuriscan_ignored_events' => '',
2008
'sucuriscan_verify_ssl_cert' => 'true',
2009
'sucuriscan_request_timeout' => 90,
@@ -2310,10 +2484,8 @@ class SucuriScanOption extends SucuriScanRequest {
2310
2311
// Encode (old) serialized data into JSON.
2312
if( self::is_serialized($post_types) ){
2313
- var_dump($post_types);
2314
$post_types_arr = @unserialize($post_types);
2315
$post_types_fix = json_encode($post_types_arr);
2316
- echo 'fixed';
2317
self::update_option( ':ignored_events', $post_types_fix );
2318
2319
return $post_types_arr;
@@ -2542,8 +2714,8 @@ class SucuriScanEvent extends SucuriScan {
2542
self::report_site_version();
2543
2544
$sucuri_fileinfo = new SucuriScanFileInfo();
2545
- $scan_interface = SucuriScanOption::get_option(':scan_interface');
2546
- $signatures = $sucuri_fileinfo->get_directory_tree_md5(ABSPATH, $scan_interface);
2547
2548
if( $signatures ){
2549
$hashes_sent = SucuriScanAPI::send_hashes( $signatures );
@@ -2631,18 +2803,22 @@ class SucuriScanEvent extends SucuriScan {
2631
$email = SucuriScanOption::get_option(':notify_to');
2632
$email_params = array();
2633
2634
if( $notify == 'enabled' ){
2635
if( $event == 'post_publication' ){
2636
$event = 'post_update';
2637
}
2638
2639
elseif( $event == 'failed_login' ){
2640
- $content .= '<br><br><em>Explanation: Someone failed to login to your site. If you
2641
- are getting too many of these messages, it is likely your site is under a brute
2642
- force attack. You can disable the notifications for failed logins from
2643
- <a href="' . SucuriScanTemplate::get_url('settings') . '" target="_blank">here</a>.
2644
- More details at <a href="http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing"
2645
- target="_blank">Password Guessing Brute Force Attacks</a>.</em>';
2646
}
2647
2648
// Send a notification even if the limit of emails per hour was reached.
@@ -2659,6 +2835,58 @@ class SucuriScanEvent extends SucuriScan {
2659
return FALSE;
2660
}
2661
2662
/**
2663
* Generate and set a new password for a specific user not in session.
2664
*
@@ -3011,12 +3239,18 @@ class SucuriScanHook extends SucuriScanEvent {
3011
public static function hook_wp_login_failed( $title='' ){
3012
if( empty($title) ){ $title = 'Unknown'; }
3013
3014
- $message = 'User authentication failed: '.$title;
3015
self::report_event( 2, 'core', $message );
3016
self::notify_event( 'failed_login', $message );
3017
3018
// Log the failed login in the internal datastore for future reports.
3019
- $logged = sucuriscan_log_failed_login($title);
3020
3021
// Check if the quantity of failed logins will be considered as a brute-force attack.
3022
if( $logged ){
@@ -3974,9 +4208,10 @@ class SucuriScanAPI extends SucuriScanOption {
3974
*/
3975
public static function get_official_checksums( $version=0 ){
3976
$url = 'http://api.wordpress.org/core/checksums/1.0/';
3977
$response = self::api_call( $url, 'GET', array(
3978
'version' => $version,
3979
- 'locale' => 'en_US',
3980
));
3981
3982
if( $response ){
@@ -4027,8 +4262,8 @@ class SucuriScanAPI extends SucuriScanOption {
4027
4028
// Get the plugin's basic information from WordPress transient data.
4029
$plugins = get_plugins();
4030
- $pattern = '/^http:\/\/wordpress\.org\/plugins\/(.*)\/#x2F;';
4031
- $wp_market = 'http://wordpress.org/plugins/%s/';
4032
4033
// Loop through each plugin data and complement its information with more attributes.
4034
foreach( $plugins as $plugin_path => $plugin_data ){
@@ -4043,7 +4278,7 @@ class SucuriScanAPI extends SucuriScanOption {
4043
&& preg_match($pattern, $plugin_data['PluginURI'], $match)
4044
){
4045
$repository = $match[0];
4046
- $repository_name = $match[1];
4047
$is_free_plugin = TRUE;
4048
}
4049
@@ -4692,6 +4927,18 @@ class SucuriScanFSScanner extends SucuriScan {
4692
return FALSE;
4693
}
4694
4695
/**
4696
* Add a new directory path to the list of ignored paths.
4697
*
@@ -4801,6 +5048,7 @@ class SucuriScanFSScanner extends SucuriScan {
4801
$sucuri_fileinfo = new SucuriScanFileInfo();
4802
$sucuri_fileinfo->ignore_files = TRUE;
4803
$sucuri_fileinfo->ignore_directories = TRUE;
4804
$directory_list = $sucuri_fileinfo->get_diretories_only(ABSPATH);
4805
4806
if( $directory_list ){
@@ -4810,6 +5058,77 @@ class SucuriScanFSScanner extends SucuriScan {
4810
return $response;
4811
}
4812
4813
}
4814
4815
/**
@@ -4988,13 +5307,9 @@ class SucuriScanInterface {
4988
* @return void
4989
*/
4990
public static function initialize(){
4991
- if(
4992
- isset($_SERVER['HTTP_X_FORWARDED_FOR'])
4993
- && SucuriScan::is_valid_ip($_SERVER['HTTP_X_FORWARDED_FOR'])
4994
- && SucuriScan::is_behind_cloudproxy()
4995
- ){
4996
$_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
4997
- $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
4998
}
4999
}
5000
@@ -5213,6 +5528,7 @@ class SucuriScanInterface {
5213
public static function setup_notice(){
5214
if(
5215
current_user_can('manage_options')
5216
&& !SucuriScanAPI::get_plugin_key()
5217
&& SucuriScanRequest::post(':plugin_api_key') === FALSE
5218
&& SucuriScanRequest::post(':recover_key') === FALSE
@@ -6563,15 +6879,14 @@ function sucuriscan_harden_phpversion(){
6563
*/
6564
function sucuriscan_cloudproxy_enabled(){
6565
$btn_string = '';
6566
- $verbosity = TRUE;
6567
- $proxy_info = SucuriScan::is_behind_cloudproxy($verbosity);
6568
$status = 1;
6569
6570
$description = 'A WAF is a protection layer for your web site, blocking all sort of attacks (brute force attempts, '
6571
. 'DDoS, SQL injections, etc) and helping it remain malware and blacklist free. This test checks if your site is '
6572
. 'using <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri\'s CloudProxy WAF</a> to protect your site.';
6573
6574
- if( $proxy_info['status'] === FALSE ){
6575
$status = 0;
6576
$btn_string = '<a href="http://cloudproxy.sucuri.net/" target="_blank" class="button button-primary">Harden</a>';
6577
}
@@ -6976,13 +7291,14 @@ function sucuriscan_get_integrity_tree( $dir='./', $recursive=FALSE ){
6976
$sucuri_fileinfo->ignore_files = FALSE;
6977
$sucuri_fileinfo->ignore_directories = FALSE;
6978
$sucuri_fileinfo->run_recursively = $recursive;
6979
- $integrity_tree = $sucuri_fileinfo->get_directory_tree_md5( $dir, 'opendir', TRUE );
6980
6981
- if( $integrity_tree ){
6982
- return $integrity_tree;
6983
}
6984
6985
- return FALSE;
6986
}
6987
6988
/**
@@ -7055,10 +7371,16 @@ function sucuriscan_auditlogs(){
7055
$template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
7056
7057
if( $total_items > 0 ){
7058
$template_variables['AuditLogs.PaginationVisibility'] = 'visible';
7059
$template_variables['AuditLogs.PaginationLinks'] = SucuriScanTemplate::get_pagination(
7060
'%%SUCURI.URL.Home%%',
7061
- $max_per_page * 5, /* TODO: Temporary value while we get the total logs. */
7062
$max_per_page
7063
);
7064
}
@@ -7339,7 +7661,7 @@ function sucuriscan_modified_files(){
7339
// Search modified files among the project's files.
7340
$content_hashes = sucuriscan_get_integrity_tree( ABSPATH.'wp-content', true );
7341
7342
- if( $content_hashes ){
7343
$template_variables['ModifiedFiles.Days'] = $back_days;
7344
$back_days = current_time('timestamp') - ( $back_days * 86400);
7345
$counter = 0;
@@ -8080,7 +8402,9 @@ if( !function_exists('sucuri_get_user_lastlogin') ){
8080
8081
$lastlogin_message = sprintf(
8082
'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>',
8083
- date('d/M/Y H:i'), $row->user_remoteaddr, $row->user_hostname
8084
);
8085
$lastlogin_message .= chr(32).'(<a href="'.SucuriScanTemplate::get_url('lastlogins').'">view all logs</a>)';
8086
SucuriScanInterface::info( $lastlogin_message );
@@ -8302,22 +8626,54 @@ function sucuriscan_failed_logins_panel(){
8302
'FailedLogins.MaxFailedLogins' => 0,
8303
'FailedLogins.NoItemsVisibility' => 'visible',
8304
'FailedLogins.WarningVisibility' => 'visible',
8305
);
8306
8307
$max_failed_logins = SucuriScanOption::get_option(':maximum_failed_logins');
8308
$notify_bruteforce_attack = SucuriScanOption::get_option(':notify_bruteforce_attack');
8309
$failed_logins = sucuriscan_get_failed_logins();
8310
8311
if( $failed_logins ){
8312
$counter = 0;
8313
8314
foreach( $failed_logins['entries'] as $login_data ){
8315
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
8316
8317
$template_variables['FailedLogins.List'] .= SucuriScanTemplate::get_snippet('lastlogins-failedlogins', array(
8318
'FailedLogins.CssClass' => $css_class,
8319
'FailedLogins.Num' => ($counter + 1),
8320
'FailedLogins.Username' => SucuriScan::escape($login_data['user_login']),
8321
'FailedLogins.RemoteAddr' => SucuriScan::escape($login_data['remote_addr']),
8322
'FailedLogins.Datetime' => SucuriScan::datetime($login_data['attempt_time']),
8323
'FailedLogins.UserAgent' => SucuriScan::escape($login_data['user_agent']),
@@ -8337,9 +8693,22 @@ function sucuriscan_failed_logins_panel(){
8337
$template_variables['FailedLogins.WarningVisibility'] = 'hidden';
8338
}
8339
8340
return SucuriScanTemplate::get_section('lastlogins-failedlogins', $template_variables);
8341
}
8342
8343
/**
8344
* Find the full path of the file where the information of the failed logins
8345
* will be stored, it will be created automatically if does not exists (and if
@@ -8348,11 +8717,13 @@ function sucuriscan_failed_logins_panel(){
8348
*
8349
* @see sucuriscan_reset_failed_logins()
8350
*
8351
- * @param boolean $reset Whether the file will be resetted or not.
8352
- * @return string The full (relative) path where the file is located.
8353
*/
8354
- function sucuriscan_failed_logins_datastore_path( $reset=FALSE ){
8355
- $datastore_path = SucuriScan::datastore_folder_path('sucuri-failedlogins.php');
8356
$default_content = sucuriscan_failed_logins_default_content();
8357
8358
// Create the file if it does not exists.
@@ -8390,10 +8761,11 @@ function sucuriscan_failed_logins_default_content(){
8390
* with the report) or reset the file after considering it a normal behavior of
8391
* the site.
8392
*
8393
- * @return array Information and entries gathered from the failed logins datastore file.
8394
*/
8395
- function sucuriscan_get_failed_logins(){
8396
- $datastore_path = sucuriscan_failed_logins_datastore_path();
8397
$default_content = sucuriscan_failed_logins_default_content();
8398
$default_content_n = substr_count($default_content, "\n");
8399
@@ -8419,6 +8791,10 @@ function sucuriscan_get_failed_logins(){
8419
$login_data['user_agent'] = 'Unknown';
8420
}
8421
8422
$failed_logins['entries'][] = $login_data;
8423
$failed_logins['count'] += 1;
8424
}
@@ -8445,15 +8821,22 @@ function sucuriscan_get_failed_logins(){
8445
* this entry will contain the username, timestamp of the login attempt, remote
8446
* address of the computer sending the request, and the user-agent.
8447
*
8448
- * @param string $user_login Information from the current failed login event.
8449
- * @return boolean Whether the information of the current failed login event was stored or not.
8450
*/
8451
- function sucuriscan_log_failed_login( $user_login='' ){
8452
$datastore_path = sucuriscan_failed_logins_datastore_path();
8453
8454
if( $datastore_path ){
8455
$login_data = json_encode(array(
8456
'user_login' => $user_login,
8457
'attempt_time' => time(),
8458
'remote_addr' => SucuriScan::get_remote_addr(),
8459
'user_agent' => SucuriScan::get_user_agent(),
@@ -8479,6 +8862,7 @@ function sucuriscan_log_failed_login( $user_login='' ){
8479
function sucuriscan_report_failed_logins( $failed_logins=array() ){
8480
if( $failed_logins && $failed_logins['count'] > 0 ){
8481
$prettify_mails = SucuriScanMail::prettify_mails();
8482
$mail_content = '';
8483
8484
if( $prettify_mails ){
@@ -8488,6 +8872,11 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
8488
$table_html .= '<thead>';
8489
$table_html .= '<tr>';
8490
$table_html .= '<th>Username</th>';
8491
$table_html .= '<th>IP Address</th>';
8492
$table_html .= '<th>Attempt Timestamp</th>';
8493
$table_html .= '<th>Attempt Date/Time</th>';
@@ -8501,6 +8890,11 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
8501
if( $prettify_mails ){
8502
$table_html .= '<tr>';
8503
$table_html .= '<td>' . esc_attr($login_data['user_login']) . '</td>';
8504
$table_html .= '<td>' . esc_attr($login_data['remote_addr']) . '</td>';
8505
$table_html .= '<td>' . $login_data['attempt_time'] . '</td>';
8506
$table_html .= '<td>' . $login_data['attempt_date'] . '</td>';
@@ -8508,6 +8902,11 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
8508
} else {
8509
$mail_content .= "\n";
8510
$mail_content .= 'Username: ' . $login_data['user_login'] . "\n";
8511
$mail_content .= 'IP Address: ' . $login_data['remote_addr'] . "\n";
8512
$mail_content .= 'Attempt Timestamp: ' . $login_data['attempt_time'] . "\n";
8513
$mail_content .= 'Attempt Date/Time: ' . $login_data['attempt_date'] . "\n";
@@ -8539,7 +8938,19 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
8539
* @return boolean Whether the datastore file was resetted or not.
8540
*/
8541
function sucuriscan_reset_failed_logins(){
8542
- return (bool) sucuriscan_failed_logins_datastore_path(TRUE);
8543
}
8544
8545
/**
@@ -8557,6 +8968,7 @@ function sucuriscan_settings_page(){
8557
'Settings.IgnoreScanning' => sucuriscan_settings_ignorescanning(),
8558
'Settings.Notifications' => sucuriscan_settings_notifications(),
8559
'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
8560
'Settings.Heartbeat' => sucuriscan_settings_heartbeat(),
8561
);
8562
@@ -8628,6 +9040,14 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
8628
SucuriScanInterface::info( 'Filesystem scanner for file integrity was <code>' . $action_d . '</code>' );
8629
}
8630
8631
// Enable or disable the filesystem scanner for error logs.
8632
if( $scan_errorlogs = SucuriScanRequest::post(':scan_errorlogs', '(en|dis)able') ){
8633
$action_d = $scan_errorlogs . 'd';
@@ -8636,6 +9056,28 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
8636
SucuriScanInterface::info( 'Filesystem scanner for error logs was <code>' . $action_d . '</code>' );
8637
}
8638
8639
// Modify the schedule of the filesystem scanner.
8640
if( $frequency = SucuriScanRequest::post(':scan_frequency') ){
8641
if( array_key_exists($frequency, $sucuriscan_schedule_allowed) ){
@@ -8723,6 +9165,44 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
8723
SucuriScanInterface::info( 'API request timeout set to <code>' . $request_timeout . '</code> seconds.' );
8724
}
8725
8726
// Update the notification settings.
8727
if( SucuriScanRequest::post(':save_notification_settings') !== FALSE ){
8728
$options_updated_counter = 0;
@@ -8806,6 +9286,38 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
8806
}
8807
}
8808
8809
// Update the settings for the heartbeat API.
8810
if( $heartbeat_status = SucuriScanRequest::post(':heartbeat_status') ){
8811
$statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
@@ -8919,6 +9431,9 @@ function sucuriscan_settings_general(){
8919
'VerifySSLCert' => 'Undefined',
8920
'VerifySSLCertOptions' => $verify_ssl_cert_options,
8921
'RequestTimeout' => SucuriScanOption::get_option(':request_timeout') . ' seconds',
8922
'ModalWhenAPIRegistered' => $api_registered_modal,
8923
);
8924
@@ -8934,6 +9449,14 @@ function sucuriscan_settings_general(){
8934
$template_variables['VerifySSLCert'] = $sucuriscan_verify_ssl_cert[$verify_ssl_cert];
8935
}
8936
8937
return SucuriScanTemplate::get_section('settings-general', $template_variables);
8938
}
8939
@@ -8954,6 +9477,9 @@ function sucuriscan_settings_scanner(){
8954
$scan_modfiles = SucuriScanOption::get_option(':scan_modfiles');
8955
$scan_checksums = SucuriScanOption::get_option(':scan_checksums');
8956
$scan_errorlogs = SucuriScanOption::get_option(':scan_errorlogs');
8957
$runtime_scan_human = SucuriScanFSScanner::get_filesystem_runtime(TRUE);
8958
8959
// Generate the HTML code for the option list in the form select fields.
@@ -8976,11 +9502,21 @@ function sucuriscan_settings_scanner(){
8976
'ScanChecksumsSwitchText' => 'Disable',
8977
'ScanChecksumsSwitchValue' => 'disable',
8978
'ScanChecksumsSwitchCssClass' => 'button-danger',
8979
/* Scan error logs. */
8980
'ScanErrorlogsStatus' => 'Enabled',
8981
'ScanErrorlogsSwitchText' => 'Disable',
8982
'ScanErrorlogsSwitchValue' => 'disable',
8983
'ScanErrorlogsSwitchCssClass' => 'button-danger',
8984
/* Filsystem scanning frequency. */
8985
'ScanningFrequency' => 'Undefined',
8986
'ScanningFrequencyOptions' => $scan_freq_options,
@@ -8988,6 +9524,7 @@ function sucuriscan_settings_scanner(){
8988
'ScanningInterfaceOptions' => $scan_interface_options,
8989
/* Filesystem scanning runtime. */
8990
'ScanningRuntimeHuman' => $runtime_scan_human,
8991
);
8992
8993
if( $fs_scanner == 'disabled' ){
@@ -9011,6 +9548,13 @@ function sucuriscan_settings_scanner(){
9011
$template_variables['ScanChecksumsSwitchCssClass'] = 'button-success';
9012
}
9013
9014
if( $scan_errorlogs == 'disabled' ){
9015
$template_variables['ScanErrorlogsStatus'] = 'Disabled';
9016
$template_variables['ScanErrorlogsSwitchText'] = 'Enable';
@@ -9018,6 +9562,13 @@ function sucuriscan_settings_scanner(){
9018
$template_variables['ScanErrorlogsSwitchCssClass'] = 'button-success';
9019
}
9020
9021
if( array_key_exists($scan_freq, $sucuriscan_schedule_allowed) ){
9022
$template_variables['ScanningFrequency'] = $sucuriscan_schedule_allowed[$scan_freq];
9023
}
@@ -9118,6 +9669,48 @@ function sucuriscan_settings_ignore_rules(){
9118
return SucuriScanTemplate::get_section('settings-ignorerules', $template_variables);
9119
}
9120
9121
/**
9122
* Read and parse the content of the ignore-scanning settings template.
9123
*
@@ -9126,45 +9719,62 @@ function sucuriscan_settings_ignore_rules(){
9126
function sucuriscan_settings_ignorescanning(){
9127
$template_variables = array(
9128
'IgnoreScanning.ResourceList' => '',
9129
);
9130
9131
- $dir_list_list = SucuriScanFSScanner::get_ignored_directories_live();
9132
- $counter = 0;
9133
9134
- foreach( $dir_list_list as $group => $dir_list ){
9135
- foreach( $dir_list as $dir_data ){
9136
- $valid_entry = FALSE;
9137
- $snippet_data = array(
9138
- 'IgnoreScanning.CssClass' => '',
9139
- 'IgnoreScanning.Directory' => '',
9140
- 'IgnoreScanning.DirectoryPath' => '',
9141
- 'IgnoreScanning.IgnoredAt' => '',
9142
- 'IgnoreScanning.IgnoredAtText' => 'ok',
9143
- 'IgnoreScanning.IgnoredCssClass' => 'success',
9144
- );
9145
9146
- if( $group == 'is_ignored' ){
9147
- $valid_entry = TRUE;
9148
- $snippet_data['IgnoreScanning.Directory'] = urlencode($dir_data['directory_path']);
9149
- $snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape($dir_data['directory_path']);
9150
- $snippet_data['IgnoreScanning.IgnoredAt'] = SucuriScan::datetime($dir_data['ignored_at']);
9151
- $snippet_data['IgnoreScanning.IgnoredAtText'] = 'ignored';
9152
- $snippet_data['IgnoreScanning.IgnoredCssClass'] = 'warning';
9153
- }
9154
9155
- elseif( $group == 'is_not_ignored' ){
9156
- $valid_entry = TRUE;
9157
- $snippet_data['IgnoreScanning.Directory'] = urlencode($dir_data);
9158
- $snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape($dir_data);
9159
- }
9160
9161
- if( $valid_entry ){
9162
- $css_class = ( $counter %2 == 0 ) ? '' : 'alternate';
9163
- $snippet_data['IgnoreScanning.CssClass'] = $css_class;
9164
- $template_variables['IgnoreScanning.ResourceList'] .= SucuriScanTemplate::get_snippet('settings-ignorescanning', $snippet_data);
9165
- $counter += 1;
9166
}
9167
}
9168
}
9169
9170
return SucuriScanTemplate::get_section('settings-ignorescanning', $template_variables);
@@ -9251,6 +9861,7 @@ function sucuriscan_infosys_page(){
9251
'Cronjobs' => sucuriscan_show_cronjobs(),
9252
'HTAccessIntegrity' => sucuriscan_infosys_htaccess(),
9253
'WordpressConfig' => sucuriscan_infosys_wpconfig(),
9254
);
9255
9256
echo SucuriScanTemplate::get_template('infosys', $template_variables);
@@ -9368,19 +9979,23 @@ function sucuriscan_infosys_wpconfig(){
9368
9369
// Parse the main configuration file and look for constants and global variables.
9370
foreach( (array) $wp_config_content as $line ){
9371
// Detect PHP constants even if the line if indented.
9372
- if( preg_match('/define\(/', $line) ){
9373
- $line = preg_replace('*define\((.+)\);*', '$1', $line);
9374
$line_parts = explode(',', $line, 2);
9375
}
9376
9377
// Detect global variables like the database table prefix.
9378
- else if( preg_match('/^\$[a-zA-Z_]+/', $line) ){
9379
$line_parts = explode('=', $line, 2);
9380
}
9381
9382
// Ignore other lines.
9383
- else{ continue; }
9384
9385
// Clean and append the rule to the wp_config_rules variable.
9386
if( isset($line_parts) && count($line_parts)==2 ){
@@ -9541,6 +10156,59 @@ function sucuriscan_infosys_form_submissions(){
9541
}
9542
}
9543
9544
/**
9545
* Gather information from the server, database engine, and PHP interpreter.
9546
*
@@ -9601,6 +10269,7 @@ function sucuriscan_server_info(){
9601
9602
$field_names = array(
9603
'safe_mode',
9604
'allow_url_fopen',
9605
'memory_limit',
9606
'upload_max_filesize',
4
Plugin URI: http://wordpress.sucuri.net/
5
Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
6
Author: Sucuri, INC
7
+ Version: 1.7.3
8
Author URI: http://sucuri.net
9
*/
10
66
/**
67
* Current version of the plugin's code.
68
*/
69
+ define('SUCURISCAN_VERSION', '1.7.3');
70
71
/**
72
* The name of the Sucuri plugin main file.
128
*/
129
define('SUCURISCAN_AUDITLOGS_PER_PAGE', 50);
130
131
+ /**
132
+ * The maximum quantity of buttons in the paginations.
133
+ */
134
+ define('SUCURISCAN_MAX_PAGINATION_BUTTONS', 20);
135
+
136
/**
137
* The minimum quantity of seconds to wait before each filesystem scan.
138
*/
245
'false' => 'Stop peer\'s cert verification',
246
);
247
248
+ $sucuriscan_no_notices_in = array(
249
+ );
250
+
251
/**
252
* Remove the WordPress generator meta-tag from the source code.
253
*/
462
* @return string The full filesystem path including the directory specified.
463
*/
464
public static function datastore_folder_path( $path='' ){
465
+ $datastore_path = SucuriScanOption::get_option(':datastore_path');
466
+
467
+ // Use the uploads folder by default.
468
+ if ( empty($datastore_path) ) {
469
+ if ( function_exists('wp_upload_dir') ) {
470
+ $wp_dir_array = wp_upload_dir();
471
+ $wp_dir_array['basedir'] = untrailingslashit($wp_dir_array['basedir']);
472
+ $datastore_path = $wp_dir_array['basedir'] . '/sucuri';
473
+ }
474
+
475
+ else {
476
+ $datastore_path = rtrim(ABSPATH, '/') . '/wp-content/uploads/sucuri';
477
+ }
478
+
479
+ SucuriScanOption::update_option( ':datastore_path', $datastore_path );
480
+ }
481
+
482
+ $wp_filepath = rtrim($datastore_path, '/') . '/' . $path;
483
484
return $wp_filepath;
485
}
592
593
if( self::is_behind_cloudproxy() ){
594
$alternatives = array(
595
+ 'HTTP_X_SUCURI_CLIENTIP',
596
'HTTP_X_REAL_IP',
597
'HTTP_CLIENT_IP',
598
'HTTP_X_FORWARDED_FOR',
599
'HTTP_X_FORWARDED',
600
'HTTP_FORWARDED_FOR',
601
'HTTP_FORWARDED',
602
'SUCURI_RIP',
603
'REMOTE_ADDR',
604
);
687
$host_by_name = @gethostbyaddr($host_by_addr);
688
$status = (bool) preg_match('/^cloudproxy[0-9]+\.sucuri\.net#x2F;', $host_by_name);
689
690
+ /*
691
+ * If the DNS reversion failed but the CloudProxy API key is set, then consider
692
+ * the site as protected by a firewall. A fake key can be used to bypass the DNS
693
+ * checking, but that is not something that will affect us, only the client.
694
+ */
695
+ if (
696
+ $status === FALSE
697
+ && SucuriScanAPI::get_cloudproxy_key()
698
+ ) {
699
+ $status = TRUE;
700
+ }
701
+
702
if( $verbose ){
703
return array(
704
'http_host' => $http_host,
865
return FALSE;
866
}
867
868
+
869
+ /**
870
+ * Check whether an IP address is formatted as CIDR or not.
871
+ *
872
+ * @param string $remote_addr The supposed ip address that will be checked.
873
+ * @return boolean Either TRUE or FALSE if the ip address specified is valid or not.
874
+ */
875
+ public static function is_valid_cidr( $remote_addr='' ){
876
+ if ( preg_match('/^([0-9\.]{7,15})\/(8|16|24)#x2F;', $remote_addr, $match) ) {
877
+ if ( self::is_valid_ip($match[1]) ) {
878
+ return TRUE;
879
+ }
880
+ }
881
+
882
+ return FALSE;
883
+ }
884
+
885
+ /**
886
+ * Separate the parts of an IP address.
887
+ *
888
+ * @param string $remote_addr The supposed ip address that will be formatted.
889
+ * @return array Clean address, CIDR range, and CIDR format; FALSE otherwise.
890
+ */
891
+ public static function get_ip_info( $remote_addr='' ){
892
+ if ( $remote_addr ) {
893
+ $addr_info = array();
894
+ $ip_parts = explode( '/', $remote_addr );
895
+ $addr_info['remote_addr'] = $ip_parts[0];
896
+ $addr_info['cidr_range'] = isset($ip_parts[1]) ? $ip_parts[1] : '32';
897
+ $addr_info['cidr_format'] = $addr_info['remote_addr'] . '/' . $addr_info['cidr_range'];
898
+
899
+
900
+ return $addr_info;
901
+ }
902
+
903
+ return FALSE;
904
+ }
905
+
906
/**
907
* Validate email address.
908
*
1034
}
1035
}
1036
1037
+ /**
1038
+ * Determine if the plugin notices can be displayed in the current page.
1039
+ *
1040
+ * @param string $current_page Identifier of the current page.
1041
+ * @return boolean TRUE if the current page must not have noticies.
1042
+ */
1043
+ public static function no_notices_here( $current_page=false ){
1044
+ global $sucuriscan_no_notices_in;
1045
+
1046
+ if ( $current_page === false ) {
1047
+ $current_page = SucuriScanRequest::get('page');
1048
+ }
1049
+
1050
+ if (
1051
+ isset($sucuriscan_no_notices_in)
1052
+ && is_array($sucuriscan_no_notices_in)
1053
+ && !empty($sucuriscan_no_notices_in)
1054
+ ) {
1055
+ return (bool) in_array($current_page, $sucuriscan_no_notices_in);
1056
+ }
1057
+
1058
+ return false;
1059
+ }
1060
+
1061
}
1062
1063
/**
1166
*/
1167
class SucuriScanFileInfo extends SucuriScan {
1168
1169
+ /**
1170
+ * Define the interface that will be used to execute the file system scans, the
1171
+ * available options are SPL, OpenDir, and Glob (all in lowercase). This can be
1172
+ * configured from the settings page.
1173
+ *
1174
+ * @var string
1175
+ */
1176
+ public $scan_interface = 'spl';
1177
+
1178
/**
1179
* Whether the list of files that can be ignored from the filesystem scan will
1180
* be used to return the directory tree, this should be disabled when scanning a
1223
* on some rules defined by the developer.
1224
*
1225
* @param string $directory Parent directory where the filesystem scan will start.
1226
* @param boolean $as_array Whether the result of the operation will be returned as an array or string.
1227
* @return array List of files in the main and subdirectories of the folder specified.
1228
*/
1229
+ public function get_directory_tree_md5( $directory='', $as_array=FALSE ){
1230
$project_signatures = '';
1231
$abs_path = rtrim( ABSPATH, '/' );
1232
+ $files = $this->get_directory_tree($directory);
1233
1234
if( $as_array ){
1235
$project_signatures = array();
1273
* on some rules defined by the developer.
1274
*
1275
* @param string $directory Parent directory where the filesystem scan will start.
1276
* @return array List of files in the main and subdirectories of the folder specified.
1277
*/
1278
+ public function get_directory_tree($directory=''){
1279
if( file_exists($directory) && is_dir($directory) ){
1280
$tree = array();
1281
1282
+ // Check whether the ignore scanning feature is enabled or not.
1283
+ if( SucuriScanFSScanner::will_ignore_scanning() ){
1284
+ $this->ignored_directories = SucuriScanFSScanner::get_ignored_directories();
1285
+ }
1286
+
1287
+ switch( $this->scan_interface ){
1288
case 'spl':
1289
if( $this->is_spl_available() ){
1290
$tree = $this->get_directory_tree_with_spl($directory);
1291
} else {
1292
+ $this->scan_interface = 'opendir';
1293
+ $tree = $this->get_directory_tree($directory);
1294
}
1295
break;
1296
1303
break;
1304
1305
default:
1306
+ $this->scan_interface = 'spl';
1307
+ $tree = $this->get_directory_tree($directory);
1308
break;
1309
}
1310
1431
foreach( $files_found as $filepath ){
1432
$filepath = realpath($filepath);
1433
$directory = dirname($filepath);
1434
+ $filepath_parts = explode('/', $filepath);
1435
+ $filename = array_pop($filepath_parts);
1436
1437
if( is_dir($filepath) ){
1438
if( $this->ignore_folderpath($directory, $filename) ){ continue; }
1439
1440
if( $this->run_recursively ){
1441
+ $sub_files = $this->get_directory_tree_with_glob($filepath);
1442
+
1443
+ if( $sub_files ){
1444
+ $files = array_merge($files, $sub_files);
1445
+ }
1446
}
1447
} else {
1448
if( $this->ignore_filepath($filename) ){ continue; }
1475
1476
if( $this->run_recursively ){
1477
$sub_files = $this->get_directory_tree_with_opendir($filepath);
1478
+
1479
+ if( $sub_files ){
1480
+ $files = array_merge($files, $sub_files);
1481
+ }
1482
}
1483
} else {
1484
if( $this->ignore_filepath($filename) ){ continue; }
1560
$dir_tree = $this->get_directory_tree($dir_tree);
1561
}
1562
1563
+ if( is_array($dir_tree) && !empty($dir_tree) ){
1564
+ foreach( $dir_tree as $filepath ){
1565
+ $dir_path = dirname($filepath);
1566
1567
+ if(
1568
+ !in_array($dir_path, $dirs)
1569
+ && !in_array($dir_path, $this->ignored_directories['directories'])
1570
+ ){
1571
+ $dirs[] = $dir_path;
1572
+ }
1573
}
1574
}
1575
1638
return @file( $filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
1639
}
1640
1641
+ /**
1642
+ * Function to emulate the UNIX tail function by displaying the last X number of
1643
+ * lines in a file. Useful for large files, such as logs, when you want to
1644
+ * process lines in PHP or write lines to a database.
1645
+ *
1646
+ * @param string $file_path Path to the file.
1647
+ * @param integer $lines Number of lines to retrieve from the end of the file.
1648
+ * @param boolean $adaptive Whether the buffer will adapt to a specific number of bytes or not.
1649
+ * @return string Text contained at the end of the file.
1650
+ */
1651
+ public static function tail_file( $file_path='', $lines=1, $adaptive=true ) {
1652
+ $file = @fopen( $file_path, 'rb' );
1653
+ $limit = $lines;
1654
+
1655
+ if ( $file ) {
1656
+ fseek( $file, -1, SEEK_END );
1657
+
1658
+ if ( $adaptive && $lines < 2 ) { $buffer = 64; }
1659
+ elseif ( $adaptive && $lines < 10 ) { $buffer = 512; }
1660
+ else { $buffer = 4096; }
1661
+
1662
+ if ( fread($file, 1) != "\n" ) { $lines -= 1; }
1663
+
1664
+ $output = '';
1665
+ $chunk = '';
1666
+
1667
+ while ( ftell($file) > 0 && $lines >= 0 ) {
1668
+ $seek = min( ftell($file), $buffer );
1669
+ fseek( $file, -$seek, SEEK_CUR );
1670
+ $chunk = fread( $file, $seek );
1671
+ $output = $chunk . $output;
1672
+ fseek( $file, -mb_strlen($chunk, '8bit'), SEEK_CUR );
1673
+ $lines -= substr_count( $chunk, "\n" );
1674
+ }
1675
+
1676
+ fclose($file);
1677
+
1678
+ $lines_arr = explode( "\n", $output );
1679
+ $lines_count = count($lines_arr);
1680
+ $result = array_slice( $lines_arr, ($lines_count - $limit) );
1681
+
1682
+ return $result;
1683
+ }
1684
+
1685
+ return FALSE;
1686
+ }
1687
+
1688
}
1689
1690
/**
2155
$defaults = array(
2156
'sucuriscan_api_key' => FALSE,
2157
'sucuriscan_account' => '',
2158
+ 'sucuriscan_datastore_path' => '',
2159
'sucuriscan_fs_scanner' => 'enabled',
2160
'sucuriscan_scan_frequency' => 'hourly',
2161
'sucuriscan_scan_interface' => 'spl',
2162
'sucuriscan_scan_modfiles' => 'enabled',
2163
'sucuriscan_scan_checksums' => 'enabled',
2164
'sucuriscan_scan_errorlogs' => 'enabled',
2165
+ 'sucuriscan_parse_errorlogs' => 'enabled',
2166
+ 'sucuriscan_errorlogs_limit' => 30,
2167
+ 'sucuriscan_ignore_scanning' => 'disabled',
2168
'sucuriscan_runtime' => 0,
2169
'sucuriscan_lastlogin_redirection' => 'enabled',
2170
'sucuriscan_notify_to' => '',
2177
'sucuriscan_notify_post_publication' => 'enabled',
2178
'sucuriscan_notify_theme_editor' => 'enabled',
2179
'sucuriscan_maximum_failed_logins' => 30,
2180
+ 'sucuriscan_collect_wrong_passwords' => 'disabled',
2181
'sucuriscan_ignored_events' => '',
2182
'sucuriscan_verify_ssl_cert' => 'true',
2183
'sucuriscan_request_timeout' => 90,
2484
2485
// Encode (old) serialized data into JSON.
2486
if( self::is_serialized($post_types) ){
2487
$post_types_arr = @unserialize($post_types);
2488
$post_types_fix = json_encode($post_types_arr);
2489
self::update_option( ':ignored_events', $post_types_fix );
2490
2491
return $post_types_arr;
2714
self::report_site_version();
2715
2716
$sucuri_fileinfo = new SucuriScanFileInfo();
2717
+ $sucuri_fileinfo->scan_interface = SucuriScanOption::get_option(':scan_interface');
2718
+ $signatures = $sucuri_fileinfo->get_directory_tree_md5(ABSPATH);
2719
2720
if( $signatures ){
2721
$hashes_sent = SucuriScanAPI::send_hashes( $signatures );
2803
$email = SucuriScanOption::get_option(':notify_to');
2804
$email_params = array();
2805
2806
+ if ( self::is_trusted_ip() ) {
2807
+ $notify = 'disabled';
2808
+ }
2809
+
2810
if( $notify == 'enabled' ){
2811
if( $event == 'post_publication' ){
2812
$event = 'post_update';
2813
}
2814
2815
elseif( $event == 'failed_login' ){
2816
+ $content .= "<br>\n<br>\n<em>Explanation: Someone failed to login to your site. If you";
2817
+ $content .= " are getting too many of these messages, it is likely your site is under a brute";
2818
+ $content .= " force attack. You can disable the notifications for failed logins from here [1].";
2819
+ $content .= " More details at Password Guessing Brute Force Attacks [2].</em><br>\n<br>\n";
2820
+ $content .= "[1] " . SucuriScanTemplate::get_url('settings') . " <br>\n";
2821
+ $content .= "[2] http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing <br>\n";
2822
}
2823
2824
// Send a notification even if the limit of emails per hour was reached.
2835
return FALSE;
2836
}
2837
2838
+ /**
2839
+ * Check whether an IP address is being trusted or not.
2840
+ *
2841
+ * @param string $remote_addr The supposed ip address that will be checked.
2842
+ * @return boolean TRUE if the IP address of the user is trusted, FALSE otherwise.
2843
+ */
2844
+ private static function is_trusted_ip( $remote_addr='' ){
2845
+ $cache = new SucuriScanCache('trustip');
2846
+ $trusted_ips = $cache->get_all();
2847
+
2848
+ if ( !$remote_addr ) {
2849
+ $remote_addr = SucuriScan::get_remote_addr();
2850
+ }
2851
+
2852
+ $addr_md5 = md5($remote_addr);
2853
+
2854
+ // Check if the CIDR in range 32 of this IP is trusted.
2855
+ if (
2856
+ is_array($trusted_ips)
2857
+ && !empty($trusted_ips)
2858
+ && array_key_exists($addr_md5, $trusted_ips)
2859
+ ) {
2860
+ return TRUE;
2861
+ }
2862
+
2863
+ foreach ( $trusted_ips as $cache_key => $ip_info ) {
2864
+ $ip_parts = explode( '.', $ip_info->remote_addr );
2865
+ $ip_pattern = FALSE;
2866
+
2867
+ // Generate the regular expression for CIDR range 24.
2868
+ if ( $ip_info->cidr_range == 24 ) {
2869
+ $ip_pattern = sprintf( '/^%d\.%d\.%d\.[0-9]{1,3}#x2F;', $ip_parts[0], $ip_parts[1], $ip_parts[2] );
2870
+ }
2871
+
2872
+ // Generate the regular expression for CIDR range 16.
2873
+ elseif ( $ip_info->cidr_range == 16 ) {
2874
+ $ip_pattern = sprintf( '/^%d\.%d(\.[0-9]{1,3}){2}#x2F;', $ip_parts[0], $ip_parts[1] );
2875
+ }
2876
+
2877
+ // Generate the regular expression for CIDR range 8.
2878
+ elseif ( $ip_info->cidr_range == 8 ) {
2879
+ $ip_pattern = sprintf( '/^%d(\.[0-9]{1,3}){3}#x2F;', $ip_parts[0] );
2880
+ }
2881
+
2882
+ if ( $ip_pattern && preg_match($ip_pattern, $remote_addr) ) {
2883
+ return TRUE;
2884
+ }
2885
+ }
2886
+
2887
+ return FALSE;
2888
+ }
2889
+
2890
/**
2891
* Generate and set a new password for a specific user not in session.
2892
*
3239
public static function hook_wp_login_failed( $title='' ){
3240
if( empty($title) ){ $title = 'Unknown'; }
3241
3242
+ $password = SucuriScanRequest::post('pwd');
3243
+ $message = 'User authentication failed: ' . $title;
3244
+
3245
+ if ( sucuriscan_collect_wrong_passwords() === true ) {
3246
+ $message .= "<br>\nUser wrong password: " . $password;
3247
+ }
3248
+
3249
self::report_event( 2, 'core', $message );
3250
self::notify_event( 'failed_login', $message );
3251
3252
// Log the failed login in the internal datastore for future reports.
3253
+ $logged = sucuriscan_log_failed_login( $title, $password );
3254
3255
// Check if the quantity of failed logins will be considered as a brute-force attack.
3256
if( $logged ){
4208
*/
4209
public static function get_official_checksums( $version=0 ){
4210
$url = 'http://api.wordpress.org/core/checksums/1.0/';
4211
+ $language = defined('WPLANG') ? WPLANG : 'en_US';
4212
$response = self::api_call( $url, 'GET', array(
4213
'version' => $version,
4214
+ 'locale' => $language,
4215
));
4216
4217
if( $response ){
4262
4263
// Get the plugin's basic information from WordPress transient data.
4264
$plugins = get_plugins();
4265
+ $pattern = '/^http(s)?:\/\/wordpress\.org\/plugins\/(.*)\/#x2F;';
4266
+ $wp_market = 'https://wordpress.org/plugins/%s/';
4267
4268
// Loop through each plugin data and complement its information with more attributes.
4269
foreach( $plugins as $plugin_path => $plugin_data ){
4278
&& preg_match($pattern, $plugin_data['PluginURI'], $match)
4279
){
4280
$repository = $match[0];
4281
+ $repository_name = $match[2];
4282
$is_free_plugin = TRUE;
4283
}
4284
4927
return FALSE;
4928
}
4929
4930
+ /**
4931
+ * Check whether the administrator enabled the feature to ignore some
4932
+ * directories during the file system scans. This function is overwritten by a
4933
+ * GET parameter in the settings page named no_scan which must be equal to the
4934
+ * number one.
4935
+ *
4936
+ * @return boolean Whether the feature to ignore files is enabled or not.
4937
+ */
4938
+ public static function will_ignore_scanning(){
4939
+ return ( SucuriScanOption::get_option(':ignore_scanning') === 'enabled' );
4940
+ }
4941
+
4942
/**
4943
* Add a new directory path to the list of ignored paths.
4944
*
5048
$sucuri_fileinfo = new SucuriScanFileInfo();
5049
$sucuri_fileinfo->ignore_files = TRUE;
5050
$sucuri_fileinfo->ignore_directories = TRUE;
5051
+ $sucuri_fileinfo->scan_interface = SucuriScanOption::get_option(':scan_interface');
5052
$directory_list = $sucuri_fileinfo->get_diretories_only(ABSPATH);
5053
5054
if( $directory_list ){
5058
return $response;
5059
}
5060
5061
+ /**
5062
+ * Read and parse the lines inside a PHP error log file.
5063
+ *
5064
+ * @param array $error_logs The content of an error log file, or an array with the lines.
5065
+ * @return array List of valid error logs with their attributes separated.
5066
+ */
5067
+ public static function parse_error_logs( $error_logs=array() ){
5068
+ $logs_arr = array();
5069
+ $pattern = '/^'
5070
+ . '(\[(\S+) ([0-9:]{5,8})( \S+)?\] )?' // Detect date, time, and timezone.
5071
+ . '(PHP )?([a-zA-Z ]+):\s' // Detect PHP error severity.
5072
+ . '(.+) in (.+)' // Detect error message, and file path.
5073
+ . '(:| on line )([0-9]+)' // Detect line number.
5074
+ . '#x2F;';
5075
+
5076
+ if ( is_string($error_logs) ) {
5077
+ $error_logs = explode( "\n", $error_logs );
5078
+ }
5079
+
5080
+ foreach ( (array) $error_logs as $line ) {
5081
+ if ( !is_string($line) || empty($line) ) { continue; }
5082
+
5083
+ if ( preg_match($pattern, $line, $match) ) {
5084
+ $data_set = array(
5085
+ 'date' => '',
5086
+ 'time' => '',
5087
+ 'timestamp' => 0,
5088
+ 'date_time' => '',
5089
+ 'time_zone' => '',
5090
+ 'error_type' => '',
5091
+ 'error_code' => 'unknown',
5092
+ 'error_message' => '',
5093
+ 'file_path' => '',
5094
+ 'line_number' => 0,
5095
+ );
5096
+
5097
+ // Basic attributes from the scrapping.
5098
+ $data_set['date'] = $match[2];
5099
+ $data_set['time'] = $match[3];
5100
+ $data_set['time_zone'] = trim($match[4]);
5101
+ $data_set['error_type'] = trim($match[6]);
5102
+ $data_set['error_message'] = trim($match[7]);
5103
+ $data_set['file_path'] = trim($match[8]);
5104
+ $data_set['line_number'] = (int) $match[10];
5105
+
5106
+ // Additional data from the attributes.
5107
+ if ( $data_set['date'] ) {
5108
+ $data_set['date_time'] = $data_set['date']
5109
+ . "\x20" . $data_set['time']
5110
+ . "\x20" . $data_set['time_zone'];
5111
+ $data_set['timestamp'] = strtotime( $data_set['date_time'] );
5112
+ }
5113
+
5114
+ if ( $data_set['error_type'] ) {
5115
+ $valid_types = array( 'warning', 'notice', 'error' );
5116
+
5117
+ foreach ( $valid_types as $valid_type ) {
5118
+ if ( stripos($data_set['error_type'], $valid_type) !== FALSE ) {
5119
+ $data_set['error_code'] = $valid_type;
5120
+ break;
5121
+ }
5122
+ }
5123
+ }
5124
+
5125
+ $logs_arr[] = (object) $data_set;
5126
+ }
5127
+ }
5128
+
5129
+ return $logs_arr;
5130
+ }
5131
+
5132
}
5133
5134
/**
5307
* @return void
5308
*/
5309
public static function initialize(){
5310
+ if ( SucuriScan::is_behind_cloudproxy() ) {
5311
$_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
5312
+ $_SERVER['REMOTE_ADDR'] = SucuriScan::get_remote_addr();
5313
}
5314
}
5315
5528
public static function setup_notice(){
5529
if(
5530
current_user_can('manage_options')
5531
+ && SucuriScan::no_notices_here() === false
5532
&& !SucuriScanAPI::get_plugin_key()
5533
&& SucuriScanRequest::post(':plugin_api_key') === FALSE
5534
&& SucuriScanRequest::post(':recover_key') === FALSE
6879
*/
6880
function sucuriscan_cloudproxy_enabled(){
6881
$btn_string = '';
6882
+ $proxy_info = SucuriScan::is_behind_cloudproxy();
6883
$status = 1;
6884
6885
$description = 'A WAF is a protection layer for your web site, blocking all sort of attacks (brute force attempts, '
6886
. 'DDoS, SQL injections, etc) and helping it remain malware and blacklist free. This test checks if your site is '
6887
. 'using <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri\'s CloudProxy WAF</a> to protect your site.';
6888
6889
+ if( $proxy_info === FALSE ){
6890
$status = 0;
6891
$btn_string = '<a href="http://cloudproxy.sucuri.net/" target="_blank" class="button button-primary">Harden</a>';
6892
}
7291
$sucuri_fileinfo->ignore_files = FALSE;
7292
$sucuri_fileinfo->ignore_directories = FALSE;
7293
$sucuri_fileinfo->run_recursively = $recursive;
7294
+ $sucuri_fileinfo->scan_interface = 'opendir';
7295
+ $integrity_tree = $sucuri_fileinfo->get_directory_tree_md5( $dir, TRUE );
7296
7297
+ if( !$integrity_tree ){
7298
+ $integrity_tree = array();
7299
}
7300
7301
+ return $integrity_tree;
7302
}
7303
7304
/**
7371
$template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
7372
7373
if( $total_items > 0 ){
7374
+ $max_pages = ceil( $audit_logs->total_entries / $max_per_page );
7375
+
7376
+ if( $max_pages > SUCURISCAN_MAX_PAGINATION_BUTTONS ){
7377
+ $max_pages = SUCURISCAN_MAX_PAGINATION_BUTTONS;
7378
+ }
7379
+
7380
$template_variables['AuditLogs.PaginationVisibility'] = 'visible';
7381
$template_variables['AuditLogs.PaginationLinks'] = SucuriScanTemplate::get_pagination(
7382
'%%SUCURI.URL.Home%%',
7383
+ $max_per_page * $max_pages,
7384
$max_per_page
7385
);
7386
}
7661
// Search modified files among the project's files.
7662
$content_hashes = sucuriscan_get_integrity_tree( ABSPATH.'wp-content', true );
7663
7664
+ if( !empty($content_hashes) ){
7665
$template_variables['ModifiedFiles.Days'] = $back_days;
7666
$back_days = current_time('timestamp') - ( $back_days * 86400);
7667
$counter = 0;
8402
8403
$lastlogin_message = sprintf(
8404
'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>',
8405
+ SucuriScan::datetime($row->user_lastlogin_timestamp),
8406
+ $row->user_remoteaddr,
8407
+ $row->user_hostname
8408
);
8409
$lastlogin_message .= chr(32).'(<a href="'.SucuriScanTemplate::get_url('lastlogins').'">view all logs</a>)';
8410
SucuriScanInterface::info( $lastlogin_message );
8626
'FailedLogins.MaxFailedLogins' => 0,
8627
'FailedLogins.NoItemsVisibility' => 'visible',
8628
'FailedLogins.WarningVisibility' => 'visible',
8629
+ 'FailedLogins.CollectPasswordsVisibility' => 'visible',
8630
);
8631
8632
$max_failed_logins = SucuriScanOption::get_option(':maximum_failed_logins');
8633
$notify_bruteforce_attack = SucuriScanOption::get_option(':notify_bruteforce_attack');
8634
$failed_logins = sucuriscan_get_failed_logins();
8635
+ $old_failed_logins = sucuriscan_get_failed_logins(true);
8636
+
8637
+ // Merge the new and old failed logins.
8638
+ if (
8639
+ is_array($old_failed_logins)
8640
+ && !empty($old_failed_logins)
8641
+ ) {
8642
+ if (
8643
+ is_array($failed_logins)
8644
+ && !empty($failed_logins)
8645
+ ) {
8646
+ $failed_logins = array_merge( $failed_logins, $old_failed_logins );
8647
+ } else {
8648
+ $failed_logins = $old_failed_logins;
8649
+ }
8650
+ }
8651
8652
if( $failed_logins ){
8653
$counter = 0;
8654
8655
foreach( $failed_logins['entries'] as $login_data ){
8656
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
8657
+ $wrong_user_password = '<span class="sucuriscan-label-default">hidden</span>';
8658
+
8659
+ if ( sucuriscan_collect_wrong_passwords() === true ) {
8660
+ if (
8661
+ isset($login_data['user_password'])
8662
+ && !empty($login_data['user_password'])
8663
+ ) {
8664
+ $wrong_user_password = SucuriScan::escape($login_data['user_password']);
8665
+ }
8666
+
8667
+ else {
8668
+ $wrong_user_password = '<span class="sucuriscan-label-info">empty</span>';
8669
+ }
8670
+ }
8671
8672
$template_variables['FailedLogins.List'] .= SucuriScanTemplate::get_snippet('lastlogins-failedlogins', array(
8673
'FailedLogins.CssClass' => $css_class,
8674
'FailedLogins.Num' => ($counter + 1),
8675
'FailedLogins.Username' => SucuriScan::escape($login_data['user_login']),
8676
+ 'FailedLogins.Password' => $wrong_user_password,
8677
'FailedLogins.RemoteAddr' => SucuriScan::escape($login_data['remote_addr']),
8678
'FailedLogins.Datetime' => SucuriScan::datetime($login_data['attempt_time']),
8679
'FailedLogins.UserAgent' => SucuriScan::escape($login_data['user_agent']),
8693
$template_variables['FailedLogins.WarningVisibility'] = 'hidden';
8694
}
8695
8696
+ if( sucuriscan_collect_wrong_passwords() !== true ){
8697
+ $template_variables['FailedLogins.CollectPasswordsVisibility'] = 'hidden';
8698
+ }
8699
+
8700
return SucuriScanTemplate::get_section('lastlogins-failedlogins', $template_variables);
8701
}
8702
8703
+ /**
8704
+ * Whether or not to collect the password of failed logins.
8705
+ *
8706
+ * @return boolean TRUE if the password must be collected, FALSE otherwise.
8707
+ */
8708
+ function sucuriscan_collect_wrong_passwords(){
8709
+ return (bool) ( SucuriScanOption::get_option(':collect_wrong_passwords') === 'enabled' );
8710
+ }
8711
+
8712
/**
8713
* Find the full path of the file where the information of the failed logins
8714
* will be stored, it will be created automatically if does not exists (and if
8717
*
8718
* @see sucuriscan_reset_failed_logins()
8719
*
8720
+ * @param boolean $get_old_logs Whether the old logs will be retrieved or not.
8721
+ * @param boolean $reset Whether the file will be resetted or not.
8722
+ * @return string The full (relative) path where the file is located.
8723
*/
8724
+ function sucuriscan_failed_logins_datastore_path( $get_old_logs=false, $reset=false ){
8725
+ $file_name = $get_old_logs ? 'sucuri-oldfailedlogins.php' : 'sucuri-failedlogins.php';
8726
+ $datastore_path = SucuriScan::datastore_folder_path($file_name);
8727
$default_content = sucuriscan_failed_logins_default_content();
8728
8729
// Create the file if it does not exists.
8761
* with the report) or reset the file after considering it a normal behavior of
8762
* the site.
8763
*
8764
+ * @param boolean $get_old_logs Whether the old logs will be retrieved or not.
8765
+ * @return array Information and entries gathered from the failed logins datastore file.
8766
*/
8767
+ function sucuriscan_get_failed_logins( $get_old_logs=false ){
8768
+ $datastore_path = sucuriscan_failed_logins_datastore_path($get_old_logs);
8769
$default_content = sucuriscan_failed_logins_default_content();
8770
$default_content_n = substr_count($default_content, "\n");
8771
8791
$login_data['user_agent'] = 'Unknown';
8792
}
8793
8794
+ if ( !isset($login_data['user_password'])) {
8795
+ $login_data['user_password'] = '';
8796
+ }
8797
+
8798
$failed_logins['entries'][] = $login_data;
8799
$failed_logins['count'] += 1;
8800
}
8821
* this entry will contain the username, timestamp of the login attempt, remote
8822
* address of the computer sending the request, and the user-agent.
8823
*
8824
+ * @param string $user_login Information from the current failed login event.
8825
+ * @param string $wrong_password Wrong password used during the supposed attack.
8826
+ * @return boolean Whether the information of the current failed login event was stored or not.
8827
*/
8828
+ function sucuriscan_log_failed_login( $user_login='', $wrong_password='' ){
8829
$datastore_path = sucuriscan_failed_logins_datastore_path();
8830
8831
+ // Do not collect wrong passwords if it is not necessary.
8832
+ if ( sucuriscan_collect_wrong_passwords() !== true ) {
8833
+ $wrong_password = '';
8834
+ }
8835
+
8836
if( $datastore_path ){
8837
$login_data = json_encode(array(
8838
'user_login' => $user_login,
8839
+ 'user_password' => $wrong_password,
8840
'attempt_time' => time(),
8841
'remote_addr' => SucuriScan::get_remote_addr(),
8842
'user_agent' => SucuriScan::get_user_agent(),
8862
function sucuriscan_report_failed_logins( $failed_logins=array() ){
8863
if( $failed_logins && $failed_logins['count'] > 0 ){
8864
$prettify_mails = SucuriScanMail::prettify_mails();
8865
+ $collect_wrong_passwords = sucuriscan_collect_wrong_passwords();
8866
$mail_content = '';
8867
8868
if( $prettify_mails ){
8872
$table_html .= '<thead>';
8873
$table_html .= '<tr>';
8874
$table_html .= '<th>Username</th>';
8875
+
8876
+ if ( $collect_wrong_passwords === true ) {
8877
+ $table_html .= '<th>Password</th>';
8878
+ }
8879
+
8880
$table_html .= '<th>IP Address</th>';
8881
$table_html .= '<th>Attempt Timestamp</th>';
8882
$table_html .= '<th>Attempt Date/Time</th>';
8890
if( $prettify_mails ){
8891
$table_html .= '<tr>';
8892
$table_html .= '<td>' . esc_attr($login_data['user_login']) . '</td>';
8893
+
8894
+ if ( $collect_wrong_passwords === true ) {
8895
+ $table_html .= '<td>' . esc_attr($login_data['user_password']) . '</td>';
8896
+ }
8897
+
8898
$table_html .= '<td>' . esc_attr($login_data['remote_addr']) . '</td>';
8899
$table_html .= '<td>' . $login_data['attempt_time'] . '</td>';
8900
$table_html .= '<td>' . $login_data['attempt_date'] . '</td>';
8902
} else {
8903
$mail_content .= "\n";
8904
$mail_content .= 'Username: ' . $login_data['user_login'] . "\n";
8905
+
8906
+ if ( $collect_wrong_passwords === true ) {
8907
+ $mail_content .= 'Password: ' . $login_data['user_password'] . "\n";
8908
+ }
8909
+
8910
$mail_content .= 'IP Address: ' . $login_data['remote_addr'] . "\n";
8911
$mail_content .= 'Attempt Timestamp: ' . $login_data['attempt_time'] . "\n";
8912
$mail_content .= 'Attempt Date/Time: ' . $login_data['attempt_date'] . "\n";
8938
* @return boolean Whether the datastore file was resetted or not.
8939
*/
8940
function sucuriscan_reset_failed_logins(){
8941
+ $datastore_path = SucuriScan::datastore_folder_path('sucuri-failedlogins.php');
8942
+ $datastore_backup_path = sucuriscan_failed_logins_datastore_path(true, false);
8943
+ $default_content = sucuriscan_failed_logins_default_content();
8944
+ $current_content = @file_get_contents($datastore_path);
8945
+ $current_content = str_replace( $default_content, '', $current_content );
8946
+
8947
+ @file_put_contents(
8948
+ $datastore_backup_path,
8949
+ $current_content,
8950
+ FILE_APPEND
8951
+ );
8952
+
8953
+ return (bool) sucuriscan_failed_logins_datastore_path(false, true);
8954
}
8955
8956
/**
8968
'Settings.IgnoreScanning' => sucuriscan_settings_ignorescanning(),
8969
'Settings.Notifications' => sucuriscan_settings_notifications(),
8970
'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
8971
+ 'Settings.TrustIP' => sucuriscan_settings_trust_ip(),
8972
'Settings.Heartbeat' => sucuriscan_settings_heartbeat(),
8973
);
8974
9040
SucuriScanInterface::info( 'Filesystem scanner for file integrity was <code>' . $action_d . '</code>' );
9041
}
9042
9043
+ // Enable or disable the filesystem scanner for error logs.
9044
+ if( $ignore_scanning = SucuriScanRequest::post(':ignore_scanning', '(en|dis)able') ){
9045
+ $action_d = $ignore_scanning . 'd';
9046
+ SucuriScanOption::update_option(':ignore_scanning', $action_d);
9047
+ SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner rules to ignore directories was: ' . $action_d );
9048
+ SucuriScanInterface::info( 'Filesystem scanner rules to ignore directories was <code>' . $action_d . '</code>' );
9049
+ }
9050
+
9051
// Enable or disable the filesystem scanner for error logs.
9052
if( $scan_errorlogs = SucuriScanRequest::post(':scan_errorlogs', '(en|dis)able') ){
9053
$action_d = $scan_errorlogs . 'd';
9056
SucuriScanInterface::info( 'Filesystem scanner for error logs was <code>' . $action_d . '</code>' );
9057
}
9058
9059
+ // Enable or disable the error logs parsing.
9060
+ if( $parse_errorlogs = SucuriScanRequest::post(':parse_errorlogs', '(en|dis)able') ){
9061
+ $action_d = $parse_errorlogs . 'd';
9062
+ SucuriScanOption::update_option(':parse_errorlogs', $action_d);
9063
+ SucuriScanEvent::notify_event( 'plugin_change', 'Analysis of error logs was: ' . $action_d );
9064
+ SucuriScanInterface::info( 'Analysis of error logs was <code>' . $action_d . '</code>' );
9065
+ }
9066
+
9067
+ // Update the limit of error log lines to parse.
9068
+ if( $errorlogs_limit = SucuriScanRequest::post(':errorlogs_limit', '[0-9]+') ){
9069
+ if ( $errorlogs_limit > 1000 ) {
9070
+ SucuriScanInterface::error( 'Analyze more than 1,000 lines will take too much time.' );
9071
+ } else {
9072
+ SucuriScanOption::update_option(':errorlogs_limit', $errorlogs_limit);
9073
+ SucuriScanInterface::info( 'Analyze last <code>' . $errorlogs_limit . '</code> entries encountered in the error logs.' );
9074
+
9075
+ if ( $errorlogs_limit == 0 ) {
9076
+ SucuriScanOption::update_option(':parse_errorlogs', 'disabled');
9077
+ }
9078
+ }
9079
+ }
9080
+
9081
// Modify the schedule of the filesystem scanner.
9082
if( $frequency = SucuriScanRequest::post(':scan_frequency') ){
9083
if( array_key_exists($frequency, $sucuriscan_schedule_allowed) ){
9165
SucuriScanInterface::info( 'API request timeout set to <code>' . $request_timeout . '</code> seconds.' );
9166
}
9167
9168
+ // Update the collection of failed passwords settings.
9169
+ if( $collect_wrong_passwords = SucuriScanRequest::post(':collect_wrong_passwords') ){
9170
+ $collect_wrong_passwords = strtolower($collect_wrong_passwords);
9171
+ $collect_action = 'disabled';
9172
+
9173
+ if ( $collect_wrong_passwords == 'yes' ) {
9174
+ $collect_action = 'enabled';
9175
+ }
9176
+
9177
+ SucuriScanOption::update_option(':collect_wrong_passwords', $collect_action);
9178
+ SucuriScanInterface::info( 'Option to collection wrong passwords updated to <code>' . $collect_action . '</code>' );
9179
+ }
9180
+
9181
+ // Update the datastore path (if the new directory exists).
9182
+ if( $datastore_path = SucuriScanRequest::post(':datastore_path') ){
9183
+ $current_datastore_path = SucuriScanOption::datastore_folder_path();
9184
+
9185
+ if ( file_exists($datastore_path) ) {
9186
+ if ( is_writable($datastore_path) ) {
9187
+ SucuriScanOption::update_option(':datastore_path', $datastore_path);
9188
+ SucuriScanInterface::info( 'Datastore path changed to <code>' . $datastore_path . '</code>' );
9189
+
9190
+ if ( file_exists($current_datastore_path) ) {
9191
+ $new_datastore_path = SucuriScanOption::datastore_folder_path();
9192
+ @rename( $current_datastore_path, $new_datastore_path );
9193
+ }
9194
+ }
9195
+
9196
+ else {
9197
+ SucuriScanInterface::error( 'The new directory path is not writable.' );
9198
+ }
9199
+ }
9200
+
9201
+ else {
9202
+ SucuriScanInterface::error( 'The directory path specified does not exists.' );
9203
+ }
9204
+ }
9205
+
9206
// Update the notification settings.
9207
if( SucuriScanRequest::post(':save_notification_settings') !== FALSE ){
9208
$options_updated_counter = 0;
9286
}
9287
}
9288
9289
+ // Trust and IP address to ignore notifications for a subnet.
9290
+ if( $trust_ip = SucuriScanRequest::post(':trust_ip') ){
9291
+ if(
9292
+ SucuriScan::is_valid_ip($trust_ip)
9293
+ || SucuriScan::is_valid_cidr($trust_ip)
9294
+ ){
9295
+ $cache = new SucuriScanCache('trustip');
9296
+ $ip_info = SucuriScan::get_ip_info($trust_ip);
9297
+ $ip_info['added_at'] = SucuriScan::local_time();
9298
+ $cache_key = md5($ip_info['remote_addr']);
9299
+
9300
+ if ( $cache->exists($cache_key) ) {
9301
+ SucuriScanInterface::error( 'The IP address specified was already trusted.' );
9302
+ } elseif ( $cache->add( $cache_key, $ip_info ) ) {
9303
+ SucuriScanInterface::info( 'The IP address specified was trusted successfully.' );
9304
+ } else {
9305
+ SucuriScanInterface::error( 'The new entry was not saved in the datastore file.' );
9306
+ }
9307
+ }
9308
+ }
9309
+
9310
+ // Trust and IP address to ignore notifications for a subnet.
9311
+ if( $del_trust_ip = SucuriScanRequest::post(':del_trust_ip', '_array') ){
9312
+ $cache = new SucuriScanCache('trustip');
9313
+
9314
+ foreach ( $del_trust_ip as $cache_key ) {
9315
+ $cache->delete($cache_key);
9316
+ }
9317
+
9318
+ SucuriScanInterface::info( 'The IP addresses selected were deleted successfully.' );
9319
+ }
9320
+
9321
// Update the settings for the heartbeat API.
9322
if( $heartbeat_status = SucuriScanRequest::post(':heartbeat_status') ){
9323
$statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
9431
'VerifySSLCert' => 'Undefined',
9432
'VerifySSLCertOptions' => $verify_ssl_cert_options,
9433
'RequestTimeout' => SucuriScanOption::get_option(':request_timeout') . ' seconds',
9434
+ 'DatastorePath' => SucuriScanOption::get_option(':datastore_path'),
9435
+ 'DatastorePathShort' => '',
9436
+ 'CollectWrongPasswords' => 'No collect passwords',
9437
'ModalWhenAPIRegistered' => $api_registered_modal,
9438
);
9439
9449
$template_variables['VerifySSLCert'] = $sucuriscan_verify_ssl_cert[$verify_ssl_cert];
9450
}
9451
9452
+ if ( !empty($template_variables['DatastorePath']) ) {
9453
+ $template_variables['DatastorePathShort'] = SucuriScan::excerpt_rev( $template_variables['DatastorePath'], 30 );
9454
+ }
9455
+
9456
+ if ( sucuriscan_collect_wrong_passwords() === true ) {
9457
+ $template_variables['CollectWrongPasswords'] = '<span class="sucuriscan-label-error">Yes, collect passwords</span>';
9458
+ }
9459
+
9460
return SucuriScanTemplate::get_section('settings-general', $template_variables);
9461
}
9462
9477
$scan_modfiles = SucuriScanOption::get_option(':scan_modfiles');
9478
$scan_checksums = SucuriScanOption::get_option(':scan_checksums');
9479
$scan_errorlogs = SucuriScanOption::get_option(':scan_errorlogs');
9480
+ $parse_errorlogs = SucuriScanOption::get_option(':parse_errorlogs');
9481
+ $errorlogs_limit = SucuriScanOption::get_option(':errorlogs_limit');
9482
+ $ignore_scanning = SucuriScanOption::get_option(':ignore_scanning');
9483
$runtime_scan_human = SucuriScanFSScanner::get_filesystem_runtime(TRUE);
9484
9485
// Generate the HTML code for the option list in the form select fields.
9502
'ScanChecksumsSwitchText' => 'Disable',
9503
'ScanChecksumsSwitchValue' => 'disable',
9504
'ScanChecksumsSwitchCssClass' => 'button-danger',
9505
+ /* Ignore scanning. */
9506
+ 'IgnoreScanningStatus' => 'Enabled',
9507
+ 'IgnoreScanningSwitchText' => 'Disable',
9508
+ 'IgnoreScanningSwitchValue' => 'disable',
9509
+ 'IgnoreScanningSwitchCssClass' => 'button-danger',
9510
/* Scan error logs. */
9511
'ScanErrorlogsStatus' => 'Enabled',
9512
'ScanErrorlogsSwitchText' => 'Disable',
9513
'ScanErrorlogsSwitchValue' => 'disable',
9514
'ScanErrorlogsSwitchCssClass' => 'button-danger',
9515
+ /* Parse error logs. */
9516
+ 'ParseErrorLogsStatus' => 'Enabled',
9517
+ 'ParseErrorLogsSwitchText' => 'Disable',
9518
+ 'ParseErrorLogsSwitchValue' => 'disable',
9519
+ 'ParseErrorLogsSwitchCssClass' => 'button-danger',
9520
/* Filsystem scanning frequency. */
9521
'ScanningFrequency' => 'Undefined',
9522
'ScanningFrequencyOptions' => $scan_freq_options,
9524
'ScanningInterfaceOptions' => $scan_interface_options,
9525
/* Filesystem scanning runtime. */
9526
'ScanningRuntimeHuman' => $runtime_scan_human,
9527
+ 'ErrorLogsLimit' => $errorlogs_limit,
9528
);
9529
9530
if( $fs_scanner == 'disabled' ){
9548
$template_variables['ScanChecksumsSwitchCssClass'] = 'button-success';
9549
}
9550
9551
+ if( $ignore_scanning == 'disabled' ){
9552
+ $template_variables['IgnoreScanningStatus'] = 'Disabled';
9553
+ $template_variables['IgnoreScanningSwitchText'] = 'Enable';
9554
+ $template_variables['IgnoreScanningSwitchValue'] = 'enable';
9555
+ $template_variables['IgnoreScanningSwitchCssClass'] = 'button-success';
9556
+ }
9557
+
9558
if( $scan_errorlogs == 'disabled' ){
9559
$template_variables['ScanErrorlogsStatus'] = 'Disabled';
9560
$template_variables['ScanErrorlogsSwitchText'] = 'Enable';
9562
$template_variables['ScanErrorlogsSwitchCssClass'] = 'button-success';
9563
}
9564
9565
+ if( $parse_errorlogs == 'disabled' ){
9566
+ $template_variables['ParseErrorLogsStatus'] = 'Disabled';
9567
+ $template_variables['ParseErrorLogsSwitchText'] = 'Enable';
9568
+ $template_variables['ParseErrorLogsSwitchValue'] = 'enable';
9569
+ $template_variables['ParseErrorLogsSwitchCssClass'] = 'button-success';
9570
+ }
9571
+
9572
if( array_key_exists($scan_freq, $sucuriscan_schedule_allowed) ){
9573
$template_variables['ScanningFrequency'] = $sucuriscan_schedule_allowed[$scan_freq];
9574
}
9669
return SucuriScanTemplate::get_section('settings-ignorerules', $template_variables);
9670
}
9671
9672
+ /**
9673
+ * Read and parse the content of the trust-ip settings template.
9674
+ *
9675
+ * @return string Parsed HTML code for the trust-ip settings panel.
9676
+ */
9677
+ function sucuriscan_settings_trust_ip(){
9678
+ $template_variables = array(
9679
+ 'TrustedIPs.List' => '',
9680
+ 'TrustedIPs.NoItems.Visibility' => 'visible',
9681
+ );
9682
+
9683
+ $cache = new SucuriScanCache('trustip');
9684
+ $trusted_ips = $cache->get_all();
9685
+
9686
+ if ( $trusted_ips ) {
9687
+ $counter = 0;
9688
+
9689
+ foreach ( $trusted_ips as $cache_key => $ip_info ) {
9690
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
9691
+
9692
+ if ( $ip_info->cidr_range == 32 ) {
9693
+ $ip_info->cidr_format = 'n/a';
9694
+ }
9695
+
9696
+ $template_variables['TrustedIPs.List'] .= SucuriScanTemplate::get_snippet('settings-trustip', array(
9697
+ 'TrustIP.CssClass' => $css_class,
9698
+ 'TrustIP.CacheKey' => $cache_key,
9699
+ 'TrustIP.RemoteAddr' => SucuriScan::escape($ip_info->remote_addr),
9700
+ 'TrustIP.CIDRFormat' => SucuriScan::escape($ip_info->cidr_format),
9701
+ 'TrustIP.AddedAt' => SucuriScan::datetime($ip_info->added_at),
9702
+ ));
9703
+ $counter += 1;
9704
+ }
9705
+
9706
+ if ( $counter > 0 ) {
9707
+ $template_variables['TrustedIPs.NoItems.Visibility'] = 'hidden';
9708
+ }
9709
+ }
9710
+
9711
+ return SucuriScanTemplate::get_section('settings-trustip', $template_variables);
9712
+ }
9713
+
9714
/**
9715
* Read and parse the content of the ignore-scanning settings template.
9716
*
9719
function sucuriscan_settings_ignorescanning(){
9720
$template_variables = array(
9721
'IgnoreScanning.ResourceList' => '',
9722
+ 'IgnoreScanning.DisabledVisibility' => 'visible',
9723
+ 'IgnoreScanning.NoItemsVisibility' => 'visible',
9724
);
9725
9726
+ $ignore_scanning = SucuriScanFSScanner::will_ignore_scanning();
9727
9728
+ // Allow disable of this option temporarily.
9729
+ if( SucuriScanRequest::get('no_scan') == 1 ){
9730
+ $ignore_scanning = FALSE;
9731
+ }
9732
9733
+ // Scan the project and get the ignored paths.
9734
+ if( $ignore_scanning === TRUE ){
9735
+ $counter = 0;
9736
+ $template_variables['IgnoreScanning.DisabledVisibility'] = 'hidden';
9737
+ $dir_list_list = SucuriScanFSScanner::get_ignored_directories_live();
9738
9739
+ foreach( $dir_list_list as $group => $dir_list ){
9740
+ foreach( $dir_list as $dir_data ){
9741
+ $valid_entry = FALSE;
9742
+ $snippet_data = array(
9743
+ 'IgnoreScanning.CssClass' => '',
9744
+ 'IgnoreScanning.Directory' => '',
9745
+ 'IgnoreScanning.DirectoryPath' => '',
9746
+ 'IgnoreScanning.IgnoredAt' => '',
9747
+ 'IgnoreScanning.IgnoredAtText' => 'ok',
9748
+ 'IgnoreScanning.IgnoredCssClass' => 'success',
9749
+ );
9750
9751
+ if( $group == 'is_ignored' ){
9752
+ $valid_entry = TRUE;
9753
+ $snippet_data['IgnoreScanning.Directory'] = urlencode($dir_data['directory_path']);
9754
+ $snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape($dir_data['directory_path']);
9755
+ $snippet_data['IgnoreScanning.IgnoredAt'] = SucuriScan::datetime($dir_data['ignored_at']);
9756
+ $snippet_data['IgnoreScanning.IgnoredAtText'] = 'ignored';
9757
+ $snippet_data['IgnoreScanning.IgnoredCssClass'] = 'warning';
9758
+ }
9759
+
9760
+ elseif( $group == 'is_not_ignored' ){
9761
+ $valid_entry = TRUE;
9762
+ $snippet_data['IgnoreScanning.Directory'] = urlencode($dir_data);
9763
+ $snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape($dir_data);
9764
+ }
9765
+
9766
+ if( $valid_entry ){
9767
+ $css_class = ( $counter %2 == 0 ) ? '' : 'alternate';
9768
+ $snippet_data['IgnoreScanning.CssClass'] = $css_class;
9769
+ $template_variables['IgnoreScanning.ResourceList'] .= SucuriScanTemplate::get_snippet('settings-ignorescanning', $snippet_data);
9770
+ $counter += 1;
9771
+ }
9772
}
9773
}
9774
+
9775
+ if( $counter > 0 ){
9776
+ $template_variables['IgnoreScanning.NoItemsVisibility'] = 'hidden';
9777
+ }
9778
}
9779
9780
return SucuriScanTemplate::get_section('settings-ignorescanning', $template_variables);
9861
'Cronjobs' => sucuriscan_show_cronjobs(),
9862
'HTAccessIntegrity' => sucuriscan_infosys_htaccess(),
9863
'WordpressConfig' => sucuriscan_infosys_wpconfig(),
9864
+ 'ErrorLogs' => sucuriscan_infosys_errorlogs(),
9865
);
9866
9867
echo SucuriScanTemplate::get_template('infosys', $template_variables);
9979
9980
// Parse the main configuration file and look for constants and global variables.
9981
foreach( (array) $wp_config_content as $line ){
9982
+ // Ignore commented lines.
9983
+ if ( preg_match('/^\s?(#|\/\/)/', $line) ) { continue; }
9984
+
9985
// Detect PHP constants even if the line if indented.
9986
+ elseif ( preg_match('/define\(/', $line) ) {
9987
+ $line = preg_replace('/.*define\((.+)\);.*/', '$1', $line);
9988
$line_parts = explode(',', $line, 2);
9989
}
9990
9991
// Detect global variables like the database table prefix.
9992
+ elseif( preg_match('/^\$[a-zA-Z_]+/', $line) ){
9993
+ $line = preg_replace( '/;\s\/\/.*/', ';', $line );
9994
$line_parts = explode('=', $line, 2);
9995
}
9996
9997
// Ignore other lines.
9998
+ else { continue; }
9999
10000
// Clean and append the rule to the wp_config_rules variable.
10001
if( isset($line_parts) && count($line_parts)==2 ){
10156