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$/', $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\/(.*)\/$/';
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$/', $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)$/', $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}$/', $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}$/', $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}$/', $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\/(.*)\/$/';
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
+ . '$/';
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
  }
10157
  }
10158
 
10159
+ /**
10160
+ * Locate, parse and display the latest error logged in the main error_log file.
10161
+ *
10162
+ * @return array A list of pseudo-variables and values that will replace them in the HTML template.
10163
+ */
10164
+ function sucuriscan_infosys_errorlogs(){
10165
+ $template_variables = array(
10166
+ 'ErrorLog.Path' => '',
10167
+ 'ErrorLog.Exists' => 'No',
10168
+ 'ErrorLog.NoItemsVisibility' => 'visible',
10169
+ 'ErrorLog.DisabledVisibility' => 'visible',
10170
+ 'ErrorLog.FileSize' => '0B',
10171
+ 'ErrorLog.List' => '',
10172
+ );
10173
+
10174
+ $error_log_path = realpath( ABSPATH . '/error_log' );
10175
+ $parse_errorlogs = ( SucuriScanOption::get_option(':parse_errorlogs') !== 'disabled' );
10176
+ $errorlogs_limit = SucuriScanOption::get_option(':errorlogs_limit');
10177
+
10178
+ if ( $error_log_path && $parse_errorlogs ) {
10179
+ $template_variables['ErrorLog.Path'] = $error_log_path;
10180
+ $template_variables['ErrorLog.Exists'] = 'Yes';
10181
+ $template_variables['ErrorLog.FileSize'] = SucuriScan::human_filesize( filesize($error_log_path) );
10182
+ $template_variables['ErrorLog.DisabledVisibility'] = 'hidden';
10183
+
10184
+ $last_lines = SucuriScanFileInfo::tail_file( $error_log_path, $errorlogs_limit );
10185
+ $error_logs = SucuriScanFSScanner::parse_error_logs($last_lines);
10186
+ $error_logs = array_reverse($error_logs);
10187
+ $counter = 0;
10188
+
10189
+ foreach ( $error_logs as $error_log ) {
10190
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
10191
+ $template_variables['ErrorLog.List'] .= SucuriScanTemplate::get_snippet('infosys-errorlogs', array(
10192
+ 'ErrorLog.CssClass' => $css_class,
10193
+ 'ErrorLog.DateTime' => SucuriScan::datetime( $error_log->timestamp ),
10194
+ 'ErrorLog.ErrorType' => SucuriScan::escape( $error_log->error_type ),
10195
+ 'ErrorLog.ErrorCode' => SucuriScan::escape($error_log->error_code),
10196
+ 'ErrorLog.ErrorAbbr' => strtoupper( substr($error_log->error_code, 0, 1) ),
10197
+ 'ErrorLog.ErrorMessage' => SucuriScan::escape( $error_log->error_message ),
10198
+ 'ErrorLog.FilePath' => SucuriScan::escape( $error_log->file_path ),
10199
+ 'ErrorLog.LineNumber' => SucuriScan::escape( $error_log->line_number ),
10200
+ ));
10201
+ $counter += 1;
10202
+ }
10203
+
10204
+ if ( $counter > 0 ) {
10205
+ $template_variables['ErrorLog.NoItemsVisibility'] = 'hidden';
10206
+ }
10207
+ }
10208
+
10209
+ return SucuriScanTemplate::get_section('infosys-errorlogs', $template_variables);
10210
+ }
10211
+
10212
  /**
10213
  * Gather information from the server, database engine, and PHP interpreter.
10214
  *
10269
 
10270
  $field_names = array(
10271
  'safe_mode',
10272
+ 'expose_php',
10273
  'allow_url_fopen',
10274
  'memory_limit',
10275
  'upload_max_filesize',