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

Version Description

  • Initial release with new auditing options.
Download this release

Release Info

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

Code changes from version 1.6.0 to 1.6.1

Files changed (45) hide show
  1. inc/css/sucuriscan-default-css.css +176 -49
  2. inc/js/sucuriscan-scripts.js +31 -2
  3. inc/tpl/about.html.tpl +0 -111
  4. inc/tpl/base.html.tpl +10 -6
  5. inc/tpl/infosys-cronjobs.snippet.tpl +1 -0
  6. inc/tpl/infosys-htaccess.html.tpl +3 -2
  7. inc/tpl/infosys-loggedin.html.tpl +2 -1
  8. inc/tpl/infosys-serverinfo.html.tpl +1 -1
  9. inc/tpl/infosys-wpconfig.html.tpl +2 -1
  10. inc/tpl/initial-page.html.tpl +0 -97
  11. inc/tpl/integrity-admins-lastlogin.snippet.tpl +0 -4
  12. inc/tpl/integrity-auditlogs.html.tpl +38 -0
  13. inc/tpl/integrity-auditlogs.snippet.tpl +9 -0
  14. inc/tpl/integrity-corefiles.html.tpl +42 -0
  15. inc/tpl/integrity-corefiles.snippet.tpl +9 -0
  16. inc/tpl/integrity-modifiedfiles.html.tpl +40 -0
  17. inc/tpl/integrity-modifiedfiles.snippet.tpl +6 -0
  18. inc/tpl/integrity-wpoutdate.html.tpl +14 -0
  19. inc/tpl/integrity.html.tpl +10 -0
  20. inc/tpl/lastlogins-admins-lastlogin.snippet.tpl +5 -0
  21. inc/tpl/{integrity-admins.html.tpl → lastlogins-admins.html.tpl} +3 -5
  22. inc/tpl/{integrity-admins.snippet.tpl → lastlogins-admins.snippet.tpl} +3 -2
  23. inc/tpl/lastlogins-all.html.tpl +23 -0
  24. inc/tpl/{lastlogins.snippet.tpl → lastlogins-all.snippet.tpl} +0 -0
  25. inc/tpl/lastlogins.html.tpl +19 -21
  26. inc/tpl/malwarescan.html.tpl +17 -0
  27. inc/tpl/modalwindow.html.tpl +26 -0
  28. inc/tpl/monitoring-logs.html.tpl +74 -0
  29. inc/tpl/monitoring-logs.snippet.tpl +64 -0
  30. inc/tpl/monitoring-settings.html.tpl +28 -0
  31. inc/tpl/monitoring-settings.snippet.tpl +5 -0
  32. inc/tpl/monitoring.html.tpl +58 -0
  33. inc/tpl/{notification.html.tpl → notification-pretty.html.tpl} +5 -5
  34. inc/tpl/{notification.txt.tpl → notification-simple.html.tpl} +1 -1
  35. inc/tpl/posthack-databasebackups.html.tpl +41 -0
  36. inc/tpl/posthack-databasebackups.snippet.tpl +10 -0
  37. inc/tpl/posthack-resetpassword.html.tpl +47 -0
  38. inc/tpl/posthack-updatesecretkeys.html.tpl +31 -0
  39. inc/tpl/posthack.html.tpl +22 -71
  40. inc/tpl/settings-apiregistered.html.tpl +16 -0
  41. inc/tpl/settings-notification.snippet.tpl +12 -0
  42. inc/tpl/settings.html.tpl +118 -0
  43. inc/tpl/setup_notice.html.tpl +23 -0
  44. readme.txt +6 -3
  45. sucuri.php +5974 -1554
inc/css/sucuriscan-default-css.css CHANGED
@@ -3,89 +3,216 @@
3
  * Copyright (C) 2010-2014 Sucuri Security - http://sucuri.net
4
  * Released under the GPL - see LICENSE file for details.
5
  */
6
- /* New styles */
7
  .sucuriscan-wrap *, .sucuriscan-wrap *:before, .sucuriscan-wrap *:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
8
  .sucuriscan-clearfix:before, .sucuriscan-clearfix:after{display:table;content:' '}
9
  .sucuriscan-clearfix:after{clear:both}
10
  .sucuriscan-visible{}
11
  .sucuriscan-hidden{display:none !important}
12
  .sucuriscan-monospace{font-family:Monospace, Courier}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  .sucuriscan-wrap .sucuriscan-maincontent{margin:20px 0}
14
- .sucuriscan-wrap .sucuriscan-leftside{width:68%;float:left}
15
- .sucuriscan-wrap .sucuriscan-sidebar{width:30%;float:right}
 
 
 
16
  .sucuriscan-header, .sucuriscan-footer{position:relative;min-width:255px;background:#333;margin:0;padding:10px;border-radius:4px}
17
  .sucuriscan-footer .sucuriscan-help{color:#fff;float:right;text-align:right}
18
  .sucuriscan-footer .sucuriscan-help p{line-height:38px;margin:0 10px 0 0;padding:0}
19
- .sucuriscan-wrap .sucuriscan-header img, .sucuriscan-wrap .sucuriscan-footer img, .sucuriscan-wrap .sucuriscan-header h2, .sucuriscan-wrap .sucuriscan-footer h2{float:left;margin:0;padding:0}
 
 
20
  .sucuriscan-wrap .sucuriscan-header h2, .sucuriscan-wrap .sucuriscan-footer h2{color:#fff;line-height:38px;margin-left:10px;text-shadow:#000 0 1px 0}
21
  .sucuriscan-leftside #poststuff .postbox:last-child{margin-bottom:0}
22
- .sucuriscan-sidebar .sucuriscan-ad{border:1px solid #ccc;margin:0 0 20px 0;padding:20px;border-radius:4px}
23
- .sucuriscan-sidebar .sucuriscan-ad h2{padding:0}
24
- .sucuriscan-sidebar .sucuriscan-ad p:last-child{margin-bottom:0}
25
- .sucuriscan-sidebar .sucuriscan-ad:nth-child(odd){background-color:#bbe8f5;border-color:#4393ac}
26
- .sucuriscan-sidebar .sucuriscan-ad:nth-child(even){background-color:#ececec;border-color:#999}
27
- .sucuriscan-maincontent .sucuriscan-border{border-left:4px solid #ddd}
28
- .sucuriscan-maincontent .sucuriscan-border-good{border-left-color:#7ad03a}
29
- .sucuriscan-maincontent .sucuriscan-border-bad{border-left-color:#dd3d36}
30
  .sucuriscan-maincontent .sucuriscan-table{margin-top:12px}
31
- .sucuriscan-maincontent .sucuriscan-table tr > th{background:#f1f1f1;background-image:-webkit-gradient(linear,left bottom,left top,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-moz-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-o-linear-gradient(bottom,#ececec,#f9f9f9);background-image:linear-gradient(to top,#ececec,#f9f9f9);border-top:1px solid #e5e5e5;border-bottom:1px solid #e5e5e5}
32
  .sucuriscan-maincontent .sucuriscan-table tr:first-child th{border-top:0}
33
  .sucuriscan-maincontent .sucuriscan-table td.check-column{padding:8px 10px}
34
- .sucuriscan-table-doubletitle tr:first-child th{border-bottom:0}
35
- .sucuriscan-maincontent .sucuriscan-corefiles td,
36
- .sucuriscan-maincontent .sucuriscan-lastmodified td,
37
- .sucuriscan-maincontent .sucuriscan-adminusers table td{font-family:Monospace, Courier, serif;font-weight:bold}
38
- .sucuriscan_wpconfig_keys_updated textarea{width:100%;height:250px;background:#f5f5f5;font-family:monospace;font-size:12px;resize:vertical;margin:20px 0 0 0}
39
- .sucuriscan-ellipsis{overflow:hidden;display:inline-block;white-space:nowrap;text-overflow:ellipsis}
40
- .sucuriscan-maincontent .sucuriscan-last-logins{margin-top:0}
41
- .sucuriscan-maincontent .sucuriscan-last-logins .sucuriscan-ellipsis{width:150px;line-height:inherit}
42
- .sucuriscan-maincontent .thead-with-button span{display:inline-block;line-height:28px}
 
 
 
 
 
43
  .sucuriscan-maincontent .thead-with-button .input-text{line-height:26px}
 
44
  .sucuriscan-maincontent .thead-topright-action{display:inline-block;float:right}
 
 
 
 
 
 
 
 
 
 
 
45
  .sucuriscan-tabs{}
46
  .sucuriscan-tabs > ul{margin:0}
47
  .sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:inline-block}
48
  .sucuriscan-tabs > ul li{margin-bottom:0}
49
  .sucuriscan-tabs > ul li > a{background:#e5e5e5;font-size:13px;font-weight:bold;color:#333;line-height:38px;text-decoration:none;padding:0 10px}
50
  .sucuriscan-tabs > ul li > a.sucuriscan-tab-active{background:#fff;border:1px solid #e1e1e1;border-bottom:0}
 
 
51
  .sucuriscan-maincontent .sucuriscan-tab-containers > div > table{margin-top:0}
52
  .sucuriscan-maincontent .sucuriscan-tab-containers > div > #poststuff{margin-top:0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  .sucuriscan-maincontent .sucuriscan-full-textarea{width:100%;height:400px;line-height:normal;resize:vertical;padding:10px}
 
 
 
 
 
 
 
54
  .sucuriscan-wpconfig-textarea{width:600px;height:525px;background:#f5f5f5;font-size:12px;line-height:1.4em;resize:none;margin:15px 0 0 0;padding:10px}
55
- .sucuriscan-scanner-results table tr:nth-child(even){background:#f5f5f5}
56
- .sucuriscan-maincontent .sucuriscan-cleanup-btn{display:block;text-align:center;margin:20px 0 0 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  /* Old styles */
58
  .sucuriscan-maincontent #poststuff{min-width:initial;padding-top:0}
59
  .sucuriscan-maincontent .widefat tbody th.check-column{padding:6px 0 3px 0}
60
  .sucuriscan-maincontent .hardening-box .primary-secondary{margin:0 0 0 10px}
61
  .sucuriscan-maincontent a.lastlogins-showall{display:inline-block;float:right}
62
- .sucuri-alert{position:relative}
63
- .sucuri-alert>a.close{position:absolute;top:8px;right:10px;font-size:18px;text-decoration:none}
64
- .sucuri-alert-updated, .sucuri-alert-error{background:#fff;margin:5px 0 15px;padding:1px 12px;border:1px solid #e5e5e5;border-left:4px solid #ccc}
65
- .sucuri-alert-updated{border-left:4px solid #7ad03a}
66
- .sucuri-alert-error{border-left:4px solid #dd3d36}
67
- .sucuri-alert-updated p, .sucuri-alert-error p{margin:.5em 0;padding:2px}
68
  .sucuri-inline-error{font-weight:bold;color:red}
69
- .sucuri-list li{list-style:disc;margin:0 0 5px 15px}
70
  .sucuriscan-maincontent .alternate{background:#f5f5f5}
71
  .sucuriscan-maincontent hr{border:none;border-top:1px solid #999}
72
  .sucuriscan-maincontent table td > table{background:#fff}
73
  .sucuriscan-maincontent table td > table th{padding:4px 8px}
74
- .sucuriscan-results .icon-ok, .sucuriscan-results .icon-warn, .sucuriscan-results .icon-error{position:relative;top:5px;width:22px;height:22px}
75
- .sucuriscan-scanner-video{width:100%;background:#fff;border:1px solid #ddd}
76
- .sucuriscan-column-left, .sucuriscan-column-right{width:49%;min-width:initial !important}
77
- .sucuriscan-column-left{float:left}
78
- .sucuriscan-column-right{float:right}
79
- .sucuriscan-hstatus{position:relative;margin:0 -12px;padding:10px 12px;border:1px solid transparent}
80
- .sucuriscan-hstatus-1{background-color:#dff0d8;color:#3c763d;border-color:#d6e9c6}
81
- .sucuriscan-hstatus-0{background-color:#f2dede;color:#a94442;border-color:#ebccd1}
82
- .sucuriscan-hstatus .button-primary, .sucuriscan-hstatus .button-secondary{position:absolute;top:5px;right:5px}
83
- .sucuriscan-initial-page{}
84
- .sucuriscan-initial-page a{text-decoration:none}
85
- .sucuriscan-initial-page .sucuriscan-column-left{width:70%}
86
- .sucuriscan-initial-page .sucuriscan-column-right{width:29%;text-align:right}
87
- .sucuriscan-initial-page #poststuff .inside, .sucuriscan-initial-page #poststuff .inside p{font-size:16px;margin:0;padding:0}
88
- .sucuriscan-initial-page #poststuff .inside{padding:20px}
89
- .sucuriscan-initial-page #poststuff .button.button-hero{width:202px;text-align:center;padding:0}
90
- .sucuriscan-initial-page .sucuriscan-disclaimer{padding:20px;padding-top:0}
91
- .sucuriscan-initial-page .sucuriscan-disclaimer p{font-size:10px;margin:0}
3
  * Copyright (C) 2010-2014 Sucuri Security - http://sucuri.net
4
  * Released under the GPL - see LICENSE file for details.
5
  */
6
+ /* Generic Styles */
7
  .sucuriscan-wrap *, .sucuriscan-wrap *:before, .sucuriscan-wrap *:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
8
  .sucuriscan-clearfix:before, .sucuriscan-clearfix:after{display:table;content:' '}
9
  .sucuriscan-clearfix:after{clear:both}
10
  .sucuriscan-visible{}
11
  .sucuriscan-hidden{display:none !important}
12
  .sucuriscan-monospace{font-family:Monospace, Courier}
13
+ .sucuriscan-ellipsis{overflow:hidden;display:inline-block;white-space:nowrap;text-overflow:ellipsis}
14
+ .sucuriscan-wraptext{word-break:break-all}
15
+ .sucuriscan-pull-left{float:left}
16
+ .sucuriscan-pull-right{float:right}
17
+ .sucuriscan-list li{list-style:disc;margin:0 0 5px 15px}
18
+ .sucuriscan-gradient, .sucuriscan-modal-header, .sucuriscan-maincontent .sucuriscan-table tr > th{background:#f1f1f1;background-image:-webkit-gradient(linear,left bottom,left top,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-moz-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-o-linear-gradient(bottom,#ececec,#f9f9f9);background-image:linear-gradient(to top,#ececec,#f9f9f9)}
19
+ /* WordPress Extra Buttons */
20
+ .wp-core-ui .button-danger.button-danger{background:#cc2e2e;border-color:#a20000;box-shadow:inset 0 1px 0 rgba(230, 120, 120, 0.6)}
21
+ .wp-core-ui .button-danger.focus, .wp-core-ui .button-danger.hover, .wp-core-ui .button-danger:focus, .wp-core-ui .button-danger:hover{background:#be1e1e}
22
+ .wp-core-ui .button-danger.focus, .wp-core-ui .button-danger:focus{border-color:#500e0e}
23
+ .wp-core-ui .button-danger.active, .wp-core-ui .button-danger.active:focus, .wp-core-ui .button-danger.active:hover, .wp-core-ui .button-danger:active{background:#a61b1b;border-color:#840000}
24
+ .wp-core-ui .button-danger-disabled, .wp-core-ui .button-danger.disabled, .wp-core-ui .button-danger:disabled, .wp-core-ui .button-danger[disabled]{color:#e79494 !important;background:#ba2929 !important;border-color:#7f1b1b !important}
25
+ /* Modal Window */
26
+ .sucuriscan-overlay{position:fixed;top:0;left:0;bottom:0;right:0;z-index:800;background:rgba(0, 0, 0, 0.5)}
27
+ .sucuriscan-modal{position:fixed;top:100px;left:50%;z-index:900}
28
+ .sucuriscan-modal-outside{position:relative;left:-50%;border:1px solid #ddd}
29
+ .sucuriscan-modal-inside{background:#fff;padding:20px}
30
+ .sucuriscan-modal-header{padding:10px 20px;border-bottom:1px solid #ddd}
31
+ .sucuriscan-modal-header .sucuriscan-modal-title{min-height:18px;margin:0}
32
+ .sucuriscan-modal-close{display:inline-block;position:absolute;top:0;right:0;font-size:16px;font-weight:bold;text-decoration:none;line-height:39px;padding:0 15px;border-left:1px solid #ddd}
33
+ .sucuriscan-modal-inside p:first-child{margin-top:0}
34
+ .sucuriscan-modal-inside p:last-child{margin-bottom:0}
35
+ /* Interface Wrapper */
36
+ .sucuriscan-wrap{margin-top:20px}
37
  .sucuriscan-wrap .sucuriscan-maincontent{margin:20px 0}
38
+ .sucuriscan-wrap .sucuriscan-leftside{width:73.5%;float:left}
39
+ .sucuriscan-wrap .sucuriscan-sidebar{width:25%;float:right}
40
+ .sucuriscan-wrap #warnings_hook{line-height:initial;padding:0}
41
+ .sucuriscan-wrap .sucuriscan-navbar{padding-top:20px;padding-left:6px}
42
+ .sucuriscan-wrap .sucuriscan-navbar .nav-tab{margin-right:0}
43
  .sucuriscan-header, .sucuriscan-footer{position:relative;min-width:255px;background:#333;margin:0;padding:10px;border-radius:4px}
44
  .sucuriscan-footer .sucuriscan-help{color:#fff;float:right;text-align:right}
45
  .sucuriscan-footer .sucuriscan-help p{line-height:38px;margin:0 10px 0 0;padding:0}
46
+ .sucuriscan-wrap .sucuriscan-logo, .sucuriscan-wrap .sucuriscan-header h2, .sucuriscan-wrap .sucuriscan-footer h2{float:left;margin:0;padding:0}
47
+ .sucuriscan-wrap .sucuriscan-logo{display:inline-block}
48
+ .sucuriscan-wrap .sucuriscan-logo img{display:block}
49
  .sucuriscan-wrap .sucuriscan-header h2, .sucuriscan-wrap .sucuriscan-footer h2{color:#fff;line-height:38px;margin-left:10px;text-shadow:#000 0 1px 0}
50
  .sucuriscan-leftside #poststuff .postbox:last-child{margin-bottom:0}
51
+ /* Page Setup Notice */
52
+ .wrap div.sucuriscan-setup-notice{background:#bbe8f5;margin:0 0 20px 0;padding:0;border:1px solid #bbb;border-radius:3px;-webkit-box-shadow:none;box-shadow:none}
53
+ .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image, .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image img{border-radius:3px 0 0 3px}
54
+ .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image{background:#333;margin:-1px 0 -1px -1px;padding:7px 10px;border-right:1px solid transparent}
55
+ .wrap div.sucuriscan-setup-notice .sucuriscan-setup-form{padding:4px;padding-left:0}
56
+ .wrap div.sucuriscan-setup-notice p{font-size:14px;line-height:20px;margin:0 0 0 10px;padding:7px 0}
57
+ .wrap div.sucuriscan-setup-notice, .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image{border-color:#4393ac}
58
+ /* Table Styles */
59
  .sucuriscan-maincontent .sucuriscan-table{margin-top:12px}
60
+ .sucuriscan-maincontent .sucuriscan-table tr > th{border-top:1px solid #e5e5e5;border-bottom:1px solid #e5e5e5}
61
  .sucuriscan-maincontent .sucuriscan-table tr:first-child th{border-top:0}
62
  .sucuriscan-maincontent .sucuriscan-table td.check-column{padding:8px 10px}
63
+ .sucuriscan-table-double-title tr:first-child th{border-bottom:0}
64
+ .sucuriscan-table-triple-title tr:first-child th, .sucuriscan-table-triple-title tr:first-child + tr th{border-bottom:0}
65
+ .sucuriscan-table-quad-title tr:first-child th, .sucuriscan-table-quad-title tr:first-child + tr th, .sucuriscan-table-quad-title tr:first-child + tr + tr th{border-bottom:0}
66
+ .sucuriscan-maincontent .sucuriscan-lastmodified td{font-family:Monospace, Courier, serif;font-weight:bold}
67
+ .widefat td.td-with-button{text-align:right;padding:3px 10px}
68
+ .widefat td.td-with-button button{min-width:90px}
69
+ .widefat td.td-with-button select{height:initial;line-height:initial;vertical-align:top;margin:0;padding:2px 0 3px 0}
70
+ .sucuriscan-list-as-table{background:#fff;border:1px solid #e5e5e5}
71
+ .sucuriscan-list-as-table li{line-height:30px;margin:0;padding:0 10px}
72
+ .sucuriscan-list-as-table li:nth-child(odd){background:#f5f5f5}
73
+ .sucuriscan-list-as-table-scrollable{height:300px;overflow:hidden;overflow-y:scroll}
74
+ /* Table Top-Right Buttons */
75
+ .sucuriscan-maincontent .thead-with-button{padding:5px 5px 5px 10px}
76
+ .sucuriscan-maincontent .thead-with-button > span{display:inline-block;line-height:28px}
77
  .sucuriscan-maincontent .thead-with-button .input-text{line-height:26px}
78
+ .sucuriscan-maincontent .thead-with-button select{margin:0;padding:0}
79
  .sucuriscan-maincontent .thead-topright-action{display:inline-block;float:right}
80
+ /* Sidebar Styles */
81
+ .sucuriscan-sidebar .sucuriscan-ad{border:1px solid #ccc;margin:0 0 20px 0;padding:20px;border-radius:4px}
82
+ .sucuriscan-sidebar .sucuriscan-ad h2{font-size:18px;line-height:normal;padding:0}
83
+ .sucuriscan-sidebar .sucuriscan-ad p:last-child{margin-bottom:0}
84
+ .sucuriscan-sidebar .sucuriscan-ad:nth-child(odd){background-color:#bbe8f5;border-color:#4393ac}
85
+ .sucuriscan-sidebar .sucuriscan-ad:nth-child(even){background-color:#ececec;border-color:#999}
86
+ .sucuriscan-scanner-video{width:100%;background:#fff;border:1px solid #ddd}
87
+ /* WordPress Alerts */
88
+ div.sucuriscan-alert{position:relative;margin:0 0 20px 0}
89
+ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:18px;font-weight:bold;text-decoration:none}
90
+ /* Tabulation Panels */
91
  .sucuriscan-tabs{}
92
  .sucuriscan-tabs > ul{margin:0}
93
  .sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:inline-block}
94
  .sucuriscan-tabs > ul li{margin-bottom:0}
95
  .sucuriscan-tabs > ul li > a{background:#e5e5e5;font-size:13px;font-weight:bold;color:#333;line-height:38px;text-decoration:none;padding:0 10px}
96
  .sucuriscan-tabs > ul li > a.sucuriscan-tab-active{background:#fff;border:1px solid #e1e1e1;border-bottom:0}
97
+ .sucuriscan-tabs > ul li.sucuriscan-red-tab a{background:#ff8a83;color:#fff}
98
+ .sucuriscan-tabs > ul li.sucuriscan-red-tab a.sucuriscan-tab-active{background:#dd3d36;border-color:#dd3d36}
99
  .sucuriscan-maincontent .sucuriscan-tab-containers > div > table{margin-top:0}
100
  .sucuriscan-maincontent .sucuriscan-tab-containers > div > #poststuff{margin-top:0}
101
+ /* Get API Form */
102
+ .sucuriscan-getapi-div{background:#fff;margin:0 0 20px 0;border:1px solid #e5e5e5;border-radius:3px}
103
+ .sucuriscan-getapi-div p{margin:0;padding:10px}
104
+ .sucuriscan-getapi-form{}
105
+ .sucuriscan-getapi-form button.button-primary{width:100%;height:initial;line-height:30px;margin:0 0 -1px 0;padding:0;border-radius:0 0 3px 3px}
106
+ /* Scanner Page */
107
+ .sucuriscan-malwarescan-message{margin-bottom:20px !important}
108
+ .sucuriscan-loading{background:#fff;text-align:center;padding:30px;padding-bottom:15px;border:1px solid #ddd;border-radius:4px}
109
+ .sucuriscan-loading p, .sucuriscan-loading h3{margin:0;padding:0}
110
+ .sucuriscan-loading .title{font-size:26px;margin-bottom:10px}
111
+ .sucuriscan-loading .description{font-size:18px}
112
+ .sucuriscan-sitelogo{width:190px;height:100px;background:url('http://sitecheck.sucuri.net/images/sucuri-sprite.png') no-repeat;margin:0 auto}
113
+ /* Scanner Results */
114
+ .sucuriscan-maincontent .sucuriscan-border{border:0;border-left:4px solid #ddd}
115
+ .sucuriscan-maincontent .sucuriscan-border > h3, .sucuriscan-maincontent .sucuriscan-border > .inside{border-top:1px solid #e5e5e5;border-right:1px solid #e5e5e5}
116
+ .sucuriscan-maincontent .sucuriscan-border > h3{border-bottom:0}
117
+ .sucuriscan-maincontent .sucuriscan-border > .inside{margin-top:0 !important;border-bottom:1px solid #ddd}
118
+ .sucuriscan-maincontent .sucuriscan-border-good{border-left-color:#7ad03a}
119
+ .sucuriscan-maincontent .sucuriscan-border-bad{border-left-color:#dd3d36}
120
+ .sucuriscan-maincontent .sucuriscan-border-info{border-left-color:#2ea2cc}
121
+ .sucuriscan-maincontent .sucuriscan-cleanup-btn{display:block;text-align:center;margin:20px 0 0 0}
122
+ .sucuriscan-scanner-results .sucuriscan-scanner-details tr:nth-child(even),
123
+ .sucuriscan-scanner-results .sucuriscan-scanner-links tr:nth-child(even){background:#f5f5f5}
124
+ .sucuriscan-scanner-results td.sucuriscan-border-bad{border-left-width:4px;border-left-style:solid}
125
+ /* Integrity Styles */
126
+ .sucuriscan-status-type{width:20px;background:#ddd;text-align:center;text-transform:uppercase;margin-right:10px;padding:0 3px;border:1px solid transparent;border-radius:3px}
127
+ .sucuriscan-status-added{background:#dff0d8;color:#3c763d;border-color:#d6e9c6}
128
+ .sucuriscan-status-modified{background:#fcf8e3;color:#8a6d3b;border-color:#faebcc}
129
+ .sucuriscan-status-removed, td.sucuriscan-corefiles-warning > div{background:#f2dede;color:#a94442;border-color:#ebccd1}
130
+ .sucuriscan-maincontent .sucuriscan-corefiles,
131
+ .sucuriscan-maincontent .sucuriscan-integrity-message,
132
+ .sucuriscan-maincontent .sucuriscan-wordpress-outdated,
133
+ .sucuriscan-maincontent .sucuriscan-auditlogs{margin-top:0;margin-bottom:20px}
134
+ .sucuriscan-maincontent .sucuriscan-corefiles td{padding:4px 10px}
135
+ .sucuriscan-corefiles-abbrs .sucuriscan-status-type{display:inline-block;width:initial;font-size:12px;text-transform:capitalize;float:left;margin-top:4px;margin-right:5px}
136
+ .sucuriscan-maincontent td.sucuriscan-corefiles-warning, .sucuriscan-maincontent td.sucuriscan-corefiles-warning p{margin:0;padding:0}
137
+ .sucuriscan-maincontent td.sucuriscan-corefiles-warning div{padding:10px;border-width:1px;border-style:solid}
138
+ .sucuriscan-maincontent td.sucuriscan-corefiles-warning code{font-size:12px;padding:0 5px}
139
+ .sucuriscan-maincontent .sucuriscan-integrity-message{position:relative}
140
+ .sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-mark{position:absolute;top:1px;right:1px;background:#7ad03a;font-weight:bold;color:#fff;line-height:35px;padding:0 10px;border-left:1px solid #ddd}
141
+ /* Monitoring Styles */
142
+ .sucuriscan-monitoring-settings{margin-bottom:20px}
143
+ .sucuriscan-monitoring-settings td.td-with-button{text-align:left}
144
+ .sucuriscan-monitoring-settings .sucuriscan-list-as-table{margin:0}
145
+ .sucuriscan-monitoring-apikey-form .input-text{width:85%}
146
+ .sucuriscan-monitoring-clear-cache-form{}
147
+ .sucuriscan-monitoring-logs{}
148
+ .sucuriscan-monitoring-logs .thead-with-button .button{width:65px}
149
+ .sucuriscan-monitoring-logs .thead-with-button .input-text, .sucuriscan-monitoring-logs .thead-with-button select{width:250px}
150
+ .sucuriscan-monitoring-logs .sucuriscan-monitoring-search-form{}
151
+ .sucuriscan-monitoring-logs .sucuriscan-monitoring-denial-types-form{}
152
+ .sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form{}
153
+ .sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form select{width:70px}
154
+ .sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form select + select{width:112px}
155
+ .sucuriscan-monitoring-logs .sucuriscan-monitoring-date-form select + select + select{width:60px}
156
+ .sucuriscan-monitoring-logs .sucuriscan-target-date{font-size:12px;color:#999;margin-right:5px}
157
+ /* Monitoring AccessLog Styles */
158
+ .sucuriscan-request-summary{margin:-15px;margin-top:-3px}
159
+ .sucuriscan-request-summary ul{margin:0}
160
+ .sucuriscan-request-summary label, .sucuriscan-request-summary span{display:inline-block;font-size:14px}
161
+ .sucuriscan-request-summary label{width:200px;font-weight:bold}
162
+ .sucuriscan-request-summary span{max-width:395px;font-family:monospace;vertical-align:top;word-break:break-all}
163
+ /* Hardening Status */
164
+ .sucuriscan-hstatus{position:relative;margin:0 -12px;padding:10px 12px;border:1px solid transparent}
165
+ .sucuriscan-hstatus-1{background-color:#dff0d8;color:#3c763d;border-color:#d6e9c6}
166
+ .sucuriscan-hstatus-0{background-color:#f2dede;color:#a94442;border-color:#ebccd1}
167
+ .sucuriscan-hstatus .button-primary, .sucuriscan-hstatus .button-secondary{position:absolute;top:5px;right:5px}
168
+ /* About Page */
169
+ .sucuriscan-about ul{margin-left:20px}
170
+ .sucuriscan-about ul li{list-style:initial}
171
+ .sucuriscan-about li label{font-weight:bold;vertical-align:initial}
172
+ /* API Registered Modal */
173
+ .sucuriscan-apikey-registered{}
174
+ .sucuriscan-apikey-registered .sucuriscan-pull-right{width:400px;margin-left:20px}
175
+ .sucuriscan-apikey-registered .sucuriscan-sitelogo{background-position:0 -17px;height:83px}
176
+ /* Resetter Styles */
177
+ .sucuriscan_wpconfig_keys_updated textarea{width:100%;height:250px;background:#f5f5f5;font-family:monospace;font-size:12px;resize:vertical;margin:20px 0 0 0}
178
+ .sucuriscan-maincontent .sucuriscan-last-logins{margin-top:0}
179
+ .sucuriscan-maincontent .sucuriscan-last-logins .sucuriscan-ellipsis{width:150px;line-height:inherit}
180
+ .sucuriscan-maincontent .sucuriscan-modifiedfiles .sucuriscan-ellipsis{width:100px}
181
  .sucuriscan-maincontent .sucuriscan-full-textarea{width:100%;height:400px;line-height:normal;resize:vertical;padding:10px}
182
+ .sucuriscan-maincontent .sucuriscan-auditlogs{margin-bottom:0}
183
+ .sucuriscan-maincontent .sucuriscan-auditlogs td small{font-style:italic}
184
+ .sucuriscan-maincontent .sucuriscan-auditlogs .sucuriscan-maxper-page{text-align:right}
185
+ .sucuriscan-maincontent .sucuriscan-settings{margin-top:0}
186
+ .sucuriscan-maincontent .sucuriscan-settings form{display:inline-block}
187
+ .sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{min-width:245px}
188
+ .sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:20px}
189
  .sucuriscan-wpconfig-textarea{width:600px;height:525px;background:#f5f5f5;font-size:12px;line-height:1.4em;resize:none;margin:15px 0 0 0;padding:10px}
190
+ /* Responsive Styles */
191
+ @media (max-width: 620px) {
192
+ .sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:block}
193
+ .sucuriscan-getapi-form button.button-primary{line-height:40px}
194
+ }
195
+ @media (max-width: 768px) {
196
+ .sucuriscan-wrap .sucuriscan-leftside,
197
+ .sucuriscan-wrap .sucuriscan-sidebar,
198
+ .sucuriscan-wrap .sucuriscan-footer .sucuriscan-logo,
199
+ .sucuriscan-wrap .sucuriscan-footer .sucuriscan-help{float:none}
200
+ .sucuriscan-wrap .sucuriscan-leftside, .sucuriscan-wrap .sucuriscan-sidebar{width:100%}
201
+ .sucuriscan-wrap .sucuriscan-sidebar{margin-top:20px}
202
+ .sucuriscan-wrap .sucuriscan-footer .sucuriscan-logo{display:table;margin:0 auto}
203
+ }
204
+ @media (max-width: 920px) {
205
+ .sucuriscan-wrap .sucuriscan-navbar{padding-left:0;padding-right:0}
206
+ .sucuriscan-wrap .sucuriscan-navbar .nav-tab{display:block}
207
+ .sucuriscan-wrap .sucuriscan-navbar .nav-tab:last-child{border-bottom:1px solid #ccc}
208
+ }
209
  /* Old styles */
210
  .sucuriscan-maincontent #poststuff{min-width:initial;padding-top:0}
211
  .sucuriscan-maincontent .widefat tbody th.check-column{padding:6px 0 3px 0}
212
  .sucuriscan-maincontent .hardening-box .primary-secondary{margin:0 0 0 10px}
213
  .sucuriscan-maincontent a.lastlogins-showall{display:inline-block;float:right}
 
 
 
 
 
 
214
  .sucuri-inline-error{font-weight:bold;color:red}
 
215
  .sucuriscan-maincontent .alternate{background:#f5f5f5}
216
  .sucuriscan-maincontent hr{border:none;border-top:1px solid #999}
217
  .sucuriscan-maincontent table td > table{background:#fff}
218
  .sucuriscan-maincontent table td > table th{padding:4px 8px}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/js/sucuriscan-scripts.js CHANGED
@@ -5,7 +5,7 @@
5
  */
6
 
7
  function sucuriscan_alert_close(id){
8
- var element = document.getElementById('sucuri-alert-'+id);
9
  element.parentNode.removeChild(element);
10
  }
11
 
@@ -13,6 +13,7 @@ jQuery(document).ready(function($){
13
  if( $('.sucuriscan-tabs').length ){
14
  var hidden_class = 'sucuriscan-hidden';
15
  var active_class = 'sucuriscan-tab-active';
 
16
 
17
  $('.sucuriscan-tabs > ul a').on('click', function(e){
18
  e.preventDefault();
@@ -30,6 +31,34 @@ jQuery(document).ready(function($){
30
  });
31
 
32
  $('.sucuriscan-tab-containers > div').addClass(hidden_class);
33
- $('.sucuriscan-tabs > ul li:first-child a').trigger('click');
 
 
 
 
 
 
 
 
 
34
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  });
5
  */
6
 
7
  function sucuriscan_alert_close(id){
8
+ var element = document.getElementById('sucuriscan-alert-'+id);
9
  element.parentNode.removeChild(element);
10
  }
11
 
13
  if( $('.sucuriscan-tabs').length ){
14
  var hidden_class = 'sucuriscan-hidden';
15
  var active_class = 'sucuriscan-tab-active';
16
+ var anchor = location.href.split('#')[1];
17
 
18
  $('.sucuriscan-tabs > ul a').on('click', function(e){
19
  e.preventDefault();
31
  });
32
 
33
  $('.sucuriscan-tab-containers > div').addClass(hidden_class);
34
+
35
+ if( anchor != undefined ){
36
+ $('.sucuriscan-tabs > ul li a').each(function(i, el){
37
+ if( $(el).data('tabname') == anchor ){
38
+ $(el).trigger('click');
39
+ }
40
+ });
41
+ } else {
42
+ $('.sucuriscan-tabs > ul li:first-child a').trigger('click');
43
+ }
44
  }
45
+
46
+ $('#sucuriscan-corefiles-show').on('click', function(e){
47
+ e.preventDefault();
48
+
49
+ var this_button = $(this);
50
+ var action = this_button.data('action');
51
+
52
+ if( action == 'show' ){
53
+ $('.sucuriscan-corefiles tbody > tr').removeClass('sucuriscan-hidden');
54
+ this_button.html('Hide files').data('action', 'hide');
55
+ } else {
56
+ $('.sucuriscan-corefiles tbody > tr').addClass('sucuriscan-hidden');
57
+ this_button.html('Show files').data('action', 'show');
58
+ }
59
+ });
60
+
61
+ $('#sucuriscan_last_days').on('change', function(){
62
+ $(this).closest('form').submit();
63
+ });
64
  });
inc/tpl/about.html.tpl DELETED
@@ -1,111 +0,0 @@
1
-
2
- <div id="poststuff">
3
- <div class="postbox">
4
- <h3>About</h3>
5
- <div class="inside">
6
- <p>
7
- Our WordPress Security Plugin will monitor your site from the inside, creating
8
- a complete audit trail, alerting you of possible security issues (file changes,
9
- password guessing attacks, etc) and blocking the attackers. This is the perfect
10
- complement for our external security scans.
11
- </p>
12
- </div>
13
- </div>
14
-
15
-
16
- <div class="postbox">
17
- <h3>How does it work?</h3>
18
- <div class="inside">
19
- <ul>
20
- <li>Web Application Firewall. Block attacks before they reach your site.</li>
21
- <li>Integrity Monitoring. Receive notifications if any of your files are modified.</li>
22
- <li>Audit Logs. Keep track of everything that happens inside WordPress, including new users, posts, login failures and successful logins.</li>
23
- <li>Activity Reporting</li>
24
- <li>1-Click Hardening. Easy-to-use hardening options for your site.</li>
25
- </ul>
26
- </div>
27
- </div>
28
-
29
-
30
- <div class="postbox">
31
- <h3>Web Application Firewall (WAF)</h3>
32
- <div class="inside">
33
- <p>
34
- The WAF is a unique feature that is designed to intelligently protect your sites
35
- from brute-force attacks like dictionary attacks and other similar unauthorized
36
- access attempts. When a bad IP is identified it is blacklisted in your admin
37
- dashboard. If it was an unintentional block, you have the ability to white-list
38
- access to any IP.
39
- </p>
40
- <p>
41
- The WAF is not tied to your application, it communicates with our servers and
42
- allows us to see malicious attacks across the network. When one client gets attacked
43
- by one bad IP in Croatia, we are able to push preventive measures to every plugin
44
- to protect against that IP.
45
- </p>
46
- </div>
47
- </div>
48
-
49
-
50
- <div class="postbox">
51
- <h3>Integrity Monitoring</h3>
52
- <div class="inside">
53
- <p>
54
- This feature compares your core install against a clean version of core. In other
55
- words, if it is not a 1-to-1 match with core you will be notified of a problem.
56
- Future add-ons include:
57
- </p>
58
- <ul>
59
- <li>Theme Integrity Checks</li>
60
- <li>Plugin Integrity Checks</li>
61
- <li>Third-party Integrity Checks</li>
62
- </ul>
63
- </div>
64
- </div>
65
-
66
-
67
- <div class="postbox">
68
- <h3>Audit Trails</h3>
69
- <div class="inside">
70
- <p>
71
- This feature is great for proactive webmasters who want to monitor their website
72
- to ensure no unauthorized access or changes are made without prior approval.
73
- Monitor your site for changes. This feature monitors for a large number of actions,
74
- including:
75
- </p>
76
- <ul>
77
- <li>Login attempts</li>
78
- <li>New Posts</li>
79
- <li>Failed Logins</li>
80
- <li>New Plugins</li>
81
- <li>File Changes</li>
82
- <li>New Users</li>
83
- <li>New Attachments</li>
84
- <li>Delete Actions (users and posts)</li>
85
- <li>Revisions</li>
86
- </ul>
87
- </div>
88
- </div>
89
-
90
-
91
- <div class="postbox">
92
- <h3>1-Click Hardening</h3>
93
- <div class="inside">
94
- <p>
95
- In our experience a high-percentage of the infections we see every day come from
96
- poor management on the end-user’s part. This feature uses common hardening
97
- measures that can be taken at any time and helps reduce infection risk. This
98
- feature performs the following:
99
- </p>
100
- <ul>
101
- <li>Checks software core version</li>
102
- <li>Hides your version (security through obscurity)</li>
103
- <li>Upload directory protected</li>
104
- <li>Secret keys and salts created</li>
105
- <li>Configuration file hardening/location verification</li>
106
- <li>Hardening of readme file</li>
107
- <li>PHP verification</li>
108
- </ul>
109
- </div>
110
- </div>
111
- </div><!-- End poststuff -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tpl/base.html.tpl CHANGED
@@ -4,12 +4,16 @@
4
  <h2 id="warnings_hook"></h2>
5
 
6
  <div class="sucuriscan-header sucuriscan-clearfix">
7
- <a href="http://sucuri.net/signup" target="_blank" title="Sucuri Security">
8
  <img src="%%SUCURI.SucuriURL%%/inc/images/logo.png" alt="Sucuri Security" />
9
  </a>
10
- <h2>SiteCheck Scanner %%SUCURI.PageTitle%%</h2>
11
  </div>
12
 
 
 
 
 
13
  <div class="sucuriscan-maincontent sucuriscan-clearfix">
14
 
15
  <div class="sucuriscan-leftside sucuriscan-%%SUCURI.PageStyleClass%%">
@@ -23,12 +27,12 @@
23
  <div class="sucuriscan-ad">
24
  <h2>Is your website infected with malware? Blacklisted by Google?</h2>
25
  <p>Don't know where to start? Get cleared today by <a href="http://sucuri.net/signup">Sucuri Security</a>!</p>
26
- <p><a class="button-primary" href="http://sucuri.net/tour">Read more &#187;</a></p>
27
  </div>
28
 
29
  <div class="sucuriscan-ad">
30
  <h2>Preventive website security in the cloud!</h2>
31
- <ul class="sucuri-list">
32
  <li>Web Application Firewall (WAF) Protection</li>
33
  <li>Virtual Website Patching</li>
34
  <li>Cloud Intrusion Prevention System (IPS)</li>
@@ -41,14 +45,14 @@
41
  </p>
42
  </div>
43
 
44
- <iframe src="https://www.youtube-nocookie.com/embed/QV3OfHmEq5c" height="250" class="sucuriscan-scanner-video"></iframe>
45
 
46
  </div>
47
 
48
  </div>
49
 
50
  <div class="sucuriscan-footer sucuriscan-clearfix">
51
- <a href="http://sucuri.net/signup" target="_blank" title="Sucuri Security">
52
  <img src="%%SUCURI.SucuriURL%%/inc/images/logo.png" alt="Sucuri Security" />
53
  </a>
54
  <div class="sucuriscan-help">
4
  <h2 id="warnings_hook"></h2>
5
 
6
  <div class="sucuriscan-header sucuriscan-clearfix">
7
+ <a href="http://sucuri.net/signup" target="_blank" title="Sucuri Security" class="sucuriscan-logo">
8
  <img src="%%SUCURI.SucuriURL%%/inc/images/logo.png" alt="Sucuri Security" />
9
  </a>
10
+ <h2>Sucuri Security %%SUCURI.PageTitle%%</h2>
11
  </div>
12
 
13
+ <h2 class="nav-tab-wrapper sucuriscan-navbar">
14
+ %%SUCURI.Navbar%%
15
+ </h2>
16
+
17
  <div class="sucuriscan-maincontent sucuriscan-clearfix">
18
 
19
  <div class="sucuriscan-leftside sucuriscan-%%SUCURI.PageStyleClass%%">
27
  <div class="sucuriscan-ad">
28
  <h2>Is your website infected with malware? Blacklisted by Google?</h2>
29
  <p>Don't know where to start? Get cleared today by <a href="http://sucuri.net/signup">Sucuri Security</a>!</p>
30
+ <p><a href="http://sucuri.net/tour" target="_blank" class="button-primary">Read more</a></p>
31
  </div>
32
 
33
  <div class="sucuriscan-ad">
34
  <h2>Preventive website security in the cloud!</h2>
35
+ <ul class="sucuriscan-list">
36
  <li>Web Application Firewall (WAF) Protection</li>
37
  <li>Virtual Website Patching</li>
38
  <li>Cloud Intrusion Prevention System (IPS)</li>
45
  </p>
46
  </div>
47
 
48
+ <iframe src="https://www.youtube-nocookie.com/embed/EVa9FY3nKuQ" height="250" class="sucuriscan-scanner-video" allowfullscreen></iframe>
49
 
50
  </div>
51
 
52
  </div>
53
 
54
  <div class="sucuriscan-footer sucuriscan-clearfix">
55
+ <a href="http://sucuri.net/signup" target="_blank" title="Sucuri Security" class="sucuriscan-logo">
56
  <img src="%%SUCURI.SucuriURL%%/inc/images/logo.png" alt="Sucuri Security" />
57
  </a>
58
  <div class="sucuriscan-help">
inc/tpl/infosys-cronjobs.snippet.tpl CHANGED
@@ -1,3 +1,4 @@
 
1
  <tr class="%%SUCURI.Cronjob.CssClass%%">
2
  <td>%%SUCURI.Cronjob.Task%%</td>
3
  <td>%%SUCURI.Cronjob.Schedule%%</td>
1
+
2
  <tr class="%%SUCURI.Cronjob.CssClass%%">
3
  <td>%%SUCURI.Cronjob.Task%%</td>
4
  <td>%%SUCURI.Cronjob.Schedule%%</td>
inc/tpl/infosys-htaccess.html.tpl CHANGED
@@ -1,7 +1,8 @@
1
 
2
- <div id="poststuff" class="sucuri-infosys-htaccess">
3
  <div class="postbox">
4
  <h3>HTAccess Integrity</h3>
 
5
  <div class="inside">
6
  <p>
7
  The <code>.htaccess</code> is a distributed configuration file, and is how Apache handles
@@ -10,7 +11,7 @@
10
  modifies this file to be able to handle pretty permalinks.
11
  </p>
12
 
13
- <div class="sucuri-alert-%%SUCURI.HTAccess.MessageType%% %%SUCURI.HTAccess.MessageVisible%%">
14
  <p>%%SUCURI.HTAccess.Message%%</p>
15
  </div>
16
 
1
 
2
+ <div id="poststuff" class="sucuriscan-infosys-htaccess">
3
  <div class="postbox">
4
  <h3>HTAccess Integrity</h3>
5
+
6
  <div class="inside">
7
  <p>
8
  The <code>.htaccess</code> is a distributed configuration file, and is how Apache handles
11
  modifies this file to be able to handle pretty permalinks.
12
  </p>
13
 
14
+ <div class="sucuriscan-alert-%%SUCURI.HTAccess.MessageType%% %%SUCURI.HTAccess.MessageVisible%%">
15
  <p>%%SUCURI.HTAccess.Message%%</p>
16
  </div>
17
 
inc/tpl/infosys-loggedin.html.tpl CHANGED
@@ -1,5 +1,5 @@
1
 
2
- <table class="wp-list-table widefat sucuri-loggedin-users">
3
  <thead>
4
  <tr>
5
  <th colspan="6">Logged in Users (%%SUCURI.LoggedInUsers.Total%% users)</th>
@@ -13,6 +13,7 @@
13
  <th>&nbsp;</th>
14
  </tr>
15
  </thead>
 
16
  <tbody>
17
  %%SUCURI.LoggedInUsers.List%%
18
  </tbody>
1
 
2
+ <table class="wp-list-table widefat sucuriscan-loggedin-users">
3
  <thead>
4
  <tr>
5
  <th colspan="6">Logged in Users (%%SUCURI.LoggedInUsers.Total%% users)</th>
13
  <th>&nbsp;</th>
14
  </tr>
15
  </thead>
16
+
17
  <tbody>
18
  %%SUCURI.LoggedInUsers.List%%
19
  </tbody>
inc/tpl/infosys-serverinfo.html.tpl CHANGED
@@ -1,5 +1,5 @@
1
 
2
- <table class="wp-list-table widefat sucuri-%%SUCURI.SettingsDisplay%%">
3
  <tbody>
4
  <tr class="alternate">
5
  <td>Sucuri Plugin version</td>
1
 
2
+ <table class="wp-list-table widefat sucuriscan-server-info">
3
  <tbody>
4
  <tr class="alternate">
5
  <td>Sucuri Plugin version</td>
inc/tpl/infosys-wpconfig.html.tpl CHANGED
@@ -1,5 +1,5 @@
1
 
2
- <table class="wp-list-table widefat sucuri-wpconfig-rules">
3
  <thead>
4
  <th colspan="7" class="thead-with-button">
5
  <span>WP-Config Variables</span>
@@ -12,6 +12,7 @@
12
  <th>Value</th>
13
  </tr>
14
  </thead>
 
15
  <tbody>
16
  %%SUCURI.WordpressConfig.Rules%%
17
  </tbody>
1
 
2
+ <table class="wp-list-table widefat sucuriscan-wpconfig-rules">
3
  <thead>
4
  <th colspan="7" class="thead-with-button">
5
  <span>WP-Config Variables</span>
12
  <th>Value</th>
13
  </tr>
14
  </thead>
15
+
16
  <tbody>
17
  %%SUCURI.WordpressConfig.Rules%%
18
  </tbody>
inc/tpl/initial-page.html.tpl DELETED
@@ -1,97 +0,0 @@
1
-
2
- <div id="poststuff">
3
-
4
- <div class="postbox">
5
- <h3>Sucuri SiteCheck</h3>
6
- <div class="inside sucuriscan-clearfix">
7
- <div class="sucuriscan-column-left">
8
- <p>
9
- <a href="http://sitecheck.sucuri.net/" target="_blank">Sucuri SiteCheck</a>
10
- scanner will check your website for known malware, blacklisting status, website
11
- errors, and out-of-date software.
12
- </p>
13
- </div>
14
- <div class="sucuriscan-column-right">
15
- <form method="post">
16
- <input type="hidden" name="wpsucuri-doscan" value="wpsucuri-doscan" />
17
- <input type="submit" name="wpsucuri_doscanrun" value="Scan this site now" class="button button-primary button-hero" />
18
- </form>
19
- </div>
20
- </div>
21
- <div class="sucuriscan-disclaimer">
22
- <p>
23
- <strong>Disclaimer</strong>: Sucuri SiteCheck is a free and remote scanner.
24
- Although we do our best to provide the best results, 100% accuracy is not
25
- realistic, and not guaranteed.
26
- </p>
27
- </div>
28
- </div>
29
-
30
-
31
- <div class="postbox">
32
- <h3>1-Click Hardening</h3>
33
- <div class="inside sucuriscan-clearfix">
34
- <div class="sucuriscan-column-left">
35
- <p>
36
- In our experience a high-percentage of the infections we see every day come from
37
- poor management on the end-user's part. This feature uses common hardening
38
- measures that can be taken at any time and helps reduce infection risk.
39
- </p>
40
- </div>
41
- <div class="sucuriscan-column-right">
42
- <a href="%%SUCURI.URL.Hardening%%" class="button button-primary button-hero">Harden this site now</a>
43
- </div>
44
- </div>
45
- </div>
46
-
47
-
48
- <div class="postbox">
49
- <h3>WordPress Integrity</h3>
50
- <div class="inside sucuriscan-clearfix">
51
- <div class="sucuriscan-column-left">
52
- <p>
53
- This feature compares your core install against a clean version of core. In
54
- other words, if it is not a 1-to-1 match with core you will be notified of a
55
- problem.
56
- </p>
57
- </div>
58
- <div class="sucuriscan-column-right">
59
- <a href="%%SUCURI.URL.CoreIntegrity%%" class="button button-primary button-hero">Check site integrity now</a>
60
- </div>
61
- </div>
62
- </div>
63
-
64
-
65
- <div class="postbox">
66
- <h3>Post-Hack</h3>
67
- <div class="inside sucuriscan-clearfix">
68
- <div class="sucuriscan-column-left">
69
- <p>
70
- After being hacked or infected with malware, we recommend that you update your
71
- wp-config keys, and also reset all your user passwords. Do it with ease using
72
- Sucuri Post-Hack.
73
- </p>
74
- </div>
75
- <div class="sucuriscan-column-right">
76
- <a href="%%SUCURI.URL.PostHack%%" class="button button-primary button-hero">Run Post-Hack resets</a>
77
- </div>
78
- </div>
79
- </div>
80
-
81
-
82
- <div class="postbox">
83
- <h3>Last Logins</h3>
84
- <div class="inside sucuriscan-clearfix">
85
- <div class="sucuriscan-column-left">
86
- <p>
87
- It's always good to know who is logging into your site. This feature allows you
88
- to view logins, where they came from, and when they logged in.
89
- </p>
90
- </div>
91
- <div class="sucuriscan-column-right">
92
- <a href="%%SUCURI.URL.LastLogins%%" class="button button-primary button-hero">View Last Logins</a>
93
- </div>
94
- </div>
95
- </div>
96
-
97
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tpl/integrity-admins-lastlogin.snippet.tpl DELETED
@@ -1,4 +0,0 @@
1
- <tr>
2
- <td>%%SUCURI.AdminUsers.RemoteAddr%%</td>
3
- <td>%%SUCURI.AdminUsers.Datetime%%</td>
4
- </tr>
 
 
 
 
inc/tpl/integrity-auditlogs.html.tpl ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-auditlogs">
3
+ <thead>
4
+ <tr>
5
+ <th colspan="2" class="thead-with-button">
6
+ <span>Audit Logs (%%SUCURI.AuditLogs.Count%% logs)</span>
7
+ <form action="%%SUCURI.URL.Home%%" method="post" class="thead-topright-action">
8
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
9
+ <button type="submit" name="sucuriscan_force_scan" class="button-primary">Force Scan</button>
10
+ </form>
11
+ </th>
12
+ </tr>
13
+ <tr>
14
+ <th width="150">Date &amp; Time</th>
15
+ <th>Event &amp; Message</th>
16
+ </tr>
17
+ </thead>
18
+
19
+ <tbody>
20
+ %%SUCURI.AuditLogs.List%%
21
+
22
+ <tr class="sucuriscan-%%SUCURI.AuditLogs.MaxItemsVisibility%%">
23
+ <td colspan="2">
24
+ <div class="sucuriscan-maxper-page">
25
+ Showing <b>%%SUCURI.AuditLogs.MaxPerPage%%</b> out of <b>%%SUCURI.AuditLogs.Count%%</b>
26
+ &nbsp;-&nbsp;
27
+ <a href="%%SUCURI.URL.Core_integrity%%&show_all=1">Show all</a>
28
+ </div>
29
+ </td>
30
+ </tr>
31
+
32
+ <tr class="sucuriscan-%%SUCURI.AuditLogs.NoItemsVisibility%%">
33
+ <td colspan="2">
34
+ <em>No logs so far.</em>
35
+ </td>
36
+ </tr>
37
+ </tbody>
38
+ </table>
inc/tpl/integrity-auditlogs.snippet.tpl ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.AuditLog.CssClass%%">
3
+ <td>%%SUCURI.AuditLog.DateTime%%</td>
4
+ <td>
5
+ <span class="sucuriscan-monospace">%%SUCURI.AuditLog.Message%%</span>
6
+
7
+ %%SUCURI.AuditLog.Extra%%
8
+ </td>
9
+ </tr>
inc/tpl/integrity-corefiles.html.tpl ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div class="postbox sucuriscan-border sucuriscan-border-good sucuriscan-integrity-message sucuriscan-%%SUCURI.CoreFiles.GoodVisibility%%">
3
+ <span class="sucuriscan-integrity-mark">OK</span>
4
+ <h3>WordPress core integrity</h3>
5
+
6
+ <div class="inside">
7
+ <p>Your WordPress core files are clean and were not modified.</p>
8
+ </div>
9
+ </div>
10
+
11
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-corefiles sucuriscan-%%SUCURI.CoreFiles.BadVisibility%%">
12
+ <thead>
13
+ <tr>
14
+ <th class="sucuriscan-clearfix thead-with-button">
15
+ <span>WordPress core integrity (%%SUCURI.CoreFiles.ListCount%% files)</span>
16
+ <div class="sucuriscan-pull-right sucuriscan-corefiles-abbrs">
17
+ <span class="sucuriscan-status-type sucuriscan-status-added">Added</span>
18
+ <span class="sucuriscan-status-type sucuriscan-status-modified">Modified</span>
19
+ <span class="sucuriscan-status-type sucuriscan-status-removed">Removed</span>
20
+ <button id="sucuriscan-corefiles-show" class="button button-primary thead-topright-action" data-action="show">Show files</button>
21
+ </div>
22
+ </th>
23
+ </tr>
24
+
25
+ <tr>
26
+ <td class="sucuriscan-corefiles-warning">
27
+ <div>
28
+ <p>
29
+ We detected changes in the integrity of your WordPress core files. There are files that
30
+ were added, modified, and/or removed in the core directories <code>/&lt;root&gt;</code>,
31
+ <code>/wp-admin</code> and/or <code>/wp-includes</code>.
32
+ </p>
33
+ </div>
34
+ </td>
35
+ </tr>
36
+ </thead>
37
+
38
+ <tbody>
39
+
40
+ %%SUCURI.CoreFiles.List%%
41
+ </tbody>
42
+ </table>
inc/tpl/integrity-corefiles.snippet.tpl ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.CoreFiles.CssClass%% sucuriscan-hidden">
3
+ <td>
4
+ <div class="sucuriscan-clearfix">
5
+ <div class="sucuriscan-pull-left sucuriscan-status-type sucuriscan-status-%%SUCURI.CoreFiles.StatusType%%">%%SUCURI.CoreFiles.StatusAbbr%%</div>
6
+ <div class="sucuriscan-pull-left sucuriscan-monospace">%%SUCURI.CoreFiles.FilePath%%</div>
7
+ </div>
8
+ </td>
9
+ </tr>
inc/tpl/integrity-modifiedfiles.html.tpl ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-modifiedfiles">
3
+ <thead>
4
+ <tr>
5
+ <th colspan="3" class="thead-with-button">
6
+ <span>Modified files <em>(inside the content directory)</em></span>
7
+
8
+ <form action="%%SUCURI.CurrentURL%%#modified-files" method="post" class="thead-topright-action">
9
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
10
+ <label>
11
+ Modified in the last
12
+ <select name="sucuriscan_last_days" id="sucuriscan_last_days">
13
+ %%SUCURI.ModifiedFiles.SelectOptions%%
14
+ </select>
15
+ days
16
+ </label>
17
+
18
+ <!-- This field was added to give backward compatibility with the SiteCheck form. -->
19
+ <input type="hidden" name="sucuriscan_malware_scan" value="1" />
20
+ </form>
21
+ </th>
22
+ </tr>
23
+
24
+ <tr>
25
+ <th width="540">Filepath</th>
26
+ <th>CheckSum</th>
27
+ <th width="160">Modification</th>
28
+ </tr>
29
+ </thead>
30
+
31
+ <tbody>
32
+ %%SUCURI.ModifiedFiles.List%%
33
+
34
+ <tr class="sucuriscan-%%SUCURI.ModifiedFiles.NoFilesVisibility%%">
35
+ <td colspan="3">
36
+ <em>No files modified in the last %%SUCURI.ModifiedFiles.Days%% days</em>
37
+ </td>
38
+ </tr>
39
+ </tbody>
40
+ </table>
inc/tpl/integrity-modifiedfiles.snippet.tpl ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+
2
+ <tr class="sucuriscan-wraptext %%SUCURI.ModifiedFiles.CssClass%%">
3
+ <td><span class="sucuriscan-monospace">%%SUCURI.ModifiedFiles.FilePath%%</span></td>
4
+ <td><span class="sucuriscan-monospace sucuriscan-ellipsis" title="%%SUCURI.ModifiedFiles.CheckSum%%">%%SUCURI.ModifiedFiles.CheckSum%%</span></td>
5
+ <td>%%SUCURI.ModifiedFiles.DateTime%%</td>
6
+ </tr>
inc/tpl/integrity-wpoutdate.html.tpl ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div class="postbox sucuriscan-wordpress-outdated sucuriscan-border sucuriscan-border-bad sucuriscan-%%SUCURI.WordPress.UpdateVisibility%%">
3
+ <h3>WordPress version outdated</h3>
4
+
5
+ <div class="inside">
6
+ <p>
7
+ The current version of your site was detected as
8
+ <code>%%SUCURI.WordPress.Version%%</code> which is different to the official
9
+ latest version. The integrity check can not run using this version number
10
+ <a href="%%SUCURI.WordPress.UpgradeURL%%" target="_blank">update now</a> to
11
+ be able to run the integrity check.
12
+ </p>
13
+ </div>
14
+ </div>
inc/tpl/integrity.html.tpl ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div id="poststuff">
3
+
4
+ %%SUCURI.WordpressVersion%%
5
+
6
+ %%SUCURI.CoreFiles%%
7
+
8
+ %%SUCURI.AuditLogs%%
9
+
10
+ </div>
inc/tpl/lastlogins-admins-lastlogin.snippet.tpl ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.AdminUsers.CssClass%%">
3
+ <td><span class="sucuriscan-monospace">%%SUCURI.AdminUsers.RemoteAddr%%</span></td>
4
+ <td><span class="sucuriscan-monospace">%%SUCURI.AdminUsers.Datetime%%</span></td>
5
+ </tr>
inc/tpl/{integrity-admins.html.tpl → lastlogins-admins.html.tpl} RENAMED
@@ -1,8 +1,6 @@
1
- <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-doubletitle sucuriscan-adminusers">
 
2
  <thead>
3
- <tr>
4
- <th colspan="4">Administrator Users</th>
5
- </tr>
6
  <tr>
7
  <th class="manage-column">Username</th>
8
  <th class="manage-column">Email</th>
@@ -12,6 +10,6 @@
12
  </thead>
13
 
14
  <tbody>
15
- %%SUCURI.AdminUsers.UserList%%
16
  </tbody>
17
  </table>
1
+
2
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-adminusers">
3
  <thead>
 
 
 
4
  <tr>
5
  <th class="manage-column">Username</th>
6
  <th class="manage-column">Email</th>
10
  </thead>
11
 
12
  <tbody>
13
+ %%SUCURI.AdminUsers.List%%
14
  </tbody>
15
  </table>
inc/tpl/{integrity-admins.snippet.tpl → lastlogins-admins.snippet.tpl} RENAMED
@@ -1,12 +1,13 @@
 
1
  <tr>
2
  <td>%%SUCURI.AdminUsers.Username%%</td>
3
  <td><a href="mailto:%%SUCURI.AdminUsers.Email%%">%%SUCURI.AdminUsers.Email%%</a></td>
4
  <td class="adminusers-lastlogin">
5
- <div class="sucuri-%%SUCURI.AdminUsers.NoLastLogins%%">
6
  <i>There isn't information available for this account.</i>
7
  </div>
8
 
9
- <table class="widefat sucuri-%%SUCURI.AdminUsers.NoLastLoginsTable%%">
10
  <thead>
11
  <tr>
12
  <th>IP Address</th>
1
+
2
  <tr>
3
  <td>%%SUCURI.AdminUsers.Username%%</td>
4
  <td><a href="mailto:%%SUCURI.AdminUsers.Email%%">%%SUCURI.AdminUsers.Email%%</a></td>
5
  <td class="adminusers-lastlogin">
6
+ <div class="sucuriscan-%%SUCURI.AdminUsers.NoLastLogins%%">
7
  <i>There isn't information available for this account.</i>
8
  </div>
9
 
10
+ <table class="widefat sucuriscan-%%SUCURI.AdminUsers.NoLastLoginsTable%%">
11
  <thead>
12
  <tr>
13
  <th>IP Address</th>
inc/tpl/lastlogins-all.html.tpl ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-last-logins">
3
+ <thead>
4
+ <tr>
5
+ <th colspan="6" class="thead-with-button">
6
+ <span>User logins (latest %%SUCURI.UserListLimit%%, newest to oldest)</span>
7
+ <a href="%%SUCURI.CurrentURL%%&limit=0" class="button button-primary lastlogins-showall thead-topright-action sucuriscan-%%SUCURI.UserList.ShowAll%%">Show all results</a>
8
+ </th>
9
+ </tr>
10
+ <tr>
11
+ <th class="manage-column">No.</th>
12
+ <th class="manage-column">User</th>
13
+ <th class="manage-column">IP Address</th>
14
+ <th class="manage-column">Hostname</th>
15
+ <th class="manage-column">Date/Time</th>
16
+ <th class="manage-column">&nbsp;</th>
17
+ </tr>
18
+ </thead>
19
+
20
+ <tbody>
21
+ %%SUCURI.UserList%%
22
+ </tbody>
23
+ </table>
inc/tpl/{lastlogins.snippet.tpl → lastlogins-all.snippet.tpl} RENAMED
File without changes
inc/tpl/lastlogins.html.tpl CHANGED
@@ -1,23 +1,21 @@
1
 
2
- <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-doubletitle sucuriscan-last-logins">
3
- <thead>
4
- <tr>
5
- <th colspan="6" class="thead-with-button">
6
- <span>User logins (latest %%SUCURI.UserListLimit%%, newest to oldest)</span>
7
- <a href="%%SUCURI.CurrentURL%%&limit=0" class="button button-primary lastlogins-showall thead-topright-action sucuri-%%SUCURI.UserList.ShowAll%%">Show all results</a>
8
- </th>
9
- </tr>
10
- <tr>
11
- <th class="manage-column">No.</th>
12
- <th class="manage-column">User</th>
13
- <th class="manage-column">IP Address</th>
14
- <th class="manage-column">Hostname</th>
15
- <th class="manage-column">Date/Time</th>
16
- <th class="manage-column">&nbsp;</th>
17
- </tr>
18
- </thead>
19
 
20
- <tbody>
21
- %%SUCURI.UserList%%
22
- </tbody>
23
- </table>
 
 
 
 
 
 
1
 
2
+ <div class="sucuriscan-tabs">
3
+ <ul>
4
+ <li>
5
+ <a href="#" data-tabname="lastlogins-allusers">All Users</a>
6
+ </li>
7
+ <li>
8
+ <a href="#" data-tabname="lastlogins-admins">Admin Users</a>
9
+ </li>
10
+ </ul>
 
 
 
 
 
 
 
 
11
 
12
+ <div class="sucuriscan-tab-containers">
13
+ <div id="sucuriscan-lastlogins-allusers">
14
+ %%SUCURI.LastLogins.AllUsers%%
15
+ </div>
16
+
17
+ <div id="sucuriscan-lastlogins-admins">
18
+ %%SUCURI.LastLogins.Admins%%
19
+ </div>
20
+ </div>
21
+ </div>
inc/tpl/malwarescan.html.tpl ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div class="sucuriscan-loading">
3
+ <h3 class="title">Website Security Scans by Sucuri Sitecheck</h3>
4
+
5
+ <p class="description">Visit our <a href="http://sucuri.net/signup?fromloader" target="_blank">coverage &amp; pricing</a> page for details on how sucuri can help you.</p>
6
+
7
+ <p><img src="http://sitecheck.sucuri.net/images/loading.gif" alt="Loading..." /></p>
8
+
9
+ <div class="sucuriscan-sitelogo">&nbsp;</div>
10
+
11
+ <form method="post" name="sucuriscan_sitecheck_form">
12
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
13
+ <input type="hidden" name="sucuriscan_malware_scan" value="1" />
14
+ </form>
15
+
16
+ <script type="text/javascript">setTimeout(function(){ document.forms.sucuriscan_sitecheck_form.submit() }, 3000)</script>
17
+ </div>
inc/tpl/modalwindow.html.tpl ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div class="sucuriscan-overlay"></div>
3
+
4
+ <div class="sucuriscan-modal">
5
+ <div class="sucuriscan-modal-outside %%SUCURI.CssClass%%">
6
+
7
+ <div class="sucuriscan-modal-header">
8
+ <a href="#" class="sucuriscan-modal-close">&times;</a>
9
+ <h3 class="sucuriscan-modal-title">%%SUCURI.Title%%</h3>
10
+ </div>
11
+
12
+ <div class="sucuriscan-modal-inside">
13
+ %%SUCURI.Content%%
14
+ </div>
15
+
16
+ </div>
17
+ </div>
18
+
19
+ <script type="text/javascript">
20
+ jQuery(function($){
21
+ $('.sucuriscan-overlay, .sucuriscan-modal-close').on('click', function(e){
22
+ e.preventDefault();
23
+ $('.sucuriscan-overlay, .sucuriscan-modal').remove();
24
+ });
25
+ });
26
+ </script>
inc/tpl/monitoring-logs.html.tpl ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-quad-title sucuriscan-monitoring-logs">
3
+ <thead>
4
+ <tr>
5
+ <th colspan="4" class="thead-with-button">
6
+ <span>Search among the logs:</span>
7
+ <div class="thead-topright-action">
8
+ <form action="%%SUCURI.URL.Monitoring%%#monitoring-logs" method="post" class="sucuriscan-monitoring-search-form">
9
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
10
+ <input type="text" name="sucuriscan_monitoring_log_filter" class="input-text" />
11
+ <input type="submit" value="Search" class="button button-primary" />
12
+ </form>
13
+ </div>
14
+ </th>
15
+ </tr>
16
+
17
+ <tr>
18
+ <th colspan="4" class="thead-with-button">
19
+ <span>Filter by the denial type:</span>
20
+ <div class="thead-topright-action">
21
+ <form action="%%SUCURI.URL.Monitoring%%#monitoring-logs" method="post" class="sucuriscan-monitoring-denial-types-form">
22
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
23
+ <select name="sucuriscan_monitoring_denial_type">%%SUCURI.AuditLogs.DenialTypeOptions%%</select>
24
+ <input type="submit" value="Filter" class="button button-primary" />
25
+ </form>
26
+ </div>
27
+ </th>
28
+ </tr>
29
+
30
+ <tr>
31
+ <th colspan="4" class="thead-with-button">
32
+ <span>Filter by date:</span>
33
+ <div class="thead-topright-action">
34
+ <form action="%%SUCURI.URL.Monitoring%%#monitoring-logs" method="post" class="sucuriscan-monitoring-date-form">
35
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
36
+ <input type="hidden" name="sucuriscan_monitoring_date" value="1" />
37
+ <em class="sucuriscan-target-date">(%%SUCURI.AuditLogs.TargetDate%%)</em>
38
+ <select name="sucuriscan_year">%%SUCURI.AuditLogs.DateYears%%</select>
39
+ <select name="sucuriscan_month">%%SUCURI.AuditLogs.DateMonths%%</select>
40
+ <select name="sucuriscan_day">%%SUCURI.AuditLogs.DateDays%%</select>
41
+ <input type="submit" value="Filter" class="button button-primary" />
42
+ </form>
43
+ </div>
44
+ </th>
45
+ </tr>
46
+
47
+ <tr>
48
+ <th>Denial Type</th>
49
+ <th>Time</th>
50
+ <th>Remote Address</th>
51
+ <th>Request Path</th>
52
+ </tr>
53
+ </thead>
54
+
55
+ <tbody>
56
+ %%SUCURI.AuditLogs.List%%
57
+
58
+ <tr class="sucuriscan-%%SUCURI.AuditLogs.NoItemsVisibility%%">
59
+ <td colspan="4">
60
+ <em>Audit trails is empty.</em>
61
+ </td>
62
+ </tr>
63
+ </tbody>
64
+
65
+ <tfoot>
66
+ <tr class="sucuriscan-%%SUCURI.AuditLogs.PaginationVisibility%%">
67
+ <td colspan="4">
68
+ <div class='pagination' style="float:right;">
69
+ %%SUCURI.AuditLogs.AuditPagination%%
70
+ </div>
71
+ </td>
72
+ </tr>
73
+ </tfoot>
74
+ </table>
inc/tpl/monitoring-logs.snippet.tpl ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.AuditLog.CssClass%%">
3
+ <td>%%SUCURI.AuditLog.SucuriBlockReason%%</td>
4
+ <td>
5
+ <span class="sucuriscan-monospace" title="%%SUCURI.AuditLog.RequestDate%% %%SUCURI.AuditLog.RequestTime%% %%SUCURI.AuditLog.RequestTimezone%%">
6
+ %%SUCURI.AuditLog.RequestTime%% %%SUCURI.AuditLog.RequestTimezone%%
7
+ </span>
8
+ </td>
9
+ <td><span class="sucuriscan-monospace">%%SUCURI.AuditLog.RemoteAddr%%</span></td>
10
+ <td>
11
+ <div class="sucuriscan-wraptext">
12
+ <a href="#TB_inline?width=600&height=300&inlineId=sucuriscan-reqsummary-%%SUCURI.AuditLog.Id%%" title="Access Log Summary" class="thickbox">
13
+ <span class="sucuriscan-monospace">%%SUCURI.AuditLog.ResourcePath%%</span>
14
+ </a>
15
+ </div>
16
+
17
+ <div id="sucuriscan-reqsummary-%%SUCURI.AuditLog.Id%%" style="display:none">
18
+ <div class="sucuriscan-request-summary">
19
+ <ul class="sucuriscan-list-as-table">
20
+ <li>
21
+ <label>Blocked Reason:</label>
22
+ <span>%%SUCURI.AuditLog.SucuriBlockReason%%</span>
23
+ </li>
24
+ <li>
25
+ <label>Remote Address:</label>
26
+ <span>%%SUCURI.AuditLog.RemoteAddr%%</span>
27
+ </li>
28
+ <li>
29
+ <label>Date/Time (Timezone)</label>
30
+ <span>%%SUCURI.AuditLog.RequestDate%% %%SUCURI.AuditLog.RequestTime%% (%%SUCURI.AuditLog.RequestTimezone%%)</span>
31
+ </li>
32
+ <li>
33
+ <label>Resource Path:</label>
34
+ <span>%%SUCURI.AuditLog.ResourcePath%%</span>
35
+ </li>
36
+ <li>
37
+ <label>Request Method:</label>
38
+ <span>%%SUCURI.AuditLog.RequestMethod%%</span>
39
+ </li>
40
+ <li>
41
+ <label>HTTP Protocol:</label>
42
+ <span>%%SUCURI.AuditLog.HttpProtocol%%</span>
43
+ </li>
44
+ <li>
45
+ <label>HTTP Status:</label>
46
+ <span>%%SUCURI.AuditLog.HttpStatus%% %%SUCURI.AuditLog.HttpStatusTitle%%</span>
47
+ </li>
48
+ <li>
49
+ <label>HTTP Bytes Sent:</label>
50
+ <span>%%SUCURI.AuditLog.HttpBytesSent%%</span>
51
+ </li>
52
+ <li>
53
+ <label>HTTP Referer:</label>
54
+ <span>%%SUCURI.AuditLog.HttpReferer%%</span>
55
+ </li>
56
+ <li>
57
+ <label>HTTP User Agent:</label>
58
+ <span>%%SUCURI.AuditLog.HttpUserAgent%%</span>
59
+ </li>
60
+ </ul>
61
+ </div>
62
+ </div>
63
+ </td>
64
+ </tr>
inc/tpl/monitoring-settings.html.tpl ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-monitoring-settings">
3
+ <tbody>
4
+ <tr>
5
+ <td width="200"><label>CloudProxy API key</label></td>
6
+ <td class="td-with-button">
7
+ <form method="post" class="sucuriscan-monitoring-apikey-form">
8
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
9
+ <input type="text" name="sucuriscan_cloudproxy_apikey" value="%%SUCURI.Monitoring.APIKey%%" class="input-text" />
10
+ <input type="submit" value="Save" class="button button-primary" />
11
+ </form>
12
+ </td>
13
+ </tr>
14
+
15
+ %%SUCURI.Monitoring.SettingOptions%%
16
+
17
+ <tr class="alternate sucuriscan-%%SUCURI.Monitoring.SettingsVisibility%%">
18
+ <td><label>Clear cache</label></td>
19
+ <td class="td-with-button">
20
+ <form method="post" class="sucuriscan-monitoring-clear-cache-form">
21
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
22
+ <input type="hidden" name="sucuriscan_clear_cache" value="1" />
23
+ <input type="submit" value="Clear Cache" class="button button-primary" />
24
+ </form>
25
+ </td>
26
+ </tr>
27
+ </tbody>
28
+ </table>
inc/tpl/monitoring-settings.snippet.tpl ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.Monitoring.OptionCssClass%%">
3
+ <td><label>%%SUCURI.Monitoring.OptionName%%</label></td>
4
+ <td><span class="sucuriscan-monospace">%%SUCURI.Monitoring.OptionValue%%</span></td>
5
+ </tr>
inc/tpl/monitoring.html.tpl ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div id="poststuff">
3
+
4
+ <div class="postbox sucuriscan-monitoring-instructions sucuriscan-%%SUCURI.Monitoring.InstructionsVisibility%%">
5
+ <h3>Instructions to enable CloudProxy WAF</h3>
6
+
7
+ <div class="inside">
8
+ <p>
9
+ A powerful <b>WAF</b> <em>(Web Application Firewall)</em> and <b>Intrusion Prevention</b>
10
+ system for any WordPress user. If you do not have an account, you can sign up for one here:
11
+ <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri CloudProxy</a>
12
+ </p>
13
+
14
+ <ol>
15
+ <li>
16
+ Sign up for a Sucuri CloudProxy account here:
17
+ <a href="https://login.sucuri.net/signup2/create?CloudProxy" target="_blank">Sign up</a>
18
+ </li>
19
+ <li>
20
+ Change your DNS to point your site to one of our servers. This link explains
21
+ <a href="https://dashboard.sucuri.net/cloudproxy/" target="_blank"> CloudProxy Dashboard</a>
22
+ or use our documentation here <a href="http://kb.sucuri.net/cloudproxy" target="_blank">
23
+ KB CloudProxy</a>.
24
+ </li>
25
+ <li>You are all set. There is nothing else to do.</li>
26
+ </ol>
27
+
28
+ <p>
29
+ Once enabled, our firewall will act as a shield, protecting your site from attacks
30
+ and preventing malware infections and reinfections. It will block SQL injection attempts,
31
+ brute force attacks, XSS, RFI, backdoors and many other threats against your site.
32
+ </p>
33
+ </div>
34
+ </div>
35
+
36
+
37
+ <div class="sucuriscan-tabs">
38
+ <ul>
39
+ <li>
40
+ <a href="#" data-tabname="monitoring-settings">Firewall (WAF) Settings</a>
41
+ </li>
42
+ <li>
43
+ <a href="#" data-tabname="monitoring-logs">Firewall (WAF) Logs</a>
44
+ </li>
45
+ </ul>
46
+
47
+ <div class="sucuriscan-tab-containers">
48
+ <div id="sucuriscan-monitoring-settings">
49
+ %%SUCURI.Monitoring.Settings%%
50
+ </div>
51
+
52
+ <div id="sucuriscan-monitoring-logs">
53
+ %%SUCURI.Monitoring.Logs%%
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ </div>
inc/tpl/{notification.html.tpl → notification-pretty.html.tpl} RENAMED
@@ -4,7 +4,7 @@
4
  <title>%%SUCURI.TemplateTitle%%</title>
5
  </head>
6
  <body>
7
- <table class="sucuri-template" style="width:90%;font-family:Arial,Helvetica,sans-serif;border-spacing:0">
8
  <thead sytle="border-bottom:1px solid #ccc">
9
  <tr style="background-color:#4b4b4b;background-image:-moz-linear-gradient(top, #555555, #3b3b3b);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#3b3b3b));background-image:-webkit-linear-gradient(top, #555555, #3b3b3b);background-image:-o-linear-gradient(top, #555555, #3b3b3b);background-image:linear-gradient(to bottom, #555555, #3b3b3b);background-repeat:repeat-x">
10
  <td sytle="font-size:20px;font-weight:normal;color:#ffffff;padding:10px;border-right:1px solid #2f2f2f;border-left:1px solid #6f6f6f;-webkit-box-shadow:inset 0 1px 0 #888888;-moz-box-shadow:inset 0 1px 0 #888888;box-shadow:inset 0 1px 0 #888888;text-shadow:1px 1px 2px rgba(0, 0, 0, 0.5)">
@@ -20,13 +20,13 @@
20
  <td style="padding:20px 20px 10px 20px;border:1px solid #ccc;border-top:none">
21
  <h4 style="margin:0">Information:</h4>
22
  <p style="margin:0 0 10px 0">
23
- User: %%SUCURI.User%%<br />
24
- Alert Time: %%SUCURI.Time%%<br />
25
  </p>
26
  <h4 style="text-transform:uppercase;margin:0">Website Information:</h4>
27
  <p style="margin:0 0 10px 0">
28
- Site: <a href="%%SUCURI.Website%%">%%SUCURI.Website%%</a><br />
29
- IP Address: %%SUCURI.RemoteAddress%%<br />
30
  </p>
31
  <h4 style="text-transform:uppercase;margin:0">Notification Message:</h4>
32
  <p style="margin:0 0 10px 0">%%SUCURI.Message%%</p>
4
  <title>%%SUCURI.TemplateTitle%%</title>
5
  </head>
6
  <body>
7
+ <table class="sucuriscan-template" style="width:90%;background:#fff;font-family:Arial,Helvetica,sans-serif;border-spacing:0">
8
  <thead sytle="border-bottom:1px solid #ccc">
9
  <tr style="background-color:#4b4b4b;background-image:-moz-linear-gradient(top, #555555, #3b3b3b);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#3b3b3b));background-image:-webkit-linear-gradient(top, #555555, #3b3b3b);background-image:-o-linear-gradient(top, #555555, #3b3b3b);background-image:linear-gradient(to bottom, #555555, #3b3b3b);background-repeat:repeat-x">
10
  <td sytle="font-size:20px;font-weight:normal;color:#ffffff;padding:10px;border-right:1px solid #2f2f2f;border-left:1px solid #6f6f6f;-webkit-box-shadow:inset 0 1px 0 #888888;-moz-box-shadow:inset 0 1px 0 #888888;box-shadow:inset 0 1px 0 #888888;text-shadow:1px 1px 2px rgba(0, 0, 0, 0.5)">
20
  <td style="padding:20px 20px 10px 20px;border:1px solid #ccc;border-top:none">
21
  <h4 style="margin:0">Information:</h4>
22
  <p style="margin:0 0 10px 0">
23
+ Alert Time: %%SUCURI.Time%%<br>
24
+ %%SUCURI.User%%
25
  </p>
26
  <h4 style="text-transform:uppercase;margin:0">Website Information:</h4>
27
  <p style="margin:0 0 10px 0">
28
+ Site: <a href="%%SUCURI.Website%%">%%SUCURI.Website%%</a><br>
29
+ IP Address: %%SUCURI.RemoteAddress%%<br>
30
  </p>
31
  <h4 style="text-transform:uppercase;margin:0">Notification Message:</h4>
32
  <p style="margin:0 0 10px 0">%%SUCURI.Message%%</p>
inc/tpl/{notification.txt.tpl → notification-simple.html.tpl} RENAMED
@@ -1,8 +1,8 @@
1
  Subject: %%SUCURI.Subject%%
2
 
3
  Login Info:
4
- Username: %%SUCURI.User%%
5
  Time: %%SUCURI.Time%%
 
6
 
7
  Website Info:
8
  Site: %%SUCURI.Website%%
1
  Subject: %%SUCURI.Subject%%
2
 
3
  Login Info:
 
4
  Time: %%SUCURI.Time%%
5
+ %%SUCURI.User%%
6
 
7
  Website Info:
8
  Site: %%SUCURI.Website%%
inc/tpl/posthack-databasebackups.html.tpl ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <form action="%%SUCURI.URL.Posthack%%#database-backups" method="post">
3
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
4
+ <input type="hidden" name="sucuriscan_database_backup" value="1" />
5
+ <input type="hidden" name="sucuriscan_process_form" value="1" />
6
+
7
+ <table class="wp-list-table widefat">
8
+ <thead>
9
+ <tr>
10
+ <th colspan="5" class="thead-with-button">
11
+ <span>Database Backups</span>
12
+ <div class="generate-dbbackup-form thead-topright-action">
13
+ <input type="submit" name="generate_dbbackup" value="Generate DB Backup" class="button button-primary" />
14
+ </div>
15
+ </th>
16
+ </tr>
17
+ <tr>
18
+ <th class="manage-column column-cb check-column">
19
+ <label class="screen-reader-text" for="cb-select-all-1">Select All</label>
20
+ <input id="cb-select-all-1" type="checkbox">
21
+ </th>
22
+ <th class="manage-column">Filename</th>
23
+ <th class="manage-column">Type</th>
24
+ <th class="manage-column">Size</th>
25
+ <th class="manage-column">Date/Time</th>
26
+ </tr>
27
+ </thead>
28
+
29
+ <tbody>
30
+ %%SUCURI.BackupList%%
31
+ </tbody>
32
+
33
+ <tfoot>
34
+ <tr>
35
+ <td colspan="5">
36
+ <input type="submit" name="remove_dbbackup" value="Remove selected files" class="button button-primary" />
37
+ </td>
38
+ </tr>
39
+ </tfoot>
40
+ </table>
41
+ </form>
inc/tpl/posthack-databasebackups.snippet.tpl ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.BackupList.CssClass%%">
3
+ <th class="check-column">
4
+ <input type="checkbox" name="dbbackup_filenames[]" value="%%SUCURI.BackupList.Filename%%" />
5
+ </th>
6
+ <td><a href="%%SUCURI.BackupList.FileURL%%" target="_blank">%%SUCURI.BackupList.Filename%%</a></td>
7
+ <td>%%SUCURI.BackupList.Filetype%%</td>
8
+ <td>%%SUCURI.BackupList.Filesize%%</td>
9
+ <td>%%SUCURI.BackupList.Filetime%%</td>
10
+ </tr>
inc/tpl/posthack-resetpassword.html.tpl ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div id="poststuff" class="sucuriscan-reset-users-password">
3
+ <div class="postbox">
4
+ <div class="inside">
5
+ <form method="post">
6
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
7
+ <input type="hidden" name="sucuriscan_reset_password" value="1" />
8
+
9
+ <p>
10
+ Use this button to reset the current password for some specific users or for all
11
+ of them. We will send an email to each of those users adivising the password change
12
+ that includes the new password automatically generated by WordPress. After the
13
+ password reset your current session will be closed and you'll need to login again.
14
+ </p>
15
+
16
+ <table class="wp-list-table widefat sucuriscan-table">
17
+ <thead>
18
+ <tr>
19
+ <th class="manage-column column-cb check-column">
20
+ <label class="screen-reader-text" for="cb-select-all-1">Select All</label>
21
+ <input id="cb-select-all-1" type="checkbox">
22
+ </th>
23
+ <th class="manage-column">User</th>
24
+ <th class="manage-column">Email address</th>
25
+ <th class="manage-column">Registered</th>
26
+ <th class="manage-column">Roles</th>
27
+ </tr>
28
+ </thead>
29
+
30
+ <tbody>
31
+ %%SUCURI.ResetPassword.UserList%%
32
+ </tbody>
33
+ </table>
34
+
35
+ <p>
36
+ <label>
37
+ <input type="hidden" name="sucuriscan_process_form" value="0" />
38
+ <input type="checkbox" name="sucuriscan_process_form" value="1" />
39
+ <span>I understand that this operation can not be reverted.</span>
40
+ </label>
41
+ </p>
42
+
43
+ <input type="submit" value="Reset User Password" class="button button-primary" />
44
+ </form>
45
+ </div>
46
+ </div>
47
+ </div>
inc/tpl/posthack-updatesecretkeys.html.tpl ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div id="poststuff" class="sucuriscan-update-secret-keys">
3
+ <div class="postbox">
4
+ <div class="inside">
5
+ <form method="post">
6
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
7
+ <input type="hidden" name="sucuriscan_update_wpconfig" value="1" />
8
+
9
+ <p>
10
+ Use this button to update the security keys stored in the <code>wp-config.php</code>
11
+ file, we will use the official WordPress Secret-Key API Generator. After the
12
+ update your current session will be closed and you'll need to login again.
13
+ </p>
14
+
15
+ <p>
16
+ <label>
17
+ <input type="hidden" name="sucuriscan_process_form" value="0" />
18
+ <input type="checkbox" name="sucuriscan_process_form" value="1" />
19
+ <span>I understand that this operation can not be reverted.</span>
20
+ </label>
21
+ </p>
22
+
23
+ <input type="submit" value="Update WP-Config Keys" class="button button-primary" />
24
+ </form>
25
+
26
+ <div class="sucuriscan_wpconfig_keys_updated sucuriscan-%%SUCURI.WPConfigUpdate.Visibility%%">
27
+ <textarea>%%SUCURI.WPConfigUpdate.NewConfig%%</textarea>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
inc/tpl/posthack.html.tpl CHANGED
@@ -1,78 +1,29 @@
1
 
2
- <div id="poststuff">
3
- <div class="postbox">
4
- <h3>Update WP-Config Keys</h3>
5
- <div class="inside">
6
- <form method="post">
7
- <input type="hidden" name="sucuri_posthack_nonce" value="%%SUCURI.PosthackNonce%%" />
8
- <input type="hidden" name="sucuri_posthack_action" value="update_wpconfig" />
9
 
10
- <p>
11
- Use this button to update the security keys stored in the <code>wp-config.php</code>
12
- file, we will use the official WordPress Secret-Key API Generator. After the
13
- update your current session will be closed and you'll need to login again.
14
- </p>
15
-
16
- <p>
17
- <label>
18
- <input type="hidden" name="sucuri_update_wpconfig" value="0" />
19
- <input type="checkbox" name="sucuri_update_wpconfig" value="1" />
20
- <span>I understand that this operation can not be reverted.</span>
21
- </label>
22
- </p>
23
-
24
- <input type="submit" value="Update WP-Config Keys" class="button button-primary" />
25
- </form>
26
-
27
- <div style="%%SUCURI.WPConfigUpdate.Display%%" class="sucuriscan_wpconfig_keys_updated">
28
- <textarea>%%SUCURI.WPConfigUpdate.NewConfig%%</textarea>
29
- </div>
30
  </div>
31
- </div>
32
-
33
- <div class="postbox">
34
- <h3>Reset user password</h3>
35
- <div class="inside">
36
- <form method="post">
37
- <input type="hidden" name="sucuri_posthack_nonce" value="%%SUCURI.PosthackNonce%%" />
38
- <input type="hidden" name="sucuri_posthack_action" value="reset_password" />
39
 
40
- <p>
41
- Use this button to reset the current password for some specific users or for all
42
- of them. We will send an email to each of those users adivising the password change
43
- that includes the new password automatically generated by WordPress. After the
44
- password reset your current session will be closed and you'll need to login again.
45
- </p>
46
-
47
- <table class="wp-list-table widefat sucuriscan-table">
48
- <thead>
49
- <tr>
50
- <th class="manage-column column-cb check-column">
51
- <label class="screen-reader-text" for="cb-select-all-1">Select All</label>
52
- <input id="cb-select-all-1" type="checkbox">
53
- </th>
54
- <th class="manage-column">User</th>
55
- <th class="manage-column">Email address</th>
56
- <th class="manage-column">Registered</th>
57
- <th class="manage-column">Roles</th>
58
- </tr>
59
- </thead>
60
-
61
- <tbody>
62
- %%SUCURI.ResetPassword.UserList%%
63
- </tbody>
64
- </table>
65
-
66
- <p>
67
- <label>
68
- <input type="hidden" name="sucuri_reset_password" value="0" />
69
- <input type="checkbox" name="sucuri_reset_password" value="1" />
70
- <span>I understand that this operation can not be reverted.</span>
71
- </label>
72
- </p>
73
 
74
- <input type="submit" value="Reset User Password" class="button button-primary" />
75
- </form>
76
  </div>
77
  </div>
78
- </div><!-- End poststuff -->
1
 
 
 
 
 
 
 
 
2
 
3
+ <div class="sucuriscan-tabs">
4
+ <ul>
5
+ <li>
6
+ <a href="#" data-tabname="update-secret-keys">Update WordPress Keys</a>
7
+ </li>
8
+ <li>
9
+ <a href="#" data-tabname="reset-users-password">Reset User's Password</a>
10
+ </li>
11
+ <li>
12
+ <a href="#" data-tabname="database-backups">Database Backups</a>
13
+ </li>
14
+ </ul>
15
+
16
+ <div class="sucuriscan-tab-containers">
17
+ <div id="sucuriscan-update-secret-keys">
18
+ %%SUCURI.UpdateSecretKeys%%
 
 
 
 
19
  </div>
 
 
 
 
 
 
 
 
20
 
21
+ <div id="sucuriscan-reset-users-password">
22
+ %%SUCURI.ResetPassword%%
23
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ <div id="sucuriscan-database-backups">
26
+ %%SUCURI.DatabaseBackups%%
27
  </div>
28
  </div>
29
+ </div>
inc/tpl/settings-apiregistered.html.tpl ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div class="sucuriscan-clearfix">
3
+ <div class="sucuriscan-pull-left sucuriscan-sitelogo">&nbsp;</div>
4
+
5
+ <div class="sucuriscan-pull-right">
6
+ <p>
7
+ Thanks so much for enabling your <strong>Sucuri Security</strong> plugin. This
8
+ product is designed to supplement existing security products. It's not a silver
9
+ bullet for your security needs, but it'll give you greater security awareness
10
+ and better posture, all with the intent of reducing risk.
11
+ </p>
12
+
13
+ <a href="%%SUCURI.URL.Home%%" class="button button-primary">Go to your Dashboard</a>
14
+ </div>
15
+ </div>
16
+
inc/tpl/settings-notification.snippet.tpl ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <tr class="%%SUCURI.Notification.CssClass%%">
3
+ <td colspan="3">
4
+ <div>
5
+ <label>
6
+ <input type="hidden" name="%%SUCURI.Notification.Name%%" value="0" />
7
+ <input type="checkbox" name="%%SUCURI.Notification.Name%%" value="1" %%SUCURI.Notification.Checked%% />
8
+ <span>%%SUCURI.Notification.Label%%</span>
9
+ </label>
10
+ </div>
11
+ </td>
12
+ </tr>
inc/tpl/settings.html.tpl ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ %%SUCURI.ModalWhenAPIRegistered%%
3
+
4
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-settings">
5
+ <thead>
6
+ <tr>
7
+ <th colspan="3" class="thead-with-button">
8
+ <span>Plugin Settings</span>
9
+ <form action="%%SUCURI.URL.Settings%%" method="post" class="thead-topright-action">
10
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
11
+ <button type="submit" name="sucuriscan_reset_options" class="button-primary">Reset plugin options</button>
12
+ </form>
13
+ </th>
14
+ </tr>
15
+ </thead>
16
+
17
+ <tbody>
18
+
19
+ <tr>
20
+ <td colspan="3">
21
+ <p>
22
+ Most of the tools in this plugin can be used without a specific configuration,
23
+ but the core features <strong>require an API key</strong> to communicate with
24
+ the Sucuri services. The key is generated using your administrator e-mail and
25
+ the domain of this site, this will allow you to have access to our free
26
+ monitoring tool forever even if you remove the API key and generate it again.
27
+ </p>
28
+ </td>
29
+ </tr>
30
+
31
+ <tr class="alternate">
32
+ <td>API Key</td>
33
+ <td>
34
+ <span class="sucuriscan-monospace">%%SUCURI.APIKey%%</span>
35
+ </td>
36
+ <td class="td-with-button">
37
+ <form method="post" class="sucuriscan-%%SUCURI.APIKey.RecoverVisibility%%">
38
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
39
+ <button type="submit" name="sucuriscan_recover_api_key" class="button-primary">Recover</button>
40
+ </form>
41
+
42
+ <form method="post" class="sucuriscan-%%SUCURI.APIKey.ManualKeyFormVisibility%%">
43
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
44
+ <input type="text" name="sucuriscan_manual_api_key" class="input-text" placeholder="API key sent to your email" />
45
+ <button type="submit" class="button-primary">Save</button>
46
+ </form>
47
+
48
+ <form method="post" class="sucuriscan-%%SUCURI.APIKey.RemoveVisibility%%">
49
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
50
+ <button type="submit" name="sucuriscan_remove_api_key" class="button-primary button-danger">Remove</button>
51
+ </form>
52
+ </td>
53
+ </tr>
54
+
55
+ <tr>
56
+ <td>Last Scanning</td>
57
+ <td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
58
+ <td class="td-with-button">
59
+ <form action="%%SUCURI.URL.Home%%" method="post">
60
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
61
+ <button type="submit" name="sucuriscan_force_scan" class="button-primary">Force Scan</button>
62
+ </form>
63
+ </td>
64
+ </tr>
65
+
66
+ <tr class="alternate">
67
+ <td>Scanning frequency</td>
68
+ <td><span class="sucuriscan-monospace">%%SUCURI.ScanningFrequency%%</span></td>
69
+ <td class="td-with-button">
70
+ <form method="post">
71
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
72
+ <select name="sucuriscan_scan_frequency">
73
+ %%SUCURI.ScanningFrequencyOptions%%
74
+ </select>
75
+ <button type="submit" class="button-primary">Change</button>
76
+ </form>
77
+ </td>
78
+ </tr>
79
+
80
+ <tr class="sucuriscan-%%SUCURI.ScanningInterfaceVisibility%%">
81
+ <td>Scanning interface</td>
82
+ <td><span class="sucuriscan-monospace">%%SUCURI.ScanningInterface%%</span></td>
83
+ <td class="td-with-button">
84
+ <form method="post">
85
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
86
+ <select name="sucuriscan_scan_interface">
87
+ %%SUCURI.ScanningInterfaceOptions%%
88
+ </select>
89
+ <button type="submit" class="button-primary">Change</button>
90
+ </form>
91
+ </td>
92
+ </tr>
93
+
94
+ </tbody>
95
+ </table>
96
+
97
+
98
+ <form method="post">
99
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-settings-notifications">
100
+ <thead>
101
+ <tr>
102
+ <th colspan="3" class="thead-with-button">
103
+ <span>Email Alerts Settings</span>
104
+ <div class="thead-topright-action">
105
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
106
+ <button type="submit" name="sucuriscan_save_notification_settings" class="button-primary">Save</button>
107
+ </div>
108
+ </th>
109
+ </tr>
110
+ </thead>
111
+
112
+ <tbody>
113
+
114
+ %%SUCURI.NotificationOptions%%
115
+
116
+ </tbody>
117
+ </table>
118
+ </form>
inc/tpl/setup_notice.html.tpl ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div class="updated sucuriscan-setup-notice sucuriscan-clearfix">
3
+ <a href="http://sucuri.net/" target="_blank" class="sucuriscan-pull-left sucuriscan-setup-image">
4
+ <img src="%%SUCURI.SucuriURL%%/inc/images/logo.png" alt="Sucuri Scanner" />
5
+ </a>
6
+
7
+ <div class="sucuriscan-pull-left">
8
+ <p>
9
+ Plugin not fully activated yet. Please generate the free API<br>
10
+ to enable audit logging, integrity checking and email alerts.
11
+ </p>
12
+ </div>
13
+
14
+ <div class="sucuriscan-pull-right sucuriscan-setup-form">
15
+ <form action="%%SUCURI.URL.Settings%%" method="post">
16
+ <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
17
+ <button type="submit" name="sucuriscan_wordpress_apikey" class="button button-primary button-hero">
18
+ <span class="sucuriscan-button-title">Generate API key</span>
19
+ <span class="sucuriscan-button-subtitle">for <b>%%SUCURI.CleanDomain%%</b> / <b>%%SUCURI.AdminEmail%%</b></span>
20
+ </button>
21
+ </form>
22
+ </div>
23
+ </div>
readme.txt CHANGED
@@ -1,12 +1,12 @@
1
- === Sucuri Security - SiteCheck Malware Scanner ===
2
  Contributors: dd@sucuri.net
3
  Donate Link: http://sitecheck.sucuri.net
4
  Tags: malware, security, firewall, scan, spam, virus, sucuri, protection
5
  Requires at least:3.2
6
- Stable tag:1.6.0
7
  Tested up to: 3.9.1
8
 
9
- The Sucuri Security - SiteCheck Malware Scanner is a security plugin enables you to scan your WordPress site using Sucuri SiteCheck for security and malware issues, and also verifies the security integrity of your core files right in your dashboard. It also includes post-hack security ions to help you reset passwords and secret keys in case it has been already hacked, or infected with malware.
10
 
11
  == Description ==
12
 
@@ -66,6 +66,9 @@ the compromise on your site).
66
 
67
  == Changelog ==
68
 
 
 
 
69
  = 1.6.0 =
70
  * A new dashboard to welcome users to the new features of the plugin.
71
  * Overall design of the interface of all the pages were modified.
1
+ === Sucuri Security - Auditing, Malware Scanner and Hardening ===
2
  Contributors: dd@sucuri.net
3
  Donate Link: http://sitecheck.sucuri.net
4
  Tags: malware, security, firewall, scan, spam, virus, sucuri, protection
5
  Requires at least:3.2
6
+ Stable tag:1.6.1
7
  Tested up to: 3.9.1
8
 
9
+ The Sucuri Security - Auditing, SiteCheck Malware Scanner and Hardening is a security plugin enables you to scan your WordPress site using Sucuri SiteCheck for security and malware issues, and also verifies the security integrity of your core files right in your dashboard. It also includes post-hack security ions to help you reset passwords and secret keys in case it has been already hacked, or infected with malware.
10
 
11
  == Description ==
12
 
66
 
67
  == Changelog ==
68
 
69
+ = 1.6.1 =
70
+ * Initial release with new auditing options.
71
+
72
  = 1.6.0 =
73
  * A new dashboard to welcome users to the new features of the plugin.
74
  * Overall design of the interface of all the pages were modified.
sucuri.php CHANGED
@@ -1,13 +1,10 @@
1
  <?php
2
  /*
3
- Plugin Name: Sucuri Security - SiteCheck Malware Scanner
4
- Plugin URI: http://sitecheck.sucuri.net/
5
- Description: The <a href="http://sucuri.net">Sucuri Security</a> - SiteCheck Malware Scanner plugin enables you to <strong>scan your WordPress site using <a href="http://sitecheck.sucuri.net">Sucuri SiteCheck</a></strong> right in your WordPress dashboard. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
6
-
7
- You can also scan your site at <a href="http://sitecheck.sucuri.net">SiteCheck.Sucuri.net</a>.
8
-
9
  Author: Sucuri, INC
10
- Version: 1.6.0
11
  Author URI: http://sucuri.net
12
  */
13
 
@@ -39,7 +36,7 @@ define('SUCURISCAN','sucuriscan');
39
  /**
40
  * Current version of the plugin's code.
41
  */
42
- define('SUCURISCAN_VERSION','1.6.0');
43
 
44
  /**
45
  * The local URL where the plugin's files and assets are served.
@@ -67,1244 +64,5550 @@ define('SUCURISCAN_PLUGIN_PATH', WP_PLUGIN_DIR.'/'.SUCURISCAN_PLUGIN_FOLDER);
67
  define('SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE);
68
 
69
  /**
70
- * The maximum quantity of entries that will be displayed in the last login page.
71
  */
72
- define('SUCURISCAN_LASTLOGINS_USERSLIMIT', 50);
73
-
74
- if( !function_exists('sucuriscan_create_uploaddir') ){
75
- /**
76
- * Create a folder in the WordPress upload directory where the plugin will
77
- * store all the temporal or dynamic information.
78
- *
79
- * @return void
80
- */
81
- function sucuriscan_create_uploaddir(){
82
- $plugin_upload_folder = sucuriscan_dir_filepath();
83
- if( !file_exists($plugin_upload_folder) ){
84
- if( @mkdir($plugin_upload_folder) ){
85
- sucuriscan_lastlogins_datastore_exists();
86
- }else{
87
- sucuriscan_admin_notice('error', "<strong>Error.</strong> Sucuri data folder doesn't
88
- exists and couldn't be created. You'll need to create this folder manually and
89
- give it write permissions:<br><code>{$plugin_upload_folder}</code>");
90
- }
91
- }
92
- }
93
-
94
- add_action('admin_init', 'sucuriscan_create_uploaddir');
95
- }
96
 
97
  /**
98
- * Define which javascript and css files will be loaded in the header of the page.
99
- * @return void
100
  */
101
- function sucuriscan_admin_script_style_registration() {
102
- wp_register_style( 'sucuriscan', SUCURI_URL . '/inc/css/sucuriscan-default-css.css' );
103
- wp_register_script( 'sucuriscan', SUCURI_URL . '/inc/js/sucuriscan-scripts.js' );
104
-
105
- wp_enqueue_style( 'sucuriscan' );
106
- wp_enqueue_script( 'sucuriscan' );
107
- }
108
- add_action( 'admin_enqueue_scripts', 'sucuriscan_admin_script_style_registration', 1 );
109
 
110
  /**
111
- * Returns the system filepath to the relevant user uploads directory for this
112
- * site. This is a multisite capable function.
113
- *
114
- * @param string $path The relative path that needs to be completed to get the absolute path.
115
- * @return string The full filesystem path including the directory specified.
116
  */
117
- function sucuriscan_dir_filepath($path = '')
118
- {
119
- $wp_dir_array = wp_upload_dir();
120
- $wp_dir_array['basedir'] = untrailingslashit($wp_dir_array['basedir']);
121
- return($wp_dir_array['basedir']."/sucuri/$path");
122
- }
123
 
124
  /**
125
- * Generate the menu and submenus for the plugin in the admin interface.
126
- *
127
- * @return void
128
  */
129
- function sucuriscan_menu()
130
- {
131
- add_menu_page('Sucuri Free', 'Sucuri Free', 'manage_options',
132
- 'sucuriscan', 'sucuri_scan_page', SUCURI_URL.'/inc/images/menu-icon.png');
133
- add_submenu_page('sucuriscan', 'Sucuri Scanner', 'Sucuri Scanner', 'manage_options',
134
- 'sucuriscan', 'sucuri_scan_page');
135
-
136
- add_submenu_page('sucuriscan', '1-Click Hardening', '1-Click Hardening', 'manage_options',
137
- 'sucuriscan_hardening', 'sucuriscan_hardening_page');
138
-
139
- add_submenu_page('sucuriscan', 'WordPress Integrity', 'WordPress Integrity', 'manage_options',
140
- 'sucuriscan_core_integrity', 'sucuriscan_core_integrity_page');
141
 
142
- add_submenu_page('sucuriscan', 'Post-Hack', 'Post-Hack', 'manage_options',
143
- 'sucuriscan_posthack', 'sucuriscan_posthack_page');
144
-
145
- add_submenu_page('sucuriscan', 'Last Logins', 'Last Logins', 'manage_options',
146
- 'sucuriscan_lastlogins', 'sucuriscan_lastlogins_page');
147
-
148
- add_submenu_page('sucuriscan', 'Site Info', 'Site Info', 'manage_options',
149
- 'sucuriscan_infosys', 'sucuriscan_infosys_page');
150
-
151
- add_submenu_page('sucuriscan', 'About', 'About', 'manage_options',
152
- 'sucuriscan_about', 'sucuriscan_about_page');
153
- }
154
-
155
- add_action('admin_menu', 'sucuriscan_menu');
156
- remove_action('wp_head', 'wp_generator');
157
 
158
  /**
159
- * Print the HTML code for the header of each plugin's page.
160
- *
161
- * @param string $sucuri_title Title of the page that will be loaded.
162
- * @return void
163
  */
164
- function sucuriscan_pagestop($sucuri_title = 'Sucuri Plugin')
165
- {
166
- if(!current_user_can('manage_options'))
167
- {
168
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Header') );
169
- }
170
- ?>
171
- <h2><?php echo htmlspecialchars($sucuri_title); ?></h2>
172
- <br class="clear"/>
173
- <?php
174
- }
175
 
176
  /**
177
- * Send a message to a specific email address.
178
- *
179
- * @param string $to The email address of the recipient that will receive the message.
180
- * @param string $subject The reason of the message that will be sent.
181
- * @param string $message Body of the message that will be sent.
182
- * @param array $data_set Optional parameter to add more information to the notification.
183
- * @param boolean $debug TRUE if you want to test the function printing the email before sending it.
184
- * @return void
185
  */
186
- function sucuriscan_send_mail($to='', $subject='', $message='', $data_set=array(), $debug=FALSE)
187
- {
188
- $headers = array();
189
- $subject = ucwords(strtolower($subject));
190
- $wp_domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : get_option('siteurl');
191
- if( get_option('sucuri_wp_prettify_mails')!='disabled' ){
192
- $headers = array( 'Content-type: text/html' );
193
- $data_set['PrettifyType'] = 'html';
194
- }
195
- $message = sucuriscan_prettify_mail($subject, $message, $data_set);
196
 
197
- if($debug){
198
- die($message);
199
- }else{
200
- wp_mail($to, "Sucuri WP Notification: {$wp_domain} - {$subject}" , $message, $headers);
201
- }
202
- }
203
 
204
  /**
205
- * Prints a HTML alert in the WordPress admin interface.
206
- *
207
- * @param string $type The type of alert, it can be either Updated or Error.
208
- * @param string $message The message that will be printed in the alert.
209
- * @return void
210
  */
211
- function sucuriscan_admin_notice($type='updated', $message='')
212
- {
213
- $alert_id = rand(100, 999);
214
- if( !empty($message) ): ?>
215
- <div id="sucuri-alert-<?php echo $alert_id; ?>" class="<?php echo $type; ?> sucuri-alert sucuri-alert-<?php echo $type; ?>">
216
- <a href="javascript:void(0)" class="close" onclick="sucuriscan_alert_close('<?php echo $alert_id; ?>')">&times;</a>
217
- <p><?php _e($message); ?></p>
218
- </div>
219
- <?php endif;
220
- }
221
 
222
  /**
223
- * Generate a HTML version of the message that will be sent through an email.
224
  *
225
- * @param string $subject The reason of the message that will be sent.
226
- * @param string $message Body of the message that will be sent.
227
- * @param array $data_set Optional parameter to add more information to the notification.
228
- * @return string The message formatted in a HTML template.
229
  */
230
- function sucuriscan_prettify_mail($subject='', $message='', $data_set=array())
231
- {
232
- $current_user = wp_get_current_user();
233
-
234
- $prettify_type = isset($data_set['PrettifyType']) ? $data_set['PrettifyType'] : 'txt';
235
- $real_ip = isset($_SERVER['SUCURI_RIP']) ? $_SERVER['SUCURI_RIP'] : $_SERVER['REMOTE_ADDR'];
236
 
237
- $mail_variables = array(
238
- 'TemplateTitle'=>'Sucuri WP Notification',
239
- 'Subject'=>$subject,
240
- 'Website'=>get_option('siteurl'),
241
- 'RemoteAddress'=>$real_ip,
242
- 'Message'=>$message,
243
- 'User'=>$current_user->display_name,
244
- 'Time'=>current_time('mysql')
245
- );
246
- foreach($data_set as $var_key=>$var_value){
247
- $mail_variables[$var_key] = $var_value;
248
  }
249
 
250
- return sucuriscan_get_template("notification.{$prettify_type}.tpl", $mail_variables);
251
- }
 
 
 
 
 
 
 
252
 
253
- /**
254
- * Generate a HTML code using a template and replacing all the pseudo-variables
255
- * by the dynamic variables provided by the developer through one of the parameters
256
- * of the function.
257
- *
258
- * @param string $template Filename of the template that will be used to generate the page.
259
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
260
- * @param boolean $type Either page, section or snippet indicating the type of template that will be retrieved.
261
- * @return string The formatted HTML page after replace all the pseudo-variables.
262
- */
263
- function sucuriscan_get_template($template='', $params=array(), $type='page'){
264
- switch( $type ){
265
- case 'page': /* no_break */
266
- case 'section':
267
- $template_path_pattern = '%s/%s/inc/tpl/%s.html.tpl';
268
- break;
269
- case 'snippet':
270
- $template_path_pattern = '%s/%s/inc/tpl/%s.snippet.tpl';
271
- break;
272
  }
273
 
274
- $template_content = '';
275
- $template_path = sprintf( $template_path_pattern, WP_PLUGIN_DIR, SUCURISCAN_PLUGIN_FOLDER, $template );
276
- $params = is_array($params) ? $params : array();
 
 
 
 
 
 
 
 
 
 
 
277
 
278
- if( file_exists($template_path) && is_readable($template_path) ){
279
- $template_content = file_get_contents($template_path);
 
 
 
 
 
 
 
 
 
280
 
281
- $current_page = isset($_GET['page']) ? htmlentities($_GET['page']) : '';
282
- $params['CurrentURL'] = sprintf( '%s/wp-admin/admin.php?page=%s', site_url(), $current_page );
283
- $params['SucuriURL'] = SUCURI_URL;
284
- $params['PageNonce'] = wp_create_nonce('sucuri_page_nonce');
285
 
286
- foreach($params as $tpl_key=>$tpl_value){
287
- $template_content = str_replace("%%SUCURI.{$tpl_key}%%", $tpl_value, $template_content);
 
 
 
 
 
 
 
 
 
 
288
  }
289
- }
290
 
291
- if( $template == 'base' || $type != 'page' ){
292
- return $template_content;
293
- } else {
294
- $base_params = array(
295
- 'PageTitle' => '',
296
- 'PageContent' => $template_content,
297
- 'PageStyleClass' => $template,
298
- 'URL.Hardening' => sucuriscan_get_url('hardening'),
299
- 'URL.CoreIntegrity' => sucuriscan_get_url('core_integrity'),
300
- 'URL.PostHack' => sucuriscan_get_url('posthack'),
301
- 'URL.LastLogins' => sucuriscan_get_url('lastlogins'),
302
- );
 
 
 
 
 
 
303
 
304
- if( isset($params['PageTitle']) ){
305
- $base_params['PageTitle'] = '('.$params['PageTitle'].')';
 
 
 
 
 
 
306
  }
307
 
308
- return sucuriscan_get_template('base', $base_params);
309
  }
310
- }
311
 
312
- /**
313
- * Generate a HTML code using a template and replacing all the pseudo-variables
314
- * by the dynamic variables provided by the developer through one of the parameters
315
- * of the function.
316
- *
317
- * @param string $template Filename of the template that will be used to generate the page.
318
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
319
- * @return string The formatted HTML page after replace all the pseudo-variables.
320
- */
321
- function sucuriscan_get_section($template='', $params=array()){
322
- return sucuriscan_get_template( $template, $params, 'section' );
323
  }
324
 
325
  /**
326
- * Generate a HTML code using a template and replacing all the pseudo-variables
327
- * by the dynamic variables provided by the developer through one of the parameters
328
- * of the function.
329
  *
330
- * @param string $template Filename of the template that will be used to generate the page.
331
- * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
332
- * @return string The formatted HTML page after replace all the pseudo-variables.
 
333
  */
334
- function sucuriscan_get_snippet($template='', $params=array()){
335
- return sucuriscan_get_template( $template, $params, 'snippet' );
336
- }
337
 
338
- /**
339
- * Generate an URL pointing to the page indicated in the function and that must
340
- * be loaded through the administrator panel.
341
- *
342
- * @param string $page Short name of the page that will be generated.
343
- * @return string Full string containing the link of the page.
344
- */
345
- function sucuriscan_get_url($page=''){
346
- if( !empty($page) ){
347
- $url_path = sprintf('%s?page=sucuriscan_%s', admin_url('admin.php'), $page);
348
- return $url_path;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  }
350
 
351
- return null;
352
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
- /**
355
- * Retrieve a new set of keys for the WordPress configuration file using the
356
- * official API provided by WordPress itself.
357
- *
358
- * @return array A list of the new set of keys generated by WordPress API.
359
- */
360
- function sucuriscan_get_new_config_keys()
361
- {
362
- $request = wp_remote_get('https://api.wordpress.org/secret-key/1.1/salt/');
363
- if( !is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200 ){
364
- if( preg_match_all("/define\('([A-Z_]+)',[ ]+'(.*)'\);/", $request['body'], $match) ){
365
- $new_keys = array();
366
- foreach($match[1] as $i=>$value){
367
- $new_keys[$value] = $match[2][$i];
 
 
 
 
 
 
 
368
  }
369
- return $new_keys;
370
  }
 
 
371
  }
372
- return FALSE;
373
- }
374
 
375
- /**
376
- * Modify the WordPress configuration file and change the keys that were defined
377
- * by a new random-generated list of keys retrieved from the official WordPress
378
- * API. The result of the operation will be either FALSE in case of error, or an
379
- * array containing multiple indexes explaining the modification, among them you
380
- * will find the old and new keys.
381
- *
382
- * @return false|array Either FALSE in case of error, or an array with the old and new keys.
383
- */
384
- function sucuriscan_set_new_config_keys()
385
- {
386
- $new_wpconfig = '';
387
- $wp_config_path = ABSPATH.'wp-config.php';
 
 
 
 
 
 
 
 
388
 
389
- if( file_exists($wp_config_path) ){
390
- $wp_config_lines = file($wp_config_path);
391
- $new_keys = sucuriscan_get_new_config_keys();
392
- $old_keys = array();
393
- $old_keys_string = $new_keys_string = '';
394
 
395
- foreach($wp_config_lines as $wp_config_line){
396
- $wp_config_line = str_replace("\n", '', $wp_config_line);
 
397
 
398
- if( preg_match("/define\('([A-Z_]+)',([ ]+)'(.*)'\);/", $wp_config_line, $match) ){
399
- $key_name = $match[1];
400
- if( array_key_exists($key_name, $new_keys) ){
401
- $white_spaces = $match[2];
402
- $old_keys[$key_name] = $match[3];
403
- $wp_config_line = "define('{$key_name}',{$white_spaces}'{$new_keys[$key_name]}');";
404
 
405
- $old_keys_string .= "define('{$key_name}',{$white_spaces}'{$old_keys[$key_name]}');\n";
406
- $new_keys_string .= "{$wp_config_line}\n";
407
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  }
409
 
410
- $new_wpconfig .= "{$wp_config_line}\n";
 
 
 
411
  }
412
 
413
- $response = array(
414
- 'updated'=>is_writable($wp_config_path),
415
- 'old_keys'=>$old_keys,
416
- 'old_keys_string'=>$old_keys_string,
417
- 'new_keys'=>$new_keys,
418
- 'new_keys_string'=>$new_keys_string,
419
- 'new_wpconfig'=>$new_wpconfig
420
- );
421
- if( $response['updated'] ){
422
- file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  }
424
- return $response;
 
425
  }
426
- return FALSE;
427
- }
428
 
429
- /**
430
- * Generate and set a new password for a specific user not in session.
431
- *
432
- * @param integer $user_id The user identifier that will be changed, this must be different than the user in session.
433
- * @return boolean Either TRUE or FALSE in case of success or error respectively.
434
- */
435
- function sucuriscan_new_password($user_id=0)
436
- {
437
- $user_id = intval($user_id);
438
- $current_user = wp_get_current_user();
 
439
 
440
- if( $user_id>0 && $user_id!=$current_user->ID ){
441
- $user = get_userdata($user_id);
442
- $new_password = wp_generate_password(15, TRUE, FALSE);
443
 
444
- $data_set = array( 'User'=>$user->display_name );
445
- $message = "The password for your user account in the website mentioned has been changed by an administrator,
446
- this is the new password automatically generated by the system, please update ASAP.<br>
447
- <div style='display:inline-block;background:#ddd;font-family:monaco,monospace,courier;
448
- font-size:30px;margin:0;padding:15px;border:1px solid #999'>{$new_password}</div>";
449
- sucuriscan_send_mail($user->user_email, 'Changed password', $message, $data_set);
450
 
451
- wp_set_password($new_password, $user_id);
 
 
 
 
 
 
 
 
452
 
453
- return TRUE;
 
454
  }
455
- return FALSE;
456
- }
457
 
458
- /**
459
- * Retrieve the real ip address of the user in the current request.
460
- *
461
- * @return string The real ip address of the user in the current request.
462
- */
463
- function sucuriscan_get_remoteaddr()
464
- {
465
- $alternatives = array(
466
- 'HTTP_X_REAL_IP',
467
- 'HTTP_CLIENT_IP',
468
- 'HTTP_X_FORWARDED_FOR',
469
- 'HTTP_X_FORWARDED',
470
- 'HTTP_FORWARDED_FOR',
471
- 'HTTP_FORWARDED',
472
- 'REMOTE_ADDR',
473
- 'SUCURI_RIP',
474
- );
475
- foreach($alternatives as $alternative){
476
- if( !isset($_SERVER[$alternative]) ){ continue; }
477
 
478
- $remote_addr = preg_replace('/[^0-9a-z.,: ]/', '', $_SERVER[$alternative]);
479
- if($remote_addr) break;
 
 
 
 
 
 
 
 
480
  }
481
 
482
- return $remote_addr;
483
- }
 
 
 
 
 
 
484
 
485
- /**
486
- * Check whether the site is behing the Sucuri CloudProxy network.
487
- *
488
- * @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
489
- */
490
- function sucuriscan_is_behind_cloudproxy(){
491
- $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
492
- if( preg_match('/^(.*):([0-9]+)/', $http_host, $match) ){ $http_host = $match[1]; }
493
- $host_by_name = gethostbyname($http_host);
494
- $host_by_addr = gethostbyaddr($host_by_name);
 
495
 
496
- if(
497
- isset($_SERVER['SUCURIREAL_REMOTE_ADDR'])
498
- || preg_match('/^cloudproxy([0-9]+)\.sucuri\.net$/', $host_by_addr)
499
- ){
500
  return TRUE;
501
  }
502
 
503
- return FALSE;
504
- }
 
 
 
 
 
 
505
 
506
- /**
507
- * Check whether the current site is working as a multi-site instance.
508
- *
509
- * @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
510
- */
511
- function sucuriscan_is_multisite(){
512
- if( function_exists('is_multisite') && is_multisite() ){ return TRUE; }
513
- return FALSE;
514
- }
515
 
516
- /**
517
- * Find and retrieve the absolute path of the WordPress configuration file.
518
- *
519
- * @return string Absolute path of the WordPress configuration file.
520
- */
521
- function sucuriscan_get_wpconfig_path(){
522
- $wp_config_path = ABSPATH.'wp-config.php';
523
 
524
- // if wp-config.php doesn't exist/not readable check one directory up
525
- if( !is_readable($wp_config_path)){
526
- $wp_config_path = ABSPATH.'/../wp-config.php';
 
 
 
527
  }
528
- return $wp_config_path;
529
- }
530
 
531
- /**
532
- * Find and retrieve the absolute path of the main WordPress htaccess file.
533
- *
534
- * @return string Absolute path of the main WordPress htaccess file.
535
- */
536
- function sucuriscan_get_htaccess_path(){
537
- $base_dirs = array(
538
- rtrim(ABSPATH, '/'),
539
- dirname(ABSPATH),
540
- dirname(dirname(ABSPATH))
541
- );
542
 
543
- foreach($base_dirs as $base_dir){
544
- $htaccess_path = sprintf('%s/.htaccess', $base_dir);
545
- if( file_exists($htaccess_path) ){
546
- return $htaccess_path;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
  }
 
 
548
  }
549
 
550
- return FALSE;
551
  }
552
 
553
  /**
554
- * Return the time passed since the specified timestamp until now.
555
  *
556
- * @param integer $timestamp The Unix time number of the date/time before now.
557
- * @return string The time passed since the timestamp specified.
 
558
  */
559
- function sucuriscan_time_ago($timestamp=0){
560
- if( !is_numeric($timestamp) ){
561
- $timestamp = strtotime($timestamp);
562
- }
563
 
564
- $diff = time() - (int)$timestamp;
 
 
 
 
 
 
565
 
566
- if( $diff == 0 ){ return 'just now'; }
 
567
 
568
- $intervals = array(
569
- 1 => array('year', 31556926),
570
- $diff < 31556926 => array('month', 2628000),
571
- $diff < 2629744 => array('week', 604800),
572
- $diff < 604800 => array('day', 86400),
573
- $diff < 86400 => array('hour', 3600),
574
- $diff < 3600 => array('minute', 60),
575
- $diff < 60 => array('second', 1)
576
- );
577
 
578
- $value = floor($diff/$intervals[1][1]);
579
- return $value.chr(32).$intervals[1][0].($value > 1 ? 's' : '').' ago';
580
- }
581
 
582
- /**
583
- * Print a HTML code with a form from where the administrator can check the state
584
- * of this site through Sucuri SiteCheck.
585
- *
586
- * @return void
587
- */
588
- function sucuri_scan_page(){
589
- $U_ERROR = NULL;
590
- if( !current_user_can('manage_options') ){
591
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Malware Scanner') );
592
  }
593
 
594
- if( isset($_POST['wpsucuri-doscan']) ){
595
- sucuriscan_print_scan();
596
- return(1);
 
 
 
 
597
  }
598
 
599
- echo sucuriscan_get_template('initial-page');
600
- }
 
 
 
 
 
 
601
 
602
- /**
603
- * Display the result of site scan made through SiteCheck.
604
- *
605
- * @return void
606
- */
607
- function sucuriscan_print_scan(){
608
- $website_scanned = home_url();
609
- $remote_url = 'http://sitecheck.sucuri.net/scanner/?serialized&clear&fromwp&scan='.$website_scanned;
610
- $scan_results = wp_remote_get($remote_url, array('timeout' => 180));
611
- ob_start();
612
- ?>
613
 
 
 
614
 
615
- <?php if( is_wp_error($scan_results) ): ?>
 
616
 
617
- <div id="poststuff">
618
- <div class="postbox">
619
- <h3>Error retrieving the scan report</h3>
620
- <div class="inside">
621
- <pre><?php print_r($scan_results); ?></pre>
622
- </div>
623
- </div>
624
- </div>
625
 
626
- <?php elseif( preg_match('/^ERROR:/', $scan_results['body']) ): ?>
 
 
 
 
 
627
 
628
- <?php sucuriscan_admin_notice('error', $scan_results['body'].' The URL scanned was: <code>'.$website_scanned.'</code>'); ?>
 
 
 
 
 
 
 
629
 
630
- <?php else: ?>
 
631
 
632
- <?php
633
- $res = @unserialize($scan_results['body']);
634
 
635
- // Check for general warnings, and return the information for Infected/Clean site.
636
- $malware_warns_exists = isset($res['MALWARE']['WARN']) ? TRUE : FALSE;
637
- $blacklist_warns_exists = isset($res['BLACKLIST']['WARN']) ? TRUE : FALSE;
638
 
639
- // Check whether this WordPress installation needs an update.
640
- global $wp_version;
641
- $wordpress_updated = FALSE;
642
- $updates = function_exists('get_core_updates') ? get_core_updates() : array();
643
 
644
- if( !is_array($updates) || empty($updates) || $updates[0]->response=='latest' ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  $wordpress_updated = TRUE;
646
  }
647
 
648
- // Generate the CSS classes for the boxes.
649
- $sucuriscan_css_malware = $malware_warns_exists ? 'sucuriscan-border-bad' : 'sucuriscan-border-good';
650
- $sucuriscan_css_blacklist = $blacklist_warns_exists ? 'sucuriscan-border-bad' : 'sucuriscan-border-good';
651
- $sucuriscan_css_wpupdate = $wordpress_updated ? 'sucuriscan-border-good' : 'sucuriscan-border-bad' ;
652
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
 
654
- <div class="sucuriscan-tabs">
655
- <ul>
656
- <li>
657
- <a href="#" data-tabname="sitecheck-results">SiteCheck Results</a>
658
- </li>
659
- <li>
660
- <a href="#" data-tabname="website-details">Website Details</a>
661
- </li>
662
- <li>
663
- <a href="#" data-tabname="blacklist-status">Blacklist Status</a>
664
- </li>
665
- </ul>
666
 
667
- <div class="sucuriscan-tab-containers">
 
 
 
668
 
669
- <div id="sucuriscan-sitecheck-results">
670
- <div id="poststuff">
671
- <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_malware) ?>">
672
- <h3>
673
- <?php if( $malware_warns_exists ): ?>
674
- Site compromised (malware was identified)
675
- <?php else: ?>
676
- Site clean (no malware was identified)
677
- <?php endif; ?>
678
- </h3>
679
 
680
- <div class="inside">
 
 
 
 
 
 
 
 
 
681
 
682
- <?php if( !$malware_warns_exists ): ?>
683
- <span><strong>Malware:</strong> No.</span><br>
684
- <span><strong>Malicious javascript:</strong> No.</span><br>
685
- <span><strong>Malicious iframes:</strong> No.</span><br>
686
- <span><strong>Suspicious redirections (htaccess):</strong> No.</span><br>
687
- <span><strong>Blackhat SEO Spam:</strong> No.</span><br>
688
- <span><strong>Anomaly detection:</strong> Clean.</span><br>
689
- <?php else: ?>
690
- <?php
691
- foreach( $res['MALWARE']['WARN'] as $malres ){
692
- if( !is_array($malres) ){
693
- echo htmlspecialchars($malres);
694
- }else{
695
- $mwdetails = explode("\n", htmlspecialchars($malres[1]));
696
- echo htmlspecialchars($malres[0])."\n<br />". substr($mwdetails[0], 1)."<br />\n";
697
- }
698
- }
699
- ?>
700
- <?php endif; ?>
701
 
702
- <p>
703
- <i>
704
- More details here: <a href="http://sitecheck.sucuri.net/results/<?php _e($website_scanned); ?>">
705
- http://sitecheck.sucuri.net/results/<?php _e($website_scanned); ?></a>
706
- </i>
707
- <hr />
708
- <i>
709
- If our free scanner did not detect any issue, you may have a more complicated and hidden
710
- problem. You can try our <a href="admin.php?page=sucuriscan_core_integrity">WordPress integrity
711
- checks</a> or sign up with Sucuri <a target="_blank" href="http://sucuri.net/signup">here</a>
712
- for a complete and in depth scan+cleanup (not included in the free checks).
713
- </i>
714
- </p>
715
 
716
- </div>
717
- </div>
718
- </div>
719
- </div>
 
 
 
720
 
721
- <div id="sucuriscan-website-details">
722
- <table class="wp-list-table widefat sucuriscan-table">
723
- <thead>
724
- <tr>
725
- <th colspan="2" class="thead-with-button">
726
- <span>System Information</span>
727
- <?php if( !$wordpress_updated ): ?>
728
- <a href="<?php echo admin_url('update-core.php'); ?>" class="button button-primary thead-topright-action">
729
- Update to <?php _e($updates[0]->version) ?>
730
- </a>
731
- <?php endif; ?>
732
- </th>
733
- </tr>
734
- </thead>
735
 
736
- <tbody>
737
- <!-- List of generic information from the site. -->
738
- <?php
739
- $possible_keys = array(
740
- 'DOMAIN' => 'Domain Scanned',
741
- 'IP' => 'Site IP Address',
742
- 'HOSTING' => 'Hosting Company',
743
- 'CMS' => 'CMS Found',
744
- );
745
- $possible_url_keys = array(
746
- 'JSLOCAL' => 'List of scripts included',
747
- 'JSEXTERNAL' => 'List of external scripts included',
748
- 'URL' => 'List of links found',
749
- );
750
- ?>
751
 
752
- <?php foreach( $possible_keys as $result_key=>$result_title ): ?>
753
- <?php if( isset($res['SCAN'][$result_key]) ): ?>
754
- <?php $result_value = implode(', ', $res['SCAN'][$result_key]); ?>
755
- <tr>
756
- <td><?php _e($result_title) ?></td>
757
- <td><span class="sucuriscan-monospace"><?php _e($result_value) ?></span></td>
758
- </tr>
759
- <?php endif; ?>
760
- <?php endforeach; ?>
761
 
762
- <tr>
763
- <td>WordPress Version</td>
764
- <td><span class="sucuriscan-monospace"><?php _e($wp_version) ?></span></td>
765
- </tr>
766
- <tr>
767
- <td>PHP Version</td>
768
- <td><span class="sucuriscan-monospace"><?php _e(phpversion()) ?></span></td>
769
- </tr>
770
 
771
- <!-- List of application details from the site. -->
772
- <tr>
773
- <th colspan="2">Web application details</th>
774
- </tr>
775
- <?php foreach( $res['WEBAPP'] as $webapp_key=>$webapp_details ): ?>
776
- <?php if( is_array($webapp_details) ): ?>
777
- <?php foreach( $webapp_details as $i=>$details ): ?>
778
- <?php if( is_array($details) ){ $details = isset($details[0]) ? $details[0] : ''; } ?>
779
- <tr>
780
- <td colspan="2">
781
- <span class="sucuriscan-monospace"><?php _e($details) ?></span>
782
- </td>
783
- </tr>
784
- <?php endforeach; ?>
785
- <?php endif; ?>
786
- <?php endforeach; ?>
787
 
788
- <?php foreach( $res['SYSTEM']['NOTICE'] as $j=>$notice ): ?>
789
- <?php if( is_array($notice) ){ $notice = implode(', ', $notice); } ?>
790
- <tr>
791
- <td colspan="2">
792
- <span class="sucuriscan-monospace"><?php _e($notice) ?></span>
793
- </td>
794
- </tr>
795
- <?php endforeach; ?>
 
 
 
 
796
 
797
- <?php foreach( $possible_url_keys as $result_url_key=>$result_url_title ): ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
798
 
799
- <?php if( isset($res['LINKS'][$result_url_key]) ): ?>
800
- <tr>
801
- <th colspan="2">
802
- <?php printf(
803
- '%s (%d found)',
804
- __($result_url_title),
805
- count($res['LINKS'][$result_url_key])
806
- ) ?>
807
- </th>
808
- </tr>
809
 
810
- <?php foreach( $res['LINKS'][$result_url_key] as $url_path ): ?>
811
- <tr>
812
- <td colspan="2">
813
- <span class="sucuriscan-monospace"><?php _e($url_path) ?></span>
814
- </td>
815
- </tr>
816
- <?php endforeach; ?>
817
- <?php endif; ?>
818
 
819
- <?php endforeach; ?>
820
- </tbody>
821
- </table>
822
- </div>
 
 
 
 
 
823
 
824
- <div id="sucuriscan-blacklist-status">
825
- <div id="poststuff">
826
- <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_blacklist) ?>">
827
- <h3>
828
- <?php if( $blacklist_warns_exists ): ?>
829
- Site blacklisted
830
- <?php else: ?>
831
- Site blacklist-free
832
- <?php endif; ?>
833
- </h3>
834
 
835
- <div class="inside">
836
- <?php
837
- foreach(array(
838
- 'INFO'=>'CLEAN',
839
- 'WARN'=>'WARNING'
840
- ) as $type=>$group_title){
841
- if( isset($res['BLACKLIST'][$type]) ){
842
- foreach($res['BLACKLIST'][$type] as $blres){
843
- $report_site = htmlspecialchars($blres[0]);
844
- $report_url = htmlspecialchars($blres[1]);
845
- echo "<b>{$group_title}: </b>{$report_site} <a href='{$report_url}' target='_blank'>{$report_url}</a><br />";
846
- }
847
- }
848
- }
849
- ?>
850
- </div>
851
- </div>
852
- </div>
853
- </div>
854
- </div>
855
- </div>
856
 
857
- <?php if( $malware_warns_exists || $blacklist_warns_exists ): ?>
858
- <a href="http://sucuri.net/signup/" target="_blank" class="button button-primary button-hero sucuriscan-cleanup-btn">
859
- Get your site protected with Sucuri
860
- </a>
861
- <?php endif; ?>
 
 
 
 
 
 
 
862
 
863
- <?php endif; ?>
 
 
 
 
 
 
 
 
 
864
 
 
 
 
 
 
 
 
 
 
865
 
866
- <?php
867
- $_html = ob_get_contents();
868
- ob_end_clean();
869
- echo sucuriscan_get_template('base', array(
870
- 'PageTitle' => '(Results)',
871
- 'PageContent' => $_html,
872
- 'PageStyleClass' => 'scanner-results',
873
- ));
874
- return;
875
  }
876
 
877
  /**
878
- * WordPress core integrity page.
879
- *
880
- * It checks whether the WordPress core files are the original ones, and the state
881
- * of the themes and plugins reporting the availability of updates. It also checks
882
- * the user accounts under the administrator group.
883
  *
884
  * @return void
885
  */
886
- function sucuriscan_core_integrity_page(){
 
 
 
887
 
888
- if( !current_user_can('manage_options') ){
889
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Integrity Check') );
 
890
  }
891
 
892
- if( isset($_POST['wpsucuri-core-integrity']) ){
893
- if( !wp_verify_nonce($_POST['sucuriscan_core_integritynonce'], 'sucuriscan_core_integritynonce') ){
894
- unset($_POST['wpsucuri-core_integrity']);
895
- }
896
- }
 
 
 
 
 
 
 
897
 
898
- ob_start();
899
- ?>
 
 
 
 
 
 
 
 
 
 
900
 
901
- <div id="poststuff">
902
- <?php
903
- sucuriscan_core_integrity_function_wrapper(
904
- 'sucuriwp_core_integrity_check',
905
- 'Verify Integrity of WordPress Core Files',
906
- 'This test will check wp-includes, wp-admin, and the top directory files against the latest WordPress
907
- hashing database. If any of those files were modified, it is a big sign of a possible compromise.'
 
 
 
 
 
 
908
  );
909
 
910
- sucuriscan_core_integrity_wp_content_wrapper();
 
 
 
 
 
 
 
 
911
 
912
- sucuriscan_core_integrity_function_wrapper(
913
- 'sucuriwp_list_admins',
914
- 'Admin User Dump',
915
- 'List all administrator users and their latest login time.'
916
- );
 
 
 
917
 
918
- sucuriscan_core_integrity_function_wrapper(
919
- 'sucuriwp_check_addons',
920
- 'Outdated Addon list',
921
- 'This test will list any outdated plugins and themes (not active addons may not be shown).'
922
- );
923
- ?>
924
- </div>
925
 
926
- <?php
927
- $_html = ob_get_contents();
928
- ob_end_clean();
929
- echo sucuriscan_get_template('base', array(
930
- 'PageTitle' => '(WordPress Integrity)',
931
- 'PageContent' => $_html,
932
- 'PageStyleClass' => 'core-integrity'
933
- ));
934
- return;
935
  }
936
 
937
  /**
938
- * Print the HTML code with the form needed to check the integrity of specific
939
- * parts of the site and administrator panel.
 
940
  *
941
- * @param string $function_name Name of the function that will be executed on form submission.
942
- * @param string $stitle Title of the HTML panel.
943
- * @param string $description Explanation of the action that will be performed once the form is submitted.
944
  * @return void
945
  */
946
- function sucuriscan_core_integrity_function_wrapper($function_name='', $stitle='', $description=''){ ?>
947
- <div class="postbox">
948
- <h3><?php _e($stitle); ?></h3>
949
-
950
- <div class="inside">
951
- <form method="post">
952
- <input type="hidden" name="<?php _e($function_name) ?>nonce" value="<?php echo wp_create_nonce($function_name.'nonce'); ?>" />
953
- <input type="hidden" name="<?php _e($function_name) ?>" value="1" />
954
- <p><?php _e($description) ?></p>
955
- <input class="button-primary" type="submit" name="<?php _e($function_name) ?>" value="Check" />
956
- </form>
957
 
958
- <?php
959
- if( isset($_POST[$function_name.'nonce']) && isset($_POST[$function_name]) ){
960
- if( function_exists($function_name) ){
961
- $function_name();
962
- }
 
 
963
  }
964
- ?>
965
- </div>
966
- </div>
967
- <?php }
 
 
 
 
 
 
 
 
 
 
 
 
 
968
 
969
  /**
970
- * List all files inside wp-content that have been modified in the last days.
 
971
  *
972
  * @return void
973
  */
974
- function sucuriscan_core_integrity_wp_content_wrapper(){ ?>
975
- <div class="postbox">
976
- <h3>Latest modified files</h3>
977
- <div class="inside">
978
- <form method="post">
979
- <input type="hidden" name="sucuriwp_content_checknonce" value="<?php echo wp_create_nonce('sucuriwp_content_checknonce'); ?>" />
980
- <input type="hidden" name="sucuriwp_content_check" value="sucuriwp_content_check" />
981
- <p>
982
- This test will list all files inside wp-content that have been modified in the past
983
- <select name="sucuriwp_content_check_back">
984
- <?php foreach(array( 1,3,7,30 ) as $days): ?>
985
- <?php $selected =
986
- ( isset($_POST['sucuriwp_content_check_back']) && $_POST['sucuriwp_content_check_back']==$days )
987
- ? 'selected="selected"' : ''; ?>
988
- <option value="<?php echo $days; ?>" <?php echo $selected; ?>><?php echo $days; ?></option>
989
- <?php endforeach; ?>
990
- </select> days. (select the number of days first)
991
- </p>
992
- <input class="button-primary" type="submit" name="sucuriwp_content_check" value="Check">
993
- </form>
994
-
995
- <?php if (
996
- isset($_POST['sucuriwp_content_checknonce'])
997
- // && wp_verify_nonce($_POST['sucuriwp_content_checknonce'], 'sucuriwp_content_checknonce')
998
- && isset($_POST['sucuriwp_content_check'])
999
- ): ?>
1000
- <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-doubletitle sucuriscan-lastmodified">
1001
- <thead>
1002
- <tr>
1003
- <th colspan="2">wp_content latest modified files</th>
1004
- </tr>
1005
- <tr>
1006
- <th class="manage-column">Filepath</th>
1007
- <th class="manage-column">Modification date/time</th>
1008
- </tr>
1009
- </thead>
1010
- <tbody>
1011
- <?php
1012
- $wp_content_hashes = read_dir_r(ABSPATH.'wp-content', true);
1013
- $days = htmlspecialchars(trim((int)$_POST['sucuriwp_content_check_back']));
1014
- $back_days = current_time( 'timestamp' ) - ( $days * 86400);
1015
- $counter = 0;
1016
-
1017
- foreach ( $wp_content_hashes as $key => $value) {
1018
- if ($value['time'] >= $back_days ){
1019
- $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
1020
- $date = date('d-m-Y H:i:s', $value['time']);
1021
- printf('<tr class="%s"><td>%s</td><td>%s</td></tr>', $css_class, $key, $date);
1022
- $counter += 1;
1023
- }
1024
- }
1025
- ?>
1026
- </tbody>
1027
- </table>
1028
- <?php endif; ?>
1029
- </div>
1030
- </div>
1031
- <?php }
1032
 
1033
  /**
1034
- * Retrieve a list of md5sum and last modification time of all the files in the
1035
- * folder specified. This is a recursive function.
1036
  *
1037
- * @param string $dir The base path where the scanning will start.
1038
- * @param boolean $recursiv Either TRUE or FALSE if the scan should be performed recursively.
1039
- * @return array List of arrays containing the md5sum and last modification time of the files found.
1040
  */
1041
- function read_dir_r($dir = "./", $recursiv = false){
1042
- $skipname = basename(__FILE__);
1043
- $skipname .= ",_sucuribackup,wp-config.php";
1044
 
1045
- $files_info = array();
1046
-
1047
- $dir_handler = opendir($dir);
1048
-
1049
- while(($entry = readdir($dir_handler)) !== false) {
1050
- if ($entry != "." && $entry != "..") {
1051
- $dir = preg_replace("/^(.*)(\/)+$/", "$1", $dir);
1052
- $item = sprintf( '%s/%s', $dir, $entry );
1053
 
1054
- if (is_file($item)) {
1055
- $skip_parts = explode(",", $skipname);
1056
 
1057
- foreach ($skip_parts as $skip) {
1058
- if (strpos($item,$skip) !== false) {
1059
- continue 2;
1060
- }
 
1061
  }
1062
 
1063
- $md5 = @md5_file($item);
1064
- $time_stamp = @filectime($item);
1065
- $item_name = str_replace(ABSPATH, "./", $item);
1066
- $files_info[$item_name] = array(
1067
- 'md5' => $md5,
1068
- 'time' => $time_stamp
1069
- );
1070
  }
 
1071
 
1072
- elseif (is_dir($item) && $recursiv) {
1073
- $files_info = array_merge( $files_info , read_dir_r($item) );
 
 
 
 
 
 
 
 
 
 
 
 
1074
  }
1075
  }
1076
  }
1077
 
1078
- closedir($dir_handler);
1079
- return $files_info;
 
 
 
 
 
 
 
 
 
 
 
 
1080
  }
1081
 
1082
  /**
1083
- * Compare the md5sum of the core files in the current site with the hashes hosted
1084
- * remotely in Sucuri servers. These hashes are updated every time a new version
1085
- * of WordPress is released.
1086
  *
1087
  * @return void
1088
  */
1089
- function sucuriwp_core_integrity_check(){
1090
-
1091
- global $wp_version;
1092
 
1093
- $curlang = get_bloginfo("language");
1094
 
1095
- $cp = 0;
1096
- $updates = get_core_updates();
1097
- if( !is_array($updates) || empty($updates) || $updates[0]->response=='latest' ){
1098
- $cp = 1;
1099
- }
1100
- if(strcmp($wp_version, "3.7") < 0)
1101
- {
1102
- $cp = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1103
  }
1104
- $wp_version = htmlspecialchars($wp_version);
1105
 
1106
- if($cp == 0)
1107
- {
1108
- echo '<p><img style="position:relative;top:5px" height="22" width="22" src="'.SUCURI_URL.'/inc/images/warn.png" />'
1109
- .'&nbsp; The current version of your site was detected as <code>'.$wp_version.'</code> which is different to the '
1110
- .'official latest version. The integrity check can not run using this version number <a href="'.admin_url('update-core.php').'">'
1111
- .'update now</a> to be able to run the integrity check.</p>';
1112
- }
1113
- else
1114
- {
1115
- $latest_hashes = sucuriscan_check_wp_integrity($wp_version);
1116
- if($latest_hashes){
1117
- sucuriscan_draw_corefiles_status(array(
1118
- 'added'=>$latest_hashes['added'],
1119
- 'removed'=>$latest_hashes['removed'],
1120
- 'modified'=>$latest_hashes['bad']
1121
- ));
1122
- }else{
1123
- sucuriscan_admin_notice('error', 'Error retrieving the wordpress core hashes, try again.');
1124
- }
 
 
 
 
1125
  }
 
 
 
 
 
 
 
 
 
 
1126
  }
1127
 
1128
  /**
1129
- * List all the WordPress core files modified until now.
 
 
1130
  *
1131
- * @param array $list List of WordPress core files modified.
1132
  * @return void
1133
  */
1134
- function sucuriscan_draw_corefiles_status($list=array()){
1135
- if( is_array($list) && !empty($list) ): ?>
1136
- <table class="wp-list-table widefat sucuriscan-table sucuriscan-corefiles">
1137
- <tbody>
1138
- <?php
1139
- foreach( $list as $diff_type=>$file_list ){
1140
- $counter = 0;
1141
- printf('<tr><th>Core File %s: %d</th></tr>', ucwords($diff_type), sizeof($file_list));
1142
- foreach($file_list as $filepath){
1143
- $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
1144
- printf('<tr class="%s"><td>%s</td></tr>', $css_class, $filepath);
1145
- $counter += 1;
1146
- }
1147
- }
1148
- ?>
1149
- </tbody>
1150
- </table>
1151
- <?php endif; ?>
1152
- <?php }
1153
 
1154
  /**
1155
- * List all the user administrator accounts.
 
1156
  *
1157
- * @see http://codex.wordpress.org/Class_Reference/WP_User_Query
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1158
  *
1159
  * @return void
1160
  */
1161
- function sucuriwp_list_admins(){
1162
 
1163
- global $wpdb;
 
 
1164
 
1165
- // Page pseudo-variables initialization.
1166
  $template_variables = array(
1167
- 'AdminUsers.UserList'=>''
 
 
 
 
 
1168
  );
1169
 
1170
- $user_query = new WP_User_Query(array( 'role' => 'Administrator' ));
1171
- $admins = $user_query->get_results();
 
1172
 
1173
- foreach( (array)$admins as $admin ){
1174
- $admin->lastlogins = sucuriscan_get_logins(5, $admin->ID);
1175
 
1176
- $user_snippet = array(
1177
- 'AdminUsers.Username'=>$admin->user_login,
1178
- 'AdminUsers.Email'=>$admin->user_email,
1179
- 'AdminUsers.LastLogins'=>'',
1180
- 'AdminUsers.UserURL'=>admin_url('user-edit.php?user_id='.$admin->ID)
1181
- );
1182
 
1183
- if( !empty($admin->lastlogins) ){
1184
- $user_snippet['AdminUsers.NoLastLogins'] = 'hidden';
1185
- $user_snippet['AdminUsers.NoLastLoginsTable'] = 'visible';
1186
 
1187
- foreach($admin->lastlogins as $lastlogin){
1188
- $user_snippet['AdminUsers.LastLogins'] .= sucuriscan_get_snippet('integrity-admins-lastlogin', array(
1189
- 'AdminUsers.RemoteAddr'=>$lastlogin->user_remoteaddr,
1190
- 'AdminUsers.Datetime'=>$lastlogin->user_lastlogin
1191
- ));
 
 
 
 
 
 
 
 
 
 
 
 
 
1192
  }
1193
- }else{
1194
- $user_snippet['AdminUsers.NoLastLogins'] = 'visible';
1195
- $user_snippet['AdminUsers.NoLastLoginsTable'] = 'hidden';
1196
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1197
 
1198
- $template_variables['AdminUsers.UserList'] .= sucuriscan_get_snippet('integrity-admins', $user_snippet);
 
1199
  }
1200
 
1201
- echo sucuriscan_get_section('integrity-admins', $template_variables);
1202
  }
1203
 
1204
  /**
1205
- * Check if any installed plugin and theme has an update available.
 
 
1206
  *
1207
  * @return void
1208
  */
1209
- function sucuriwp_check_addons(){ ?>
 
1210
 
1211
- <table class="wp-list-table widefat sucuriscan-table sucuriscan-table-doubletitle sucuriscan-plugins-update">
1212
- <tbody>
1213
- <tr>
1214
- <th colspan="4">Outdated Plugins</th>
1215
- </tr>
 
1216
 
1217
- <?php
1218
- // Check plugins.
1219
- do_action('wp_update_plugins');
1220
- wp_update_plugins();
1221
- $update_plugins = get_site_transient('update_plugins');
1222
- $plugins_need_update = (bool) !empty($update_plugins->response);
1223
- ?>
1224
 
1225
- <tr>
1226
- <th>Plugin</th>
1227
- <th>Installed Version</th>
1228
- <th>New Version</th>
1229
- <th>&nbsp;</th>
1230
- </tr>
1231
-
1232
- <?php if( $plugins_need_update ): ?>
1233
- <?php
1234
- $counter = 0;
1235
- foreach( $update_plugins->response as $rel_path => $plugin_info ):
1236
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $rel_path );
1237
- $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
1238
- $counter += 1;
1239
- ?>
1240
-
1241
- <tr class="<?php _e($css_class) ?>">
1242
- <td><?php _e($plugin_data['Title']) ?></td>
1243
- <td><?php _e($plugin_data['Version']) ?></td>
1244
- <td><?php _e($plugin_info->new_version) ?></td>
1245
- <td><a href="<?php _e($plugin_info->package) ?>" target="_blank">Download</a></td>
1246
- </tr>
1247
- <?php endforeach; ?>
1248
- <?php else: ?>
1249
- <tr>
1250
- <td colspan="4">All plugins are up-to-date.</td>
1251
- </tr>
1252
- <?php endif; ?>
1253
 
1254
- <?php
1255
- // Check themes.
1256
- do_action('wp_update_themes');
1257
- wp_update_themes();
1258
- $update_themes = get_theme_updates();
1259
- $themes_need_update = (bool) !empty($update_themes);
1260
- ?>
 
1261
 
1262
- <tr>
1263
- <th>Theme</th>
1264
- <th>Installed Version</th>
1265
- <th>New Version</th>
1266
- <th>&nbsp;</th>
1267
- </tr>
1268
-
1269
- <?php if( $themes_need_update ): ?>
1270
- <?php
1271
- $counter = 0;
1272
- foreach( $update_themes as $stylesheet => $theme ):
1273
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
 
 
 
 
 
 
1274
  $counter += 1;
1275
- ?>
1276
-
1277
- <tr class="<?php _e($css_class) ?>">
1278
- <td><?php _e($theme->display('Name')) ?></td>
1279
- <td><?php _e($theme->display('Version')) ?></td>
1280
- <td><?php _e($theme->update['new_version']) ?></td>
1281
- <td><a href="<?php _e($theme->update['package']) ?>" target="_blank">Download</a></td>
1282
- </tr>
1283
- <?php endforeach; ?>
1284
- <?php else: ?>
1285
- <tr>
1286
- <td colspan="4">All themes are up-to-date.</td>
1287
- </tr>
1288
- <?php endif; ?>
1289
- </tbody>
1290
- </table>
1291
 
1292
- <?php }
 
 
 
 
 
 
 
 
 
 
 
1293
 
1294
  /**
1295
  * Retrieve a list with the checksums of the files in a specific version of WordPress.
1296
  *
 
 
1297
  * @param integer $version Valid version number of the WordPress project.
1298
  * @return object Associative object with the relative filepath and the checksums of the project files.
1299
  */
1300
- function sucuriscan_get_official_checksums($version=0){
1301
  $api_url = sprintf('http://api.wordpress.org/core/checksums/1.0/?version=%s&locale=en_US', $version);
1302
-
1303
  $request = wp_remote_get($api_url);
 
1304
  if( !is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200 ){
1305
  $json_data = json_decode($request['body']);
 
1306
  if( $json_data->checksums !== FALSE ){
1307
- return $json_data->checksums;
 
 
 
 
 
 
 
1308
  }
1309
  }
1310
 
@@ -1317,47 +5620,59 @@ function sucuriscan_get_official_checksums($version=0){
1317
  * these keys:
1318
  *
1319
  * <ul>
1320
- * <li>bad: Files with a different checksum according to the official files of the WordPress version filtered,</li>
1321
- * <li>good: Files with the same checksums than the official files,</li>
1322
  * <li>removed: Official files which are not present in the local project,</li>
1323
  * <li>added: Files present in the local project but not in the official WordPress packages.</li>
1324
  * </ul>
1325
  *
1326
  * @param integer $version Valid version number of the WordPress project.
1327
- * @return array Associative array with these keys: bad, good, removed, added.
1328
  */
1329
- function sucuriscan_check_wp_integrity($version=0){
1330
  $latest_hashes = sucuriscan_get_official_checksums($version);
1331
 
1332
  if( !$latest_hashes ){ return FALSE; }
1333
 
1334
- $output = array( 'bad'=>array(), 'good'=>array(), 'removed'=>array(), 'added'=>array() );
 
 
 
 
 
1335
 
1336
  // Get current filesystem tree.
1337
- $wp_top_hashes = read_dir_r( ABSPATH , false);
1338
- $wp_admin_hashes = read_dir_r( ABSPATH . 'wp-admin', true);
1339
- $wp_includes_hashes = read_dir_r( ABSPATH . 'wp-includes', true);
1340
  $wp_core_hashes = array_merge( $wp_top_hashes, $wp_admin_hashes, $wp_includes_hashes );
1341
 
1342
- // Compare remote and local md5sums and search removed files.
1343
- foreach( $latest_hashes as $filepath=>$remote_checksum ){
 
 
1344
  $full_filepath = sprintf('%s/%s', ABSPATH, $filepath);
 
1345
  if( file_exists($full_filepath) ){
1346
  $local_checksum = @md5_file($full_filepath);
 
1347
  if( $local_checksum && $local_checksum == $remote_checksum ){
1348
- $output['good'][] = $filepath;
1349
- }else{
1350
- $output['bad'][] = $filepath;
1351
  }
1352
- }else{
1353
  $output['removed'][] = $filepath;
1354
  }
1355
  }
1356
 
1357
  // Search added files (files not common in a normal wordpress installation).
1358
- foreach( $wp_core_hashes as $filepath=>$extra_info ){
1359
  $filepath = preg_replace('/^\.\/(.*)/', '$1', $filepath);
1360
- if( !property_exists($latest_hashes, $filepath) ){
 
 
 
1361
  $output['added'][] = $filepath;
1362
  }
1363
  }
@@ -1366,621 +5681,462 @@ function sucuriscan_check_wp_integrity($version=0){
1366
  }
1367
 
1368
  /**
1369
- * Sucuri one-click hardening page.
1370
- *
1371
- * It loads all the functions defined in /lib/hardening.php and shows the forms
1372
- * that the administrator can use to harden multiple parts of the site.
1373
  *
1374
- * @return void
 
1375
  */
1376
- function sucuriscan_hardening_page(){
1377
-
1378
- if( !current_user_can('manage_options') ){
1379
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Hardening') );
1380
- }
 
 
 
 
 
 
 
 
 
 
1381
 
1382
- if( isset($_POST['wpsucuri-doharden']) ){
1383
- if( !wp_verify_nonce($_POST['sucuriscan_hardening_nonce'], 'sucuriscan_hardening_nonce') ){
1384
- unset($_POST['wpsucuri-doharden']);
1385
- }
 
 
1386
  }
1387
 
1388
- ob_start();
1389
- ?>
1390
-
1391
- <div id="poststuff">
1392
- <form method="post">
1393
- <input type="hidden" name="sucuriscan_hardening_nonce" value="<?php echo wp_create_nonce('sucuriscan_hardening_nonce'); ?>" />
1394
- <input type="hidden" name="wpsucuri-doharden" value="wpsucuri-doharden" />
1395
-
1396
- <?php
1397
- sucuriscan_harden_version();
1398
- sucuriscan_cloudproxy_enabled();
1399
- sucuri_harden_removegenerator();
1400
- sucuriscan_harden_upload();
1401
- sucuriscan_harden_wpcontent();
1402
- sucuriscan_harden_wpincludes();
1403
- sucuriscan_harden_phpversion();
1404
- ?>
1405
- </form>
1406
- </div>
1407
-
1408
- <?php
1409
- $_html = ob_get_contents();
1410
- ob_end_clean();
1411
- echo sucuriscan_get_template('base', array(
1412
- 'PageTitle' => '(1-Click Hardening)',
1413
- 'PageContent' => $_html,
1414
- 'PageStyleClass' => 'hardening'
1415
- ));
1416
- return;
1417
- }
1418
-
1419
- /**
1420
- * Print the HTML code to show the title of a hardening option box.
1421
- *
1422
- * @param string $msg The title of the hardening option.
1423
- * @return void
1424
- */
1425
- function sucuriscan_wrapper_open($msg){
1426
- ?>
1427
- <div class="postbox">
1428
- <h3><?php echo $msg; ?></h3>
1429
- <div class="inside">
1430
- <?php
1431
  }
1432
 
1433
  /**
1434
- * Close the HTML tags of the containers opened with __ss_wraphardeningboxopen()
1435
  *
1436
  * @return void
1437
  */
1438
- function sucuriscan_wrapper_close(){
1439
- ?>
1440
- </div>
1441
- </div>
1442
- <?php
1443
- }
 
 
1444
 
1445
- /**
1446
- * Print an error message in the interface.
1447
- *
1448
- * @param string $message The text string that will be shown inside the error box.
1449
- * @return void
1450
- */
1451
- function sucuriscan_harden_error($message){
1452
- return('<div id="message" class="error"><p>'.$message.'</p></div>');
1453
- }
1454
 
1455
- /**
1456
- * Print a success message in the interface.
1457
- *
1458
- * @param string $message The text string that will be shown inside the success box.
1459
- * @return void
1460
- */
1461
- function sucuriscan_harden_ok($message){
1462
- return( '<div id="message" class="updated"><p>'.$message.'</p></div>');
1463
- }
1464
 
1465
- /**
1466
- * Generate the HTML code necessary to show a form with the options to harden
1467
- * a specific part of the WordPress installation, if the Status variable is
1468
- * set as a positive integer the button is shown as "unharden".
1469
- *
1470
- * @param integer $status Either one or zero representing the state of the hardening, one for secure, zero for insecure.
1471
- * @param string $type Name of the hardening option, this will be used through out the form generation.
1472
- * @param string $messageok Message that will be shown if the hardening was executed.
1473
- * @param string $messagewarn Message that will be shown if the hardening is not executed.
1474
- * @param string $desc Optional description of the hardening.
1475
- * @param string $updatemsg Optional explanation of the hardening after the submission of the form.
1476
- * @return void
1477
- */
1478
- function sucuriscan_harden_status($status=0, $type='', $messageok='', $messagewarn='', $desc = NULL, $updatemsg = NULL){
1479
- if($desc != NULL)
1480
- {
1481
- echo "<p>$desc</p>";
1482
  }
1483
 
1484
- $btn_string = '';
1485
- if( $type != NULL ){
1486
- if( $status == 1 ){
1487
- $btn_string = sprintf('<input type="submit" name="%s_unharden" value="Revert hardening" class="button-secondary" />', $type);
1488
- } else {
1489
- $btn_string = sprintf('<input type="submit" name="%s" value="Harden" class="button-primary" />', $type);
 
 
 
 
 
 
 
 
 
 
 
 
1490
  }
1491
  }
1492
 
1493
- $message = ( $status == 1 ) ? $messageok : $messagewarn;
1494
- printf( '<div class="sucuriscan-hstatus sucuriscan-hstatus-%d">%s<span>%s</span></div>', $status, $btn_string, $message );
1495
- if($updatemsg != NULL){
1496
- printf( '<p>%s</p>', $updatemsg );
1497
  }
 
 
1498
  }
1499
 
1500
  /**
1501
- * Check whether the version number of the WordPress installed is the latest
1502
- * version available officially.
1503
  *
1504
  * @return void
1505
  */
1506
- function sucuriscan_harden_version(){
1507
- global $wp_version;
1508
-
1509
- $updates = get_core_updates();
1510
- if(
1511
- !is_array($updates)
1512
- || empty($updates)
1513
- || $updates[0]->response == 'latest'
1514
- ){
1515
- $cp = 1;
1516
- } else {
1517
- $cp = 0;
1518
  }
1519
 
1520
- if(strcmp($wp_version, "3.7") < 0)
1521
- {
1522
- $cp = 0;
1523
- }
1524
 
1525
- $wp_version = htmlspecialchars($wp_version);
1526
- $initial_msg = 'Why keep your site updated? WordPress is an open-source
1527
- project which means that with every update the details of the changes made
1528
- to the source code are made public, if there were security fixes then
1529
- someone with malicious intent can use this information to attack any site
1530
- that has not been upgraded.';
1531
- $messageok = sprintf('Your WordPress installation (%s) is current.', $wp_version);
1532
- $messagewarn = sprintf(
1533
- 'Your current version (%s) is not current.<br>
1534
- <a href="update-core.php" class="button-primary">Update now!</a>',
1535
- $wp_version
1536
  );
1537
 
1538
- sucuriscan_wrapper_open('Verify WordPress Version');
1539
- sucuriscan_harden_status( $cp, NULL, $messageok, $messagewarn, $initial_msg );
1540
- sucuriscan_wrapper_close();
1541
  }
1542
 
1543
  /**
1544
- * Notify the state of the hardening for the removal of the Generator tag in
1545
- * HTML code printed by WordPress to show the current version number of the
1546
- * installation.
1547
  *
1548
- * @return void
1549
  */
1550
- function sucuri_harden_removegenerator(){
1551
- /* Enabled by default with this plugin. */
1552
- $cp = 1;
1553
-
1554
- sucuriscan_wrapper_open("Remove WordPress Version");
1555
-
1556
- sucuriscan_harden_status($cp, NULL,
1557
- "WordPress version properly hidden", NULL,
1558
- "It checks if your WordPress version is being hidden".
1559
- " from being displayed in the generator tag ".
1560
- "(enabled by default with this plugin).");
1561
 
1562
- sucuriscan_wrapper_close();
1563
  }
1564
 
1565
  /**
1566
- * Check whether the WordPress upload folder is protected or not.
1567
- *
1568
- * A htaccess file is placed in the upload folder denying the access to any php
1569
- * file that could be uploaded through a vulnerability in a Plugin, Theme or
1570
- * WordPress itself.
1571
  *
1572
- * @return void
 
1573
  */
1574
- function sucuriscan_harden_upload(){
1575
- $cp = 1;
1576
- $upmsg = NULL;
1577
- $htaccess_upload = dirname(sucuriscan_dir_filepath())."/.htaccess";
1578
-
1579
- if(!is_readable($htaccess_upload))
1580
- {
1581
- $cp = 0;
1582
- }
1583
- else
1584
- {
1585
- $cp = 0;
1586
- $fcontent = file($htaccess_upload);
1587
- foreach($fcontent as $fline)
1588
- {
1589
- if(strpos($fline, "deny from all") !== FALSE)
1590
- {
1591
- $cp = 1;
1592
- break;
1593
- }
1594
- }
1595
- }
1596
-
1597
- if( isset($_POST['wpsucuri-doharden']) ){
1598
- if( isset($_POST['sucuriscan_harden_upload']) && $cp == 0 )
1599
- {
1600
- if(@file_put_contents($htaccess_upload,
1601
- "\n<Files *.php>\ndeny from all\n</Files>")===FALSE)
1602
- {
1603
- $upmsg = sucuriscan_harden_error("ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.");
1604
- }
1605
- else
1606
- {
1607
- $upmsg = sucuriscan_harden_ok("COMPLETE: Upload directory successfully hardened");
1608
- $cp = 1;
1609
- }
1610
- }
1611
-
1612
- elseif( isset($_POST['sucuriscan_harden_upload_unharden']) ){
1613
- $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
1614
- $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
1615
 
1616
- if( $htaccess_upload_writable ){
1617
- $cp = 0;
1618
- if( preg_match('/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match) ){
1619
- $htaccess_content = str_replace("<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content);
1620
- @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
1621
- }
1622
- sucuriscan_admin_notice('updated', '<strong>OK.</strong> WP-Content Uploads directory protection reverted.');
1623
- }else{
1624
- $harden_process = '<strong>Error.</strong> The <code>wp-content/uploads/.htaccess</code> does
1625
- not exists or is not writable, you will need to remove the following code manually there:
1626
- <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>';
1627
- sucuriscan_admin_notice('error', $harden_process);
 
 
 
 
 
1628
  }
 
 
1629
  }
1630
  }
1631
 
1632
- sucuriscan_wrapper_open("Protect Uploads Directory");
1633
- sucuriscan_harden_status($cp, "sucuriscan_harden_upload",
1634
- "Upload directory properly hardened",
1635
- "Upload directory not hardened",
1636
- "It checks if your upload directory allows PHP ".
1637
- "execution or if it is browsable.", $upmsg);
1638
- sucuriscan_wrapper_close();
1639
  }
1640
 
1641
  /**
1642
- * Check whether the WordPress content folder is protected or not.
1643
- *
1644
- * A htaccess file is placed in the content folder denying the access to any php
1645
- * file that could be uploaded through a vulnerability in a Plugin, Theme or
1646
- * WordPress itself.
1647
  *
1648
- * @return void
 
1649
  */
1650
- function sucuriscan_harden_wpcontent(){
1651
- $cp = 1;
1652
- $upmsg = NULL;
1653
- $htaccess_upload = ABSPATH."/wp-content/.htaccess";
1654
 
1655
- if(!is_readable($htaccess_upload))
1656
- {
1657
- $cp = 0;
1658
- }
1659
- else
1660
- {
1661
- $cp = 0;
1662
- $fcontent = file($htaccess_upload);
1663
- foreach($fcontent as $fline)
1664
- {
1665
- if(strpos($fline, "deny from all") !== FALSE)
1666
- {
1667
- $cp = 1;
1668
- break;
1669
- }
1670
- }
1671
- }
1672
 
1673
- if( isset($_POST['wpsucuri-doharden']) ){
1674
- if( isset($_POST['sucuriscan_harden_wpcontent']) && $cp == 0 )
1675
- {
1676
- if(@file_put_contents($htaccess_upload,
1677
- "\n<Files *.php>\ndeny from all\n</Files>")===FALSE)
1678
- {
1679
- $upmsg = sucuriscan_harden_error("ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.");
1680
- }
1681
- else
1682
- {
1683
- $upmsg = sucuriscan_harden_ok("COMPLETE: wp-content directory successfully hardened");
1684
- $cp = 1;
1685
- }
1686
- }
1687
 
1688
- elseif( isset($_POST['sucuriscan_harden_wpcontent_unharden']) ){
1689
- $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
1690
- $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
1691
 
1692
- if( $htaccess_upload_writable ){
1693
- $cp = 0;
1694
- if( preg_match('/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match) ){
1695
- $htaccess_content = str_replace("<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content);
1696
- @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
1697
- }
1698
- sucuriscan_admin_notice('updated', '<strong>OK.</strong> WP-Content directory protection reverted.');
1699
- }else{
1700
- $harden_process = '<strong>Error.</strong> The <code>wp-content/.htaccess</code> does
1701
- not exists or is not writable, you will need to remove the following code manually there:
1702
- <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>';
1703
- sucuriscan_admin_notice('error', $harden_process);
1704
- }
 
 
 
 
1705
  }
1706
  }
1707
 
1708
- sucuriscan_wrapper_open("Restrict wp-content Access");
1709
- sucuriscan_harden_status(
1710
- $cp,
1711
- 'sucuriscan_harden_wpcontent',
1712
- 'WP-content directory properly hardened',
1713
- 'WP-content directory not hardened',
1714
- 'This option blocks direct PHP access to any file inside wp-content. If you experience any
1715
- issue after this with a theme or plugin in your site, like for example images not displaying,
1716
- remove the <code>.htaccess</code> file located at the <code>/wp-content/</code> directory.',
1717
- $upmsg);
1718
- sucuriscan_wrapper_close();
1719
  }
1720
 
1721
  /**
1722
- * Check whether the WordPress includes folder is protected or not.
1723
- *
1724
- * A htaccess file is placed in the includes folder denying the access to any php
1725
- * file that could be uploaded through a vulnerability in a Plugin, Theme or
1726
- * WordPress itself, there are some exceptions for some specific files that must
1727
- * be available publicly.
1728
  *
 
1729
  * @return void
1730
  */
1731
- function sucuriscan_harden_wpincludes(){
1732
- $cp = 1;
1733
- $upmsg = NULL;
1734
- $htaccess_upload = ABSPATH."/wp-includes/.htaccess";
 
 
 
 
 
 
 
 
 
 
 
1735
 
1736
- if(!is_readable($htaccess_upload))
1737
- {
1738
- $cp = 0;
1739
- }
1740
- else
1741
- {
1742
- $cp = 0;
1743
- $fcontent = file($htaccess_upload);
1744
- foreach($fcontent as $fline)
1745
- {
1746
- if(strpos($fline, "deny from all") !== FALSE)
1747
- {
1748
- $cp = 1;
1749
- break;
1750
  }
1751
- }
1752
- }
1753
 
1754
- if( isset($_POST['wpsucuri-doharden']) ){
1755
- if( isset($_POST['sucuriscan_harden_wpincludes']) && $cp == 0 )
1756
- {
1757
- if(@file_put_contents($htaccess_upload,
1758
- "\n<Files *.php>\ndeny from all\n</Files>\n<Files wp-tinymce.php>\nallow from all\n</Files>\n")===FALSE)
1759
- {
1760
- $upmsg = sucuriscan_harden_error("ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.");
1761
- }
1762
- else
1763
- {
1764
- $upmsg = sucuriscan_harden_ok("COMPLETE: wp-includes directory successfully hardened.");
1765
- $cp = 1;
1766
  }
 
 
1767
  }
 
 
1768
 
1769
- elseif( isset($_POST['sucuriscan_harden_wpincludes_unharden']) ){
1770
- $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
1771
- $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
 
 
 
 
 
 
 
1772
 
1773
- if( $htaccess_upload_writable ){
1774
- $cp = 0;
1775
- if( preg_match_all('/<Files (\*|wp-tinymce|ms-files)\.php>\n(deny|allow) from all\n<\/Files>/', $htaccess_content, $match) ){
1776
- foreach($match[0] as $restriction){
1777
- $htaccess_content = str_replace($restriction, '', $htaccess_content);
1778
- }
1779
- @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
1780
- }
1781
- sucuriscan_admin_notice('updated', '<strong>OK.</strong> WP-Includes directory protection reverted.');
1782
- }else{
1783
- $harden_process = '<strong>Error.</strong> The <code>wp-includes/.htaccess</code> does
1784
- not exists or is not writable, you will need to remove the following code manually there:
1785
- <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>';
1786
- sucuriscan_admin_notice('error', $harden_process);
1787
- }
 
 
 
 
 
 
 
1788
  }
1789
  }
1790
 
1791
- sucuriscan_wrapper_open("Restrict wp-includes Access");
1792
- sucuriscan_harden_status($cp, "sucuriscan_harden_wpincludes",
1793
- "wp-includes directory properly hardened",
1794
- "wp-includes directory not hardened",
1795
- "This option blocks direct PHP access to any file inside wp-includes. ", $upmsg);
1796
- sucuriscan_wrapper_close();
1797
  }
1798
 
1799
  /**
1800
- * Check the version number of the PHP interpreter set to work with the site,
1801
- * is considered that old versions of the PHP interpreter are insecure.
1802
  *
 
1803
  * @return void
1804
  */
1805
- function sucuriscan_harden_phpversion(){
1806
- $phpv = phpversion();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1807
 
1808
- if(strncmp($phpv, "5.", 2) < 0)
1809
- {
1810
- $cp = 0;
1811
  }
1812
- else
1813
- {
1814
- $cp = 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1815
  }
1816
 
1817
- sucuriscan_wrapper_open("Verify PHP Version");
1818
- sucuriscan_harden_status($cp, NULL,
1819
- "Using an updated version of PHP (v $phpv)",
1820
- "The version of PHP you are using ($phpv) is not current, not recommended, and/or not supported",
1821
- "This checks if you have the latest version of PHP installed.", NULL);
1822
- sucuriscan_wrapper_close();
1823
  }
1824
 
1825
  /**
1826
- * Check whether the site is behind a secure proxy server or not.
1827
  *
1828
- * @return void
 
 
1829
  */
1830
- function sucuriscan_cloudproxy_enabled(){
1831
- $btn_string = '';
1832
- $enabled = sucuriscan_is_behind_cloudproxy();
1833
- if( $enabled!==TRUE ){
1834
- $btn_string = '<a href="http://cloudproxy.sucuri.net/" target="_blank" class="button button-primary">Harden</a>';
1835
  }
1836
 
1837
- sucuriscan_wrapper_open('Verify if your site is protected by a Web Firewall');
1838
- sucuriscan_harden_status(
1839
- $enabled, NULL,
1840
- 'Your website is protected by a Website Firewall (WAF)',
1841
- $btn_string . 'Your website is not protected by a Website Firewall (WAF)',
1842
- 'A WAF is a protection layer for your web site, blocking all sort of attacks (brute force attempts, DDoS,
1843
- SQL injections, etc) and helping it remain malware and blacklist free. This test checks if your site is
1844
- using <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri\'s CloudProxy WAF</a> to protect your site. ',
1845
- NULL
1846
  );
1847
- sucuriscan_wrapper_close();
 
1848
  }
1849
 
1850
  /**
1851
- * Generate and print the HTML code for the Post-Hack page.
 
 
1852
  *
1853
  * @return void
1854
  */
1855
- function sucuriscan_posthack_page(){
1856
-
1857
- if( !current_user_can('manage_options') ){
1858
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Post-Hack') );
1859
- }
1860
-
1861
  // Page pseudo-variables initialization.
1862
  $template_variables = array(
1863
- 'PageTitle' => 'Post-Hack',
1864
- 'PosthackNonce' => wp_create_nonce('sucuri_posthack_nonce'),
1865
- 'WPConfigUpdate.Display' => 'display:none',
1866
- 'WPConfigUpdate.NewConfig' => '',
1867
- 'ResetPassword.UserList' => ''
1868
  );
1869
 
1870
- // Process form submission
1871
- if( isset($_POST['sucuri_posthack_action']) ){
1872
- if( !wp_verify_nonce($_POST['sucuri_posthack_nonce'], 'sucuri_posthack_nonce') ){
1873
- wp_die(__('WordPress Nonce verification failed, try again going back and checking the form.') );
1874
- }
1875
 
1876
- switch($_POST['sucuri_posthack_action']){
1877
- case 'update_wpconfig':
1878
- $update_wpconfig = ( isset($_POST['sucuri_update_wpconfig']) && $_POST['sucuri_update_wpconfig']==1 ) ? TRUE : FALSE;
1879
 
1880
- if( $update_wpconfig ){
1881
- $wpconfig_process = sucuriscan_set_new_config_keys();
1882
- $template_variables['WPConfigUpdate.Display'] = 'display:block';
 
 
 
 
 
1883
 
1884
- if($wpconfig_process){
1885
- if( $wpconfig_process['updated']===TRUE ){
1886
- sucuriscan_admin_notice('updated', '<strong>OK.</strong> WP-Config keys updated successfully. In the textarea bellow you will see the old-keys and the new-keys updated.');
1887
- $template_variables['WPConfigUpdate.NewConfig'] .= "// Old Keys\n";
1888
- $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['old_keys_string'];
1889
- $template_variables['WPConfigUpdate.NewConfig'] .= "//\n";
1890
- $template_variables['WPConfigUpdate.NewConfig'] .= "// New Keys\n";
1891
- $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['new_keys_string'];
1892
- }else{
1893
- sucuriscan_admin_notice('error', '<strong>Error.</strong> The wp-config.php file is not writable, please copy and paste the code shown bellow in the textarea into that file manually.');
1894
- $template_variables['WPConfigUpdate.NewConfig'] = $wpconfig_process['new_wpconfig'];
1895
- }
1896
- }else{
1897
- sucuriscan_admin_notice('error', '<strong>Error.</strong> The wp-config.php file was not found in the default location.');
1898
- }
1899
- }else{
1900
- sucuriscan_admin_notice('error', '<strong>Error.</strong> You need to confirm that you understand the risk of this operation');
1901
- }
1902
- break;
1903
- case 'reset_password':
1904
- $reset_password = ( isset($_POST['sucuri_reset_password']) && $_POST['sucuri_reset_password']==1 ) ? TRUE : FALSE;
1905
-
1906
- if( $reset_password ){
1907
- $user_identifiers = isset($_POST['user_ids']) ? $_POST['user_ids'] : array();
1908
- $pwd_changed = $pwd_not_changed = array();
1909
-
1910
- if( is_array($user_identifiers) && !empty($user_identifiers) ){
1911
- arsort($user_identifiers);
1912
- foreach($user_identifiers as $user_id){
1913
- if( sucuriscan_new_password($user_id) ){
1914
- $pwd_changed[] = $user_id;
1915
- }else{
1916
- $pwd_not_changed[] = $user_id;
1917
- }
1918
- }
1919
- if( !empty($pwd_changed) ){
1920
- sucuriscan_admin_notice('updated', '<strong>OK.</strong> Password changed successfully for users: '.implode(', ',$pwd_changed));
1921
- }
1922
- if( !empty($pwd_not_changed) ){
1923
- sucuriscan_admin_notice('error', '<strong>Error.</strong> Password change failed for users: '.implode(', ',$pwd_not_changed));
1924
- }
1925
- }else{
1926
- sucuriscan_admin_notice('error', '<strong>Error.</strong> You did not select any user account to be reseted');
1927
- }
1928
- }else{
1929
- sucuriscan_admin_notice('error', '<strong>Error.</strong> You need to confirm that you understand the risk of this operation');
1930
- }
1931
- break;
1932
- default:
1933
- wp_die(__('Sucuri WP Plugin, invalid form action, go back and try again.'));
1934
- break;
1935
  }
1936
- }
1937
 
1938
- // Fill the user list for ResetPassword action.
1939
- $counter = 0;
1940
- $user_list = get_users();
1941
- foreach($user_list as $user){
1942
- $counter += 1;
1943
- $user->user_registered_timestamp = strtotime($user->user_registered);
1944
- $user->user_registered_formatted = date('D, M/Y H:i', $user->user_registered_timestamp);
1945
- $user_snippet = sucuriscan_get_snippet('resetpassword', array(
1946
- 'ResetPassword.UserId' => $user->ID,
1947
- 'ResetPassword.Username' => $user->user_login,
1948
- 'ResetPassword.Displayname' => $user->display_name,
1949
- 'ResetPassword.Email' => $user->user_email,
1950
- 'ResetPassword.Registered' => $user->user_registered_formatted,
1951
- 'ResetPassword.Roles' => implode(', ', $user->roles),
1952
- 'ResetPassword.CssClass' => ( $counter%2 == 0 ) ? '' : 'alternate'
1953
- ));
1954
- $template_variables['ResetPassword.UserList'] .= $user_snippet;
1955
  }
1956
 
1957
- echo sucuriscan_get_template('posthack', $template_variables);
1958
  }
1959
 
1960
  /**
1961
- * Generate and print the HTML code for the Last Logins page.
1962
  *
1963
  * This page will contains information of all the logins of the registered users.
1964
  *
1965
- * @return void
1966
  */
1967
- function sucuriscan_lastlogins_page(){
1968
- if( !current_user_can('manage_options') ){
1969
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Last-Logins') );
1970
- }
1971
-
1972
- // Page pseudo-variables initialization.
1973
  $template_variables = array(
1974
- 'PageTitle' => 'Last Logins',
1975
- 'LastLoginsNonce' => wp_create_nonce('sucuriscan_lastlogins_nonce'),
1976
  'UserList' => '',
1977
  'UserListLimit' => SUCURISCAN_LASTLOGINS_USERSLIMIT,
1978
  );
1979
 
1980
  if( !sucuriscan_lastlogins_datastore_is_writable() ){
1981
- sucuriscan_admin_notice('error', '<strong>Error.</strong> The last-logins datastore
1982
- file is not writable, gives permissions to write in this location:<br>'.
1983
- '<code>'.sucuriscan_lastlogins_datastore_filepath().'</code>');
1984
  }
1985
 
1986
  $limit = isset($_GET['limit']) ? intval($_GET['limit']) : SUCURISCAN_LASTLOGINS_USERSLIMIT;
@@ -1988,26 +6144,37 @@ function sucuriscan_lastlogins_page(){
1988
 
1989
  $counter = 0;
1990
  $user_list = sucuriscan_get_logins($limit);
1991
- foreach($user_list as $user){
 
1992
  $counter += 1;
1993
- $user_snippet = sucuriscan_get_snippet('lastlogins', array(
 
 
1994
  'UserList.Number' => $counter,
1995
- 'UserList.UserId' => intval($user->ID),
1996
- 'UserList.Username' => ( !is_null($user->user_login) ? $user->user_login : '<em>Unknown</em>' ),
1997
- 'UserList.Displayname' => $user->display_name,
1998
- 'UserList.Email' => $user->user_email,
1999
- 'UserList.Registered' => $user->user_registered,
2000
  'UserList.RemoteAddr' => $user->user_remoteaddr,
2001
  'UserList.Hostname' => $user->user_hostname,
2002
  'UserList.Datetime' => $user->user_lastlogin,
2003
  'UserList.TimeAgo' => sucuriscan_time_ago($user->user_lastlogin),
2004
- 'UserList.UserURL' => admin_url('user-edit.php?user_id='.$user->ID),
2005
- 'UserList.CssClass' => ( $counter % 2 == 0 ) ? 'alternate' : ''
2006
- ));
2007
- $template_variables['UserList'] .= $user_snippet;
 
 
 
 
 
 
 
 
2008
  }
2009
 
2010
- echo sucuriscan_get_template('lastlogins', $template_variables);
2011
  }
2012
 
2013
  /**
@@ -2108,15 +6275,17 @@ if( !function_exists('sucuri_set_lastlogin') ){
2108
  * @param integer $user_id Optional user identifier to filter the results.
2109
  * @return array The list of all the user logins through the time until now.
2110
  */
2111
- function sucuriscan_get_logins($limit=10, $user_id=0){
2112
  $lastlogins = array();
2113
  $datastore_filepath = sucuriscan_lastlogins_datastore_is_readable();
2114
 
2115
- if($datastore_filepath){
2116
  $parsed_lines = 0;
2117
  $lastlogins_lines = array_reverse(file($datastore_filepath));
2118
- foreach($lastlogins_lines as $line){
 
2119
  $line = str_replace("\n", '', $line);
 
2120
  if( preg_match('/^a:/', $line) ){
2121
  $user_lastlogin = unserialize($line);
2122
 
@@ -2132,11 +6301,18 @@ function sucuriscan_get_logins($limit=10, $user_id=0){
2132
  }
2133
 
2134
  /* Get the WP_User object and add extra information from the last-login data */
 
2135
  $user_account = get_userdata($user_lastlogin['user_id']);
2136
- foreach($user_lastlogin as $user_extrainfo_key=>$user_extrainfo_value){
2137
- $user_account->data->{$user_extrainfo_key} = $user_extrainfo_value;
 
 
 
 
 
2138
  }
2139
- $lastlogins[] = $user_account;
 
2140
  $parsed_lines += 1;
2141
  }
2142
 
@@ -2159,14 +6335,20 @@ if( !function_exists('sucuri_login_redirect') ){
2159
  * @param boolean $user WordPress user object with the information of the account involved in the operation.
2160
  * @return string URL where the browser must be redirected to.
2161
  */
2162
- function sucuriscan_login_redirect($redirect_to='', $request=NULL, $user=FALSE){
2163
  $login_url = !empty($redirect_to) ? $redirect_to : admin_url();
 
2164
  if( $user instanceof WP_User && $user->ID ){
2165
- $login_url = add_query_arg( 'sucuriscan_lastlogin_message', 1, $login_url );
2166
  }
 
2167
  return $login_url;
2168
  }
2169
- add_filter('login_redirect', 'sucuriscan_login_redirect', 10, 3);
 
 
 
 
2170
  }
2171
 
2172
  if( !function_exists('sucuri_get_user_lastlogin') ){
@@ -2176,7 +6358,7 @@ if( !function_exists('sucuri_get_user_lastlogin') ){
2176
  * @return void
2177
  */
2178
  function sucuriscan_get_user_lastlogin(){
2179
- if( isset($_GET['sucuriscan_lastlogin_message']) && current_user_can('manage_options') ){
2180
  $current_user = wp_get_current_user();
2181
 
2182
  // Select the penultimate entry, not the last one.
@@ -2187,7 +6369,7 @@ if( !function_exists('sucuri_get_user_lastlogin') ){
2187
  $message_tpl = 'The last time you logged in was: %s, from %s - %s';
2188
  $lastlogin_message = sprintf( $message_tpl, date('Y/M/d'), $row->user_remoteaddr, $row->user_hostname );
2189
  $lastlogin_message .= chr(32).'(<a href="'.site_url('wp-admin/admin.php?page='.SUCURISCAN.'_lastlogins').'">View Last-Logins</a>)';
2190
- sucuriscan_admin_notice('updated', $lastlogin_message);
2191
  }
2192
  }
2193
  }
@@ -2628,10 +6810,9 @@ function sucuriscan_show_cronjobs(){
2628
  }
2629
 
2630
  /**
2631
- * Gather information from the server, database engine and PHP interpreter.
2632
  *
2633
- * @param array $template_variables The hash for the template system, keys are pseudo-variables.
2634
- * @return array A list of pseudo-variables and values that will replace them in the HTML template.
2635
  */
2636
  function sucuriscan_server_info(){
2637
  global $wpdb;
@@ -2641,21 +6822,19 @@ function sucuriscan_server_info(){
2641
  $mysql_version = $wpdb->get_var('SELECT VERSION() AS version');
2642
  $mysql_info = $wpdb->get_results('SHOW VARIABLES LIKE "sql_mode"');
2643
  $sql_mode = ( is_array($mysql_info) && !empty($mysql_info[0]->Value) ) ? $mysql_info[0]->Value : 'Not set';
2644
- $plugin_runtime_filepath = sucuriscan_dir_filepath('.runtime');
2645
- $plugin_runtime_datetime = file_exists($plugin_runtime_filepath) ? date('r',filemtime($plugin_runtime_filepath)) : 'N/A';
2646
 
2647
  $template_variables = array(
2648
- 'SettingsDisplay'=>'block',
2649
- 'PluginVersion'=>SUCURISCAN_VERSION,
2650
- 'PluginForceUpdate'=>admin_url('admin.php?page=sucurisec_settings&sucuri_force_update=1'),
2651
- 'PluginMD5'=>md5_file(SUCURISCAN_PLUGIN_FILEPATH),
2652
- 'PluginRuntimeDatetime'=>$plugin_runtime_datetime,
2653
- 'OperatingSystem'=>sprintf('%s (%d Bit)', PHP_OS, PHP_INT_SIZE*8),
2654
- 'Server'=>isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'Unknown',
2655
- 'MemoryUsage'=>$memory_usage,
2656
- 'MySQLVersion'=>$mysql_version,
2657
- 'SQLMode'=>$sql_mode,
2658
- 'PHPVersion'=>PHP_VERSION,
2659
  );
2660
 
2661
  $field_names = array(
@@ -2681,21 +6860,262 @@ function sucuriscan_server_info(){
2681
 
2682
 
2683
  /**
2684
- * Print the HTML code for the plugin about page with information of the plugin,
2685
- * the scheduled tasks, and some settings from the PHP environment and server.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2686
  *
2687
  * @return void
2688
  */
2689
- function sucuriscan_about_page(){
2690
 
2691
- if( !current_user_can('manage_options') ){
2692
- wp_die(__('You do not have sufficient permissions to access this page: Sucuri Last-Logins') );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2693
  }
2694
 
2695
  $template_variables = array(
2696
- 'PageTitle' => 'About'
 
 
 
 
 
 
 
 
 
 
 
 
 
2697
  );
2698
 
2699
- echo sucuriscan_get_template('about', $template_variables);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2700
  }
2701
 
1
  <?php
2
  /*
3
+ Plugin Name: Sucuri Security - Auditing, Malware Scanner and Hardening
4
+ Plugin URI: http://wordpress.sucuri.net/
5
+ Description: The <a href="http://sucuri.net/" target="_blank">Sucuri Security</a> <em>(Auditing, Malware Scanner and Hardening)</em> plugin enables you to scan your WordPress site using <a href="http://sitecheck.sucuri.net/" target="_blank">Sucuri SiteCheck</a> right in your dashboard. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
 
 
 
6
  Author: Sucuri, INC
7
+ Version: 1.6.1
8
  Author URI: http://sucuri.net
9
  */
10
 
36
  /**
37
  * Current version of the plugin's code.
38
  */
39
+ define('SUCURISCAN_VERSION','1.6.1');
40
 
41
  /**
42
  * The local URL where the plugin's files and assets are served.
64
  define('SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE);
65
 
66
  /**
67
+ * Checksum of this file to check the integrity of the plugin.
68
  */
69
+ define('SUCURISCAN_PLUGIN_CHECKSUM', @md5_file(SUCURISCAN_PLUGIN_FILEPATH));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  /**
72
+ * Remote URL where the public Sucuri API service is running.
 
73
  */
74
+ define('SUCURISCAN_API', 'https://wordpress.sucuri.net/api/');
 
 
 
 
 
 
 
75
 
76
  /**
77
+ * Latest version of the public Sucuri API.
 
 
 
 
78
  */
79
+ define('SUCURISCAN_API_VERSION', 'v1');
 
 
 
 
 
80
 
81
  /**
82
+ * Remote URL where the CloudProxy API service is running.
 
 
83
  */
84
+ define('SUCURISCAN_CLOUDPROXY_API', 'https://waf.sucuri.net/api');
 
 
 
 
 
 
 
 
 
 
 
85
 
86
+ /**
87
+ * Latest version of the CloudProxy API.
88
+ */
89
+ define('SUCURISCAN_CLOUDPROXY_API_VERSION', 'v2');
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  /**
92
+ * The maximum quantity of entries that will be displayed in the last login page.
 
 
 
93
  */
94
+ define('SUCURISCAN_LASTLOGINS_USERSLIMIT', 50);
 
 
 
 
 
 
 
 
 
 
95
 
96
  /**
97
+ * The maximum quantity of entries that will be displayed in the audit logs page.
 
 
 
 
 
 
 
98
  */
99
+ define('SUCURISCAN_AUDITLOGS_PER_PAGE', 50);
 
 
 
 
 
 
 
 
 
100
 
101
+ /**
102
+ * The minimum quantity of seconds to wait before each filesystem scan.
103
+ */
104
+ define('SUCURISCAN_MINIMUM_RUNTIME', 10800);
 
 
105
 
106
  /**
107
+ * The life time of the cache for the results of the SiteCheck scans.
 
 
 
 
108
  */
109
+ define('SUCURISCAN_SITECHECK_LIFETIME', 1200);
 
 
 
 
 
 
 
 
 
110
 
111
  /**
112
+ * Miscellaneous library.
113
  *
114
+ * Multiple and generic functions that will be used through out the code of
115
+ * other libraries extending from this and functions defined in other files, be
116
+ * aware of the hierarchy and check the other libraries for duplicated methods.
 
117
  */
118
+ class SucuriScan {
 
 
 
 
 
119
 
120
+ /**
121
+ * Class constructor.
122
+ */
123
+ public function __construct(){
 
 
 
 
 
 
 
124
  }
125
 
126
+ /**
127
+ * Generates a lowercase random string with an specific length.
128
+ *
129
+ * @param integer $length Length of the string that will be generated.
130
+ * @return string The random string generated.
131
+ */
132
+ public static function random_char( $length=4 ){
133
+ $string = '';
134
+ $chars = range('a','z');
135
 
136
+ for( $i=0; $i<$length; $i++ ){
137
+ $string .= $chars[ rand(0, count($chars)-1) ];
138
+ }
139
+
140
+ return $string;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
 
143
+ /**
144
+ * Translate a given number in bytes to a human readable file size using the
145
+ * a approximate value in Kylo, Mega, Giga, etc.
146
+ *
147
+ * @link http://www.php.net/manual/en/function.filesize.php#106569
148
+ * @param integer $bytes An integer representing a file size in bytes.
149
+ * @param integer $decimals How many decimals should be returned after the translation.
150
+ * @return string Human readable representation of the given number in Kylo, Mega, Giga, etc.
151
+ */
152
+ public static function human_filesize( $bytes=0, $decimals=2 ){
153
+ $sz = 'BKMGTP';
154
+ $factor = floor((strlen($bytes) - 1) / 3);
155
+ return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
156
+ }
157
 
158
+ /**
159
+ * Returns the system filepath to the relevant user uploads directory for this
160
+ * site. This is a multisite capable function.
161
+ *
162
+ * @param string $path The relative path that needs to be completed to get the absolute path.
163
+ * @return string The full filesystem path including the directory specified.
164
+ */
165
+ public static function datastore_folder_path( $path='' ){
166
+ $wp_dir_array = wp_upload_dir();
167
+ $wp_dir_array['basedir'] = untrailingslashit($wp_dir_array['basedir']);
168
+ $wp_filepath = $wp_dir_array['basedir'] . '/sucuri/' . $path;
169
 
170
+ return $wp_filepath;
171
+ }
 
 
172
 
173
+ /**
174
+ * Check the nonce comming from any of the settings pages.
175
+ *
176
+ * @return boolean TRUE if the nonce is valid, FALSE otherwise.
177
+ */
178
+ public static function sucuriscan_check_options_wpnonce(){
179
+ // Create the option_page value if permalink submission.
180
+ if(
181
+ !isset($_POST['option_page'])
182
+ && isset($_POST['permalink_structure'])
183
+ ){
184
+ $_POST['option_page'] = 'permalink';
185
  }
 
186
 
187
+ // Check if the option_page has an allowed value.
188
+ if( isset($_POST['option_page']) ){
189
+ $nonce='_wpnonce';
190
+ $action = '';
191
+
192
+ switch( $_POST['option_page'] ){
193
+ case 'general': /* no_break */
194
+ case 'writing': /* no_break */
195
+ case 'reading': /* no_break */
196
+ case 'discussion': /* no_break */
197
+ case 'media': /* no_break */
198
+ case 'options': /* no_break */
199
+ $action = $_POST['option_page'] . '-options';
200
+ break;
201
+ case 'permalink':
202
+ $action = 'update-permalink';
203
+ break;
204
+ }
205
 
206
+ // Check the nonce validity.
207
+ if(
208
+ !empty($action)
209
+ && isset($_REQUEST[$nonce])
210
+ && wp_verify_nonce($_REQUEST[$nonce], $action)
211
+ ){
212
+ return TRUE;
213
+ }
214
  }
215
 
216
+ return FALSE;
217
  }
 
218
 
 
 
 
 
 
 
 
 
 
 
 
219
  }
220
 
221
  /**
222
+ * Class to process files and folders.
 
 
223
  *
224
+ * Here are implemented the functions needed to open, scan, read, create files
225
+ * and folders using the built-in PHP class SplFileInfo. The SplFileInfo class
226
+ * offers a high-level object oriented interface to information for an individual
227
+ * file.
228
  */
229
+ class SucuriScanFileInfo extends SucuriScan {
 
 
230
 
231
+ /**
232
+ * Whether the list of files that can be ignored from the filesystem scan will
233
+ * be used to return the directory tree, this should be disabled when scanning a
234
+ * directory without the need to filter the items in the list.
235
+ *
236
+ * @var boolean
237
+ */
238
+ public $ignore_files = TRUE;
239
+
240
+ /**
241
+ * Whether the list of folders that can be ignored from the filesystem scan will
242
+ * be used to return the directory tree, this should be disabled when scanning a
243
+ * path without the need to filter the items in the list.
244
+ *
245
+ * @var boolean
246
+ */
247
+ public $ignore_directories = TRUE;
248
+
249
+ /**
250
+ * Whether the filesystem scanner should run recursively or not.
251
+ *
252
+ * @var boolean
253
+ */
254
+ public $run_recursively = TRUE;
255
+
256
+ /**
257
+ * Class constructor.
258
+ */
259
+ public function __construct(){
260
  }
261
 
262
+ /**
263
+ * Retrieve a long text string with signatures of all the files contained
264
+ * in the main and subdirectories of the folder specified, also the filesize
265
+ * and md5sum of that file. Some folders and files will be ignored depending
266
+ * on some rules defined by the developer.
267
+ *
268
+ * @param string $directory Parent directory where the filesystem scan will start.
269
+ * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
270
+ * @param boolean $as_array Whether the result of the operation will be returned as an array or string.
271
+ * @return array List of files in the main and subdirectories of the folder specified.
272
+ */
273
+ public function get_directory_tree_md5( $directory='', $scan_with='spl', $as_array=FALSE ){
274
+ $project_signatures = '';
275
+ $abs_path = rtrim( ABSPATH, '/' );
276
+ $files = $this->get_directory_tree($directory, $scan_with);
277
+ sort($files);
278
+
279
+ if( $as_array ){
280
+ $project_signatures = array();
281
+ }
282
 
283
+ foreach( $files as $filepath){
284
+ $file_checksum = @md5_file($filepath);
285
+ $filesize = @filesize($filepath);
286
+
287
+ if( $as_array ){
288
+ $basename = str_replace( $abs_path . '/', '', $filepath );
289
+ $project_signatures[$basename] = array(
290
+ 'filepath' => $filepath,
291
+ 'checksum' => $file_checksum,
292
+ 'filesize' => $filesize,
293
+ 'filetime' => filectime($filepath),
294
+ );
295
+ } else {
296
+ $filepath = str_replace( $abs_path, $abs_path . '/', $filepath );
297
+ $project_signatures .= sprintf(
298
+ "%s%s%s%s\n",
299
+ $file_checksum,
300
+ $filesize,
301
+ chr(32),
302
+ $filepath
303
+ );
304
  }
 
305
  }
306
+
307
+ return $project_signatures;
308
  }
 
 
309
 
310
+ /**
311
+ * Retrieve a list with all the files contained in the main and subdirectories
312
+ * of the folder specified. Some folders and files will be ignored depending
313
+ * on some rules defined by the developer.
314
+ *
315
+ * @param string $directory Parent directory where the filesystem scan will start.
316
+ * @param string $scan_with Set the tool used to scan the filesystem, SplFileInfo by default.
317
+ * @return array List of files in the main and subdirectories of the folder specified.
318
+ */
319
+ public function get_directory_tree($directory='', $scan_with='spl'){
320
+ if( file_exists($directory) && is_dir($directory) ){
321
+ $tree = array();
322
+
323
+ switch( $scan_with ){
324
+ case 'spl':
325
+ if( $this->is_spl_available() ){
326
+ $tree = $this->get_directory_tree_with_spl($directory);
327
+ } else {
328
+ $tree = $this->get_directory_tree($directory, 'opendir');
329
+ }
330
+ break;
331
 
332
+ case 'glob':
333
+ $tree = $this->get_directory_tree_with_glob($directory);
334
+ break;
 
 
335
 
336
+ case 'opendir':
337
+ $tree = $this->get_directory_tree_with_opendir($directory);
338
+ break;
339
 
340
+ default:
341
+ $tree = $this->get_directory_tree($directory, 'spl');
342
+ break;
343
+ }
 
 
344
 
345
+ return $tree;
346
+ }
347
+
348
+ return FALSE;
349
+ }
350
+
351
+ /**
352
+ * Check whether the built-in class SplFileObject is available in the system
353
+ * or not, it is required to have PHP >= 5.1.0. The SplFileObject class offers
354
+ * an object oriented interface for a file.
355
+ *
356
+ * @link http://www.php.net/manual/en/class.splfileobject.php
357
+ *
358
+ * @return boolean Whether the PHP class "SplFileObject" is available or not.
359
+ */
360
+ public static function is_spl_available(){
361
+ return (bool) class_exists('SplFileObject');
362
+ }
363
+
364
+ /**
365
+ * Retrieve a list with all the files contained in the main and subdirectories
366
+ * of the folder specified. Some folders and files will be ignored depending
367
+ * on some rules defined by the developer.
368
+ *
369
+ * @link http://www.php.net/manual/en/class.recursivedirectoryiterator.php
370
+ * @see RecursiveDirectoryIterator extends FilesystemIterator
371
+ * @see FilesystemIterator extends DirectoryIterator
372
+ * @see DirectoryIterator extends SplFileInfo
373
+ * @see SplFileInfo
374
+ *
375
+ * @param string $directory Parent directory where the filesystem scan will start.
376
+ * @return array List of files in the main and subdirectories of the folder specified.
377
+ */
378
+ private function get_directory_tree_with_spl($directory=''){
379
+ $files = array();
380
+ $filepath = realpath($directory);
381
+
382
+ if( !class_exists('FilesystemIterator') ){
383
+ return $this->get_directory_tree($directory, 'opendir');
384
+ }
385
+
386
+ if( $this->run_recursively ){
387
+ $flags = FilesystemIterator::KEY_AS_PATHNAME
388
+ | FilesystemIterator::CURRENT_AS_FILEINFO
389
+ | FilesystemIterator::SKIP_DOTS
390
+ | FilesystemIterator::UNIX_PATHS;
391
+ $objects = new RecursiveIteratorIterator(
392
+ new RecursiveDirectoryIterator($filepath, $flags),
393
+ RecursiveIteratorIterator::SELF_FIRST
394
+ );
395
+ } else {
396
+ $objects = new DirectoryIterator($filepath);
397
+ }
398
+
399
+ foreach( $objects as $filepath => $fileinfo ){
400
+ if( $this->run_recursively ){
401
+ $directory = dirname($filepath);
402
+ $filename = $fileinfo->getFilename();
403
+ } else {
404
+ if( $fileinfo->isDot() || $fileinfo->isDir() ){ continue; }
405
+
406
+ $directory = $fileinfo->getPath();
407
+ $filename = $fileinfo->getFilename();
408
+ $filepath = $directory . '/' . $filename;
409
  }
410
 
411
+ if( $this->ignore_folderpath($directory, $filename) ){ continue; }
412
+ if( $this->ignore_filepath($filename) ){ continue; }
413
+
414
+ $files[] = $filepath;
415
  }
416
 
417
+ return $files;
418
+ }
419
+
420
+ /**
421
+ * Retrieve a list with all the files contained in the main and subdirectories
422
+ * of the folder specified. Some folders and files will be ignored depending
423
+ * on some rules defined by the developer.
424
+ *
425
+ * @param string $directory Parent directory where the filesystem scan will start.
426
+ * @return array List of files in the main and subdirectories of the folder specified.
427
+ */
428
+ private function get_directory_tree_with_glob($directory=''){
429
+ $files = array();
430
+
431
+ $directory_pattern = sprintf( '%s/*', rtrim($directory,'/') );
432
+ $files_found = glob($directory_pattern);
433
+
434
+ if( is_array($files_found) ){
435
+ foreach( $files_found as $filepath ){
436
+ $filepath = realpath($filepath);
437
+ $directory = dirname($filepath);
438
+ $filename = array_pop(explode('/', $filepath));
439
+
440
+ if( is_dir($filepath) ){
441
+ if( $this->ignore_folderpath($directory, $filename) ){ continue; }
442
+
443
+ if( $this->run_recursively ){
444
+ $sub_files = $this->get_directory_tree_with_opendir($filepath);
445
+ $files = array_merge($files, $sub_files);
446
+ }
447
+ } else {
448
+ if( $this->ignore_filepath($filename) ){ continue; }
449
+ $files[] = $filepath;
450
+ }
451
+ }
452
  }
453
+
454
+ return $files;
455
  }
 
 
456
 
457
+ /**
458
+ * Retrieve a list with all the files contained in the main and subdirectories
459
+ * of the folder specified. Some folders and files will be ignored depending
460
+ * on some rules defined by the developer.
461
+ *
462
+ * @param string $directory Parent directory where the filesystem scan will start.
463
+ * @return array List of files in the main and subdirectories of the folder specified.
464
+ */
465
+ private function get_directory_tree_with_opendir($directory=''){
466
+ $dh = @opendir($directory);
467
+ if( !$dh ){ return FALSE; }
468
 
469
+ $files = array();
470
+ while( ($filename = readdir($dh)) !== FALSE ){
471
+ $filepath = realpath($directory.'/'.$filename);
472
 
473
+ if( is_dir($filepath) ){
474
+ if( $this->ignore_folderpath($directory, $filename) ){ continue; }
 
 
 
 
475
 
476
+ if( $this->run_recursively ){
477
+ $sub_files = $this->get_directory_tree_with_opendir($filepath);
478
+ $files = array_merge($files, $sub_files);
479
+ }
480
+ } else {
481
+ if( $this->ignore_filepath($filename) ){ continue; }
482
+ $files[] = $filepath;
483
+ }
484
+ }
485
 
486
+ closedir($dh);
487
+ return $files;
488
  }
 
 
489
 
490
+ /**
491
+ * Skip some specific directories and filepaths from the filesystem scan.
492
+ *
493
+ * @param string $directory Directory where the scanner is located at the moment.
494
+ * @param string $filename Name of the folder or file being scanned at the moment.
495
+ * @return boolean Either TRUE or FALSE representing that the scan should ignore this folder or not.
496
+ */
497
+ private function ignore_folderpath( $directory='', $filename='' ){
498
+ // Ignoring current and parent folders.
499
+ if( $filename == '.' || $filename == '..' ){ return TRUE; }
 
 
 
 
 
 
 
 
 
500
 
501
+ if( $this->ignore_directories ){
502
+ $filepath = realpath( $directory . '/' . $filename );
503
+ $pattern = '/\/wp-content\/(uploads|cache|backup|w3tc)/';
504
+
505
+ if( preg_match($pattern, $filepath) ){
506
+ return TRUE;
507
+ }
508
+ }
509
+
510
+ return FALSE;
511
  }
512
 
513
+ /**
514
+ * Skip some specific files from the filesystem scan.
515
+ *
516
+ * @param string $filename Name of the folder or file being scanned at the moment.
517
+ * @return boolean Either TRUE or FALSE representing that the scan should ignore this filename or not.
518
+ */
519
+ private function ignore_filepath( $filename='' ){
520
+ if( !$this->ignore_files ){ return FALSE; }
521
 
522
+ // Ignoring backup files from our clean ups.
523
+ if( strpos($filename, '_sucuribackup.') !== FALSE ){ return TRUE; }
524
+
525
+ // Any file maching one of these rules WILL NOT be ignored.
526
+ if(
527
+ ( strpos($filename, '.php') !== FALSE) ||
528
+ ( strpos($filename, '.htm') !== FALSE) ||
529
+ ( strpos($filename, '.js') !== FALSE) ||
530
+ ( strcmp($filename, '.htaccess') == 0 ) ||
531
+ ( strcmp($filename, 'php.ini') == 0 )
532
+ ){ return FALSE; }
533
 
 
 
 
 
534
  return TRUE;
535
  }
536
 
537
+ /**
538
+ * Retrieve a list of unique directory paths.
539
+ *
540
+ * @param array $dir_tree A list of files under a directory.
541
+ * @return array A list of unique directory paths.
542
+ */
543
+ public function get_diretories_only( $dir_tree=array() ){
544
+ $dirs = array();
545
 
546
+ if( is_string($dir_tree) ){
547
+ $dir_tree = $this->get_directory_tree($dir_tree);
548
+ }
 
 
 
 
 
 
549
 
550
+ foreach( $dir_tree as $filepath ){
551
+ $dir_path = dirname($filepath);
 
 
 
 
 
552
 
553
+ if( !in_array($dir_path, $dirs) ){
554
+ $dirs[] = $dir_path;
555
+ }
556
+ }
557
+
558
+ return $dirs;
559
  }
 
 
560
 
561
+ /**
562
+ * Remove a directory recursively.
563
+ *
564
+ * @param string $directory Path of the existing directory that will be removed.
565
+ * @return boolean TRUE if all the files and folder inside the directory were removed.
566
+ */
567
+ public function remove_directory_tree( $directory='' ){
568
+ $all_removed = TRUE;
569
+ $dir_tree = $this->get_directory_tree($directory);
 
 
570
 
571
+ if( $dir_tree ){
572
+ $dirs_only = array();
573
+
574
+ foreach( $dir_tree as $filepath ){
575
+ if( is_file($filepath) ){
576
+ $removed = @unlink($filepath);
577
+
578
+ if( !$removed ){
579
+ $all_removed = FALSE;
580
+ }
581
+ }
582
+
583
+ elseif( is_dir($filepath) ){
584
+ $dirs_only[] = $filepath;
585
+ }
586
+ }
587
+
588
+ if( !function_exists('sucuriscan_strlen_diff') ){
589
+ /**
590
+ * Evaluates the difference between the length of two strings.
591
+ *
592
+ * @param string $a First string of characters that will be measured.
593
+ * @param string $b Second string of characters that will be measured.
594
+ * @return integer The difference in length between the two strings.
595
+ */
596
+ function sucuriscan_strlen_diff( $a='', $b='' ){
597
+ return strlen($b) - strlen($a);
598
+ }
599
+ }
600
+
601
+ usort($dirs_only, 'sucuriscan_strlen_diff');
602
+
603
+ foreach( $dirs_only as $dir_path ){
604
+ @rmdir($dir_path);
605
+ }
606
  }
607
+
608
+ return $all_removed;
609
  }
610
 
 
611
  }
612
 
613
  /**
614
+ * Class responsible for the processing of all the tasks associated to the database.
615
  *
616
+ * Here are implemented the functions needed to rename tables, generate random names,
617
+ * change the Wordpress table prefix and modify the name of all the options linked to
618
+ * the previous database prefix.
619
  */
620
+ class SucuriScanDatabase extends SucuriScan {
 
 
 
621
 
622
+ /**
623
+ * List all database tables in a clean array of strings.
624
+ *
625
+ * @return array Array of strings.
626
+ */
627
+ public function get_dbtables(){
628
+ global $wpdb;
629
 
630
+ $table_names = array();
631
+ $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N);
632
 
633
+ foreach($tables as $table){
634
+ $table_names[] = $table[0];
635
+ }
 
 
 
 
 
 
636
 
637
+ return $table_names;
638
+ }
 
639
 
640
+ /**
641
+ * Set a new database table prefix to improve the security.
642
+ *
643
+ * @return void
644
+ */
645
+ public function new_table_prefix(){
646
+ $new_table_prefix = $this->random_char( rand(4,7) ).'_';
647
+ $this->set_table_prefix($new_table_prefix);
 
 
648
  }
649
 
650
+ /**
651
+ * Reset the database table prefix with the default value 'wp_'.
652
+ *
653
+ * @return void
654
+ */
655
+ public function reset_table_prefix(){
656
+ $this->set_table_prefix('wp_');
657
  }
658
 
659
+ /**
660
+ * Set a new table prefix and changes table names, options, configuration files, etc.
661
+ *
662
+ * @param string $new_table_prefix The new table prefix.
663
+ * @return void
664
+ */
665
+ private function set_table_prefix( $new_table_prefix='wp_' ){
666
+ $resp_parts = array();
667
 
668
+ // Set the new table prefix in the configuration file.
669
+ $resp_parts[] = $this->new_table_prefix_wpconfig($new_table_prefix);
 
 
 
 
 
 
 
 
 
670
 
671
+ // Update options table with the new table prefix.
672
+ $resp_parts[] = $this->new_table_prefix_optionstable($new_table_prefix);
673
 
674
+ // Update usermeta table with the new table prefix.
675
+ $resp_parts[] = $this->new_table_prefix_usermetatable($new_table_prefix);
676
 
677
+ // Rename table names with the new table prefix.
678
+ $resp_parts[] = $this->new_table_prefix_tablerename($new_table_prefix);
 
 
 
 
 
 
679
 
680
+ foreach( $resp_parts as $response ){
681
+ if( $response['process'] !== TRUE ){
682
+ sucuriscan_error( $response['message'] );
683
+ }
684
+ }
685
+ }
686
 
687
+ /**
688
+ * Using the new database table prefix, it modifies the main configuration file with that new value.
689
+ *
690
+ * @param string $new_table_prefix
691
+ * @return array An array with two default indexes containing the result of the operation and a message.
692
+ */
693
+ private function new_table_prefix_wpconfig( $new_table_prefix='' ){
694
+ global $wpdb;
695
 
696
+ $response = array( 'process'=>FALSE, 'message'=>'' );
697
+ $wp_config_path = sucuriscan_get_wpconfig_path();
698
 
699
+ if( file_exists($wp_config_path) ){
700
+ @chmod($wp_config_path, 0777);
701
 
702
+ if( is_writable($wp_config_path) ){
703
+ $new_wpconfig = '';
704
+ $wpconfig_lines = @file($wp_config_path);
705
 
706
+ foreach( $wpconfig_lines as $line ){
707
+ $line = str_replace("\n", '', $line);
 
 
708
 
709
+ if( preg_match('/.*\$table_prefix([ ]+)?=.*/', $line, $match) ){
710
+ $line = str_replace($wpdb->prefix, $new_table_prefix, $match[0]);
711
+ }
712
+
713
+ $new_wpconfig .= "{$line}\n";
714
+ }
715
+
716
+ $handle = fopen($wp_config_path, 'w');
717
+ @fwrite($handle, $new_wpconfig);
718
+ @fclose($handle);
719
+ @chmod($wp_config_path, 0644);
720
+
721
+ $response['process'] = TRUE;
722
+ $response['message'] = 'Main configuration file modified.';
723
+ } else {
724
+ $response['message'] = 'Main configuration file is not writable, you will need to put the new
725
+ table prefix <code>'.$new_table_prefix.'</code> manually in <code>wp-config.php</code>.';
726
+ }
727
+ } else {
728
+ $response['message'] = 'Main configuration file was not located: <code>'.$wp_config_path.'</code>.';
729
+ }
730
+
731
+ return $response;
732
+ }
733
+
734
+ /**
735
+ * Returns a list of all the tables in the selected database containing the same prefix.
736
+ *
737
+ * @param string $prefix A text string used to filter the tables with a specific prefix.
738
+ * @return array A list of all the tables with the prefix specified.
739
+ */
740
+ public function get_prefixed_tables( $prefix='' ){
741
+ global $wpdb;
742
+
743
+ $tables = array();
744
+ $prefix = empty($prefix) ? $wpdb->prefix : $prefix;
745
+ $db_tables = $this->get_dbtables();
746
+
747
+ foreach( $db_tables as $table_name ){
748
+ if( preg_match("/^{$prefix}/", $table_name) ){
749
+ $tables[] = $table_name;
750
+ }
751
+ }
752
+
753
+ return $tables;
754
+ }
755
+
756
+ /**
757
+ * Using the new database table prefix, it modifies the name of all tables with the new value.
758
+ *
759
+ * @param string $new_table_prefix
760
+ * @return array An array with two default indexes containing the result of the operation and a message.
761
+ */
762
+ private function new_table_prefix_tablerename( $new_table_prefix='' ){
763
+ global $wpdb;
764
+
765
+ $response = array( 'process'=>FALSE, 'message'=>'' );
766
+ $db_tables = $this->get_prefixed_tables();
767
+
768
+ $renamed_count = 0;
769
+ $total_tables = count($db_tables);
770
+ $tables_not_renamed = array();
771
+
772
+ foreach( $db_tables as $table_name ){
773
+ $table_new_name = $new_table_prefix . str_replace($wpdb->prefix, '', $table_name);
774
+ $sql = 'RENAME TABLE `%s` TO `%s`';
775
+
776
+ /* Don't use WPDB->Prepare() */
777
+ if( $wpdb->query(sprintf($sql, $table_name, $table_new_name))===FALSE ){
778
+ $tables_not_renamed[] = $table_name;
779
+ } else {
780
+ $renamed_count += 1;
781
+ }
782
+ }
783
+
784
+ $response['message'] = 'Database tables renamed: '.$renamed_count.' out of '.$total_tables;
785
+
786
+ if( $renamed_count>0 && $renamed_count==$total_tables ){
787
+ $response['process'] = TRUE;
788
+ $error = $wpdb->set_prefix($new_table_prefix);
789
+
790
+ if( is_wp_error($error) ){
791
+ foreach( $error->errors as $error_index=>$error_data ){
792
+ if( is_array($error_data) ){
793
+ foreach( $error_data as $error_data_value ){
794
+ $response['message'] .= chr(32) . $error_data_value . '.';
795
+ }
796
+ }
797
+ }
798
+ }
799
+ } else {
800
+ $response['message'] .= '<br>These tables were not renamed, you will need to do it manually:';
801
+ $response['message'] .= chr(32) . implode( ',' . chr(32), $table_not_renamed );
802
+ }
803
+
804
+ return $response;
805
+ }
806
+
807
+ /**
808
+ * Using the new database table prefix, it modifies the name of all options with the new value.
809
+ *
810
+ * @param string $new_table_prefix
811
+ * @return array An array with two default indexes containing the result of the operation and a message.
812
+ */
813
+ private function new_table_prefix_optionstable( $new_table_prefix='' ){
814
+ global $wpdb;
815
+
816
+ $response = array( 'process'=>TRUE, 'message'=>'' );
817
+ $results = $wpdb->get_results("SELECT option_id, option_name FROM {$wpdb->prefix}options WHERE option_name LIKE '{$wpdb->prefix}%'");
818
+
819
+ foreach( $results as $row ){
820
+ $row->new_option_name = $new_table_prefix.str_replace($wpdb->prefix, '', $row->option_name);
821
+ $sql = "UPDATE {$wpdb->prefix}options SET option_name=%s WHERE option_id=%s LIMIT 1";
822
+
823
+ if( $wpdb->query($wpdb->prepare($sql, $row->new_option_name, $row->option_id))===FALSE ){
824
+ $response['process'] = FALSE;
825
+ }
826
+ }
827
+
828
+ $response['message'] = $response['process']
829
+ ? 'Database table options updated.'
830
+ : 'Some entries in the database table <strong>Options</strong> were not updated';
831
+
832
+ return $response;
833
+ }
834
+
835
+ /**
836
+ * Using the new database table prefix, it modifies the name of all usermeta keys with the new value.
837
+ *
838
+ * @param string $new_table_prefix
839
+ * @return array An array with two default indexes containing the result of the operation and a message.
840
+ */
841
+ private function new_table_prefix_usermetatable( $new_table_prefix='' ){
842
+ global $wpdb;
843
+
844
+ $response = array( 'process'=>TRUE, 'message'=>'' );
845
+ $results = $wpdb->get_results("SELECT umeta_id, meta_key FROM {$wpdb->prefix}usermeta WHERE meta_key LIKE '{$wpdb->prefix}%'");
846
+
847
+ foreach( $results as $row ){
848
+ $row->new_meta_key = $new_table_prefix.str_replace($wpdb->prefix, '', $row->meta_key);
849
+ $sql = "UPDATE {$wpdb->prefix}usermeta SET meta_key=%s WHERE umeta_id=%s LIMIT 1";
850
+
851
+ if( $wpdb->query($wpdb->prepare($sql, $row->new_meta_key, $row->umeta_id))===FALSE ){
852
+ $response['process'] = FALSE;
853
+ }
854
+ }
855
+
856
+ $response['message'] = $response['process']
857
+ ? 'Database table usermeta updated.'
858
+ : 'Some entries in the database table <strong>UserMeta</strong> were not updated';
859
+
860
+ return $response;
861
+ }
862
+
863
+ }
864
+
865
+ /**
866
+ * Class responsible for the processing of the generation of backups for the database.
867
+ *
868
+ * Here are implemented the functions needed to get the list of databases, information
869
+ * of single tables, generate the backup file (which can be a SQL or Zip file), a way
870
+ * to download and remove old backup files.
871
+ */
872
+ class SucuriScanBackup extends SucuriScanDatabase {
873
+
874
+ /**
875
+ * Class constructor.
876
+ */
877
+ public function __construct(){
878
+ ini_set('memory_limit', '-1');
879
+ }
880
+
881
+ /**
882
+ * Generate a SQL or Zip file with the current state of the database in use.
883
+ *
884
+ * @return string Returns the SQL or Zip full file path created.
885
+ */
886
+ public function all_database(){
887
+ $sql_output = '';
888
+ $tables = $this->get_dbtables();
889
+
890
+ foreach($tables as $table_name){
891
+ $sql_output .= $this->single_table($table_name, TRUE);
892
+ $sql_output .= PHP_EOL.PHP_EOL;
893
+ }
894
+
895
+ $sql_output .= PHP_EOL.PHP_EOL;
896
+
897
+ return $this->generate_backup($sql_output);
898
+ }
899
+
900
+ /**
901
+ * Generate a SQL or Zip file containing all the content inside a single table in the current database.
902
+ *
903
+ * @param string $table_name Specify the table name to dump.
904
+ * @param boolean $batch_mode Specify whether if return the SQL generated or generate the SQL/Zip file.
905
+ * @return string Returns the SQL generated or the SQL/Zip full file path created.
906
+ */
907
+ public function single_table( $table_name='', $batch_mode=FALSE ){
908
+ global $wpdb;
909
+
910
+ if( $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'")==$table_name ){
911
+ $sql_output = '';
912
+
913
+ $results = $wpdb->get_results("SELECT * FROM `{$table_name}`", ARRAY_N);
914
+ $fields = $wpdb->get_col("DESCRIBE `{$table_name}`", 0);
915
+ $num_fields = count($fields);
916
+
917
+ $sql_output .= "DROP TABLE IF EXISTS `{$table_name}`;";
918
+ $table_definition = $wpdb->get_row("SHOW CREATE TABLE `{$table_name}`", ARRAY_N);
919
+ $sql_output .= PHP_EOL.PHP_EOL . $table_definition[1].';' . PHP_EOL.PHP_EOL;
920
+
921
+ foreach($results as $row){
922
+ $sql_output .= "INSERT INTO `{$table_name}` VALUES(";
923
+
924
+ for( $i=0; $i<$num_fields; $i++ ) {
925
+ $row[$i] = esc_sql($row[$i]);
926
+ $row[$i] = preg_replace('#'.PHP_EOL.'#', "\n", $row[$i]);
927
+
928
+ if( isset($row[$i]) ){
929
+ $sql_output .= "'{$row[$i]}'";
930
+ } else {
931
+ $sql_output .= "''";
932
+ }
933
+
934
+ if( $i < ($num_fields-1) ){
935
+ $sql_output .= ','.chr(32);
936
+ }
937
+ }
938
+
939
+ $sql_output .= ');'.PHP_EOL;
940
+ }
941
+
942
+ return $batch_mode===TRUE ? $sql_output : $this->generate_backup($sql_output);
943
+ }
944
+
945
+ return FALSE;
946
+ }
947
+
948
+ /**
949
+ * Create a SQL or Zip file with the passed content.
950
+ *
951
+ * @param string $content SQL generated.
952
+ * @return string|boolean Return FALSE or the SQL/Zip full file path created.
953
+ */
954
+ private function generate_backup( $content='' ){
955
+ $plugin_upload_folder = $this->datastore_folder_path();
956
+
957
+ if( is_writable($plugin_upload_folder) ){
958
+ $filename = 'sucuri-dbbackup-'.current_time('timestamp').'.sql';
959
+ $filepath = rtrim($plugin_upload_folder,'/').'/'.$filename;
960
+ $handle = @fopen($filepath, 'w+');
961
+ @fwrite($handle, $content);
962
+ @fclose($handle);
963
+
964
+ if( class_exists('ZipArchive') ){
965
+ $package_path = $filepath.'.zip';
966
+
967
+ $zip = new ZipArchive();
968
+ $archive = $zip->open($package_path, ZipArchive::CREATE);
969
+ $zip->addFile($filepath, $filename);
970
+ $zip->close();
971
+
972
+ if( file_exists($package_path) && filesize($package_path)>0 ){
973
+ @unlink($filepath); /* Remove the old SQL file to keep the new Zip file */
974
+ $filename = $filename.'.zip';
975
+ $filepath = $package_path;
976
+ }
977
+ }
978
+
979
+ return ( file_exists($filepath) && filesize($filepath)>0 ) ? $filepath : FALSE;
980
+ } else {
981
+ sucuriscan_error('Upload folder is not writable, can not continue with the backup: <code>'.$plugin_upload_folder.'</code>');
982
+ }
983
+
984
+ return FALSE;
985
+ }
986
+
987
+ /**
988
+ * Get extra information of the filepath specified, including full filepath, filesize, timeatime, etc.
989
+ *
990
+ * @param string $filepath Relative path of the file.
991
+ * @return object Extra information of the file specified.
992
+ */
993
+ private function get_backup_file_info( $filepath='' ){
994
+ $backup_file = FALSE;
995
+ $plugin_upload_folder = $this->datastore_folder_path();
996
+
997
+ if( file_exists($filepath) && is_file($filepath) && is_readable($filepath) ){
998
+ $filesize = filesize($filepath);
999
+ $filename_exploded = explode('.', $filepath);
1000
+ $backup_file = (object)array(
1001
+ 'filepath' => $filepath,
1002
+ 'filename' => basename($filepath),
1003
+ 'filesize' => $filesize,
1004
+ 'filehumansize' => $this->human_filesize($filesize),
1005
+ 'filetime' => fileatime($filepath),
1006
+ 'fileext' => array_pop($filename_exploded),
1007
+ 'fileurl' => site_url( str_replace(ABSPATH, '', rtrim($plugin_upload_folder,'/').'/'.basename($filepath)) ),
1008
+ );
1009
+ }
1010
+
1011
+ return $backup_file;
1012
+ }
1013
+
1014
+ /**
1015
+ * List all database backup files generated with extra information.
1016
+ *
1017
+ * @return array Key-value list of backup files.
1018
+ */
1019
+ public function get_backup_files(){
1020
+ $backup_files = array();
1021
+ $plugin_upload_folder = $this->datastore_folder_path();
1022
+
1023
+ $files = glob( rtrim($plugin_upload_folder,'/').'/sucuri-dbbackup*.{sql,zip}', GLOB_BRACE );
1024
+ if( is_array($files) && !empty($files) ){
1025
+ $files_ordered = array_reverse($files);
1026
+
1027
+ foreach( $files_ordered as $filepath ){
1028
+ $backup_file = $this->get_backup_file_info($filepath);
1029
+
1030
+ if( $backup_file ){
1031
+ $backup_files[] = $backup_file;
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ return $backup_files;
1037
+ }
1038
+
1039
+ /**
1040
+ * Get extra information of the filename specified, including full filepath, filesize, timeatime, etc.
1041
+ *
1042
+ * @param string $filename Simple filename with extension.
1043
+ * @return object Extra information of the file specified.
1044
+ */
1045
+ public function get_backup_file_from_filename( $filename='' ){
1046
+ $plugin_upload_folder = $this->datastore_folder_path();
1047
+ $filepath = rtrim($plugin_upload_folder,'/').'/'.$filename;
1048
+
1049
+ return $this->get_backup_file_info($filepath);
1050
+ }
1051
+
1052
+ /**
1053
+ * Remove a list of backup files selected by the user through the plugin interface.
1054
+ *
1055
+ * @param array $files List of filenames that will be deleted.
1056
+ * @return void
1057
+ */
1058
+ public function remove_backup_file( $files=array() ){
1059
+ $files = is_array($files) ? $files : array($files);
1060
+
1061
+ if( !empty($files) ){
1062
+ $num_files_to_remove = count($files);
1063
+ $num_removed_files = 0;
1064
+
1065
+ foreach($files as $filename){
1066
+ $backup_file = $this->get_backup_file_from_filename($filename);
1067
+
1068
+ if($backup_file && @unlink($backup_file->filepath) ){
1069
+ $num_removed_files += 1;
1070
+ }
1071
+ }
1072
+
1073
+ $message = sprintf( 'Database backups removed: %d out of %d', $num_removed_files, $num_files_to_remove );
1074
+
1075
+ if( $num_removed_files == $num_files_to_remove ){
1076
+ sucuriscan_info( $message );
1077
+ } else {
1078
+ sucuriscan_error( $message );
1079
+ }
1080
+ } else {
1081
+ sucuriscan_error('You did not select any backup file to remove.');
1082
+ }
1083
+ }
1084
+
1085
+ }
1086
+
1087
+ /**
1088
+ * File-based cache library.
1089
+ *
1090
+ * WP_Object_Cache [1] is WordPress' class for caching data which may be
1091
+ * computationally expensive to regenerate, such as the result of complex
1092
+ * database queries. However the object cache is non-persistent. This means that
1093
+ * data stored in the cache resides in memory only and only for the duration of
1094
+ * the request. Cached data will not be stored persistently across page loads
1095
+ * unless of the installation of a 3party persistent caching plugin [2].
1096
+ *
1097
+ * [1] http://codex.wordpress.org/Class_Reference/WP_Object_Cache
1098
+ * [2] http://codex.wordpress.org/Class_Reference/WP_Object_Cache#Persistent_Caching
1099
+ */
1100
+ class SucuriScanCache extends SucuriScan {
1101
+
1102
+ /**
1103
+ * The unique name (or identifier) of the file with the data.
1104
+ *
1105
+ * The file should be located in the same folder where the dynamic data
1106
+ * generated by the plugin is stored, and using the following format [1], it
1107
+ * most be a PHP file because it is expected to have an exit point in the first
1108
+ * line of the file causing it to stop the execution if a unauthorized user
1109
+ * tries to access it directly.
1110
+ *
1111
+ * [1] /public/data/sucuri-DATASTORE.php
1112
+ *
1113
+ * @var null|string
1114
+ */
1115
+ private $datastore = NULL;
1116
+
1117
+ /**
1118
+ * The full path of the datastore file.
1119
+ *
1120
+ * @var string
1121
+ */
1122
+ private $datastore_path = '';
1123
+
1124
+ /**
1125
+ * Whether the datastore file is usable or not.
1126
+ *
1127
+ * This variable will only be TRUE if the datastore file specified exists, is
1128
+ * writable and readable, in any other case it will always be FALSE.
1129
+ *
1130
+ * @var boolean
1131
+ */
1132
+ private $usable_datastore = FALSE;
1133
+
1134
+ /**
1135
+ * Class constructor.
1136
+ *
1137
+ * @param string $datastore Unique name (or identifier) of the file with the data.
1138
+ * @return void
1139
+ */
1140
+ public function __construct( $datastore='' ){
1141
+ $this->datastore = $datastore;
1142
+ $this->datastore_path = $this->datastore_file_path();
1143
+ $this->usable_datastore = (bool) $this->datastore_path;
1144
+ }
1145
+
1146
+ /**
1147
+ * Default attributes for every datastore file.
1148
+ *
1149
+ * @return string Default attributes for every datastore file.
1150
+ */
1151
+ private function datastore_default_info(){
1152
+ $attrs = array(
1153
+ 'datastore' => $this->datastore,
1154
+ 'created_on' => time(),
1155
+ 'updated_on' => time(),
1156
+ );
1157
+
1158
+ return $attrs;
1159
+ }
1160
+
1161
+ /**
1162
+ * Default content of every datastore file.
1163
+ *
1164
+ * @param array $finfo Rainbow table with the key names and decoded values.
1165
+ * @return string Default content of every datastore file.
1166
+ */
1167
+ private function datastore_info( $finfo=array() ){
1168
+ $attrs = $this->datastore_default_info();
1169
+ $info_is_available = (bool) isset($finfo['info']);
1170
+ $info = "<?php\n";
1171
+
1172
+ foreach( $attrs as $attr_name => $attr_value ){
1173
+ if(
1174
+ $info_is_available
1175
+ && $attr_name != 'updated_on'
1176
+ && isset($finfo['info'][$attr_name])
1177
+ ){
1178
+ $attr_value = $finfo['info'][$attr_name];
1179
+ }
1180
+
1181
+ $info .= sprintf( "// %s=%s;\n", $attr_name, $attr_value );
1182
+ }
1183
+
1184
+ $info .= "exit(0);\n";
1185
+ $info .= "?>\n";
1186
+
1187
+ return $info;
1188
+ }
1189
+
1190
+ /**
1191
+ * Check if the datastore file exists, if it's writable and readable by the same
1192
+ * user running the server, in case that it does not exists the function will
1193
+ * tries to create it by itself with the right permissions to use it.
1194
+ *
1195
+ * @return string The full path where the datastore file is located, FALSE otherwise.
1196
+ */
1197
+ private function datastore_file_path(){
1198
+ if( !is_null($this->datastore) ){
1199
+ $folder_path = $this->datastore_folder_path();
1200
+ $file_path = $folder_path . 'sucuri-' . $this->datastore . '.php';
1201
+
1202
+ // Create the datastore file is it does not exists and the folder is writable.
1203
+ if(
1204
+ !file_exists($file_path)
1205
+ && is_writable($folder_path)
1206
+ ){
1207
+ @file_put_contents( $file_path, $this->datastore_info(), LOCK_EX );
1208
+ }
1209
+
1210
+ // Continue the operation after an attemp to create the datastore file.
1211
+ if(
1212
+ file_exists($file_path)
1213
+ && is_writable($file_path)
1214
+ && is_readable($file_path)
1215
+ ){
1216
+ return $file_path;
1217
+ }
1218
+ }
1219
+
1220
+ return FALSE;
1221
+ }
1222
+
1223
+ /**
1224
+ * Check whether a key has a valid name or not.
1225
+ *
1226
+ * @param string $key Unique name to identify the data in the datastore file.
1227
+ * @return boolean TRUE if the format of the key name is valid, FALSE otherwise.
1228
+ */
1229
+ private function valid_key_name( $key='' ){
1230
+ $key = trim($key);
1231
+
1232
+ if( !empty($key) ){
1233
+ return (bool) preg_match('/^([a-zA-Z_]+)$/', $key);
1234
+ }
1235
+
1236
+ return FALSE;
1237
+ }
1238
+
1239
+ /**
1240
+ * Update the content of the datastore file with the new entries.
1241
+ *
1242
+ * @param array $finfo Rainbow table with the key names and decoded values.
1243
+ * @return boolean TRUE if the operation finished successfully, FALSE otherwise.
1244
+ */
1245
+ private function save_new_entries( $finfo=array() ){
1246
+ $data_string = $this->datastore_info($finfo);
1247
+
1248
+ if( !empty($finfo) ){
1249
+ foreach( $finfo['entries'] as $key => $data ){
1250
+ if( $this->valid_key_name($key) ){
1251
+ $data = json_encode($data);
1252
+ $data_string .= sprintf( "%s:%s\n", $key, $data );
1253
+ }
1254
+ }
1255
+ }
1256
+
1257
+ $saved = @file_put_contents( $this->datastore_path, $data_string, LOCK_EX );
1258
+
1259
+ return (bool) $saved;
1260
+ }
1261
+
1262
+ /**
1263
+ * Retrieve and parse the datastore file, and generate a rainbow table with the
1264
+ * key names and decoded data as the values of each entry. Duplicated key names
1265
+ * will be removed automatically while adding the keys to the array and their
1266
+ * values will correspond to the first occurrence found in the file.
1267
+ *
1268
+ * @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
1269
+ * @return array Rainbow table with the key names and decoded values.
1270
+ */
1271
+ private function get_datastore_content( $assoc=FALSE ){
1272
+ $data_object = array(
1273
+ 'info' => array(),
1274
+ 'entries' => array(),
1275
+ );
1276
+
1277
+ if( $this->usable_datastore ){
1278
+ $data_lines = @file($this->datastore_path);
1279
+
1280
+ if( !empty($data_lines) ){
1281
+ foreach( $data_lines as $line ){
1282
+ $line = trim($line);
1283
+
1284
+ if( preg_match('/^\/\/ ([a-z_]+)=(.*);$/', $line, $match) ){
1285
+ $data_object['info'][$match[1]] = $match[2];
1286
+ }
1287
+
1288
+ elseif( preg_match('/^([a-z_]+):(.+)/', $line, $match) ){
1289
+ if(
1290
+ $this->valid_key_name($match[1])
1291
+ && !array_key_exists($match[1], $data_object)
1292
+ ){
1293
+ $data_object['entries'][$match[1]] = json_decode( $match[2], $assoc );
1294
+ }
1295
+ }
1296
+ }
1297
+ }
1298
+ }
1299
+
1300
+ return $data_object;
1301
+ }
1302
+
1303
+ /**
1304
+ * Retrieve the headers of the datastore file.
1305
+ *
1306
+ * Each datastore file has a list of attributes at the beginning of the it with
1307
+ * information like the creation and last update time. If you are extending the
1308
+ * functionality of these headers please refer to the function that contains the
1309
+ * default attributes and their values [1].
1310
+ *
1311
+ * [1] SucuriScanCache::datastore_default_info()
1312
+ *
1313
+ * @return array Default content of every datastore file.
1314
+ */
1315
+ public function get_datastore_info(){
1316
+ $finfo = $this->get_datastore_content();
1317
+
1318
+ if( !empty($finfo['info']) ){
1319
+ return $finfo['info'];
1320
+ }
1321
+
1322
+ return FALSE;
1323
+ }
1324
+
1325
+ /**
1326
+ * Get the total number of unique entries in the datastore file.
1327
+ *
1328
+ * @param array $finfo Rainbow table with the key names and decoded values.
1329
+ * @return integer Total number of unique entries found in the datastore file.
1330
+ */
1331
+ public function get_count( $finfo=NULL ){
1332
+ if( !is_array($finfo) ){
1333
+ $finfo = $this->get_datastore_content();
1334
+ }
1335
+
1336
+ return count($finfo['entries']);
1337
+ }
1338
+
1339
+ /**
1340
+ * Check whether the last update time of the datastore file has surpassed the
1341
+ * lifetime specified for a key name. This function is the only one related with
1342
+ * the caching process, any others besides this are just methods used to handle
1343
+ * the data inside those files.
1344
+ *
1345
+ * @param integer $lifetime Life time of the key in the datastore file.
1346
+ * @param array $finfo Rainbow table with the key names and decoded values.
1347
+ * @return boolean TRUE if the life time of the data has expired, FALSE otherwise.
1348
+ */
1349
+ public function data_has_expired( $lifetime=0, $finfo=NULL ){
1350
+ if( is_null($finfo) ){
1351
+ $finfo = $this->get_datastore_content();
1352
+ }
1353
+
1354
+ if( $lifetime > 0 && !empty($finfo['info']) ){
1355
+ $diff_time = time() - intval($finfo['info']['updated_on']);
1356
+
1357
+ if( $diff_time >= $lifetime ){
1358
+ return TRUE;
1359
+ }
1360
+ }
1361
+
1362
+ return FALSE;
1363
+ }
1364
+
1365
+ /**
1366
+ * Execute the action using the key name and data specified.
1367
+ *
1368
+ * @param string $key Unique name to identify the data in the datastore file.
1369
+ * @param string $data Mixed data stored in the datastore file following the unique key name.
1370
+ * @param string $action Either add, set, get, or delete.
1371
+ * @param integer $lifetime Life time of the key in the datastore file.
1372
+ * @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
1373
+ * @return boolean TRUE if the operation finished successfully, FALSE otherwise.
1374
+ */
1375
+ private function handle_key_data( $key='', $data=NULL, $action='', $lifetime=0, $assoc=FALSE ){
1376
+ if( preg_match('/^(add|set|get|delete)$/', $action) ){
1377
+ if( $this->valid_key_name($key) && $this->usable_datastore ){
1378
+ $finfo = $this->get_datastore_content($assoc);
1379
+
1380
+ switch( $action ){
1381
+ case 'add': /* no_break */
1382
+ case 'set':
1383
+ $finfo['entries'][$key] = $data;
1384
+ return $this->save_new_entries($finfo);
1385
+ break;
1386
+ case 'get':
1387
+ if(
1388
+ !$this->data_has_expired($lifetime, $finfo)
1389
+ && array_key_exists($key, $finfo['entries'])
1390
+ ){
1391
+ return $finfo['entries'][$key];
1392
+ }
1393
+ break;
1394
+ case 'delete':
1395
+ unset($finfo['entries'][$key]);
1396
+ return $this->save_new_entries($finfo);
1397
+ break;
1398
+ }
1399
+ }
1400
+ }
1401
+
1402
+ return FALSE;
1403
+ }
1404
+
1405
+ /**
1406
+ * JSON-encode the data and store it in the datastore file identifying it with
1407
+ * the key name, the data will be added to the file even if the key is
1408
+ * duplicated, but when getting the value of the same key later again it will
1409
+ * return only the value of the first occurrence found in the file.
1410
+ *
1411
+ * @param string $key Unique name to identify the data in the datastore file.
1412
+ * @param string $data Mixed data stored in the datastore file following the unique key name.
1413
+ * @return boolean TRUE if the data was stored successfully, FALSE otherwise.
1414
+ */
1415
+ public function add( $key='', $data='' ){
1416
+ return $this->handle_key_data( $key, $data, 'add' );
1417
+ }
1418
+
1419
+ /**
1420
+ * Update the data of all the key names matching the one specified.
1421
+ *
1422
+ * @param string $key Unique name to identify the data in the datastore file.
1423
+ * @param string $data Mixed data stored in the datastore file following the unique key name.
1424
+ * @return boolean TRUE if the data was stored successfully, FALSE otherwise.
1425
+ */
1426
+ public function set( $key='', $data='' ){
1427
+ return $this->handle_key_data( $key, $data, 'set' );
1428
+ }
1429
+
1430
+ /**
1431
+ * Retrieve the first occurrence of the key found in the datastore file.
1432
+ *
1433
+ * @param string $key Unique name to identify the data in the datastore file.
1434
+ * @param integer $lifetime Life time of the key in the datastore file.
1435
+ * @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
1436
+ * @return string Mixed data stored in the datastore file following the unique key name.
1437
+ */
1438
+ public function get( $key='', $lifetime=0, $assoc=FALSE ){
1439
+ $assoc = ( $assoc == 'array' ? TRUE : $assoc );
1440
+
1441
+ return $this->handle_key_data( $key, NULL, 'get', $lifetime, $assoc );
1442
+ }
1443
+
1444
+ /**
1445
+ * Delete any entry from the datastore file matching the key name specified.
1446
+ *
1447
+ * @param string $key Unique name to identify the data in the datastore file.
1448
+ * @return boolean TRUE if the entries were removed, FALSE otherwise.
1449
+ */
1450
+ public function delete( $key='' ){
1451
+ return $this->handle_key_data( $key, NULL, 'delete' );
1452
+ }
1453
+
1454
+ /**
1455
+ * Remove all the entries from the datastore file.
1456
+ *
1457
+ * @return boolean Always TRUE unless the datastore file is not writable.
1458
+ */
1459
+ public function flush(){
1460
+ $finfo = $this->get_datastore_content();
1461
+
1462
+ return $this->save_new_entries($finfo);
1463
+ }
1464
+
1465
+ }
1466
+
1467
+ /**
1468
+ * Check whether the current site is working as a multi-site instance.
1469
+ *
1470
+ * @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
1471
+ */
1472
+ function sucuriscan_is_multisite(){
1473
+ if( function_exists('is_multisite') && is_multisite() ){ return TRUE; }
1474
+ return FALSE;
1475
+ }
1476
+
1477
+ /**
1478
+ * Check whether the IP address specified is a valid IPv4 format.
1479
+ *
1480
+ * @param string $remote_addr The host IP address.
1481
+ * @return boolean TRUE if the address specified is a valid IPv4 format, FALSE otherwise.
1482
+ */
1483
+ function sucuriscan_is_valid_ipv4( $remote_addr='' ){
1484
+ if( preg_match('/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/', $remote_addr, $match) ){
1485
+ for( $i=0; $i<4; $i++ ){
1486
+ if( $match[$i] > 255 ){ return FALSE; }
1487
+ }
1488
+
1489
+ return TRUE;
1490
+ }
1491
+
1492
+ return FALSE;
1493
+ }
1494
+
1495
+ if( !function_exists('sucuriscan_init') ){
1496
+ /**
1497
+ * Initialization code for the plugin.
1498
+ *
1499
+ * The initial variables and information needed by the plugin during the
1500
+ * execution of other functions will be generated. Things like the real IP
1501
+ * address of the client when it has been forwarded or it's behind an external
1502
+ * service like a Proxy.
1503
+ *
1504
+ * @return void
1505
+ */
1506
+ function sucuriscan_init(){
1507
+ if(
1508
+ isset($_SERVER['HTTP_X_FORWARDED_FOR'])
1509
+ && preg_match("/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/", $_SERVER['HTTP_X_FORWARDED_FOR'])
1510
+ && sucuriscan_is_valid_ipv4($_SERVER['HTTP_X_FORWARDED_FOR'])
1511
+ ){
1512
+ $_SERVER['SUCURIREAL_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
1513
+ $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
1514
+ }
1515
+ }
1516
+
1517
+ add_action('init', 'sucuriscan_init', 1);
1518
+ }
1519
+
1520
+ if( !function_exists('sucuriscan_create_uploaddir') ){
1521
+ /**
1522
+ * Create a folder in the WordPress upload directory where the plugin will
1523
+ * store all the temporal or dynamic information.
1524
+ *
1525
+ * @return void
1526
+ */
1527
+ function sucuriscan_create_uploaddir(){
1528
+ $plugin_upload_folder = sucuriscan_dir_filepath();
1529
+
1530
+ if( !file_exists($plugin_upload_folder) ){
1531
+ if( @mkdir($plugin_upload_folder) ){
1532
+ sucuriscan_lastlogins_datastore_exists();
1533
+ } else {
1534
+ sucuriscan_error(
1535
+ 'Data folder does not exists and could not be created. You will need to
1536
+ create this folder manually and give it write permissions:<br><br><code>'
1537
+ . $plugin_upload_folder . '</code>'
1538
+ );
1539
+ }
1540
+ }
1541
+ }
1542
+
1543
+ add_action('admin_init', 'sucuriscan_create_uploaddir');
1544
+ }
1545
+
1546
+ if( !function_exists('sucuriscan_admin_script_style_registration') ){
1547
+ /**
1548
+ * Define which javascript and css files will be loaded in the header of the page.
1549
+ * @return void
1550
+ */
1551
+ function sucuriscan_admin_script_style_registration(){
1552
+ $asset_version = '';
1553
+
1554
+ if( strlen(SUCURISCAN_PLUGIN_CHECKSUM) >= 7 ){
1555
+ $asset_version = substr(SUCURISCAN_PLUGIN_CHECKSUM, 0, 7);
1556
+ }
1557
+
1558
+ wp_register_style( 'sucuriscan', SUCURI_URL . '/inc/css/sucuriscan-default-css.css', array(), $asset_version );
1559
+ wp_register_script( 'sucuriscan', SUCURI_URL . '/inc/js/sucuriscan-scripts.js', array(), $asset_version );
1560
+
1561
+ wp_enqueue_style( 'sucuriscan' );
1562
+ wp_enqueue_script( 'sucuriscan' );
1563
+ }
1564
+
1565
+ add_action( 'admin_enqueue_scripts', 'sucuriscan_admin_script_style_registration', 1 );
1566
+ }
1567
+
1568
+ /**
1569
+ * Returns the system filepath to the relevant user uploads directory for this
1570
+ * site. This is a multisite capable function.
1571
+ *
1572
+ * @param string $path The relative path that needs to be completed to get the absolute path.
1573
+ * @return string The full filesystem path including the directory specified.
1574
+ */
1575
+ function sucuriscan_dir_filepath($path = ''){
1576
+ return SucuriScan::datastore_folder_path($path);
1577
+ }
1578
+
1579
+ /**
1580
+ * List an associative array with the sub-pages of this plugin.
1581
+ *
1582
+ * @param boolean $for_navbar Either TRUE or FALSE indicanting that the first page will be named Dashboard.
1583
+ * @return array List of pages and sub-pages of this plugin.
1584
+ */
1585
+ function sucuriscan_pages( $for_navbar=FALSE ){
1586
+ $pages = array(
1587
+ 'sucuriscan' => 'Dashboard',
1588
+ 'sucuriscan_scanner' => 'Malware Scan',
1589
+ 'sucuriscan_monitoring' => 'Firewall (WAF)',
1590
+ 'sucuriscan_hardening' => 'Hardening',
1591
+ 'sucuriscan_posthack' => 'Post-Hack',
1592
+ 'sucuriscan_lastlogins' => 'Last Logins',
1593
+ 'sucuriscan_settings' => 'Settings',
1594
+ 'sucuriscan_infosys' => 'Site Info',
1595
+ );
1596
+
1597
+ return $pages;
1598
+ }
1599
+
1600
+ /**
1601
+ * Generate the menu and submenus for the plugin in the admin interface.
1602
+ *
1603
+ * @return void
1604
+ */
1605
+ function sucuriscan_menu(){
1606
+ // Add main menu link.
1607
+ add_menu_page(
1608
+ 'Sucuri Security',
1609
+ 'Sucuri Security',
1610
+ 'manage_options',
1611
+ 'sucuriscan',
1612
+ 'sucuriscan_page',
1613
+ SUCURI_URL . '/inc/images/menu-icon.png'
1614
+ );
1615
+
1616
+ $sub_pages = sucuriscan_pages();
1617
+
1618
+ foreach( $sub_pages as $sub_page_func => $sub_page_title ){
1619
+ $page_func = $sub_page_func . '_page';
1620
+
1621
+ add_submenu_page(
1622
+ 'sucuriscan',
1623
+ $sub_page_title,
1624
+ $sub_page_title,
1625
+ 'manage_options',
1626
+ $sub_page_func,
1627
+ $page_func
1628
+ );
1629
+ }
1630
+ }
1631
+
1632
+ if( !function_exists('sucuriscan_handle_old_plugin') ){
1633
+ /**
1634
+ * Remove the old Sucuri plugins considering that with the new version (after
1635
+ * 1.6.0) all the functionality of the others will be merged here, this will
1636
+ * remove duplicated functionality, duplicated bugs and/or duplicated
1637
+ * maintenance reports allowing us to focus in one unique project.
1638
+ *
1639
+ * @return void
1640
+ */
1641
+ function sucuriscan_handle_old_plugin(){
1642
+ $sucuri_fileinfo = new SucuriScanFileInfo();
1643
+ $sucuri_fileinfo->ignore_files = FALSE;
1644
+ $sucuri_fileinfo->ignore_directories = FALSE;
1645
+
1646
+ $plugins = array(
1647
+ 'sucuri-wp-plugin/sucuri.php',
1648
+ 'sucuri-cloudproxy-waf/cloudproxy.php',
1649
+ );
1650
+
1651
+ foreach( $plugins as $plugin ){
1652
+ $plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin );
1653
+
1654
+ if( file_exists($plugin_directory) ){
1655
+ if( is_plugin_active($plugin) ){
1656
+ deactivate_plugins($plugin);
1657
+ }
1658
+
1659
+ $plugin_removed = $sucuri_fileinfo->remove_directory_tree($plugin_directory);
1660
+ }
1661
+ }
1662
+ }
1663
+
1664
+ add_action('admin_init', 'sucuriscan_handle_old_plugin');
1665
+ }
1666
+
1667
+ /**
1668
+ * Initialize the execute of the main plugin's functions.
1669
+ *
1670
+ * This will load the menu options in the WordPress administrator panel, and
1671
+ * execute the bootstrap function of the plugin.
1672
+ */
1673
+ add_action('admin_menu', 'sucuriscan_menu');
1674
+ add_action('sucuriscan_scheduled_scan', 'sucuriscan_filesystem_scan');
1675
+ remove_action('wp_head', 'wp_generator');
1676
+
1677
+ /**
1678
+ * Validate email address.
1679
+ *
1680
+ * This use the native PHP function filter_var which is available in PHP >=
1681
+ * 5.2.0 if it is not found in the interpreter this function will sue regular
1682
+ * expressions to check whether the email address passed is valid or not.
1683
+ *
1684
+ * @see http://www.php.net/manual/en/function.filter-var.php
1685
+ *
1686
+ * @param string $email The string that will be validated as an email address.
1687
+ * @return boolean TRUE if the email address passed to the function is valid, FALSE if not.
1688
+ */
1689
+ function is_valid_email( $email='' ){
1690
+ if( function_exists('filter_var') ){
1691
+ return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
1692
+ } else {
1693
+ $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix';
1694
+ return (bool) preg_match($pattern, $email);
1695
+ }
1696
+ }
1697
+
1698
+ /**
1699
+ * Send a message to a specific email address.
1700
+ *
1701
+ * @param string $to The email address of the recipient that will receive the message.
1702
+ * @param string $subject The reason of the message that will be sent.
1703
+ * @param string $message Body of the message that will be sent.
1704
+ * @param array $data_set Optional parameter to add more information to the notification.
1705
+ * @param boolean $debug TRUE if you want to test the function printing the email before sending it.
1706
+ * @return void
1707
+ */
1708
+ function sucuriscan_send_mail( $to='', $subject='', $message='', $data_set=array(), $debug=FALSE ){
1709
+ $headers = array();
1710
+ $subject = ucwords(strtolower($subject));
1711
+ $wp_domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : get_option('siteurl');
1712
+
1713
+ if( sucuriscan_get_option('sucuriscan_prettify_mails') == 'enabled' ){
1714
+ $headers = array( 'Content-type: text/html' );
1715
+ $data_set['PrettifyType'] = 'pretty';
1716
+ }
1717
+
1718
+ $message = sucuriscan_prettify_mail($subject, $message, $data_set);
1719
+
1720
+ if( $debug ){
1721
+ die($message);
1722
+ } else {
1723
+ wp_mail($to, "Sucuri WP Notification: {$wp_domain} - {$subject}" , $message, $headers);
1724
+ }
1725
+ }
1726
+
1727
+ /**
1728
+ * Generate a HTML version of the message that will be sent through an email.
1729
+ *
1730
+ * @param string $subject The reason of the message that will be sent.
1731
+ * @param string $message Body of the message that will be sent.
1732
+ * @param array $data_set Optional parameter to add more information to the notification.
1733
+ * @return string The message formatted in a HTML template.
1734
+ */
1735
+ function sucuriscan_prettify_mail( $subject='', $message='', $data_set=array() ){
1736
+ $prettify_type = isset($data_set['PrettifyType']) ? $data_set['PrettifyType'] : 'simple';
1737
+ $template_name = 'notification-' . $prettify_type;
1738
+ $remote_addr = sucuriscan_get_remoteaddr();
1739
+ $user = wp_get_current_user();
1740
+ $display_name = '';
1741
+
1742
+ if(
1743
+ $user instanceof WP_User
1744
+ && isset($user->user_login)
1745
+ && !empty($user->user_login)
1746
+ ){
1747
+ $display_name = sprintf( 'User: %s (%s)', $user->display_name, $user->user_login );
1748
+ }
1749
+
1750
+ $mail_variables = array(
1751
+ 'TemplateTitle' => 'Sucuri WP Notification',
1752
+ 'Subject' => $subject,
1753
+ 'Website' => get_option('siteurl'),
1754
+ 'RemoteAddress' => $remote_addr,
1755
+ 'Message' => $message,
1756
+ 'User' => $display_name,
1757
+ 'Time' => current_time('mysql'),
1758
+ );
1759
+
1760
+ foreach($data_set as $var_key=>$var_value){
1761
+ $mail_variables[$var_key] = $var_value;
1762
+ }
1763
+
1764
+ return sucuriscan_get_section( $template_name, $mail_variables );
1765
+ }
1766
+
1767
+ /**
1768
+ * Prints a HTML alert in the WordPress admin interface.
1769
+ *
1770
+ * @param string $type The type of alert, it can be either Updated or Error.
1771
+ * @param string $message The message that will be printed in the alert.
1772
+ * @return void
1773
+ */
1774
+ function sucuriscan_admin_notice($type='updated', $message=''){
1775
+ $alert_id = rand(100, 999);
1776
+ if( !empty($message) ): ?>
1777
+ <div id="sucuriscan-alert-<?php echo $alert_id; ?>" class="<?php echo $type; ?> sucuriscan-alert sucuriscan-alert-<?php echo $type; ?>">
1778
+ <a href="javascript:void(0)" class="close" onclick="sucuriscan_alert_close('<?php echo $alert_id; ?>')">&times;</a>
1779
+ <p><?php _e($message); ?></p>
1780
+ </div>
1781
+ <?php endif;
1782
+ }
1783
+
1784
+ /**
1785
+ * Prints a HTML alert of type ERROR in the WordPress admin interface.
1786
+ *
1787
+ * @param string $error_msg The message that will be printed in the alert.
1788
+ * @return void
1789
+ */
1790
+ function sucuriscan_error( $error_msg='' ){
1791
+ sucuriscan_admin_notice( 'error', '<b>Sucuri:</b> ' . $error_msg );
1792
+ }
1793
+
1794
+ /**
1795
+ * Prints a HTML alert of type INFO in the WordPress admin interface.
1796
+ *
1797
+ * @param string $info_msg The message that will be printed in the alert.
1798
+ * @return void
1799
+ */
1800
+ function sucuriscan_info( $info_msg='' ){
1801
+ sucuriscan_admin_notice( 'updated', '<b>Sucuri:</b> ' . $info_msg );
1802
+ }
1803
+
1804
+ /**
1805
+ * Verify the nonce of the previous page after a form submission. If the
1806
+ * validation fails the execution of the script will be stopped and a dead page
1807
+ * will be printed to the client using the official WordPress method.
1808
+ *
1809
+ * @return boolean Either TRUE or FALSE if the nonce is valid or not respectively.
1810
+ */
1811
+ function sucuriscan_check_page_nonce(){
1812
+ if( !empty($_POST) ){
1813
+ $nonce_name = 'sucuriscan_page_nonce';
1814
+
1815
+ if( !isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name], $nonce_name) ){
1816
+ wp_die(__('WordPress Nonce verification failed, try again going back and checking the form.') );
1817
+
1818
+ return FALSE;
1819
+ }
1820
+ }
1821
+
1822
+ return TRUE;
1823
+ }
1824
+
1825
+ /**
1826
+ * Replace all pseudo-variables from a string of characters.
1827
+ *
1828
+ * @param string $content The content of a template file which contains pseudo-variables.
1829
+ * @param array $params List of pseudo-variables that will be replaced in the template.
1830
+ * @return string The content of the template with the pseudo-variables replated.
1831
+ */
1832
+ function sucuriscan_replace_pseudovars( $content='', $params=array() ){
1833
+ if( is_array($params) ){
1834
+ foreach( $params as $tpl_key => $tpl_value ){
1835
+ $tpl_key = '%%SUCURI.' . $tpl_key . '%%';
1836
+ $content = str_replace( $tpl_key, $tpl_value, $content );
1837
+ }
1838
+
1839
+ return $content;
1840
+ }
1841
+
1842
+ return FALSE;
1843
+ }
1844
+
1845
+ /**
1846
+ * Complement the list of pseudo-variables that will be used in the base
1847
+ * template files, this will also generate the navigation bar and detect which
1848
+ * items in it are selected by the current page.
1849
+ *
1850
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1851
+ * @return array A complementary list of pseudo-variables for the template files.
1852
+ */
1853
+ function sucuriscan_links_and_navbar( $params=array() ){
1854
+ $params = is_array($params) ? $params : array();
1855
+ $sub_pages = sucuriscan_pages(TRUE);
1856
+
1857
+ $params['Navbar'] = '';
1858
+ $params['CurrentPageFunc'] = isset($_GET['page']) ? $_GET['page'] : '';
1859
+
1860
+ foreach( $sub_pages as $sub_page_func => $sub_page_title ){
1861
+ $func_parts = explode( '_', $sub_page_func, 2 );
1862
+
1863
+ if( isset($func_parts[1]) ){
1864
+ $unique_name = $func_parts[1];
1865
+ $pseudo_var = 'URL.' . ucwords($unique_name);
1866
+ } else {
1867
+ $unique_name = '';
1868
+ $pseudo_var = 'URL.Home';
1869
+ }
1870
+
1871
+ $params[$pseudo_var] = sucuriscan_get_url($unique_name);
1872
+
1873
+ $navbar_item_css_class = 'nav-tab';
1874
+
1875
+ if( $params['CurrentPageFunc'] == $sub_page_func ){
1876
+ $navbar_item_css_class .= chr(32) . 'nav-tab-active';
1877
+ }
1878
+
1879
+ $params['Navbar'] .= sprintf(
1880
+ '<a class="%s" href="%s">%s</a>' . "\n",
1881
+ $navbar_item_css_class,
1882
+ $params[$pseudo_var],
1883
+ $sub_page_title
1884
+ );
1885
+ }
1886
+
1887
+ return $params;
1888
+ }
1889
+
1890
+ /**
1891
+ * Generate a HTML code using a template and replacing all the pseudo-variables
1892
+ * by the dynamic variables provided by the developer through one of the parameters
1893
+ * of the function.
1894
+ *
1895
+ * @param string $template Filename of the template that will be used to generate the page.
1896
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1897
+ * @param boolean $type Either page, section or snippet indicating the type of template that will be retrieved.
1898
+ * @return string The formatted HTML page after replace all the pseudo-variables.
1899
+ */
1900
+ function sucuriscan_get_template( $template='', $params=array(), $type='page' ){
1901
+ switch( $type ){
1902
+ case 'page': /* no_break */
1903
+ case 'section':
1904
+ $template_path_pattern = '%s/%s/inc/tpl/%s.html.tpl';
1905
+ break;
1906
+ case 'snippet':
1907
+ $template_path_pattern = '%s/%s/inc/tpl/%s.snippet.tpl';
1908
+ break;
1909
+ }
1910
+
1911
+ $template_content = '';
1912
+ $template_path = sprintf( $template_path_pattern, WP_PLUGIN_DIR, SUCURISCAN_PLUGIN_FOLDER, $template );
1913
+ $params = is_array($params) ? $params : array();
1914
+
1915
+ if( file_exists($template_path) && is_readable($template_path) ){
1916
+ $template_content = file_get_contents($template_path);
1917
+
1918
+ $current_page = isset($_GET['page']) ? htmlentities($_GET['page']) : '';
1919
+ $params['CurrentURL'] = sprintf( '%s/wp-admin/admin.php?page=%s', site_url(), $current_page );
1920
+ $params['SucuriURL'] = SUCURI_URL;
1921
+
1922
+ // Replace the global pseudo-variables in the section/snippets templates.
1923
+ if(
1924
+ $template == 'base'
1925
+ && isset($params['PageContent'])
1926
+ && preg_match('/%%SUCURI\.(.+)%%/', $params['PageContent'])
1927
+ ){
1928
+ $params['PageContent'] = sucuriscan_replace_pseudovars( $params['PageContent'], $params );
1929
+ }
1930
+
1931
+ $template_content = sucuriscan_replace_pseudovars( $template_content, $params );
1932
+ }
1933
+
1934
+ if( $template == 'base' || $type != 'page' ){
1935
+ return $template_content;
1936
+ }
1937
+
1938
+ return sucuriscan_get_base_template( $template_content, $params );
1939
+ }
1940
+
1941
+ /**
1942
+ * Gather and generate the information required globally by all the template files.
1943
+ *
1944
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1945
+ * @return array A complementary list of pseudo-variables for the template files.
1946
+ */
1947
+ function sucuriscan_shared_params( $params=array() ){
1948
+ $params = is_array($params) ? $params : array();
1949
+
1950
+ // Base parameters, required to render all the pages.
1951
+ $params = sucuriscan_links_and_navbar($params);
1952
+
1953
+ // Global parameters, used through out all the pages.
1954
+ $params['PageTitle'] = isset($params['PageTitle']) ? '('.$params['PageTitle'].')' : '';
1955
+ $params['PageNonce'] = wp_create_nonce('sucuriscan_page_nonce');
1956
+ $params['PageStyleClass'] = isset($params['PageStyleClass']) ? $params['PageStyleClass'] : 'base';
1957
+ $params['CleanDomain'] = sucuriscan_get_domain();
1958
+ $params['AdminEmail'] = sucuriscan_get_site_email();
1959
+
1960
+ return $params;
1961
+ }
1962
+
1963
+ /**
1964
+ * Generate a HTML code using a template and replacing all the pseudo-variables
1965
+ * by the dynamic variables provided by the developer through one of the parameters
1966
+ * of the function.
1967
+ *
1968
+ * @param string $html The HTML content of a template file with its pseudo-variables parsed.
1969
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1970
+ * @return string The formatted HTML content of the base template.
1971
+ */
1972
+ function sucuriscan_get_base_template( $html='', $params=array() ){
1973
+ $params = is_array($params) ? $params : array();
1974
+
1975
+ $params = sucuriscan_shared_params($params);
1976
+ $params['PageContent'] = $html;
1977
+
1978
+ return sucuriscan_get_template( 'base', $params );
1979
+ }
1980
+
1981
+ /**
1982
+ * Generate a HTML code using a template and replacing all the pseudo-variables
1983
+ * by the dynamic variables provided by the developer through one of the parameters
1984
+ * of the function.
1985
+ *
1986
+ * @param string $template Filename of the template that will be used to generate the page.
1987
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
1988
+ * @return string The formatted HTML page after replace all the pseudo-variables.
1989
+ */
1990
+ function sucuriscan_get_section($template='', $params=array()){
1991
+ $params = sucuriscan_shared_params($params);
1992
+
1993
+ return sucuriscan_get_template( $template, $params, 'section' );
1994
+ }
1995
+
1996
+ /**
1997
+ * Generate a HTML code using a template and replacing all the pseudo-variables
1998
+ * by the dynamic variables provided by the developer through one of the parameters
1999
+ * of the function.
2000
+ *
2001
+ * @param string $template Filename of the template that will be used to generate the page.
2002
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
2003
+ * @return string The formatted HTML page after replace all the pseudo-variables.
2004
+ */
2005
+ function sucuriscan_get_modal($template='', $params=array()){
2006
+ $required = array(
2007
+ 'Title' => 'Lorem ipsum dolor sit amet',
2008
+ 'CssClass' => '',
2009
+ 'Content' => '<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
2010
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
2011
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
2012
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
2013
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
2014
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>',
2015
+ );
2016
+
2017
+ if( !empty($template) && $template != 'none' ){
2018
+ $params['Content'] = sucuriscan_get_section($template);
2019
+ }
2020
+
2021
+ foreach( $required as $param_name => $param_value ){
2022
+ if( !isset($params[$param_name]) ){
2023
+ $params[$param_name] = $param_value;
2024
+ }
2025
+ }
2026
+
2027
+ $params = sucuriscan_shared_params($params);
2028
+
2029
+ return sucuriscan_get_template( 'modalwindow', $params, 'section' );
2030
+ }
2031
+
2032
+ /**
2033
+ * Generate a HTML code using a template and replacing all the pseudo-variables
2034
+ * by the dynamic variables provided by the developer through one of the parameters
2035
+ * of the function.
2036
+ *
2037
+ * @param string $template Filename of the template that will be used to generate the page.
2038
+ * @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
2039
+ * @return string The formatted HTML page after replace all the pseudo-variables.
2040
+ */
2041
+ function sucuriscan_get_snippet($template='', $params=array()){
2042
+ return sucuriscan_get_template( $template, $params, 'snippet' );
2043
+ }
2044
+
2045
+ /**
2046
+ * Generate an URL pointing to the page indicated in the function and that must
2047
+ * be loaded through the administrator panel.
2048
+ *
2049
+ * @param string $page Short name of the page that will be generated.
2050
+ * @return string Full string containing the link of the page.
2051
+ */
2052
+ function sucuriscan_get_url($page=''){
2053
+ $url_path = admin_url('admin.php?page=sucuriscan');
2054
+
2055
+ if( !empty($page) ){
2056
+ $url_path .= '_' . $page;
2057
+ }
2058
+
2059
+ return $url_path;
2060
+ }
2061
+
2062
+ /**
2063
+ * Retrieve a new set of keys for the WordPress configuration file using the
2064
+ * official API provided by WordPress itself.
2065
+ *
2066
+ * @return array A list of the new set of keys generated by WordPress API.
2067
+ */
2068
+ function sucuriscan_get_new_config_keys(){
2069
+ $request = wp_remote_get('https://api.wordpress.org/secret-key/1.1/salt/');
2070
+
2071
+ if( !is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200 ){
2072
+ if( preg_match_all("/define\('([A-Z_]+)',[ ]+'(.*)'\);/", $request['body'], $match) ){
2073
+ $new_keys = array();
2074
+
2075
+ foreach($match[1] as $i=>$value){
2076
+ $new_keys[$value] = $match[2][$i];
2077
+ }
2078
+
2079
+ return $new_keys;
2080
+ }
2081
+ }
2082
+
2083
+ return FALSE;
2084
+ }
2085
+
2086
+ /**
2087
+ * Modify the WordPress configuration file and change the keys that were defined
2088
+ * by a new random-generated list of keys retrieved from the official WordPress
2089
+ * API. The result of the operation will be either FALSE in case of error, or an
2090
+ * array containing multiple indexes explaining the modification, among them you
2091
+ * will find the old and new keys.
2092
+ *
2093
+ * @return false|array Either FALSE in case of error, or an array with the old and new keys.
2094
+ */
2095
+ function sucuriscan_set_new_config_keys(){
2096
+ $new_wpconfig = '';
2097
+ $wp_config_path = ABSPATH.'wp-config.php';
2098
+
2099
+ if( file_exists($wp_config_path) ){
2100
+ $wp_config_lines = file($wp_config_path);
2101
+ $new_keys = sucuriscan_get_new_config_keys();
2102
+ $old_keys = array();
2103
+ $old_keys_string = $new_keys_string = '';
2104
+
2105
+ foreach($wp_config_lines as $wp_config_line){
2106
+ $wp_config_line = str_replace("\n", '', $wp_config_line);
2107
+
2108
+ if( preg_match("/define\('([A-Z_]+)',([ ]+)'(.*)'\);/", $wp_config_line, $match) ){
2109
+ $key_name = $match[1];
2110
+ if( array_key_exists($key_name, $new_keys) ){
2111
+ $white_spaces = $match[2];
2112
+ $old_keys[$key_name] = $match[3];
2113
+ $wp_config_line = "define('{$key_name}',{$white_spaces}'{$new_keys[$key_name]}');";
2114
+
2115
+ $old_keys_string .= "define('{$key_name}',{$white_spaces}'{$old_keys[$key_name]}');\n";
2116
+ $new_keys_string .= "{$wp_config_line}\n";
2117
+ }
2118
+ }
2119
+
2120
+ $new_wpconfig .= "{$wp_config_line}\n";
2121
+ }
2122
+
2123
+ $response = array(
2124
+ 'updated' => is_writable($wp_config_path),
2125
+ 'old_keys' => $old_keys,
2126
+ 'old_keys_string' => $old_keys_string,
2127
+ 'new_keys' => $new_keys,
2128
+ 'new_keys_string' => $new_keys_string,
2129
+ 'new_wpconfig' => $new_wpconfig,
2130
+ );
2131
+
2132
+ if( $response['updated'] ){
2133
+ file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
2134
+ }
2135
+ return $response;
2136
+ }
2137
+ return FALSE;
2138
+ }
2139
+
2140
+ /**
2141
+ * Generate and set a new password for a specific user not in session.
2142
+ *
2143
+ * @param integer $user_id The user identifier that will be changed, this must be different than the user in session.
2144
+ * @return boolean Either TRUE or FALSE in case of success or error respectively.
2145
+ */
2146
+ function sucuriscan_new_password($user_id=0){
2147
+ $user_id = intval($user_id);
2148
+ $current_user = wp_get_current_user();
2149
+
2150
+ if( $user_id>0 && $user_id!=$current_user->ID ){
2151
+ $user = get_userdata($user_id);
2152
+ $new_password = wp_generate_password(15, TRUE, FALSE);
2153
+
2154
+ $data_set = array( 'User'=>$user->display_name );
2155
+ $message = "The password for your user account in the website mentioned has been changed by an administrator,
2156
+ this is the new password automatically generated by the system, please update ASAP.<br>
2157
+ <div style='display:inline-block;background:#ddd;font-family:monaco,monospace,courier;
2158
+ font-size:30px;margin:0;padding:15px;border:1px solid #999'>{$new_password}</div>";
2159
+ sucuriscan_send_mail($user->user_email, 'Changed password', $message, $data_set);
2160
+
2161
+ wp_set_password($new_password, $user_id);
2162
+
2163
+ return TRUE;
2164
+ }
2165
+ return FALSE;
2166
+ }
2167
+
2168
+ /**
2169
+ * Retrieve the real ip address of the user in the current request.
2170
+ *
2171
+ * @return string The real ip address of the user in the current request.
2172
+ */
2173
+ function sucuriscan_get_remoteaddr(){
2174
+ $alternatives = array(
2175
+ 'HTTP_X_REAL_IP',
2176
+ 'HTTP_CLIENT_IP',
2177
+ 'HTTP_X_FORWARDED_FOR',
2178
+ 'HTTP_X_FORWARDED',
2179
+ 'HTTP_FORWARDED_FOR',
2180
+ 'HTTP_FORWARDED',
2181
+ 'REMOTE_ADDR',
2182
+ 'SUCURI_RIP',
2183
+ );
2184
+ foreach($alternatives as $alternative){
2185
+ if( !isset($_SERVER[$alternative]) ){ continue; }
2186
+
2187
+ $remote_addr = preg_replace('/[^0-9a-z.,: ]/', '', $_SERVER[$alternative]);
2188
+ if($remote_addr) break;
2189
+ }
2190
+
2191
+ if( $remote_addr == '::1' ){
2192
+ $remote_addr = '127.0.0.1';
2193
+ }
2194
+
2195
+ return $remote_addr;
2196
+ }
2197
+
2198
+ /**
2199
+ * Check whether the site is behing the Sucuri CloudProxy network.
2200
+ *
2201
+ * @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
2202
+ */
2203
+ function sucuriscan_is_behind_cloudproxy(){
2204
+ $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
2205
+ if( preg_match('/^(.*):([0-9]+)/', $http_host, $match) ){ $http_host = $match[1]; }
2206
+ $host_by_name = gethostbyname($http_host);
2207
+ $host_by_addr = gethostbyaddr($host_by_name);
2208
+
2209
+ if(
2210
+ isset($_SERVER['SUCURIREAL_REMOTE_ADDR'])
2211
+ || preg_match('/^cloudproxy([0-9]+)\.sucuri\.net$/', $host_by_addr)
2212
+ ){
2213
+ return TRUE;
2214
+ }
2215
+
2216
+ return FALSE;
2217
+ }
2218
+
2219
+ /**
2220
+ * Find and retrieve the current version of Wordpress installed.
2221
+ *
2222
+ * @return string The version number of Wordpress installed.
2223
+ */
2224
+ function sucuriscan_get_wpversion(){
2225
+ $version = get_option('version');
2226
+ if( $version ){ return $version; }
2227
+
2228
+ $wp_version_path = ABSPATH . WPINC . '/version.php';
2229
+ if( file_exists($wp_version_path) ){
2230
+ include($wp_version_path);
2231
+ if( isset($wp_version) ){ return $wp_version; }
2232
+ }
2233
+
2234
+ return md5_file(ABSPATH . WPINC . '/class-wp.php');
2235
+ }
2236
+
2237
+ /**
2238
+ * Find and retrieve the absolute path of the WordPress configuration file.
2239
+ *
2240
+ * @return string Absolute path of the WordPress configuration file.
2241
+ */
2242
+ function sucuriscan_get_wpconfig_path(){
2243
+ $wp_config_path = ABSPATH.'wp-config.php';
2244
+
2245
+ // if wp-config.php doesn't exist/not readable check one directory up
2246
+ if( !is_readable($wp_config_path)){
2247
+ $wp_config_path = ABSPATH.'/../wp-config.php';
2248
+ }
2249
+
2250
+ return $wp_config_path;
2251
+ }
2252
+
2253
+ /**
2254
+ * Find and retrieve the absolute path of the main WordPress htaccess file.
2255
+ *
2256
+ * @return string Absolute path of the main WordPress htaccess file.
2257
+ */
2258
+ function sucuriscan_get_htaccess_path(){
2259
+ $base_dirs = array(
2260
+ rtrim(ABSPATH, '/'),
2261
+ dirname(ABSPATH),
2262
+ dirname(dirname(ABSPATH))
2263
+ );
2264
+
2265
+ foreach($base_dirs as $base_dir){
2266
+ $htaccess_path = sprintf('%s/.htaccess', $base_dir);
2267
+ if( file_exists($htaccess_path) ){
2268
+ return $htaccess_path;
2269
+ }
2270
+ }
2271
+
2272
+ return FALSE;
2273
+ }
2274
+
2275
+ /**
2276
+ * Get the email address set by the administrator to receive the notifications
2277
+ * sent by the plugin, if the email is missing the WordPress email address is
2278
+ * chosen by default.
2279
+ *
2280
+ * @return string The administrator email address.
2281
+ */
2282
+ function sucuriscan_get_site_email(){
2283
+ $email = get_option('admin_email');
2284
+
2285
+ if( is_valid_email($email) ){
2286
+ return $email;
2287
+ }
2288
+
2289
+ return FALSE;
2290
+ }
2291
+
2292
+ /**
2293
+ * Get the clean version of the current domain.
2294
+ *
2295
+ * @return string The domain of the current site.
2296
+ */
2297
+ function sucuriscan_get_domain(){
2298
+ $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
2299
+ $domain_name = preg_replace( '/^www\./', '', $http_host );
2300
+
2301
+ return $domain_name;
2302
+ }
2303
+
2304
+ /**
2305
+ * Generate an user-agent for the HTTP requests.
2306
+ *
2307
+ * @return string An user-agent for the HTTP requests.
2308
+ */
2309
+ function sucuriscan_user_agent(){
2310
+ global $wp_version;
2311
+
2312
+ $user_agent = 'WordPress/' . $wp_version . '; ' . sucuriscan_get_domain();
2313
+
2314
+ return $user_agent;
2315
+ }
2316
+
2317
+ /**
2318
+ * Return the time passed since the specified timestamp until now.
2319
+ *
2320
+ * @param integer $timestamp The Unix time number of the date/time before now.
2321
+ * @return string The time passed since the timestamp specified.
2322
+ */
2323
+ function sucuriscan_time_ago($timestamp=0){
2324
+ if( !is_numeric($timestamp) ){
2325
+ $timestamp = strtotime($timestamp);
2326
+ }
2327
+
2328
+ $diff = time() - (int)$timestamp;
2329
+
2330
+ if( $diff == 0 ){ return 'just now'; }
2331
+
2332
+ $intervals = array(
2333
+ 1 => array('year', 31556926),
2334
+ $diff < 31556926 => array('month', 2628000),
2335
+ $diff < 2629744 => array('week', 604800),
2336
+ $diff < 604800 => array('day', 86400),
2337
+ $diff < 86400 => array('hour', 3600),
2338
+ $diff < 3600 => array('minute', 60),
2339
+ $diff < 60 => array('second', 1)
2340
+ );
2341
+
2342
+ $value = floor($diff/$intervals[1][1]);
2343
+ return $value.chr(32).$intervals[1][0].($value > 1 ? 's' : '').' ago';
2344
+ }
2345
+
2346
+ /**
2347
+ * Convert an string of characters into a valid variable name.
2348
+ *
2349
+ * @see http://www.php.net/manual/en/language.variables.basics.php
2350
+ *
2351
+ * @param string $string A text containing alpha-numeric and special characters.
2352
+ * @return string A valid variable name.
2353
+ */
2354
+ function sucuriscan_str_human2var($string=''){
2355
+ $pattern = '/[^a-zA-Z0-9_]/';
2356
+ $var_name = preg_replace($pattern, '_', strtolower($string));
2357
+
2358
+ return $var_name;
2359
+ }
2360
+
2361
+ /**
2362
+ * Retrieve specific options from the database.
2363
+ *
2364
+ * Considering the case in which this plugin is installed in a multisite instance
2365
+ * of Wordpress, the allowed values for the first parameter of this function will
2366
+ * be treated like this:
2367
+ *
2368
+ * <ul>
2369
+ * <li>all_sucuriscan_options: Will retrieve all the option values created by this plugin in the main site (aka. network),</li>
2370
+ * <li>site_options: Will retrieve all the option values stored in the current site visited by the user (aka. sub-site) excluding the transient options,</li>
2371
+ * <li>sucuriscan_option: Will retrieve one specific option from the network site only if the option starts with the prefix <i>sucuri_<i>.</li>
2372
+ * </ul>
2373
+ *
2374
+ * @param string $filter_by Criteria to filter the results, valid values: all_sucuriscan_options, site_options, sucuri_option.
2375
+ * @param string $option_name Optional parameter with the name of the option that will be filtered.
2376
+ * @return array List of options retrieved from the query in the database.
2377
+ */
2378
+ function sucuriscan_get_options_from_db( $filter_by='', $option_name='' ){
2379
+ global $wpdb;
2380
+
2381
+ $output = FALSE;
2382
+ switch($filter_by){
2383
+ case 'all_sucuriscan_options':
2384
+ $output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name LIKE 'sucuriscan%' ORDER BY option_id ASC");
2385
+ break;
2386
+ case 'site_options':
2387
+ $output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name NOT LIKE '%_transient_%' ORDER BY option_id ASC");
2388
+ break;
2389
+ case 'sucuriscan_option':
2390
+ $row = $wpdb->get_row( $wpdb->prepare("SELECT option_value FROM {$wpdb->base_prefix}options WHERE option_name = %s LIMIT 1", $option_name) );
2391
+ if( $row ){ $output = $row->option_value; }
2392
+ break;
2393
+ }
2394
+
2395
+ return $output;
2396
+ }
2397
+
2398
+ /**
2399
+ * Alias function for the method Common::SucuriScan_Get_Options()
2400
+ *
2401
+ * This function search the specified option in the database, not only the options
2402
+ * set by the plugin but all the options set for the site. If the value retrieved
2403
+ * is FALSE the method tries to search for a default value.
2404
+ *
2405
+ * @param string $option_name Optional parameter that you can use to filter the results to one option.
2406
+ * @return string The value (or default value) of the option specified.
2407
+ */
2408
+ function sucuriscan_get_option( $option_name='' ){
2409
+ return sucuriscan_get_options($option_name);
2410
+ }
2411
+
2412
+ /**
2413
+ * Retrieve all the options created by this Plugin from the Wordpress database.
2414
+ *
2415
+ * The function acts as an alias of WP::get_option() and if the returned value
2416
+ * is FALSE it tries to search for a default value to complement the information.
2417
+ *
2418
+ * @param string $option_name Optional parameter that you can use to filter the results to one option.
2419
+ * @return array Either FALSE or an Array containing all the sucuri options in the database.
2420
+ */
2421
+ function sucuriscan_get_options( $option_name='' ){
2422
+ if( !empty($option_name) ){
2423
+ return sucuriscan_get_single_option($option_name);
2424
+ }
2425
+
2426
+ $settings = array();
2427
+ $results = sucuriscan_get_options_from_db('all_sucuriscan_options');
2428
+ foreach( $results as $row ){
2429
+ $settings[$row->option_name] = $row->option_value;
2430
+ }
2431
+
2432
+ return sucuriscan_get_default_options($settings);
2433
+ }
2434
+
2435
+ /**
2436
+ * Retrieve a single option from the database.
2437
+ *
2438
+ * @param string $option_name Name of the option that will be retrieved.
2439
+ * @return string Value of the option stored in the database, FALSE if not found.
2440
+ */
2441
+ function sucuriscan_get_single_option( $option_name='' ){
2442
+ $is_sucuri_option = preg_match('/^sucuriscan_/', $option_name) ? TRUE : FALSE;
2443
+
2444
+ if( sucuriscan_is_multisite() && $is_sucuri_option ){
2445
+ $option_value = sucuriscan_get_options_from_db('sucuriscan_option', $option_name);
2446
+ }else{
2447
+ $option_value = get_option($option_name);
2448
+ }
2449
+
2450
+ if( $option_value === FALSE && $is_sucuri_option ){
2451
+ $option_value = sucuriscan_get_default_options($option_name);
2452
+ }
2453
+
2454
+ return $option_value;
2455
+ }
2456
+
2457
+ /**
2458
+ * Retrieve the default values for some specific options.
2459
+ *
2460
+ * @param string|array $settings Either an array that will be complemented or a string with the name of the option.
2461
+ * @return string|array The default values for the specified options.
2462
+ */
2463
+ function sucuriscan_get_default_options( $settings='' ){
2464
+ $default_options = array(
2465
+ 'sucuriscan_api_key' => FALSE,
2466
+ 'sucuriscan_account' => get_option('admin_email'),
2467
+ 'sucuriscan_scan_frequency' => 'hourly',
2468
+ 'sucuriscan_scan_interface' => 'spl',
2469
+ 'sucuriscan_runtime' => 0,
2470
+ 'sucuriscan_lastlogin_redirection' => 'enabled',
2471
+ 'sucuriscan_prettify_mails' => 'enabled',
2472
+ 'sucuriscan_notify_success_login' => 'enabled',
2473
+ 'sucuriscan_notify_failed_login' => 'enabled',
2474
+ 'sucuriscan_notify_post_publication' => 'enabled',
2475
+ 'sucuriscan_notify_theme_editor' => 'enabled',
2476
+ );
2477
+
2478
+ if( is_array($settings) ){
2479
+ foreach( $default_options as $option_name => $option_value ){
2480
+ if( !isset($settings[$option_name]) ){
2481
+ $settings[$option_name] = $option_value;
2482
+ }
2483
+ }
2484
+ return $settings;
2485
+ }
2486
+
2487
+ if( is_string($settings) ){
2488
+ if( isset($default_options[$settings]) ){
2489
+ return $default_options[$settings];
2490
+ }
2491
+ }
2492
+
2493
+ return FALSE;
2494
+ }
2495
+
2496
+ /**
2497
+ * Retrieve all the options stored by Wordpress in the database. The options
2498
+ * containing the word "transient" are excluded from the results, this function
2499
+ * is compatible with multisite instances.
2500
+ *
2501
+ * @return array All the options stored by Wordpress in the database, except the transient options.
2502
+ */
2503
+ function sucuriscan_get_wp_options(){
2504
+ $settings = array();
2505
+
2506
+ $results = sucuriscan_get_options_from_db('site_options');
2507
+ foreach( $results as $row ){
2508
+ $settings[$row->option_name] = $row->option_value;
2509
+ }
2510
+
2511
+ return $settings;
2512
+ }
2513
+
2514
+ /**
2515
+ * Check what Wordpress options were changed comparing the values in the database
2516
+ * with the values sent through a simple request using a GET or POST method.
2517
+ *
2518
+ * @param array $request The content of the global variable GET or POST considering SERVER[REQUEST_METHOD].
2519
+ * @return array A list of all the options that were changes through this request.
2520
+ */
2521
+ function sucuriscan_what_options_were_changed( $request=array() ){
2522
+ $options_changed = array(
2523
+ 'original' => array(),
2524
+ 'changed' => array()
2525
+ );
2526
+ $wp_options = sucuriscan_get_wp_options();
2527
+
2528
+ foreach( $request as $req_name => $req_value ){
2529
+ if(
2530
+ array_key_exists($req_name, $wp_options)
2531
+ && $wp_options[$req_name] != $req_value
2532
+ ){
2533
+ $options_changed['original'][$req_name] = $wp_options[$req_name];
2534
+ $options_changed['changed'][$req_name] = $req_value;
2535
+ }
2536
+ }
2537
+ return $options_changed;
2538
+ }
2539
+
2540
+ if( !function_exists('sucuriscan_plugin_setup_notice') ){
2541
+ /**
2542
+ * Display a notice message with instructions to continue the setup of the
2543
+ * plugin, this includes the generation of the API key and other steps that need
2544
+ * to be done to fully activate this plugin.
2545
+ *
2546
+ * @return void
2547
+ */
2548
+ function sucuriscan_plugin_setup_notice(){
2549
+ echo sucuriscan_get_section('setup_notice');
2550
+ }
2551
+
2552
+ if( !sucuriscan_wordpress_apikey() ){
2553
+ $sucuriscan_admin_notice_name = sucuriscan_is_multisite() ? 'network_admin_notices' : 'admin_notices';
2554
+ add_action( $sucuriscan_admin_notice_name, 'sucuriscan_plugin_setup_notice' );
2555
+ }
2556
+ }
2557
+
2558
+ /**
2559
+ * Display the page with a temporary message explaining the action that will be
2560
+ * performed once the hidden form is submitted to retrieve the scanning results
2561
+ * from the public SiteCheck API.
2562
+ *
2563
+ * @return void
2564
+ */
2565
+ function sucuriscan_scanner_page(){
2566
+ if(
2567
+ sucuriscan_check_page_nonce()
2568
+ && isset($_POST['sucuriscan_malware_scan'])
2569
+ ){
2570
+ sucuriscan_sitecheck_info();
2571
+ } else {
2572
+ echo sucuriscan_get_template('malwarescan');
2573
+ }
2574
+ }
2575
+
2576
+ /**
2577
+ * Display the result of site scan made through SiteCheck.
2578
+ *
2579
+ * @return void
2580
+ */
2581
+ function sucuriscan_sitecheck_info(){
2582
+ $sucuri_cache = new SucuriScanCache('sitecheck');
2583
+ $scan_results = $sucuri_cache->get( 'scan_results', SUCURISCAN_SITECHECK_LIFETIME, 'array' );
2584
+ $clean_domain = sucuriscan_get_domain();
2585
+ $display_results = FALSE;
2586
+
2587
+ ob_start();
2588
+
2589
+ if( !$scan_results ){
2590
+ $remote_url = 'http://sitecheck.sucuri.net/scanner/?serialized&clear&fromwp&scan='.$clean_domain;
2591
+ $scan_results = wp_remote_get($remote_url, array('timeout' => 180));
2592
+
2593
+ if( is_wp_error($scan_results) ){
2594
+ sucuriscan_error( $scan_results->get_error_message() );
2595
+ }
2596
+
2597
+ elseif( isset($scan_results['body']) ){
2598
+ if( preg_match('/^ERROR:(.*)/', $scan_results['body'], $error_m) ){
2599
+ sucuriscan_error( 'The site <code>' . $clean_domain . '</code> was not scanned: ' . $error_m[1] );
2600
+ }
2601
+
2602
+ else {
2603
+ $scan_results = @unserialize($scan_results['body']);
2604
+ $display_results = TRUE;
2605
+
2606
+ if( !$sucuri_cache->add( 'scan_results', $scan_results ) ){
2607
+ sucuriscan_error( 'Could not cache the results of the SiteCheck scanning' );
2608
+ }
2609
+ }
2610
+ }
2611
+ } else {
2612
+ $display_results = TRUE;
2613
+ // sucuriscan_info( 'SiteCheck results retrieved from cache.' );
2614
+ }
2615
+ ?>
2616
+
2617
+
2618
+ <?php if( $display_results ): ?>
2619
+
2620
+ <?php
2621
+ $res = ( is_array($scan_results) ? $scan_results : array() );
2622
+
2623
+ // Check for general warnings, and return the information for Infected/Clean site.
2624
+ $malware_warns_exist = isset($res['MALWARE']['WARN']) ? TRUE : FALSE;
2625
+ $blacklist_warns_exist = isset($res['BLACKLIST']['WARN']) ? TRUE : FALSE;
2626
+ $outdated_warns_exist = isset($res['OUTDATEDSCAN']) ? TRUE : FALSE;
2627
+ $recommendations_exist = isset($res['RECOMMENDATIONS']) ? TRUE : FALSE;
2628
+
2629
+ // Check whether this WordPress installation needs an update.
2630
+ global $wp_version;
2631
+ $wordpress_updated = FALSE;
2632
+ $updates = function_exists('get_core_updates') ? get_core_updates() : array();
2633
+
2634
+ if( !is_array($updates) || empty($updates) || $updates[0]->response=='latest' ){
2635
  $wordpress_updated = TRUE;
2636
  }
2637
 
2638
+ if( TRUE ){
2639
+ // Initialize the CSS classes with default values.
2640
+ $sucuriscan_css_blacklist = 'sucuriscan-border-good';
2641
+ $sucuriscan_css_malware = 'sucuriscan-border-good';
2642
+ $sitecheck_results_tab = '';
2643
+ $blacklist_status_tab = '';
2644
+ $website_details_tab = '';
2645
+
2646
+ // Generate the CSS classes for the blacklist status.
2647
+ if( $blacklist_warns_exist ){
2648
+ $sucuriscan_css_blacklist = 'sucuriscan-border-bad';
2649
+ $blacklist_status_tab = 'sucuriscan-red-tab';
2650
+ }
2651
+
2652
+ // Generate the CSS classes for the SiteCheck scanning results.
2653
+ if( $malware_warns_exist ){
2654
+ $sucuriscan_css_malware = 'sucuriscan-border-bad';
2655
+ $sitecheck_results_tab = 'sucuriscan-red-tab';
2656
+ }
2657
+
2658
+ // Generate the CSS classes for the outdated/recommendations panel.
2659
+ if( $outdated_warns_exist || $recommendations_exist ){
2660
+ $website_details_tab = 'sucuriscan-red-tab';
2661
+ }
2662
+
2663
+ $sucuriscan_css_wpupdate = $wordpress_updated ? 'sucuriscan-border-good' : 'sucuriscan-border-bad';
2664
+ }
2665
+ ?>
2666
+
2667
+ <div id="poststuff">
2668
+ <div class="postbox sucuriscan-border sucuriscan-border-info sucuriscan-malwarescan-message">
2669
+ <h3>SiteCheck Scanner</h3>
2670
+
2671
+ <div class="inside">
2672
+ <p>
2673
+ If your site was recently hacked, you can see which files were modified
2674
+ recently, to assist with any investigation.
2675
+ </p>
2676
+ </div>
2677
+ </div>
2678
+ </div>
2679
+
2680
+
2681
+ <div class="sucuriscan-tabs">
2682
+
2683
+
2684
+ <ul>
2685
+ <li class="<?php _e($sitecheck_results_tab) ?>">
2686
+ <a href="#" data-tabname="sitecheck-results">Remote Scanner Results</a>
2687
+ </li>
2688
+ <li class="<?php _e($website_details_tab) ?>">
2689
+ <a href="#" data-tabname="website-details">Website Details</a>
2690
+ </li>
2691
+ <li>
2692
+ <a href="#" data-tabname="website-links">IFrames / Links / Scripts</a>
2693
+ </li>
2694
+ <li class="<?php _e($blacklist_status_tab) ?>">
2695
+ <a href="#" data-tabname="blacklist-status">Blacklist Status</a>
2696
+ </li>
2697
+ <li>
2698
+ <a href="#" data-tabname="modified-files">Modified Files</a>
2699
+ </li>
2700
+ </ul>
2701
+
2702
+
2703
+ <div class="sucuriscan-tab-containers">
2704
+
2705
+
2706
+ <div id="sucuriscan-sitecheck-results">
2707
+ <div id="poststuff">
2708
+ <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_malware) ?>">
2709
+ <h3>
2710
+ <?php if( $malware_warns_exist ): ?>
2711
+ Site compromised (malware was identified)
2712
+ <?php else: ?>
2713
+ Site clean (no malware was identified)
2714
+ <?php endif; ?>
2715
+ </h3>
2716
+
2717
+ <div class="inside">
2718
+
2719
+ <?php if( !$malware_warns_exist ): ?>
2720
+ <p>
2721
+ <span><strong>Malware:</strong> No.</span><br>
2722
+ <span><strong>Malicious javascript:</strong> No.</span><br>
2723
+ <span><strong>Malicious iframes:</strong> No.</span><br>
2724
+ <span><strong>Suspicious redirections (htaccess):</strong> No.</span><br>
2725
+ <span><strong>Blackhat SEO Spam:</strong> No.</span><br>
2726
+ <span><strong>Anomaly detection:</strong> Clean.</span>
2727
+ </p>
2728
+ <?php else: ?>
2729
+ <ul>
2730
+ <?php
2731
+ foreach( $res['MALWARE']['WARN'] as $malres ){
2732
+ if( !is_array($malres) ){
2733
+ echo '<li>' . htmlspecialchars($malres) . '</li>';
2734
+ } else {
2735
+ $mwdetails = explode("\n", htmlspecialchars($malres[1]));
2736
+ $mw_name_link = isset($mwdetails[0]) ? substr($mwdetails[0], 1) : '';
2737
+
2738
+ if( preg_match('/(.*)\. Details: (.*)/', $mw_name_link, $mw_match) ){
2739
+ $mw_name_link = sprintf(
2740
+ '%s. Details: <a href="%s" target="_blank">%s</a>',
2741
+ $mw_match[1], $mw_match[2], $mw_match[2]
2742
+ );
2743
+ }
2744
+
2745
+ echo '<li>'. htmlspecialchars($malres[0]) . "\n<br>" . $mw_name_link . "</li>\n";
2746
+ }
2747
+ }
2748
+ ?>
2749
+ </ul>
2750
+ <?php endif; ?>
2751
+
2752
+ <p>
2753
+ <i>
2754
+ More details here: <a href="http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?>"
2755
+ target="_blank">http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?></a>
2756
+ </i>
2757
+ </p>
2758
+
2759
+ <hr />
2760
+
2761
+ <p>
2762
+ <i>
2763
+ If our free scanner did not detect any issue, you may have a more complicated
2764
+ and hidden problem. You can <a href="http://sucuri.net/signup" target="_blank">
2765
+ sign up</a> with Sucuri for a complete and in depth scan+cleanup (not included
2766
+ in the free checks).
2767
+ </i>
2768
+ </p>
2769
+
2770
+ </div>
2771
+ </div>
2772
+ </div>
2773
+ </div>
2774
+
2775
+
2776
+ <div id="sucuriscan-website-details">
2777
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-details">
2778
+ <thead>
2779
+ <tr>
2780
+ <th colspan="2" class="thead-with-button">
2781
+ <span>System Information</span>
2782
+ <?php if( !$wordpress_updated ): ?>
2783
+ <a href="<?php echo admin_url('update-core.php'); ?>" class="button button-primary thead-topright-action">
2784
+ Update to <?php _e($updates[0]->version) ?>
2785
+ </a>
2786
+ <?php endif; ?>
2787
+ </th>
2788
+ </tr>
2789
+ </thead>
2790
+
2791
+ <tbody>
2792
+ <!-- List of generic information from the site. -->
2793
+ <?php
2794
+ $possible_keys = array(
2795
+ 'DOMAIN' => 'Domain Scanned',
2796
+ 'IP' => 'Site IP Address',
2797
+ 'HOSTING' => 'Hosting Company',
2798
+ 'CMS' => 'CMS Found',
2799
+ );
2800
+ $possible_url_keys = array(
2801
+ 'IFRAME' => 'List of iframes found',
2802
+ 'JSEXTERNAL' => 'List of external scripts included',
2803
+ 'JSLOCAL' => 'List of scripts included',
2804
+ 'URL' => 'List of links found',
2805
+ );
2806
+ ?>
2807
+
2808
+ <?php foreach( $possible_keys as $result_key=>$result_title ): ?>
2809
+ <?php if( isset($res['SCAN'][$result_key]) ): ?>
2810
+ <?php $result_value = implode(', ', $res['SCAN'][$result_key]); ?>
2811
+ <tr>
2812
+ <td><?php _e($result_title) ?></td>
2813
+ <td><span class="sucuriscan-monospace"><?php _e($result_value) ?></span></td>
2814
+ </tr>
2815
+ <?php endif; ?>
2816
+ <?php endforeach; ?>
2817
+
2818
+ <tr>
2819
+ <td>WordPress Version</td>
2820
+ <td><span class="sucuriscan-monospace"><?php _e($wp_version) ?></span></td>
2821
+ </tr>
2822
+ <tr>
2823
+ <td>PHP Version</td>
2824
+ <td><span class="sucuriscan-monospace"><?php _e(phpversion()) ?></span></td>
2825
+ </tr>
2826
+
2827
+ <!-- List of application details from the site. -->
2828
+ <tr>
2829
+ <th colspan="2">Web application details</th>
2830
+ </tr>
2831
+ <?php foreach( $res['WEBAPP'] as $webapp_key=>$webapp_details ): ?>
2832
+ <?php if( is_array($webapp_details) ): ?>
2833
+ <?php foreach( $webapp_details as $i=>$details ): ?>
2834
+ <?php if( is_array($details) ){ $details = isset($details[0]) ? $details[0] : ''; } ?>
2835
+ <tr>
2836
+ <td colspan="2">
2837
+ <span class="sucuriscan-monospace"><?php _e($details) ?></span>
2838
+ </td>
2839
+ </tr>
2840
+ <?php endforeach; ?>
2841
+ <?php endif; ?>
2842
+ <?php endforeach; ?>
2843
+
2844
+ <?php foreach( $res['SYSTEM']['NOTICE'] as $j=>$notice ): ?>
2845
+ <?php if( is_array($notice) ){ $notice = implode(', ', $notice); } ?>
2846
+ <tr>
2847
+ <td colspan="2">
2848
+ <span class="sucuriscan-monospace"><?php _e($notice) ?></span>
2849
+ </td>
2850
+ </tr>
2851
+ <?php endforeach; ?>
2852
+
2853
+ <!-- Possible recommendations or outdated software on the site. -->
2854
+ <?php if( $outdated_warns_exist || $recommendations_exist ): ?>
2855
+ <tr>
2856
+ <th colspan="2">Recommendations for the site</th>
2857
+ </tr>
2858
+ <?php endif; ?>
2859
+
2860
+ <!-- Possible outdated software on the site. -->
2861
+ <?php if( $outdated_warns_exist ): ?>
2862
+ <?php foreach( $res['OUTDATEDSCAN'] as $outdated ): ?>
2863
+ <?php if( count($outdated) >= 3 ): ?>
2864
+ <tr>
2865
+ <td colspan="2" class="sucuriscan-border-bad">
2866
+ <strong><?php _e($outdated[0]) ?></strong>
2867
+ <em>(<?php _e($outdated[2]) ?>)</em>
2868
+ <span><?php _e($outdated[1]) ?></span>
2869
+ </td>
2870
+ </tr>
2871
+ <?php endif; ?>
2872
+ <?php endforeach; ?>
2873
+ <?php endif; ?>
2874
+
2875
+ <!-- Possible recommendations for the site. -->
2876
+ <?php if( $recommendations_exist ): ?>
2877
+ <?php foreach( $res['RECOMMENDATIONS'] as $recommendation ): ?>
2878
+ <?php if( count($recommendation) >= 3 ): ?>
2879
+ <tr>
2880
+ <td colspan="2" class="sucuriscan-border-bad">
2881
+ <?php printf(
2882
+ '<strong>%s</strong><br><span>%s</span><br><a href="%s" target="_blank">%s</a>',
2883
+ $recommendation[0],
2884
+ $recommendation[1],
2885
+ $recommendation[2],
2886
+ $recommendation[2]
2887
+ ); ?>
2888
+ </td>
2889
+ </tr>
2890
+ <?php endif; ?>
2891
+ <?php endforeach; ?>
2892
+ <?php endif; ?>
2893
+ </tbody>
2894
+ </table>
2895
+ </div>
2896
+
2897
+
2898
+ <div id="sucuriscan-website-links">
2899
+ <table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-links">
2900
+ <tbody>
2901
+ <?php foreach( $possible_url_keys as $result_url_key=>$result_url_title ): ?>
2902
+
2903
+ <?php if( isset($res['LINKS'][$result_url_key]) ): ?>
2904
+ <tr>
2905
+ <th colspan="2">
2906
+ <?php printf(
2907
+ '%s (%d found)',
2908
+ __($result_url_title),
2909
+ count($res['LINKS'][$result_url_key])
2910
+ ) ?>
2911
+ </th>
2912
+ </tr>
2913
+
2914
+ <?php foreach( $res['LINKS'][$result_url_key] as $url_path ): ?>
2915
+ <tr>
2916
+ <td colspan="2">
2917
+ <span class="sucuriscan-monospace sucuriscan-wraptext"><?php _e($url_path) ?></span>
2918
+ </td>
2919
+ </tr>
2920
+ <?php endforeach; ?>
2921
+ <?php endif; ?>
2922
+
2923
+ <?php endforeach; ?>
2924
+ </tbody>
2925
+ </table>
2926
+ </div>
2927
+
2928
+
2929
+ <div id="sucuriscan-blacklist-status">
2930
+ <div id="poststuff">
2931
+ <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_blacklist) ?>">
2932
+ <h3>
2933
+ <?php if( $blacklist_warns_exist ): ?>
2934
+ Site blacklisted
2935
+ <?php else: ?>
2936
+ Site blacklist-free
2937
+ <?php endif; ?>
2938
+ </h3>
2939
+
2940
+ <div class="inside">
2941
+ <ul>
2942
+ <?php
2943
+ foreach(array(
2944
+ 'INFO' => 'CLEAN',
2945
+ 'WARN' => 'WARNING'
2946
+ ) as $type => $group_title){
2947
+ if( isset($res['BLACKLIST'][$type]) ){
2948
+ foreach( $res['BLACKLIST'][$type] as $blres ){
2949
+ $report_site = htmlspecialchars($blres[0]);
2950
+ $report_url = htmlspecialchars($blres[1]);
2951
+ printf(
2952
+ '<li><b>%s:</b> %s.<br>Details at <a href="%s" target="_blank">%s</a></li>',
2953
+ $group_title, $report_site, $report_url, $report_url
2954
+ );
2955
+ }
2956
+ }
2957
+ }
2958
+ ?>
2959
+ </ul>
2960
+ </div>
2961
+ </div>
2962
+ </div>
2963
+ </div>
2964
+
2965
+
2966
+ <div id="sucuriscan-modified-files">
2967
+ <?php echo sucuriscan_modified_files(); ?>
2968
+ </div>
2969
+
2970
+
2971
+ </div>
2972
+ </div>
2973
+
2974
+ <?php if( $malware_warns_exist || $blacklist_warns_exist ): ?>
2975
+ <a href="http://sucuri.net/signup/" target="_blank" class="button button-primary button-hero sucuriscan-cleanup-btn">
2976
+ Get your site protected with Sucuri
2977
+ </a>
2978
+ <?php endif; ?>
2979
+
2980
+ <?php endif; ?>
2981
+
2982
+
2983
+ <?php
2984
+ $_html = ob_get_contents();
2985
+ ob_end_clean();
2986
+ echo sucuriscan_get_base_template($_html, array(
2987
+ 'PageTitle' => 'Malware Scan',
2988
+ 'PageContent' => $_html,
2989
+ 'PageStyleClass' => 'scanner-results',
2990
+ ));
2991
+ return;
2992
+ }
2993
+
2994
+ /**
2995
+ * Retrieves a URL using a changeable HTTP method, returning results in an
2996
+ * array. Results include HTTP headers and content.
2997
+ *
2998
+ * @see http://codex.wordpress.org/Function_Reference/wp_remote_post
2999
+ * @see http://codex.wordpress.org/Function_Reference/wp_remote_get
3000
+ *
3001
+ * @param string $url The target URL where the request will be sent.
3002
+ * @param string $method HTTP method that will be used to send the request.
3003
+ * @param array $params Parameters for the request defined in an associative array of key-value.
3004
+ * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
3005
+ * @return array Array of results including HTTP headers or WP_Error if the request failed.
3006
+ */
3007
+ function sucuriscan_api_call( $url='', $method='GET', $params=array(), $args=array() ){
3008
+ if( !$url ){ return FALSE; }
3009
+
3010
+ $req_args = array(
3011
+ 'method' => $method,
3012
+ 'timeout' => 90,
3013
+ 'redirection' => 2,
3014
+ 'httpversion' => '1.0',
3015
+ 'user-agent' => sucuriscan_user_agent(),
3016
+ 'blocking' => TRUE,
3017
+ 'headers' => array(),
3018
+ 'cookies' => array(),
3019
+ 'compress' => FALSE,
3020
+ 'decompress' => FALSE,
3021
+ 'sslverify' => TRUE,
3022
+ );
3023
+
3024
+ // Update the request arguments with the values passed tot he function.
3025
+ foreach( $args as $arg_name => $arg_value ){
3026
+ if( array_key_exists($arg_name, $req_args) ){
3027
+ $req_args[$arg_name] = $arg_value;
3028
+ }
3029
+ }
3030
+
3031
+ if( $method == 'GET' ){
3032
+ $url = sprintf( '%s?%s', $url, http_build_query($params) );
3033
+ $response = wp_remote_get( $url, $req_args );
3034
+ }
3035
+
3036
+ elseif( $method == 'POST' ){
3037
+ $req_args['body'] = $params;
3038
+ $response = wp_remote_post( $url, $req_args );
3039
+ }
3040
+
3041
+ if( isset($response) ){
3042
+ if( is_wp_error($response) ){
3043
+ sucuriscan_error(sprintf(
3044
+ 'Something went wrong with an API call (%s action): %s',
3045
+ ( isset($params['a']) ? $params['a'] : 'unknown' ),
3046
+ $response->get_error_message()
3047
+ ));
3048
+ } else {
3049
+ $response['body_raw'] = $response['body'];
3050
+
3051
+ if(
3052
+ isset($response['headers']['content-type'])
3053
+ && $response['headers']['content-type'] = 'application/json'
3054
+ ){
3055
+ $response['body'] = json_decode($response['body_raw']);
3056
+ }
3057
+
3058
+ return $response;
3059
+ }
3060
+ } else {
3061
+ sucuriscan_error( 'HTTP method not allowed: ' . $method );
3062
+ }
3063
+
3064
+ return FALSE;
3065
+ }
3066
+
3067
+ /**
3068
+ * Store the API key locally.
3069
+ *
3070
+ * @param string $api_key An unique string of characters to identify this installation.
3071
+ * @param boolean $validate Whether the format of the key should be validated before store it.
3072
+ * @return boolean Either TRUE or FALSE if the key was saved successfully or not respectively.
3073
+ */
3074
+ function sucuriscan_set_api_key( $api_key='', $validate=FALSE ){
3075
+ if( $validate ){
3076
+ if( !preg_match('/^([a-z0-9]{32})$/', $api_key) ){
3077
+ sucuriscan_error( 'Invalid API key format' );
3078
+ return FALSE;
3079
+ }
3080
+ }
3081
+
3082
+ if( !empty($api_key) ){
3083
+ sucuriscan_notify_event( 'plugin_change', 'API key updated successfully: ' . $api_key );
3084
+ }
3085
+
3086
+ return (bool) update_option( 'sucuriscan_api_key', $api_key );
3087
+ }
3088
+
3089
+ /**
3090
+ * Retrieve the API key from the local storage.
3091
+ *
3092
+ * @return string|boolean The API key or FALSE if it does not exists.
3093
+ */
3094
+ function sucuriscan_wordpress_apikey(){
3095
+ $api_key = get_option('sucuriscan_api_key');
3096
+
3097
+ if( $api_key && strlen($api_key) > 10 ){
3098
+ return $api_key;
3099
+ }
3100
+
3101
+ return FALSE;
3102
+ }
3103
+
3104
+ /**
3105
+ * Check whether the CloudProxy API key is valid or not.
3106
+ *
3107
+ * @param string $api_key The CloudProxy API key.
3108
+ * @param boolean $return_match Whether the parts of the API key must be returned or not.
3109
+ * @return boolean TRUE if the API key specified is valid, FALSE otherwise.
3110
+ */
3111
+ function sucuriscan_valid_cloudproxy_apikey( $api_key='', $return_match=FALSE ){
3112
+ $pattern = '/^([a-z0-9]{32})\/([a-z0-9]{32})$/';
3113
+
3114
+ if( $api_key && preg_match($pattern, $api_key, $match) ){
3115
+ if( $return_match ){ return $match; }
3116
+
3117
+ return TRUE;
3118
+ }
3119
+
3120
+ return FALSE;
3121
+ }
3122
+
3123
+ /**
3124
+ * Check and return the API key for the plugin.
3125
+ *
3126
+ * In this plugin the key is a pair of two strings concatenated by a single
3127
+ * slash, the first part of it is in fact the key and the second part is the
3128
+ * unique identifier of the site in the remote server.
3129
+ *
3130
+ * @return array|boolean FALSE if the key is invalid or not present, an array otherwise.
3131
+ */
3132
+ function sucuriscan_cloudproxy_apikey(){
3133
+ $api_key = get_option('sucuriscan_cloudproxy_apikey');
3134
+ $match = sucuriscan_valid_cloudproxy_apikey( $api_key, TRUE );
3135
+
3136
+ if( $match ){
3137
+ return array(
3138
+ 'string' => $match[1].'/'.$match[2],
3139
+ 'k' => $match[1],
3140
+ 's' => $match[2]
3141
+ );
3142
+ }
3143
+
3144
+ return FALSE;
3145
+ }
3146
+
3147
+ /**
3148
+ * Call an action from the remote API interface of our WordPress service.
3149
+ *
3150
+ * @param string $method HTTP method that will be used to send the request.
3151
+ * @param array $params Parameters for the request defined in an associative array of key-value.
3152
+ * @param boolean $send_api_key Whether the API key should be added to the request parameters or not.
3153
+ * @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
3154
+ * @return array Array of results including HTTP headers or WP_Error if the request failed.
3155
+ */
3156
+ function sucuriscan_api_call_wordpress( $method='GET', $params=array(), $send_api_key=TRUE, $args=array() ){
3157
+ $url = SUCURISCAN_API;
3158
+ $params[SUCURISCAN_API_VERSION] = 1;
3159
+ $params['p'] = 'wordpress';
3160
+
3161
+ if( $send_api_key ){
3162
+ $api_key = sucuriscan_wordpress_apikey();
3163
+
3164
+ if( !$api_key ){ return FALSE; }
3165
+
3166
+ $params['k'] = $api_key;
3167
+ }
3168
+
3169
+ $response = sucuriscan_api_call( $url, $method, $params, $args );
3170
+
3171
+ return $response;
3172
+ }
3173
+
3174
+ /**
3175
+ * Call an action from the remote API interface of our CloudProxy service.
3176
+ *
3177
+ * @param string $method HTTP method that will be used to send the request.
3178
+ * @param array $params Parameters for the request defined in an associative array of key-value.
3179
+ * @return array Array of results including HTTP headers or WP_Error if the request failed.
3180
+ */
3181
+ function sucuriscan_api_call_cloudproxy( $method='GET', $params=array() ){
3182
+ $send_request = FALSE;
3183
+
3184
+ if( isset($params['k']) && isset($params['s']) ){
3185
+ $send_request = TRUE;
3186
+ } else {
3187
+ $api_key = sucuriscan_cloudproxy_apikey();
3188
+
3189
+ if( $api_key ){
3190
+ $send_request = TRUE;
3191
+ $params['k'] = $api_key['k'];
3192
+ $params['s'] = $api_key['s'];
3193
+ }
3194
+ }
3195
+
3196
+ if( $send_request ){
3197
+ $url = SUCURISCAN_CLOUDPROXY_API;
3198
+ $params[SUCURISCAN_CLOUDPROXY_API_VERSION] = 1;
3199
+
3200
+ $response = sucuriscan_api_call( $url, $method, $params );
3201
+
3202
+ return $response;
3203
+ }
3204
+
3205
+ return FALSE;
3206
+ }
3207
+
3208
+ /**
3209
+ * Determine whether an API response was successful or not checking the expected
3210
+ * generic variables and types, in case of an error a notification will appears
3211
+ * in the administrator panel explaining the result of the operation.
3212
+ *
3213
+ * @param array $response Array of results including HTTP headers or WP_Error if the request failed.
3214
+ * @return boolean Either TRUE or FALSE in case of success or failure of the API response (respectively).
3215
+ */
3216
+ function sucuriscan_handle_response( $response=array() ){
3217
+ if( $response ){
3218
+ if( $response['body'] instanceof stdClass ){
3219
+ if( isset($response['body']->status) ){
3220
+ if( $response['body']->status == 1 ){
3221
+ return TRUE;
3222
+ } else {
3223
+ sucuriscan_error( ucwords($response['body']->action) . ': ' . $response['body']->messages[0] );
3224
+ }
3225
+ } else {
3226
+ sucuriscan_error( 'Could not determine the status of an API call.' );
3227
+ }
3228
+ } else {
3229
+ sucuriscan_error( 'Unknown API content-type, it was not a JSON-encoded response.' );
3230
+ }
3231
+ }
3232
+
3233
+ return FALSE;
3234
+ }
3235
+
3236
+ /**
3237
+ * Send a request to the API to register this site.
3238
+ *
3239
+ * @return boolean TRUE if the API key was generated, FALSE otherwise.
3240
+ */
3241
+ function sucuriscan_register_site(){
3242
+ $response = sucuriscan_api_call_wordpress( 'POST', array(
3243
+ 'e' => sucuriscan_get_site_email(),
3244
+ 's' => sucuriscan_get_domain(),
3245
+ 'a' => 'register_site',
3246
+ ), FALSE );
3247
+
3248
+ if( sucuriscan_handle_response($response) ){
3249
+ sucuriscan_set_api_key( $response['body']->output->api_key );
3250
+ sucuriscan_create_scheduled_task();
3251
+ sucuriscan_notify_event( 'plugin_change', 'Site registered and API key generated' );
3252
+ sucuriscan_info( 'The API key for your site was successfully generated and saved.');
3253
+
3254
+ return TRUE;
3255
+ }
3256
+
3257
+ return FALSE;
3258
+ }
3259
+
3260
+ /**
3261
+ * Send a request to recover a previously registered API key.
3262
+ *
3263
+ * @return boolean TRUE if the API key was sent to the administrator email, FALSE otherwise.
3264
+ */
3265
+ function sucuriscan_recover_api_key(){
3266
+ $clean_domain = sucuriscan_get_domain();
3267
+
3268
+ $response = sucuriscan_api_call_wordpress( 'GET', array(
3269
+ 'e' => sucuriscan_get_site_email(),
3270
+ 's' => $clean_domain,
3271
+ 'a' => 'recover_key',
3272
+ ), FALSE );
3273
+
3274
+ if( sucuriscan_handle_response($response) ){
3275
+ sucuriscan_notify_event( 'plugin_change', 'API key recovered for domain: ' . $clean_domain );
3276
+ sucuriscan_info( $response['body']->output->message );
3277
+
3278
+ return TRUE;
3279
+ }
3280
+
3281
+ return FALSE;
3282
+ }
3283
+
3284
+ /**
3285
+ * Send a request to the API to store and analyze the events of the site. An
3286
+ * event can be anything from a simple request, an internal modification of the
3287
+ * settings or files in the administrator panel, or a notification generated by
3288
+ * this plugin.
3289
+ *
3290
+ * @param string $event The information gathered through out the normal functioning of the site.
3291
+ * @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
3292
+ */
3293
+ function sucuriscan_send_log( $event='' ){
3294
+ if( !empty($event) ){
3295
+ $response = sucuriscan_api_call_wordpress( 'POST', array(
3296
+ 'a' => 'send_log',
3297
+ 'm' => $event,
3298
+ ), TRUE, array( 'timeout' => 20 ) );
3299
+
3300
+ if( sucuriscan_handle_response($response) ){
3301
+ return TRUE;
3302
+ }
3303
+ }
3304
+
3305
+ return FALSE;
3306
+ }
3307
+
3308
+ /**
3309
+ * Retrieve the event logs registered by the API service.
3310
+ *
3311
+ * @return string The response of the API service.
3312
+ */
3313
+ function sucuriscan_get_logs(){
3314
+ $response = sucuriscan_api_call_wordpress( 'GET', array(
3315
+ 'a' => 'get_logs',
3316
+ 'l' => 50,
3317
+ ) );
3318
+
3319
+ if( sucuriscan_handle_response($response) ){
3320
+ $response['body']->output_data = array();
3321
+ $log_pattern = '/^([0-9-: ]+) (.*) : (.*)/';
3322
+ $extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
3323
+
3324
+ foreach( $response['body']->output as $log ){
3325
+ if( preg_match($log_pattern, $log, $log_match) ){
3326
+ $log_data = array(
3327
+ 'datetime' => $log_match[1],
3328
+ 'timestamp' => strtotime($log_match[1]),
3329
+ 'account' => $log_match[2],
3330
+ 'message' => $log_match[3],
3331
+ 'extra' => FALSE,
3332
+ 'extra_total' => 0,
3333
+ );
3334
+
3335
+ $log_data['message'] = str_replace( ', new size', '; new size', $log_data['message'] );
3336
+
3337
+ if( preg_match($extra_pattern, $log_data['message'], $log_extra) ){
3338
+ $log_data['message'] = $log_extra[1];
3339
+ $log_data['extra'] = explode(',', $log_extra[2]);
3340
+ $log_data['extra_total'] = count($log_data['extra']);
3341
+ }
3342
+
3343
+ $response['body']->output_data[] = $log_data;
3344
+ }
3345
+ }
3346
+
3347
+ return $response['body'];
3348
+ }
3349
+
3350
+ return FALSE;
3351
+ }
3352
+
3353
+ /**
3354
+ * Send a request to the API to store and analyze the file's hashes of the site.
3355
+ * This will be the core of the monitoring tools and will enhance the
3356
+ * information of the audit logs alerting the administrator of suspicious
3357
+ * changes in the system.
3358
+ *
3359
+ * @param string $hashes The information gathered after the scanning of the site's files.
3360
+ * @return boolean TRUE if the hashes were stored, FALSE otherwise.
3361
+ */
3362
+ function sucuriscan_send_hashes( $hashes='' ){
3363
+ if( !empty($hashes) ){
3364
+ $response = sucuriscan_api_call_wordpress( 'POST', array(
3365
+ 'a' => 'send_hashes',
3366
+ 'h' => $hashes,
3367
+ ) );
3368
+
3369
+ if( sucuriscan_handle_response($response) ){
3370
+ return TRUE;
3371
+ }
3372
+ }
3373
+
3374
+ return FALSE;
3375
+ }
3376
+
3377
+ /**
3378
+ * Checks last time we ran to avoid running twice (or too often).
3379
+ *
3380
+ * @param integer $runtime When the filesystem scan must be scheduled to run.
3381
+ * @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
3382
+ * @return boolean Either TRUE or FALSE representing the success or fail of the operation respectively.
3383
+ */
3384
+ function sucuriscan_verify_run( $runtime=0, $force_scan=FALSE ){
3385
+ $runtime_name = 'sucuriscan_runtime';
3386
+ $last_run = get_option($runtime_name);
3387
+ $current_time = time();
3388
+
3389
+ if( $last_run && !$force_scan ){
3390
+ $runtime_diff = $current_time - $runtime;
3391
+
3392
+ if( $last_run >= $runtime_diff ){
3393
+ return FALSE;
3394
+ }
3395
+ }
3396
+
3397
+ update_option( $runtime_name, $current_time );
3398
+ return TRUE;
3399
+ }
3400
+
3401
+ /**
3402
+ * Check whether the current WordPress version must be reported to the API
3403
+ * service or not, this is to avoid duplicated information in the audit logs.
3404
+ *
3405
+ * @return boolean TRUE if the current WordPress version must be reported, FALSE otherwise.
3406
+ */
3407
+ function sucuriscan_report_wpversion(){
3408
+ $option_name = 'sucuriscan_wp_version';
3409
+ $reported_version = get_option($option_name);
3410
+ $wp_version = sucuriscan_get_wpversion();
3411
+
3412
+ if( $reported_version != $wp_version ){
3413
+ sucuriscan_send_log( 'WordPress version: ' . $wp_version );
3414
+ update_option( $option_name, $wp_version );
3415
+
3416
+ return TRUE;
3417
+ }
3418
+
3419
+ return FALSE;
3420
+ }
3421
+
3422
+ /**
3423
+ * Schedule the task to run the first filesystem scan.
3424
+ *
3425
+ * @return void
3426
+ */
3427
+ function sucuriscan_create_scheduled_task(){
3428
+ $task_name = 'sucuriscan_scheduled_scan';
3429
+
3430
+ if( !wp_next_scheduled($task_name) ){
3431
+ wp_schedule_event( time() + 10, 'twicedaily', $task_name );
3432
+ }
3433
+
3434
+ wp_schedule_single_event( time() + 300, $task_name );
3435
+ sucuriscan_info( 'The first filesystem scan was scheduled.' );
3436
+ }
3437
+
3438
+ /**
3439
+ * Gather all the checksums (aka. file hashes) of this site, send them, and
3440
+ * analyze them using the Sucuri Monitoring service, this will generate the
3441
+ * audit logs for this site and be part of the integrity checks.
3442
+ *
3443
+ * @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
3444
+ * @return boolean TRUE if the filesystem scan was successful, FALSE otherwise.
3445
+ */
3446
+ function sucuriscan_filesystem_scan( $force_scan=FALSE ){
3447
+ $minimum_runtime = SUCURISCAN_MINIMUM_RUNTIME;
3448
+
3449
+ if(
3450
+ sucuriscan_wordpress_apikey()
3451
+ && class_exists('SucuriScanFileInfo')
3452
+ && sucuriscan_verify_run( $minimum_runtime, $force_scan )
3453
+ ){
3454
+ sucuriscan_report_wpversion();
3455
+
3456
+ $sucuri_fileinfo = new SucuriScanFileInfo();
3457
+ $scan_interface = get_option('sucuriscan_scan_interface');
3458
+ $signatures = $sucuri_fileinfo->get_directory_tree_md5(ABSPATH, $scan_interface);
3459
+
3460
+ if( $signatures ){
3461
+ $hashes_sent = sucuriscan_send_hashes( $signatures );
3462
+
3463
+ if( $hashes_sent ){
3464
+ sucuriscan_info( 'Successful filesystem scan' );
3465
+ return TRUE;
3466
+ } else {
3467
+ sucuriscan_error( 'The file hashes could not be stored.' );
3468
+ }
3469
+ } else {
3470
+ sucuriscan_error( 'The file hashes could not be retrieved, the filesystem scan failed.' );
3471
+ }
3472
+ }
3473
+
3474
+ return FALSE;
3475
+ }
3476
+
3477
+ /**
3478
+ * Generates an audit event log (to be sent later).
3479
+ *
3480
+ * @param integer $severity Importance of the event that will be reported, values from one to five.
3481
+ * @param string $location In which part of the system was the event triggered.
3482
+ * @param string $message The explanation of the event.
3483
+ * @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
3484
+ */
3485
+ function sucuriscan_report_event( $severity=0, $location='', $message='' ){
3486
+ $user = wp_get_current_user();
3487
+ $username = FALSE;
3488
+ $current_time = date( 'Y-m-d H:i:s' );
3489
+ $remote_ip = sucuriscan_get_remoteaddr();
3490
+
3491
+ // Fixing severity value.
3492
+ $severity = (int) $severity;
3493
+ if( $severity > 0 ){ $severity = 1; }
3494
+ elseif( $severity > 5 ){ $severity = 5; }
3495
+
3496
+ // Identify current user in session.
3497
+ if(
3498
+ $user instanceof WP_User
3499
+ && isset($user->user_login)
3500
+ && !empty($user->user_login)
3501
+ ){
3502
+ if( $user->user_login != $user->display_name ){
3503
+ $username = sprintf( ' %s (%s),', $user->display_name, $user->user_login );
3504
+ } else {
3505
+ $username = sprintf( ' %s,', $user->user_login );
3506
+ }
3507
+ }
3508
+
3509
+ // Convert the severity number into a readable string.
3510
+ switch( $severity ){
3511
+ case 0: $severity_name = 'Debug'; break;
3512
+ case 1: $severity_name = 'Notice'; break;
3513
+ case 2: $severity_name = 'Info'; break;
3514
+ case 3: $severity_name = 'Warning'; break;
3515
+ case 4: $severity_name = 'Error'; break;
3516
+ case 5: $severity_name = 'Critical'; break;
3517
+ default: $severity_name = 'Info'; break;
3518
+ }
3519
+
3520
+ $message = str_replace( array("\n", "\r"), array('', ''), $message );
3521
+ $event_message = sprintf(
3522
+ '%s:%s %s; %s',
3523
+ $severity_name,
3524
+ $username,
3525
+ $remote_ip,
3526
+ $message
3527
+ );
3528
+
3529
+ return sucuriscan_send_log($event_message);
3530
+ }
3531
+
3532
+ /**
3533
+ * Send a notification to the administrator of the specified events, only if
3534
+ * the administrator accepted to receive alerts for this type of events.
3535
+ *
3536
+ * @param string $event The name of the event that was triggered.
3537
+ * @param string $content Body of the email that will be sent to the administrator.
3538
+ * @return void
3539
+ */
3540
+ function sucuriscan_notify_event( $event='', $content='' ){
3541
+ $event_name = 'sucuriscan_notify_' . $event;
3542
+ $notify = sucuriscan_get_option($event_name);
3543
+ $email = sucuriscan_get_option('admin_email');
3544
+
3545
+ if( $notify == 'enabled' ){
3546
+ if( $event == 'post_publication' ){
3547
+ $event = 'post_update';
3548
+ }
3549
+
3550
+ $title = sprintf( 'Sucuri notification (%s)', str_replace('_', chr(32), $event) );
3551
+ $mail_sent = sucuriscan_send_mail( $email, $title, $content );
3552
+
3553
+ return $mail_sent;
3554
+ }
3555
+
3556
+ return FALSE;
3557
+ }
3558
+
3559
+ /**
3560
+ * Retrieve the public settings of the account associated with the API keys
3561
+ * registered by the administrator of the site. This function will send a HTTP
3562
+ * request to the remote API service and process its response, when successful
3563
+ * it will return an array/object containing the public attributes of the site.
3564
+ *
3565
+ * @param boolean $api_key The CloudProxy API key.
3566
+ * @return array A hash with the settings of a CloudProxy account.
3567
+ */
3568
+ function sucuriscan_cloudproxy_settings( $api_key=FALSE ){
3569
+ $params = array( 'a' => 'show_settings' );
3570
+
3571
+ if( $api_key ){
3572
+ $params = array_merge( $params, $api_key );
3573
+ }
3574
+
3575
+ $response = sucuriscan_api_call_cloudproxy( 'GET', $params );
3576
+
3577
+ if( sucuriscan_handle_response($response) ){
3578
+ return $response['body']->output;
3579
+ }
3580
+
3581
+ return FALSE;
3582
+ }
3583
+
3584
+ /**
3585
+ * Flush the cache of the site(s) associated with the API key.
3586
+ *
3587
+ * @param boolean $api_key The CloudProxy API key.
3588
+ * @return string Message explaining the result of the operation.
3589
+ */
3590
+ function sucuriscan_cloudproxy_clear_cache( $api_key=FALSE ){
3591
+ $params = array( 'a' => 'clear_cache' );
3592
+
3593
+ if( $api_key ){
3594
+ $params = array_merge( $params, $api_key );
3595
+ }
3596
+
3597
+ $response = sucuriscan_api_call_cloudproxy( 'GET', $params );
3598
+
3599
+ if( sucuriscan_handle_response($response) ){
3600
+ return $response['body'];
3601
+ }
3602
+
3603
+ return FALSE;
3604
+ }
3605
+
3606
+ /**
3607
+ * Retrieve the audit logs of the account associated with the API keys
3608
+ * registered b the administrator of the site. This function will send a HTTP
3609
+ * request to the remote API service and process its response, when successful
3610
+ * it will return an array/object containing a list of requests blocked by our
3611
+ * CloudProxy.
3612
+ *
3613
+ * By default the logs that will be retrieved are from today, if you need to see
3614
+ * the logs of previous days you will need to add a new parameter to the request
3615
+ * URL named "date" with format yyyy-mm-dd.
3616
+ *
3617
+ * @param boolean $api_key The CloudProxy API key.
3618
+ * @param string $date An optional date to filter the result to a specific timespan: yyyy-mm-dd.
3619
+ * @return array A list of objects with the detailed version of each request blocked by our service.
3620
+ */
3621
+ function sucuriscan_cloudproxy_logs( $api_key=FALSE, $date='' ){
3622
+ $params = array(
3623
+ 'a' => 'audit_trails',
3624
+ 'date' => date('Y-m-d'),
3625
+ );
3626
+
3627
+ if( preg_match('/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/', $date) ){
3628
+ $params['date'] = $date;
3629
+ }
3630
+
3631
+ if( $api_key ){
3632
+ $params = array_merge( $params, $api_key );
3633
+ }
3634
+
3635
+ $response = sucuriscan_api_call_cloudproxy( 'GET', $params );
3636
+
3637
+ if( sucuriscan_handle_response($response) ){
3638
+ return $response['body']->output;
3639
+ }
3640
+
3641
+ return FALSE;
3642
+ }
3643
+
3644
+ $sucuriscan_hooks = array(
3645
+ 'add_attachment',
3646
+ 'create_category',
3647
+ 'delete_post',
3648
+ 'private_to_published',
3649
+ 'publish_page',
3650
+ 'publish_post',
3651
+ 'publish_phone',
3652
+ 'xmlrpc_publish_post',
3653
+ 'add_link',
3654
+ 'switch_theme',
3655
+ 'delete_user',
3656
+ 'retrieve_password',
3657
+ 'user_register',
3658
+ 'wp_login',
3659
+ 'wp_login_failed',
3660
+ 'login_form_resetpass',
3661
+ );
3662
+
3663
+ /**
3664
+ * Send to Sucuri servers an alert advising that an attachment was added to a post.
3665
+ *
3666
+ * @param integer $id The post identifier.
3667
+ * @return void
3668
+ */
3669
+ function sucuriscan_hook_add_attachment( $id=0 ){
3670
+ $data = ( is_int($id) ? get_post($id) : FALSE );
3671
+ $title = ( $data ? $data->post_title : 'Unknown' );
3672
+
3673
+ $message = 'Media file added #'.$id.' ('.$title.')';
3674
+ sucuriscan_report_event( 1, 'core', $message );
3675
+ sucuriscan_notify_event( 'post_publication', $message );
3676
+ }
3677
+
3678
+ /**
3679
+ * Send to Sucuri servers an alert advising that a category was created.
3680
+ *
3681
+ * @param integer $id The identifier of the category created.
3682
+ * @return void
3683
+ */
3684
+ function sucuriscan_hook_create_category( $id=0 ){
3685
+ $title = ( is_int($id) ? get_cat_name($id) : 'Unknown' );
3686
+
3687
+ $message = 'Category created #'.$id.' ('.$title.')';
3688
+ sucuriscan_report_event( 1, 'core', $message );
3689
+ sucuriscan_notify_event( 'post_publication', $message );
3690
+ }
3691
+
3692
+ /**
3693
+ * Send to Sucuri servers an alert advising that a post was deleted.
3694
+ *
3695
+ * @param integer $id The identifier of the post deleted.
3696
+ * @return void
3697
+ */
3698
+ function sucuriscan_hook_delete_post( $id=0 ){
3699
+ sucuriscan_report_event( 3, 'core', 'Post deleted #'.$id );
3700
+ }
3701
+
3702
+ /**
3703
+ * Send to Sucuri servers an alert advising that the state of a post was changed
3704
+ * from private to published. This will only applies for posts not pages.
3705
+ *
3706
+ * @param integer $id The identifier of the post changed.
3707
+ * @return void
3708
+ */
3709
+ function sucuriscan_hook_private_to_published( $id=0 ){
3710
+ $data = ( is_int($id) ? get_post($id) : FALSE );
3711
+
3712
+ if( $data ){
3713
+ $title = $data->post_title;
3714
+ $p_type = ucwords($data->post_type);
3715
+ } else {
3716
+ $title = 'Unknown';
3717
+ $p_type = 'Publication';
3718
+ }
3719
+
3720
+ $message = $p_type.' changed from private to published #'.$id.' ('.$title.')';
3721
+ sucuriscan_report_event( 2, 'core', $message );
3722
+ sucuriscan_notify_event( 'post_publication', $message );
3723
+ }
3724
+
3725
+ /**
3726
+ * Send to Sucuri servers an alert advising that a post was published.
3727
+ *
3728
+ * @param integer $id The identifier of the post or page published.
3729
+ * @return void
3730
+ */
3731
+ function sucuriscan_hook_publish( $id=0 ){
3732
+ $data = ( is_int($id) ? get_post($id) : FALSE );
3733
+
3734
+ if( $data ){
3735
+ $title = $data->post_title;
3736
+ $p_type = ucwords($data->post_type);
3737
+ $action = ( $data->post_date == $data->post_modified ? 'created' : 'updated' );
3738
+ } else {
3739
+ $title = 'Unknown';
3740
+ $p_type = 'Publication';
3741
+ $action = 'published';
3742
+ }
3743
+
3744
+ $message = $p_type.' was '.$action.' #'.$id.' ('.$title.')';
3745
+ sucuriscan_report_event( 2, 'core', $message );
3746
+ sucuriscan_notify_event( 'post_publication', $message );
3747
+ }
3748
+
3749
+ /**
3750
+ * Alias function for hook_publish()
3751
+ *
3752
+ * @param integer $id The identifier of the post or page published.
3753
+ * @return void
3754
+ */
3755
+ function sucuriscan_hook_publish_page( $id=0 ){ sucuriscan_hook_publish($id); }
3756
+
3757
+ /**
3758
+ * Alias function for hook_publish()
3759
+ *
3760
+ * @param integer $id The identifier of the post or page published.
3761
+ * @return void
3762
+ */
3763
+ function sucuriscan_hook_publish_post( $id=0 ){ sucuriscan_hook_publish($id); }
3764
+
3765
+ /**
3766
+ * Alias function for hook_publish()
3767
+ *
3768
+ * @param integer $id The identifier of the post or page published.
3769
+ * @return void
3770
+ */
3771
+ function sucuriscan_hook_publish_phone( $id=0 ){ sucuriscan_hook_publish($id); }
3772
+
3773
+ /**
3774
+ * Alias function for hook_publish()
3775
+ *
3776
+ * @param integer $id The identifier of the post or page published.
3777
+ * @return void
3778
+ */
3779
+ function sucuriscan_hook_xmlrpc_publish_post( $id=0 ){ sucuriscan_hook_publish($id); }
3780
+
3781
+ /**
3782
+ * Send to Sucuri servers an alert advising that a new link was added to the bookmarks.
3783
+ *
3784
+ * @param integer $id Identifier of the new link created;
3785
+ * @return void
3786
+ */
3787
+ function sucuriscan_hook_add_link( $id=0 ){
3788
+ $data = ( is_int($id) ? get_bookmark($id) : FALSE );
3789
+
3790
+ if( $data ){
3791
+ $title = $data->link_name;
3792
+ $url = $data->link_url;
3793
+ } else {
3794
+ $title = 'Unknown';
3795
+ $url = 'undefined/url';
3796
+ }
3797
+
3798
+ $message = 'New link added #'.$id.' ('.$title.': '.$url.')';
3799
+ sucuriscan_report_event( 2, 'core', $message );
3800
+ sucuriscan_notify_event( 'post_publication', $message );
3801
+ }
3802
+
3803
+ /**
3804
+ * Send to Sucuri servers an alert advising that the theme of the site was changed.
3805
+ *
3806
+ * @param string $title The name of the new theme selected to used through out the site.
3807
+ * @return void
3808
+ */
3809
+ function sucuriscan_hook_switch_theme( $title='' ){
3810
+ if( empty($title) ){ $title = 'Unknown'; }
3811
+
3812
+ $message = 'Theme switched to: '.$title;
3813
+ sucuriscan_report_event( 3, 'core', $message );
3814
+ sucuriscan_notify_event( 'theme_switched', $message );
3815
+ }
3816
+
3817
+ /**
3818
+ * Send to Sucuri servers an alert advising that a user account was deleted.
3819
+ *
3820
+ * @param integer $id The identifier of the user account deleted.
3821
+ * @return void
3822
+ */
3823
+ function sucuriscan_hook_delete_user( $id=0 ){
3824
+ sucuriscan_report_event( 3, 'core', 'User account deleted #'.$id );
3825
+ }
3826
+
3827
+ /**
3828
+ * Send to Sucuri servers an alert advising that an attempt to retrieve the password
3829
+ * of an user account was tried.
3830
+ *
3831
+ * @param string $title The name of the user account involved in the trasaction.
3832
+ * @return void
3833
+ */
3834
+ function sucuriscan_hook_retrieve_password( $title='' ){
3835
+ if( empty($title) ){ $title = 'Unknown'; }
3836
+
3837
+ sucuriscan_report_event( 3, 'core', 'Password retrieval attempt for user: '.$title );
3838
+ }
3839
+
3840
+ /**
3841
+ * Send to Sucuri servers an alert advising that a new user account was created.
3842
+ *
3843
+ * @param integer $id The identifier of the new user account created.
3844
+ * @return void
3845
+ */
3846
+ function sucuriscan_hook_user_register( $id=0 ){
3847
+ $data = ( is_int($id) ? get_userdata($id) : FALSE );
3848
+ $title = ( $data ? $data->display_name : 'Unknown' );
3849
+
3850
+ $message = 'New user account registered #'.$id.' ('.$title.')';
3851
+ sucuriscan_report_event( 3, 'core', $message );
3852
+ sucuriscan_notify_event( 'user_registration', $message );
3853
+ }
3854
+
3855
+ /**
3856
+ * Send to Sucuri servers an alert advising that an attempt to login into the
3857
+ * administration panel was successful.
3858
+ *
3859
+ * @param string $title The name of the user account involved in the transaction.
3860
+ * @return void
3861
+ */
3862
+ function sucuriscan_hook_wp_login( $title='' ){
3863
+ if( empty($title) ){ $title = 'Unknown'; }
3864
+
3865
+ $message = 'User logged in: '.$title;
3866
+ sucuriscan_report_event( 2, 'core', $message );
3867
+ sucuriscan_notify_event( 'success_login', $message );
3868
+ }
3869
+
3870
+ /**
3871
+ * Send to Sucuri servers an alert advising that an attempt to login into the
3872
+ * administration panel failed.
3873
+ *
3874
+ * @param string $title The name of the user account involved in the transaction.
3875
+ * @return void
3876
+ */
3877
+ function sucuriscan_hook_wp_login_failed( $title='' ){
3878
+ if( empty($title) ){ $title = 'Unknown'; }
3879
+
3880
+ $message = 'User authentication failed: '.$title;
3881
+ sucuriscan_report_event( 2, 'core', $message );
3882
+ sucuriscan_notify_event( 'failed_login', $message );
3883
+ }
3884
+
3885
+ /**
3886
+ * Send to Sucuri servers an alert advising that an attempt to reset the password
3887
+ * of an user account was executed.
3888
+ *
3889
+ * @return void
3890
+ */
3891
+ function sucuriscan_hook_login_form_resetpass(){
3892
+ // Detecting wordpress 2.8.3 vulnerability - $key is array.
3893
+ if( isset($_GET['key']) && is_array($_GET['key']) ){
3894
+ sucuriscan_report_event( 3, 'core', 'Attempt to reset password by attacking WP/2.8.3 bug' );
3895
+ }
3896
+ }
3897
+
3898
+ // Configure the hooks defined above to be triggered automatically.
3899
+ if( isset($sucuriscan_hooks) ){
3900
+ foreach( $sucuriscan_hooks as $hook_name ){
3901
+ $hook_func = 'sucuriscan_hook_' . $hook_name;
3902
+
3903
+ if( function_exists($hook_func) ){
3904
+ add_action( $hook_name, $hook_func, 50 );
3905
+ }
3906
+ }
3907
+ }
3908
+
3909
+ if( !function_exists('sucuriscan_hook_undefined_actions') ){
3910
+
3911
+ /**
3912
+ * Send a notifications to the administrator of some specific events that are
3913
+ * not triggered through an hooked action, but through a simple request in the
3914
+ * admin interface.
3915
+ *
3916
+ * @return integer Either one or zero representing the success or fail of the operation.
3917
+ */
3918
+ function sucuriscan_hook_undefined_actions(){
3919
+
3920
+ // Plugin activation and/or deactivation.
3921
+ if(
3922
+ current_user_can('activate_plugins')
3923
+ && (
3924
+ ( isset($_GET['action']) && preg_match('/^(activate|deactivate)$/', $_GET['action']) ) ||
3925
+ ( isset($_POST['action']) && preg_match('/^(activate|deactivate)-selected$/', $_POST['action']))
3926
+ )
3927
+ ){
3928
+ $plugin_list = array();
3929
+
3930
+ if(
3931
+ isset($_GET['plugin'])
3932
+ && !empty($_GET['plugin'])
3933
+ && strpos($_SERVER['REQUEST_URI'], 'plugins.php') !== FALSE
3934
+ ){
3935
+ $action_d = $_GET['action'] . 'd';
3936
+ $plugin_list[] = $_GET['plugin'];
3937
+ }
3938
+
3939
+ elseif( isset($_POST['checked']) ){
3940
+ $action_d = str_replace('-selected', 'd', $_POST['action']);
3941
+ $plugin_list = $_POST['checked'];
3942
+ }
3943
+
3944
+ foreach( $plugin_list as $plugin ){
3945
+ $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3946
+ $message = sprintf(
3947
+ 'Plugin %s: %s (v%s; %s)',
3948
+ $action_d,
3949
+ $plugin_info['Name'],
3950
+ $plugin_info['Version'],
3951
+ esc_attr($plugin)
3952
+ );
3953
+
3954
+ sucuriscan_report_event( 3, 'core', $message );
3955
+ sucuriscan_notify_event( 'plugin_' . $action_d, $message );
3956
+ }
3957
+ }
3958
+
3959
+ // Plugin update request.
3960
+ elseif(
3961
+ current_user_can('update_plugins')
3962
+ && (
3963
+ ( isset($_GET['action']) && preg_match('/(upgrade-plugin|do-plugin-upgrade)/', $_GET['action']) ) ||
3964
+ ( isset($_POST['action']) && $_POST['action'] == 'update-selected' )
3965
+ )
3966
+ ){
3967
+ $plugin_list = array();
3968
+
3969
+ if(
3970
+ isset($_GET['plugin'])
3971
+ && !empty($_GET['plugin'])
3972
+ && strpos($_SERVER['REQUEST_URI'], 'wp-admin/update.php') !== FALSE
3973
+ ){
3974
+ $plugin_list[] = $_GET['plugin'];
3975
+ }
3976
+
3977
+ elseif( isset($_POST['checked']) ){
3978
+ $plugin_list = $_POST['checked'];
3979
+ }
3980
+
3981
+ foreach( $plugin_list as $plugin ){
3982
+ $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3983
+ $message = sprintf(
3984
+ 'Plugin request to be updated: %s (v%s; %s)',
3985
+ $plugin_info['Name'],
3986
+ $plugin_info['Version'],
3987
+ esc_attr($plugin)
3988
+ );
3989
+
3990
+ sucuriscan_report_event( 3, 'core', $message );
3991
+ sucuriscan_notify_event( 'plugin_updated', $message );
3992
+ }
3993
+ }
3994
+
3995
+ // Plugin installation request.
3996
+ elseif(
3997
+ current_user_can('install_plugins')
3998
+ && isset($_GET['action'])
3999
+ && preg_match('/^(install|upload)-plugin$/', $_GET['action'])
4000
+ && current_user_can('install_plugins')
4001
+ ){
4002
+ if( isset($_FILES['pluginzip']) ){
4003
+ $plugin = $_FILES['pluginzip']['name'];
4004
+ } elseif( isset($_GET['plugin']) ){
4005
+ $plugin = $_GET['plugin'];
4006
+ } else {
4007
+ $plugin = 'Unknown';
4008
+ }
4009
+
4010
+ $message = 'Plugin request to be installed: ' . esc_attr($plugin);
4011
+ sucuriscan_report_event( 3, 'core', $message );
4012
+ sucuriscan_notify_event( 'plugin_installed', $message );
4013
+ }
4014
+
4015
+ // Plugin deletion request.
4016
+ elseif(
4017
+ current_user_can('delete_plugins')
4018
+ && isset($_POST['action'])
4019
+ && $_POST['action'] == 'delete-selected'
4020
+ && isset($_POST['verify-delete'])
4021
+ && $_POST['verify-delete'] == 1
4022
+ ){
4023
+ $plugin_list = (array) $_POST['checked'];
4024
+
4025
+ foreach( $plugin_list as $plugin ){
4026
+ $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4027
+ $message = sprintf(
4028
+ 'Plugin request to be deleted: %s (v%s; %s)',
4029
+ $plugin_info['Name'],
4030
+ $plugin_info['Version'],
4031
+ esc_attr($plugin)
4032
+ );
4033
+
4034
+ sucuriscan_report_event( 3, 'core', $message );
4035
+ sucuriscan_notify_event( 'plugin_deleted', $message );
4036
+ }
4037
+ }
4038
+
4039
+ // Plugin editor request.
4040
+ elseif(
4041
+ current_user_can('edit_plugins')
4042
+ && isset($_POST['action'])
4043
+ && $_POST['action'] == 'update'
4044
+ && isset($_POST['file'])
4045
+ && isset($_POST['plugin'])
4046
+ && strpos($_SERVER['REQUEST_URI'], 'plugin-editor.php') !== FALSE
4047
+ ){
4048
+ $message = 'Plugin editor modification: ' . esc_attr($_POST['file']);
4049
+ sucuriscan_report_event( 3, 'core', $message );
4050
+ sucuriscan_notify_event( 'theme_editor', $message );
4051
+ }
4052
+
4053
+ // Theme editor request.
4054
+ elseif(
4055
+ current_user_can('edit_themes')
4056
+ && isset($_POST['action'])
4057
+ && $_POST['action'] == 'update'
4058
+ && isset($_POST['file'])
4059
+ && isset($_POST['theme'])
4060
+ && strpos($_SERVER['REQUEST_URI'], 'theme-editor.php') !== FALSE
4061
+ ){
4062
+ $message = 'Theme editor modification: ' . esc_attr($_POST['theme']) . '/' . esc_attr($_POST['file']);
4063
+ sucuriscan_report_event( 3, 'core', $message );
4064
+ sucuriscan_notify_event( 'theme_editor', $message );
4065
+ }
4066
+
4067
+ // Theme activation and/or deactivation (same hook for switch_theme).
4068
+ // Theme installation request (hook not available).
4069
+ // Theme deletion request (hook not available).
4070
+
4071
+ // Theme update request.
4072
+ elseif(
4073
+ current_user_can('update_themes')
4074
+ && isset($_GET['action'])
4075
+ && preg_match('/^(upgrade-theme|do-theme-upgrade)$/', $_GET['action'])
4076
+ && isset($_POST['checked'])
4077
+ ){
4078
+ $theme_list = (array) $_POST['checked'];
4079
+
4080
+ foreach( $theme_list as $theme ){
4081
+ $theme_info = wp_get_theme($theme);
4082
+ $theme_name = ucwords($theme);
4083
+ $theme_version = '0.0';
4084
+
4085
+ if( $theme_info->exists() ){
4086
+ $theme_name = $theme_info->get('Name');
4087
+ $theme_version = $theme_info->get('Version');
4088
+ }
4089
+
4090
+ $message = sprintf(
4091
+ 'Theme request to be updated: %s (v%s; %s)',
4092
+ $theme_name,
4093
+ $theme_version,
4094
+ esc_attr($theme)
4095
+ );
4096
+
4097
+ sucuriscan_report_event( 3, 'core', $message );
4098
+ sucuriscan_notify_event( 'theme_updated', $message );
4099
+ }
4100
+ }
4101
+
4102
+ // WordPress update request.
4103
+ elseif(
4104
+ current_user_can('update_core')
4105
+ && isset($_GET['action'])
4106
+ && $_GET['action'] == 'do-core-reinstall'
4107
+ && isset($_POST['upgrade'])
4108
+ && isset($_POST['version'])
4109
+ ){
4110
+ $message = 'WordPress updated (or re-installed) to version: ' . esc_attr($_POST['version']);
4111
+ sucuriscan_report_event( 3, 'core', $message );
4112
+ sucuriscan_notify_event( 'website_updated', $message );
4113
+ }
4114
+
4115
+ // Widget addition or deletion.
4116
+ elseif(
4117
+ current_user_can('edit_theme_options')
4118
+ && isset($_POST['action'])
4119
+ && $_POST['action'] == 'save-widget'
4120
+ && isset($_POST['id_base'])
4121
+ && isset($_POST['sidebar'])
4122
+ ){
4123
+ if(
4124
+ isset($_POST['delete_widget'])
4125
+ && $_POST['delete_widget'] == 1
4126
+ ){
4127
+ $action_d = 'deleted';
4128
+ $action_text = 'deleted from';
4129
+ } else {
4130
+ $action_d = 'added';
4131
+ $action_text = 'added to';
4132
+ }
4133
+
4134
+ $message = sprintf(
4135
+ 'Widget %s (%s) %s %s (#%d; size %dx%d)',
4136
+ esc_attr($_POST['id_base']),
4137
+ esc_attr($_POST['widget-id']),
4138
+ $action_text,
4139
+ esc_attr($_POST['sidebar']),
4140
+ esc_attr($_POST['widget_number']),
4141
+ esc_attr($_POST['widget-width']),
4142
+ esc_attr($_POST['widget-height'])
4143
+ );
4144
+
4145
+ sucuriscan_report_event( 3, 'core', $message );
4146
+ sucuriscan_notify_event( 'widget_' . $action_d, $message );
4147
+ }
4148
+
4149
+ // Detect any Wordpress settings modification.
4150
+ elseif(
4151
+ isset($_POST['option_page'])
4152
+ && current_user_can('manage_options')
4153
+ && SucuriScan::sucuriscan_check_options_wpnonce()
4154
+ ){
4155
+ // Get the settings available in the database and compare them with the submission.
4156
+ $all_options = sucuriscan_get_wp_options();
4157
+ $options_changed = sucuriscan_what_options_were_changed($_POST);
4158
+ $options_changed_str = '';
4159
+ $options_changed_count = 0;
4160
+
4161
+ // Generate the list of options changed.
4162
+ foreach( $options_changed['original'] as $option_name => $option_value ){
4163
+ $options_changed_count += 1;
4164
+ $options_changed_str .= sprintf(
4165
+ "The value of the option <b>%s</b> was changed from <b>'%s'</b> to <b>'%s'</b>.<br>\n",
4166
+ $option_name, $option_value, $options_changed['changed'][$option_name]
4167
+ );
4168
+ }
4169
+
4170
+ // Get the option group (name of the page where the request was originated).
4171
+ $option_page = isset($_POST['option_page']) ? $_POST['option_page'] : 'options';
4172
+ $page_referer = FALSE;
4173
+
4174
+ // Check which of these option groups where modified.
4175
+ switch( $option_page ){
4176
+ case 'options':
4177
+ $page_referer = 'Global';
4178
+ break;
4179
+ case 'general': /* no_break */
4180
+ case 'writing': /* no_break */
4181
+ case 'reading': /* no_break */
4182
+ case 'discussion': /* no_break */
4183
+ case 'media': /* no_break */
4184
+ case 'permalink':
4185
+ $page_referer = ucwords($option_page);
4186
+ break;
4187
+ default:
4188
+ $page_referer = 'Common';
4189
+ break;
4190
+ }
4191
+
4192
+ if( $page_referer && $options_changed_count > 0 ){
4193
+ $message = $page_referer.' settings changed';
4194
+ sucuriscan_report_event( 3, 'core', $message );
4195
+ sucuriscan_notify_event( 'settings_updated', $message . "<br>\n" . $options_changed_str );
4196
+ }
4197
+ }
4198
+
4199
+ }
4200
+
4201
+ add_action( 'admin_init', 'sucuriscan_hook_undefined_actions' );
4202
+ add_action( 'login_form', 'sucuriscan_hook_undefined_actions' );
4203
+ }
4204
+
4205
+ /**
4206
+ * CloudProxy monitoring page.
4207
+ *
4208
+ * It checks whether the WordPress core files are the original ones, and the state
4209
+ * of the themes and plugins reporting the availability of updates. It also checks
4210
+ * the user accounts under the administrator group.
4211
+ *
4212
+ * @return void
4213
+ */
4214
+ function sucuriscan_monitoring_page(){
4215
+ if( !current_user_can('manage_options') ){
4216
+ wp_die(__('You do not have sufficient permissions to access this page: Firewall (WAF)') );
4217
+ }
4218
+
4219
+ // Process all form submissions.
4220
+ sucuriscan_monitoring_form_submissions();
4221
+
4222
+ // Get the dynamic values for the template variables.
4223
+ $api_key = sucuriscan_cloudproxy_apikey();
4224
+
4225
+ // Page pseudo-variables initialization.
4226
+ $template_variables = array(
4227
+ 'PageTitle' => 'Firewall WAF',
4228
+ 'Monitoring.InstructionsVisibility' => 'visible',
4229
+ 'Monitoring.Settings' => sucuriscan_monitoring_settings($api_key),
4230
+ 'Monitoring.Logs' => sucuriscan_monitoring_logs($api_key),
4231
+
4232
+ /* Pseudo-variables for the monitoring logs. */
4233
+ 'AuditLogs.List' => '',
4234
+ 'AuditLogs.CountText' => '',
4235
+ 'AuditLogs.DenialTypeOptions' => '',
4236
+ 'AuditLogs.NoItemsVisibility' => '',
4237
+ 'AuditLogs.PaginationVisibility' => '',
4238
+ 'AuditLogs.AuditPagination' => '',
4239
+ );
4240
+
4241
+ if( $api_key ){
4242
+ $template_variables['Monitoring.InstructionsVisibility'] = 'hidden';
4243
+ }
4244
+
4245
+ echo sucuriscan_get_template('monitoring', $template_variables);
4246
+ }
4247
+
4248
+ /**
4249
+ * Process the requests sent by the form submissions originated in the monitoring
4250
+ * page, all forms must have a nonce field that will be checked agains the one
4251
+ * generated in the template render function.
4252
+ *
4253
+ * @return void
4254
+ */
4255
+ function sucuriscan_monitoring_form_submissions(){
4256
+
4257
+ if( sucuriscan_check_page_nonce() ){
4258
+
4259
+ // Add and/or Update the Sucuri WAF API Key (do it before anything else).
4260
+ $option_name = 'sucuriscan_cloudproxy_apikey';
4261
+
4262
+ if( isset($_POST[$option_name]) ){
4263
+ $api_key = $_POST[$option_name];
4264
+
4265
+ if( sucuriscan_valid_cloudproxy_apikey($api_key) ){
4266
+ update_option($option_name, $api_key);
4267
+ sucuriscan_info( 'Sucuri CloudProxy WAF API key saved successfully' );
4268
+ } elseif( empty($api_key) ){
4269
+ delete_option($option_name);
4270
+ sucuriscan_info( 'Sucuri CloudProxy WAF API key removed successfully' );
4271
+ } else {
4272
+ sucuriscan_error( 'Invalid CloudProxy API key, check your settings and try again.' );
4273
+ }
4274
+ }
4275
+
4276
+ // Flush the cache of the site(s) associated with the API key.
4277
+ if( isset($_POST['sucuriscan_clear_cache']) ){
4278
+ $clear_cache_resp = sucuriscan_cloudproxy_clear_cache();
4279
+
4280
+ if( $clear_cache_resp ){
4281
+ if( isset($clear_cache_resp->messages[0]) ){
4282
+ sucuriscan_info($clear_cache_resp->messages[0]);
4283
+ } else {
4284
+ sucuriscan_error('Could not clear the cache of your site, try later again.');
4285
+ }
4286
+ } else {
4287
+ sucuriscan_error( 'CloudProxy WAF is not enabled on your site, or your API key is invalid.' );
4288
+ }
4289
+ }
4290
+
4291
+ }
4292
+
4293
+ }
4294
+
4295
+ /**
4296
+ * Generate the HTML code for the monitoring settings panel.
4297
+ *
4298
+ * @param string $api_key The CloudProxy API key.
4299
+ * @return string The parsed-content of the monitoring settings panel.
4300
+ */
4301
+ function sucuriscan_monitoring_settings( $api_key='' ){
4302
+ $template_variables = array(
4303
+ 'Monitoring.APIKey' => '',
4304
+ 'Monitoring.SettingsVisibility' => 'hidden',
4305
+ 'Monitoring.SettingOptions' => '',
4306
+ );
4307
+
4308
+ if( $api_key ){
4309
+ $settings = sucuriscan_cloudproxy_settings($api_key);
4310
+
4311
+ $template_variables['Monitoring.APIKey'] = $api_key['string'];
4312
+
4313
+ if( $settings ){
4314
+ $counter = 0;
4315
+ $template_variables['Monitoring.SettingsVisibility'] = 'visible';
4316
+ $settings = sucuriscan_explain_monitoring_settings($settings);
4317
+
4318
+ foreach( $settings as $option_name => $option_value ){
4319
+ $css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
4320
+ $option_title = ucwords(str_replace('_', chr(32), $option_name));
4321
+
4322
+ // Generate a HTML list when the option's value is an array.
4323
+ if( is_array($option_value) ){
4324
+ $css_scrollable = count($option_value) > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
4325
+ $html_list = '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
4326
+
4327
+ foreach( $option_value as $single_value ){
4328
+ $html_list .= '<li>' . $single_value . '</li>';
4329
+ }
4330
+
4331
+ $html_list .= '</ul>';
4332
+ $option_value = $html_list;
4333
+ }
4334
+
4335
+ // Parse the snippet template and replace the pseudo-variables.
4336
+ $template_variables['Monitoring.SettingOptions'] .= sucuriscan_get_snippet('monitoring-settings', array(
4337
+ 'Monitoring.OptionCssClass' => $css_class,
4338
+ 'Monitoring.OptionName' => $option_title,
4339
+ 'Monitoring.OptionValue' => $option_value,
4340
+ ));
4341
+ $counter += 1;
4342
+ }
4343
+ }
4344
+ }
4345
+
4346
+ return sucuriscan_get_section( 'monitoring-settings', $template_variables );
4347
+ }
4348
+
4349
+ /**
4350
+ * Converts the value of some of the monitoring settings into a human-readable
4351
+ * text, for example changing numbers or variable names into a more explicit
4352
+ * text so the administrator can understand the meaning of these settings.
4353
+ *
4354
+ * @param array $settings A hash with the settings of a CloudProxy account.
4355
+ * @return array The explained version of the CloudProxy settings.
4356
+ */
4357
+ function sucuriscan_explain_monitoring_settings( $settings=array() ){
4358
+ if( $settings ){
4359
+ foreach( $settings as $option_name => $option_value ){
4360
+ switch( $option_name ){
4361
+ case 'security_level':
4362
+ $new_value = ucwords($option_value);
4363
+ break;
4364
+ case 'proxy_active':
4365
+ $new_value = ( $option_value == 1 ) ? 'Active' : 'not active';
4366
+ break;
4367
+ case 'cache_mode':
4368
+ $new_value = sucuriscan_cache_mode_title($option_value);
4369
+ break;
4370
+ }
4371
+
4372
+ if( isset($new_value) ){
4373
+ $settings->{$option_name} = $new_value;
4374
+ }
4375
+ }
4376
+
4377
+ return $settings;
4378
+ }
4379
+
4380
+ return FALSE;
4381
+ }
4382
+
4383
+ /**
4384
+ * Get an explaination of the meaning of the value set for the account's attribute cache_mode.
4385
+ *
4386
+ * @param string $mode The value set for the cache settings of the site.
4387
+ * @return string Explaination of the meaning of the cache_mode value.
4388
+ */
4389
+ function sucuriscan_cache_mode_title( $mode='' ){
4390
+ $title = '';
4391
+
4392
+ switch( $mode ){
4393
+ case 'docache': $title = 'Enabled (recommended)'; break;
4394
+ case 'sitecache': $title = 'Site caching (using your site headers)'; break;
4395
+ case 'nocache': $title = 'Minimial (only for a few minutes)'; break;
4396
+ case 'nocacheatall': $title = 'Caching didabled (use with caution)'; break;
4397
+ default: $title = 'Unknown'; break;
4398
+ }
4399
+
4400
+ return $title;
4401
+ }
4402
+
4403
+ /**
4404
+ * Generate the HTML code for the monitoring logs panel.
4405
+ *
4406
+ * @param string $api_key The CloudProxy API key.
4407
+ * @return string The parsed-content of the monitoring logs panel.
4408
+ */
4409
+ function sucuriscan_monitoring_logs( $api_key='' ){
4410
+ $template_variables = array(
4411
+ 'AuditLogs.List' => '',
4412
+ 'AuditLogs.CountText' => 0,
4413
+ 'AuditLogs.DenialTypeOptions' => '',
4414
+ 'AuditLogs.NoItemsVisibility' => 'visible',
4415
+ 'AuditLogs.PaginationVisibility' => 'hidden',
4416
+ 'AuditLogs.AuditPagination' => '',
4417
+ 'AuditLogs.TargetDate' => '',
4418
+ 'AuditLogs.DateYears' => sucuriscan_monitoring_dates('years'),
4419
+ 'AuditLogs.DateMonths' => sucuriscan_monitoring_dates('months'),
4420
+ 'AuditLogs.DateDays' => sucuriscan_monitoring_dates('days'),
4421
+ );
4422
+
4423
+ $date = date('Y-m-d');
4424
+
4425
+ if( $api_key ){
4426
+ // Retrieve the date filter from the request (if any).
4427
+ if( isset($_GET['date']) ){
4428
+ $date = $_GET['date'];
4429
+ }
4430
+
4431
+ elseif(
4432
+ isset($_POST['sucuriscan_year']) &&
4433
+ isset($_POST['sucuriscan_month']) &&
4434
+ isset($_POST['sucuriscan_day'])
4435
+ ){
4436
+ $date = sprintf(
4437
+ '%s-%s-%s',
4438
+ $_POST['sucuriscan_year'],
4439
+ $_POST['sucuriscan_month'],
4440
+ $_POST['sucuriscan_day']
4441
+ );
4442
+ }
4443
+
4444
+ $logs_data = sucuriscan_cloudproxy_logs( $api_key, $date );
4445
+
4446
+ if( $logs_data ){
4447
+ add_thickbox(); /* Include the Thickbox library. */
4448
+ $template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
4449
+ $template_variables['AuditLogs.CountText'] = $logs_data->limit . '/' . $logs_data->total_lines;
4450
+ $template_variables['AuditLogs.List'] = sucuriscan_monitoring_access_logs($logs_data->access_logs);
4451
+ $template_variables['AuditLogs.DenialTypeOptions'] = sucuriscan_monitoring_denial_types($logs_data->access_logs);
4452
+ }
4453
+ }
4454
+
4455
+ $template_variables['AuditLogs.TargetDate'] = htmlentities($date);
4456
+
4457
+ return sucuriscan_get_section( 'monitoring-logs', $template_variables );
4458
+ }
4459
+
4460
+ /**
4461
+ * Generate the HTML code to show the table with the access-logs.
4462
+ *
4463
+ * @param array $access_logs The logs retrieved from the remote API service.
4464
+ * @return string The HTML code to show the access-logs in the page as a table.
4465
+ */
4466
+ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
4467
+ $logs_html = '';
4468
+
4469
+ if( $access_logs && !empty($access_logs) ){
4470
+ $counter = 0;
4471
+ $needed_attrs = array(
4472
+ 'request_date',
4473
+ 'request_time',
4474
+ 'request_timezone',
4475
+ 'remote_addr',
4476
+ 'sucuri_block_reason',
4477
+ 'resource_path',
4478
+ 'request_method',
4479
+ 'http_protocol',
4480
+ 'http_status',
4481
+ 'http_status_title',
4482
+ 'http_bytes_sent',
4483
+ 'http_referer',
4484
+ 'http_user_agent',
4485
+ );
4486
+
4487
+ $filter_by_denial_type = FALSE;
4488
+ $filter_by_keyword = FALSE;
4489
+ $filter_query = FALSE;
4490
+
4491
+ if( isset($_POST['sucuriscan_monitoring_denial_type']) ){
4492
+ $filter_by_denial_type = TRUE;
4493
+ $filter_query = htmlentities($_POST['sucuriscan_monitoring_denial_type']);
4494
+ }
4495
+
4496
+ if( isset($_POST['sucuriscan_monitoring_log_filter']) ){
4497
+ $filter_by_keyword = TRUE;
4498
+ $filter_query = htmlentities($_POST['sucuriscan_monitoring_log_filter']);
4499
+ }
4500
+
4501
+ foreach( $access_logs as $access_log ){
4502
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
4503
+ $audit_log_snippet = array(
4504
+ 'AuditLog.Id' => $counter,
4505
+ 'AuditLog.CssClass' => $css_class,
4506
+ );
4507
+
4508
+ // If there is a filter, check the access_log data and break the operation if needed.
4509
+ if( $filter_query ){
4510
+ if( $filter_by_denial_type ){
4511
+ $denial_type_slug = sucuriscan_str_human2var($access_log->sucuri_block_reason);
4512
+
4513
+ if( $denial_type_slug != $filter_query ){ continue; }
4514
+ }
4515
+
4516
+ if(
4517
+ $filter_by_keyword
4518
+ && strpos($access_log->remote_addr, $filter_query) === FALSE
4519
+ && strpos($access_log->resource_path, $filter_query) === FALSE
4520
+ ){
4521
+ continue;
4522
+ }
4523
+ }
4524
+
4525
+ // Generate (dynamically) the pseudo-variables for the template.
4526
+ foreach( $needed_attrs as $attr_name ){
4527
+ $attr_value = '';
4528
+
4529
+ $attr_title = str_replace('_', chr(32), $attr_name);
4530
+ $attr_title = ucwords($attr_title);
4531
+ $attr_title = str_replace(chr(32), '', $attr_title);
4532
+ $attr_title = 'AuditLog.' . $attr_title;
4533
+
4534
+ if( isset($access_log->{$attr_name}) ){
4535
+ $attr_value = $access_log->{$attr_name};
4536
+ }
4537
+
4538
+ $audit_log_snippet[$attr_title] = $attr_value;
4539
+ }
4540
+
4541
+ $logs_html .= sucuriscan_get_snippet('monitoring-logs', $audit_log_snippet);
4542
+ $counter += 1;
4543
+ }
4544
+ }
4545
+
4546
+ return $logs_html;
4547
+ }
4548
+
4549
+ /**
4550
+ * Get a list of denial types using the reason of the blocking of a request from
4551
+ * the from the audit logs. Examples of denial types can be: "Bad bot access
4552
+ * denied", "Access to restricted folder", "Blocked by IDS", etc.
4553
+ *
4554
+ * @param array $access_logs A list of objects with the detailed version of each request blocked by our service.
4555
+ * @param boolean $in_html Whether the list should be converted to a HTML select options or not.
4556
+ * @return array Either a list of unique blocking types, or a HTML code.
4557
+ */
4558
+ function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=TRUE ){
4559
+ $types = array();
4560
+ $selected = '';
4561
+
4562
+ if( $access_logs && !empty($access_logs) ){
4563
+ foreach( $access_logs as $access_log ){
4564
+ if( !array_key_exists($access_log->sucuri_block_reason, $types) ){
4565
+ $denial_type_k = sucuriscan_str_human2var($access_log->sucuri_block_reason);
4566
+ $types[$denial_type_k] = $access_log->sucuri_block_reason;
4567
+ }
4568
+ }
4569
+ }
4570
+
4571
+ if( $in_html ){
4572
+ $html_types = '<option value="">Filter</option>';
4573
+
4574
+ if( isset($_REQUEST['sucuriscan_monitoring_denial_type']) ){
4575
+ $selected = htmlentities($_REQUEST['sucuriscan_monitoring_denial_type']);
4576
+ }
4577
+
4578
+ foreach( $types as $type_key => $type_value ){
4579
+ $selected_tag = ( $type_key == $selected ) ? 'selected="selected"' : '';
4580
+ $html_types .= sprintf( '<option value="%s" %s>%s</option>', $type_key, $selected_tag, $type_value );
4581
+ }
4582
+
4583
+ return $html_types;
4584
+ }
4585
+
4586
+ return $types;
4587
+ }
4588
+
4589
+ /**
4590
+ * Get a list of years, months or days depending of the type specified.
4591
+ *
4592
+ * @param string $type Either years, months or days.
4593
+ * @param boolean $in_html Whether the list should be converted to a HTML select options or not.
4594
+ * @return array Either an array with the expected values, or a HTML code.
4595
+ */
4596
+ function sucuriscan_monitoring_dates( $type='', $in_html=TRUE ){
4597
+ $options = array();
4598
+ $selected = '';
4599
+
4600
+ switch( $type ){
4601
+ case 'years':
4602
+ $current_year = (int) date('Y');
4603
+ $max_years = 5; /* Maximum number of years to keep the logs. */
4604
+ $options = range( ($current_year - $max_years), $current_year );
4605
+
4606
+ if( isset($_REQUEST['sucuriscan_year']) ){
4607
+ $selected = $_REQUEST['sucuriscan_year'];
4608
+ }
4609
+ break;
4610
+ case 'months':
4611
+ $options = array(
4612
+ '01' => 'January',
4613
+ '02' => 'February',
4614
+ '03' => 'March',
4615
+ '04' => 'April',
4616
+ '05' => 'May',
4617
+ '06' => 'June',
4618
+ '07' => 'July',
4619
+ '08' => 'August',
4620
+ '09' => 'September',
4621
+ '10' => 'October',
4622
+ '11' => 'November',
4623
+ '12' => 'December'
4624
+ );
4625
+
4626
+ if( isset($_REQUEST['sucuriscan_month']) ){
4627
+ $selected = $_REQUEST['sucuriscan_month'];
4628
+ }
4629
+ break;
4630
+ case 'days':
4631
+ $options = range(1, 31);
4632
+
4633
+ if( isset($_REQUEST['sucuriscan_day']) ){
4634
+ $selected = $_REQUEST['sucuriscan_day'];
4635
+ }
4636
+ break;
4637
+ }
4638
+
4639
+ if( $in_html ){
4640
+ $html_options = '';
4641
+
4642
+ foreach( $options as $key => $value ){
4643
+ if( is_numeric($value) ){ $value = str_pad($value, 2, 0, STR_PAD_LEFT); }
4644
+
4645
+ if( $type != 'months' ){ $key = $value; }
4646
+
4647
+ $selected_tag = ( $key == $selected ) ? 'selected="selected"' : '';
4648
+ $html_options .= sprintf( '<option value="%s" %s>%s</option>', $key, $selected_tag, $value );
4649
+ }
4650
+
4651
+ return $html_options;
4652
+ }
4653
+
4654
+ return $options;
4655
+ }
4656
+
4657
+ /**
4658
+ * Sucuri one-click hardening page.
4659
+ *
4660
+ * It loads all the functions defined in /lib/hardening.php and shows the forms
4661
+ * that the administrator can use to harden multiple parts of the site.
4662
+ *
4663
+ * @return void
4664
+ */
4665
+ function sucuriscan_hardening_page(){
4666
+
4667
+ if( !current_user_can('manage_options') ){
4668
+ wp_die(__('You do not have sufficient permissions to access this page: Sucuri Hardening') );
4669
+ }
4670
+
4671
+ if( isset($_POST['wpsucuri-doharden']) ){
4672
+ if( !wp_verify_nonce($_POST['sucuriscan_hardening_nonce'], 'sucuriscan_hardening_nonce') ){
4673
+ unset($_POST['wpsucuri-doharden']);
4674
+ }
4675
+ }
4676
+
4677
+ ob_start();
4678
+ ?>
4679
+
4680
+ <div id="poststuff">
4681
+ <form method="post">
4682
+ <input type="hidden" name="sucuriscan_hardening_nonce" value="<?php echo wp_create_nonce('sucuriscan_hardening_nonce'); ?>" />
4683
+ <input type="hidden" name="wpsucuri-doharden" value="wpsucuri-doharden" />
4684
+
4685
+ <?php
4686
+ sucuriscan_harden_version();
4687
+ sucuriscan_cloudproxy_enabled();
4688
+ sucuriscan_harden_removegenerator();
4689
+ sucuriscan_harden_upload();
4690
+ sucuriscan_harden_wpcontent();
4691
+ sucuriscan_harden_wpincludes();
4692
+ sucuriscan_harden_phpversion();
4693
+ sucuriscan_harden_secretkeys();
4694
+ sucuriscan_harden_readme();
4695
+ sucuriscan_harden_adminuser();
4696
+ sucuriscan_harden_fileeditor();
4697
+ sucuriscan_harden_dbtables();
4698
+ ?>
4699
+ </form>
4700
+ </div>
4701
+
4702
+ <?php
4703
+ $_html = ob_get_contents();
4704
+ ob_end_clean();
4705
+ echo sucuriscan_get_base_template($_html, array(
4706
+ 'PageTitle' => 'Hardening',
4707
+ 'PageContent' => $_html,
4708
+ 'PageStyleClass' => 'hardening'
4709
+ ));
4710
+ return;
4711
+ }
4712
+
4713
+ /**
4714
+ * Generate the HTML code necessary to show a form with the options to harden
4715
+ * a specific part of the WordPress installation, if the Status variable is
4716
+ * set as a positive integer the button is shown as "unharden".
4717
+ *
4718
+ * @param string $title Title of the panel.
4719
+ * @param integer $status Either one or zero representing the state of the hardening, one for secure, zero for insecure.
4720
+ * @param string $type Name of the hardening option, this will be used through out the form generation.
4721
+ * @param string $messageok Message that will be shown if the hardening was executed.
4722
+ * @param string $messagewarn Message that will be shown if the hardening is not executed.
4723
+ * @param string $desc Optional description of the hardening.
4724
+ * @param string $updatemsg Optional explanation of the hardening after the submission of the form.
4725
+ * @return void
4726
+ */
4727
+ function sucuriscan_harden_status( $title='', $status=0, $type='', $messageok='', $messagewarn='', $desc=NULL, $updatemsg=NULL ){ ?>
4728
+ <div class="postbox">
4729
+ <h3><?php _e($title) ?></h3>
4730
+
4731
+ <div class="inside">
4732
+ <?php if( $desc != NULL ): ?>
4733
+ <p><?php _e($desc) ?></p>
4734
+ <?php endif; ?>
4735
+
4736
+ <div class="sucuriscan-hstatus sucuriscan-hstatus-<?php _e($status) ?>">
4737
+ <?php if( $type != NULL ): ?>
4738
+ <?php if( $status == 1 ): ?>
4739
+ <input type="submit" name="<?php _e($type) ?>_unharden" value="Revert hardening" class="button-secondary" />
4740
+ <?php else: ?>
4741
+ <input type="submit" name="<?php _e($type) ?>" value="Harden" class="button-primary" />
4742
+ <?php endif; ?>
4743
+ <?php endif; ?>
4744
+
4745
+ <span>
4746
+ <?php if( $status == 1 ): ?>
4747
+ <?php _e($messageok) ?>
4748
+ <?php else: ?>
4749
+ <?php _e($messagewarn) ?>
4750
+ <?php endif; ?>
4751
+ </span>
4752
+ </div>
4753
+
4754
+ <?php if( $updatemsg != NULL ): ?>
4755
+ <p><?php _e($updatemsg) ?></p>
4756
+ <?php endif; ?>
4757
+ </div>
4758
+ </div>
4759
+
4760
+ <?php }
4761
+
4762
+ /**
4763
+ * Check whether the version number of the WordPress installed is the latest
4764
+ * version available officially.
4765
+ *
4766
+ * @return void
4767
+ */
4768
+ function sucuriscan_harden_version(){
4769
+ global $wp_version;
4770
+
4771
+ $updates = get_core_updates();
4772
+ if(
4773
+ !is_array($updates)
4774
+ || empty($updates)
4775
+ || $updates[0]->response == 'latest'
4776
+ ){
4777
+ $cp = 1;
4778
+ } else {
4779
+ $cp = 0;
4780
+ }
4781
+
4782
+ if(strcmp($wp_version, "3.7") < 0)
4783
+ {
4784
+ $cp = 0;
4785
+ }
4786
+
4787
+ $wp_version = htmlspecialchars($wp_version);
4788
+ $initial_msg = 'Why keep your site updated? WordPress is an open-source
4789
+ project which means that with every update the details of the changes made
4790
+ to the source code are made public, if there were security fixes then
4791
+ someone with malicious intent can use this information to attack any site
4792
+ that has not been upgraded.';
4793
+ $messageok = sprintf('Your WordPress installation (%s) is current.', $wp_version);
4794
+ $messagewarn = sprintf(
4795
+ 'Your current version (%s) is not current.<br>
4796
+ <a href="update-core.php" class="button-primary">Update now!</a>',
4797
+ $wp_version
4798
+ );
4799
+
4800
+ sucuriscan_harden_status( 'Verify WordPress version', $cp, NULL, $messageok, $messagewarn, $initial_msg );
4801
+ }
4802
+
4803
+ /**
4804
+ * Notify the state of the hardening for the removal of the Generator tag in
4805
+ * HTML code printed by WordPress to show the current version number of the
4806
+ * installation.
4807
+ *
4808
+ * @return void
4809
+ */
4810
+ function sucuriscan_harden_removegenerator(){
4811
+ sucuriscan_harden_status(
4812
+ 'Remove WordPress version',
4813
+ 1,
4814
+ NULL,
4815
+ 'WordPress version properly hidden',
4816
+ NULL,
4817
+ 'It checks if your WordPress version is being hidden from being displayed '
4818
+ .'in the generator tag (enabled by default with this plugin).'
4819
+ );
4820
+ }
4821
+
4822
+ /**
4823
+ * Check whether the WordPress upload folder is protected or not.
4824
+ *
4825
+ * A htaccess file is placed in the upload folder denying the access to any php
4826
+ * file that could be uploaded through a vulnerability in a Plugin, Theme or
4827
+ * WordPress itself.
4828
+ *
4829
+ * @return void
4830
+ */
4831
+ function sucuriscan_harden_upload(){
4832
+ $cp = 1;
4833
+ $upmsg = NULL;
4834
+ $htaccess_upload = dirname(sucuriscan_dir_filepath())."/.htaccess";
4835
+
4836
+ if( !is_readable($htaccess_upload) ){
4837
+ $cp = 0;
4838
+ } else {
4839
+ $cp = 0;
4840
+ $fcontent = file($htaccess_upload);
4841
+
4842
+ foreach( $fcontent as $fline ){
4843
+ if( strpos($fline, 'deny from all') !== FALSE ){
4844
+ $cp = 1;
4845
+ break;
4846
+ }
4847
+ }
4848
+ }
4849
+
4850
+ if( isset($_POST['wpsucuri-doharden']) ){
4851
+ if( isset($_POST['sucuriscan_harden_upload']) && $cp == 0 ){
4852
+ if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
4853
+ $upmsg = sucuriscan_error('ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.');
4854
+ } else {
4855
+ $upmsg = sucuriscan_info('COMPLETE: Upload directory successfully hardened');
4856
+ $cp = 1;
4857
+ }
4858
+ }
4859
+
4860
+ elseif( isset($_POST['sucuriscan_harden_upload_unharden']) ){
4861
+ $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
4862
+ $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
4863
 
4864
+ if( $htaccess_upload_writable ){
4865
+ $cp = 0;
 
 
 
 
 
 
 
 
 
 
4866
 
4867
+ if( preg_match('/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match) ){
4868
+ $htaccess_content = str_replace("<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content);
4869
+ @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
4870
+ }
4871
 
4872
+ sucuriscan_info('Hardening removed for the WordPress upload directory <code>/wp-content/uploads</code>');
4873
+ } else {
4874
+ sucuriscan_error(
4875
+ '<code>wp-content/uploads/.htaccess</code> does not exists or is not
4876
+ writable, you will need to remove the following code (manually):
4877
+ <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
4878
+ );
4879
+ }
4880
+ }
4881
+ }
4882
 
4883
+ sucuriscan_harden_status(
4884
+ 'Protect uploads directory',
4885
+ $cp,
4886
+ 'sucuriscan_harden_upload',
4887
+ 'Upload directory properly hardened',
4888
+ 'Upload directory not hardened',
4889
+ 'It checks if your upload directory allows PHP execution or if it is browsable.',
4890
+ $upmsg
4891
+ );
4892
+ }
4893
 
4894
+ /**
4895
+ * Check whether the WordPress content folder is protected or not.
4896
+ *
4897
+ * A htaccess file is placed in the content folder denying the access to any php
4898
+ * file that could be uploaded through a vulnerability in a Plugin, Theme or
4899
+ * WordPress itself.
4900
+ *
4901
+ * @return void
4902
+ */
4903
+ function sucuriscan_harden_wpcontent(){
4904
+ $cp = 1;
4905
+ $upmsg = NULL;
4906
+ $htaccess_upload = ABSPATH . '/wp-content/.htaccess';
 
 
 
 
 
 
4907
 
4908
+ if( !is_readable($htaccess_upload) ){
4909
+ $cp = 0;
4910
+ } else {
4911
+ $cp = 0;
4912
+ $fcontent = file($htaccess_upload);
 
 
 
 
 
 
 
 
4913
 
4914
+ foreach( $fcontent as $fline ){
4915
+ if( strpos($fline, 'deny from all') !== FALSE ){
4916
+ $cp = 1;
4917
+ break;
4918
+ }
4919
+ }
4920
+ }
4921
 
4922
+ if( isset($_POST['wpsucuri-doharden']) ){
4923
+ if( isset($_POST['sucuriscan_harden_wpcontent']) && $cp == 0 ){
4924
+ if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") === FALSE ){
4925
+ $upmsg = sucuriscan_error('ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.');
4926
+ } else {
4927
+ $upmsg = sucuriscan_info('COMPLETE: wp-content directory successfully hardened');
4928
+ $cp = 1;
4929
+ }
4930
+ }
 
 
 
 
 
4931
 
4932
+ elseif( isset($_POST['sucuriscan_harden_wpcontent_unharden']) ){
4933
+ $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
4934
+ $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
 
 
 
 
 
 
 
 
 
 
 
 
4935
 
4936
+ if( $htaccess_upload_writable ){
4937
+ $cp = 0;
 
 
 
 
 
 
 
4938
 
4939
+ if( preg_match('/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match) ){
4940
+ $htaccess_content = str_replace("<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content);
4941
+ @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
4942
+ }
 
 
 
 
4943
 
4944
+ sucuriscan_info('WP-Content directory protection reverted.');
4945
+ } else {
4946
+ sucuriscan_info(
4947
+ '<code>wp-content/.htaccess</code> does not exists or is not writable,
4948
+ you will need to remove the following code manually there:
4949
+ <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
4950
+ );
4951
+ }
4952
+ }
4953
+ }
 
 
 
 
 
 
4954
 
4955
+ sucuriscan_harden_status(
4956
+ 'Restrict wp-content access',
4957
+ $cp,
4958
+ 'sucuriscan_harden_wpcontent',
4959
+ 'WP-content directory properly hardened',
4960
+ 'WP-content directory not hardened',
4961
+ 'This option blocks direct PHP access to any file inside wp-content. If you experience any '
4962
+ .'issue after this with a theme or plugin in your site, like for example images not displaying, '
4963
+ .'remove the <code>.htaccess</code> file located at the <code>/wp-content/</code> directory.',
4964
+ $upmsg
4965
+ );
4966
+ }
4967
 
4968
+ /**
4969
+ * Check whether the WordPress includes folder is protected or not.
4970
+ *
4971
+ * A htaccess file is placed in the includes folder denying the access to any php
4972
+ * file that could be uploaded through a vulnerability in a Plugin, Theme or
4973
+ * WordPress itself, there are some exceptions for some specific files that must
4974
+ * be available publicly.
4975
+ *
4976
+ * @return void
4977
+ */
4978
+ function sucuriscan_harden_wpincludes(){
4979
+ $cp = 1;
4980
+ $upmsg = NULL;
4981
+ $htaccess_upload = ABSPATH . '/wp-includes/.htaccess';
4982
 
4983
+ if( !is_readable($htaccess_upload) ){
4984
+ $cp = 0;
4985
+ } else {
4986
+ $cp = 0;
4987
+ $fcontent = file($htaccess_upload);
 
 
 
 
 
4988
 
4989
+ foreach( $fcontent as $fline ){
4990
+ if( strpos($fline, 'deny from all') !== FALSE ){
4991
+ $cp = 1;
4992
+ break;
4993
+ }
4994
+ }
4995
+ }
 
4996
 
4997
+ if( isset($_POST['wpsucuri-doharden']) ){
4998
+ if( isset($_POST['sucuriscan_harden_wpincludes']) && $cp == 0 ){
4999
+ if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>\n<Files wp-tinymce.php>\nallow from all\n</Files>\n")===FALSE ){
5000
+ $upmsg = sucuriscan_error('ERROR: Unable to create <code>.htaccess</code> file, folder destination is not writable.');
5001
+ } else {
5002
+ $upmsg = sucuriscan_info('COMPLETE: wp-includes directory successfully hardened.');
5003
+ $cp = 1;
5004
+ }
5005
+ }
5006
 
5007
+ elseif( isset($_POST['sucuriscan_harden_wpincludes_unharden']) ){
5008
+ $htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ? TRUE : FALSE;
5009
+ $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
 
 
 
 
 
 
 
5010
 
5011
+ if( $htaccess_upload_writable ){
5012
+ $cp = 0;
5013
+ if( preg_match_all('/<Files (\*|wp-tinymce|ms-files)\.php>\n(deny|allow) from all\n<\/Files>/', $htaccess_content, $match) ){
5014
+ foreach($match[0] as $restriction){
5015
+ $htaccess_content = str_replace($restriction, '', $htaccess_content);
5016
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5017
 
5018
+ @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
5019
+ }
5020
+ sucuriscan_info('WP-Includes directory protection reverted.');
5021
+ } else {
5022
+ sucuriscan_error(
5023
+ '<code>wp-includes/.htaccess</code> does not exists or is not
5024
+ writable, you will need to remove the following code manually
5025
+ there: <code>&lt;Files *.php&gt;deny from all&lt;/Files&gt;</code>'
5026
+ );
5027
+ }
5028
+ }
5029
+ }
5030
 
5031
+ sucuriscan_harden_status(
5032
+ 'Restrict wp-includes access',
5033
+ $cp,
5034
+ 'sucuriscan_harden_wpincludes',
5035
+ 'WP-Includes directory properly hardened',
5036
+ 'WP-Includes directory not hardened',
5037
+ 'This option blocks direct PHP access to any file inside <code>wp-includes</code>.',
5038
+ $upmsg
5039
+ );
5040
+ }
5041
 
5042
+ /**
5043
+ * Check the version number of the PHP interpreter set to work with the site,
5044
+ * is considered that old versions of the PHP interpreter are insecure.
5045
+ *
5046
+ * @return void
5047
+ */
5048
+ function sucuriscan_harden_phpversion(){
5049
+ $phpv = phpversion();
5050
+ $cp = ( strncmp($phpv, '5.', 2) < 0 ) ? 0 : 1;
5051
 
5052
+ sucuriscan_harden_status(
5053
+ 'Verify PHP version',
5054
+ $cp,
5055
+ NULL,
5056
+ 'Using an updated version of PHP (' . $phpv . ')',
5057
+ 'The version of PHP you are using (' . $phpv . ') is not current, not recommended, and/or not supported',
5058
+ 'This checks if you have the latest version of PHP installed.',
5059
+ NULL
5060
+ );
5061
  }
5062
 
5063
  /**
5064
+ * Check whether the site is behind a secure proxy server or not.
 
 
 
 
5065
  *
5066
  * @return void
5067
  */
5068
+ function sucuriscan_cloudproxy_enabled(){
5069
+ $btn_string = '';
5070
+ $enabled = sucuriscan_is_behind_cloudproxy();
5071
+ $status = 1;
5072
 
5073
+ if( $enabled !== TRUE ){
5074
+ $status = 0;
5075
+ $btn_string = '<a href="http://cloudproxy.sucuri.net/" target="_blank" class="button button-primary">Harden</a>';
5076
  }
5077
 
5078
+ sucuriscan_harden_status(
5079
+ 'Website Firewall protection',
5080
+ $status,
5081
+ NULL,
5082
+ 'Your website is protected by a Website Firewall (WAF)',
5083
+ $btn_string . 'Your website is not protected by a Website Firewall (WAF)',
5084
+ 'A WAF is a protection layer for your web site, blocking all sort of attacks (brute force attempts, DDoS, '
5085
+ .'SQL injections, etc) and helping it remain malware and blacklist free. This test checks if your site is '
5086
+ .'using <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri\'s CloudProxy WAF</a> to protect your site. ',
5087
+ NULL
5088
+ );
5089
+ }
5090
 
5091
+ /**
5092
+ * Check whether the Wordpress configuration file has the security keys recommended
5093
+ * to avoid any unauthorized access to the interface.
5094
+ *
5095
+ * WordPress Security Keys is a set of random variables that improve encryption of
5096
+ * information stored in the user’s cookies. There are a total of four security
5097
+ * keys: AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, and NONCE_KEY.
5098
+ *
5099
+ * @return void
5100
+ */
5101
+ function sucuriscan_harden_secretkeys(){
5102
+ $wp_config_path = sucuriscan_get_wpconfig_path();
5103
 
5104
+ if( $wp_config_path ){
5105
+ $cp = 1;
5106
+ $message = 'The main configuration file was found at: <code>'.$wp_config_path.'</code><br>';
5107
+
5108
+ $secret_key_names = array(
5109
+ 'AUTH_KEY',
5110
+ 'SECURE_AUTH_KEY',
5111
+ 'LOGGED_IN_KEY',
5112
+ 'NONCE_KEY',
5113
+ 'AUTH_SALT',
5114
+ 'SECURE_AUTH_SALT',
5115
+ 'LOGGED_IN_SALT',
5116
+ 'NONCE_SALT',
5117
  );
5118
 
5119
+ foreach( $secret_key_names as $key_name){
5120
+ if( !defined($key_name) ){
5121
+ $cp = 0;
5122
+ $message .= 'The secret key <strong>'.$key_name.'</strong> is not defined.<br>';
5123
+ } elseif( constant($key_name) == 'put your unique phrase here' ){
5124
+ $cp = 0;
5125
+ $message .= 'The secret key <strong>'.$key_name.'</strong> is not properly set.<br>';
5126
+ }
5127
+ }
5128
 
5129
+ if( $cp == 0 ){
5130
+ $admin_url = admin_url('admin.php?page=sucuriscan_posthack');
5131
+ $message .= '<br><a href="'.$admin_url.'" class="button button-primary">Update WP-Config Keys</a><br>';
5132
+ }
5133
+ }else{
5134
+ $cp = 0;
5135
+ $message = 'The <code>wp-config</code> file was not found.<br>';
5136
+ }
5137
 
5138
+ $message .= '<br>It checks whether you have proper random keys/salts created for WordPress. A
5139
+ <a href="http://codex.wordpress.org/Editing_wp-config.php#Security_Keys" target="_blank">
5140
+ secret key</a> makes your site harder to hack and access harder to crack by adding
5141
+ random elements to the password. In simple terms, a secret key is a password with
5142
+ elements that make it harder to generate enough options to break through your
5143
+ security barriers.';
 
5144
 
5145
+ sucuriscan_harden_status(
5146
+ 'Secret keys validity',
5147
+ $cp,
5148
+ NULL,
5149
+ 'WordPress secret keys and salts properly created',
5150
+ 'WordPress secret keys and salts not set, we recommend creating them for security reasons',
5151
+ $message,
5152
+ NULL
5153
+ );
5154
  }
5155
 
5156
  /**
5157
+ * Check whether the "readme.html" file is still available in the root of the
5158
+ * site or not, which can lead to an attacker to know which version number of
5159
+ * Wordpress is being used and search for possible vulnerabilities.
5160
  *
 
 
 
5161
  * @return void
5162
  */
5163
+ function sucuriscan_harden_readme(){
5164
+ $upmsg = NULL;
5165
+ $cp = is_readable(ABSPATH.'/readme.html') ? 0 : 1;
 
 
 
 
 
 
 
 
5166
 
5167
+ if( isset($_POST['wpsucuri-doharden']) ){
5168
+ if( isset($_POST['sucuriscan_harden_readme']) && $cp == 0 ){
5169
+ if( @unlink(ABSPATH.'/readme.html') === FALSE ){
5170
+ $upmsg = sucuriscan_error('Unable to remove <code>readme.html</code> file.');
5171
+ } else {
5172
+ $cp = 1;
5173
+ $upmsg = sucuriscan_info('<code>readme.html</code> file removed successfully.');
5174
  }
5175
+ }
5176
+
5177
+ elseif( isset($_POST['sucuriscan_harden_readme_unharden']) ){
5178
+ sucuriscan_error('We can not revert this action, you should create the <code>readme.html</code> file at your own.');
5179
+ }
5180
+ }
5181
+
5182
+ sucuriscan_harden_status(
5183
+ 'Information leakage (readme.html)',
5184
+ $cp,
5185
+ 'sucuriscan_harden_readme',
5186
+ '<code>readme.html</code> file properly deleted',
5187
+ '<code>readme.html</code> not deleted and leaking the WordPress version',
5188
+ 'It checks whether you have the <code>readme.html</code> file available that leaks your WordPress version',
5189
+ $upmsg
5190
+ );
5191
+ }
5192
 
5193
  /**
5194
+ * Check whether the main administrator user still has the default name "admin"
5195
+ * or not, which can lead to an attacker to perform a brute force attack.
5196
  *
5197
  * @return void
5198
  */
5199
+ function sucuriscan_harden_adminuser(){
5200
+ global $wpdb;
5201
+
5202
+ $upmsg = NULL;
5203
+ $res = $wpdb->get_results("SELECT user_login FROM {$wpdb->prefix}users WHERE user_login = 'admin'");
5204
+ $account_removed = ( count($res) == 0 ? 1 : 0 );
5205
+
5206
+ if( $account_removed == 0 ){
5207
+ $upmsg = '<i><strong>We do not offer the option</strong> to automatically change the user name.
5208
+ Go to the <a href="'.admin_url('users.php').'" target="_blank">user list</a> and create a new
5209
+ admin user name. Once created, log in as that user and remove the default "admin" from there
5210
+ (make sure to assign all the admin posts to the new user too!).</i>';
5211
+ }
5212
+
5213
+ sucuriscan_harden_status(
5214
+ 'Default admin account',
5215
+ $account_removed,
5216
+ NULL,
5217
+ 'Default admin user account (admin) not being used',
5218
+ 'Default admin user account (admin) being used. Not recommended',
5219
+ 'It checks whether you have the default <code>admin</code> account enabled, security guidelines recommend creating a new admin user name.',
5220
+ $upmsg
5221
+ );
5222
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5223
 
5224
  /**
5225
+ * Enable or disable the user of the built-in Wordpress file editor.
 
5226
  *
5227
+ * @return void
 
 
5228
  */
5229
+ function sucuriscan_harden_fileeditor(){
5230
+ $file_editor_disabled = defined('DISALLOW_FILE_EDIT') ? DISALLOW_FILE_EDIT : FALSE;
 
5231
 
5232
+ if( isset($_POST['wpsucuri-doharden']) ){
5233
+ $current_time = date('r');
5234
+ $wp_config_path = sucuriscan_get_wpconfig_path();
 
 
 
 
 
5235
 
5236
+ $wp_config_writable = ( file_exists($wp_config_path) && is_writable($wp_config_path) ) ? TRUE : FALSE;
5237
+ $new_wpconfig = $wp_config_writable ? file_get_contents($wp_config_path) : '';
5238
 
5239
+ if( isset($_POST['sucuriscan_harden_fileeditor']) ){
5240
+ if( $wp_config_writable ){
5241
+ if( preg_match('/(.*define\(.DB_COLLATE..*)/', $new_wpconfig, $match) ){
5242
+ $disallow_fileedit_definition = "\n\ndefine('DISALLOW_FILE_EDIT', TRUE); // Sucuri Security: {$current_time}\n";
5243
+ $new_wpconfig = str_replace($match[0], $match[0].$disallow_fileedit_definition, $new_wpconfig);
5244
  }
5245
 
5246
+ @file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
5247
+ sucuriscan_info( 'WP-Config file updated successfully, the plugin and theme editor was disabled.' );
5248
+ $file_editor_disabled = TRUE;
5249
+ } else {
5250
+ sucuriscan_error( 'The <code>wp-config.php</code> file is not in the default location
5251
+ or is not writable, you will need to put the following code manually there:
5252
+ <code>define("DISALLOW_FILE_EDIT", TRUE);</code>' );
5253
  }
5254
+ }
5255
 
5256
+ elseif( isset($_POST['sucuriscan_harden_fileeditor_unharden']) ){
5257
+ if( preg_match("/(.*define\('DISALLOW_FILE_EDIT', TRUE\);.*)/", $new_wpconfig, $match) ){
5258
+ if( $wp_config_writable ){
5259
+ $new_wpconfig = str_replace("\n{$match[1]}", '', $new_wpconfig);
5260
+ file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
5261
+ sucuriscan_info( 'WP-Config file updated successfully, the plugin and theme editor was enabled.' );
5262
+ $file_editor_disabled = FALSE;
5263
+ } else {
5264
+ sucuriscan_error( 'The <code>wp-config.php</code> file is not in the default location
5265
+ or is not writable, you will need to remove the following code manually from there:
5266
+ <code>define("DISALLOW_FILE_EDIT", TRUE);</code>' );
5267
+ }
5268
+ } else {
5269
+ sucuriscan_error( 'We did not find a definition to disallow the file editor.' );
5270
  }
5271
  }
5272
  }
5273
 
5274
+ $message = 'Occasionally you may wish to disable the plugin or theme editor to prevent overzealous users
5275
+ from being able to edit sensitive files and potentially crash the site. Disabling these also
5276
+ provides an additional layer of security if a hacker gains access to a well-privileged user
5277
+ account.';
5278
+
5279
+ sucuriscan_harden_status(
5280
+ 'Plugin &amp; Theme editor',
5281
+ ( $file_editor_disabled === FALSE ? 0 : 1 ),
5282
+ 'sucuriscan_harden_fileeditor',
5283
+ 'File editor for Plugins and Themes is disabled',
5284
+ 'File editor for Plugins and Themes is enabled',
5285
+ $message,
5286
+ NULL
5287
+ );
5288
  }
5289
 
5290
  /**
5291
+ * Check whether the prefix of each table in the database designated for the site
5292
+ * is the same as the default prefix defined by Wordpress "_wp", in that case the
5293
+ * "harden" button will generate randomly a new prefix and rename all those tables.
5294
  *
5295
  * @return void
5296
  */
5297
+ function sucuriscan_harden_dbtables(){
5298
+ global $table_prefix;
 
5299
 
5300
+ $hardened = ( $table_prefix == 'wp_' ? 0 : 1 );
5301
 
5302
+ if(
5303
+ isset($_POST['wpsucuri-doharden'])
5304
+ && (
5305
+ isset($_POST['sucuriscan_harden_dbtables']) ||
5306
+ isset($_POST['sucuriscan_harden_dbtables_unharden'])
5307
+ )
5308
+ ){
5309
+ $sucuri_backup = new SucuriScanBackup();
5310
+ $dbbackup_filepath = $sucuri_backup->all_database();
5311
+
5312
+ if( $dbbackup_filepath ){
5313
+ sucuriscan_info( 'A new database table prefix change was initialized, if you have
5314
+ problems after this operation finishes you can find a backup of the current
5315
+ database here: <code>'.$dbbackup_filepath.'</code>' );
5316
+
5317
+ if( isset($_POST['sucuriscan_harden_dbtables']) ){
5318
+ $hardened = 1;
5319
+ $sucuri_backup->new_table_prefix();
5320
+ } elseif( isset($_POST['sucuriscan_harden_dbtables_unharden']) ){
5321
+ $hardened = 0;
5322
+ $sucuri_backup->reset_table_prefix();
5323
+ }
5324
+ } else {
5325
+ sucuriscan_error( 'Error generating a backup for your database.' );
5326
+ }
5327
  }
 
5328
 
5329
+ sucuriscan_harden_status(
5330
+ 'Database table prefix',
5331
+ $hardened,
5332
+ 'sucuriscan_harden_dbtables',
5333
+ 'Database table prefix properly modified',
5334
+ 'Database table set to the default value <code>wp_</code>.',
5335
+ 'It checks whether your database table prefix has been changed from the default <code>wp_</code>',
5336
+ '<strong>Be aware that this hardening procedure can cause your site to go down</strong>'
5337
+ );
5338
+ }
5339
+
5340
+ /**
5341
+ * WordPress core integrity page.
5342
+ *
5343
+ * It checks whether the WordPress core files are the original ones, and the state
5344
+ * of the themes and plugins reporting the availability of updates. It also checks
5345
+ * the user accounts under the administrator group.
5346
+ *
5347
+ * @return void
5348
+ */
5349
+ function sucuriscan_page(){
5350
+ if( !current_user_can('manage_options') ){
5351
+ wp_die(__('You do not have sufficient permissions to access this page: Sucuri Integrity Check') );
5352
  }
5353
+
5354
+ sucuriscan_integrity_form_submissions();
5355
+
5356
+ $template_variables = array(
5357
+ 'WordpressVersion' => sucuriscan_wordpress_outdated(),
5358
+ 'AuditLogs' => sucuriscan_auditlogs(),
5359
+ 'CoreFiles' => sucuriscan_core_files(),
5360
+ );
5361
+
5362
+ echo sucuriscan_get_template('integrity', $template_variables);
5363
  }
5364
 
5365
  /**
5366
+ * Process the requests sent by the form submissions originated in the integrity
5367
+ * page, all forms must have a nonce field that will be checked agains the one
5368
+ * generated in the template render function.
5369
  *
 
5370
  * @return void
5371
  */
5372
+ function sucuriscan_integrity_form_submissions(){
5373
+ if( sucuriscan_check_page_nonce() ){
5374
+
5375
+ // Manually force a filesystem scan (by an administrator user).
5376
+ if( isset($_POST['sucuriscan_force_scan']) ){
5377
+ if( current_user_can('manage_options') ){
5378
+ sucuriscan_notify_event( 'plugin_change', 'Filesystem scan forced at: ' . date('r') );
5379
+ sucuriscan_filesystem_scan(TRUE);
5380
+ } else {
5381
+ sucuriscan_error( 'Your privileges are not sufficient to execute this action.' );
5382
+ }
5383
+ }
5384
+
5385
+ }
5386
+ }
 
 
 
 
5387
 
5388
  /**
5389
+ * Retrieve a list of md5sum and last modification time of all the files in the
5390
+ * folder specified. This is a recursive function.
5391
  *
5392
+ * @param string $dir The base path where the scanning will start.
5393
+ * @param boolean $recursive Either TRUE or FALSE if the scan should be performed recursively.
5394
+ * @return array List of arrays containing the md5sum and last modification time of the files found.
5395
+ */
5396
+ function sucuriscan_get_integrity_tree( $dir='./', $recursive=FALSE ){
5397
+ $abs_path = rtrim( ABSPATH, '/' );
5398
+
5399
+ $sucuri_fileinfo = new SucuriScanFileInfo();
5400
+ $sucuri_fileinfo->ignore_files = FALSE;
5401
+ $sucuri_fileinfo->ignore_directories = FALSE;
5402
+ $sucuri_fileinfo->run_recursively = $recursive;
5403
+ $integrity_tree = $sucuri_fileinfo->get_directory_tree_md5( $dir, 'opendir', TRUE );
5404
+
5405
+ if( $integrity_tree ){
5406
+ return $integrity_tree;
5407
+ }
5408
+
5409
+ return FALSE;
5410
+ }
5411
+
5412
+ /**
5413
+ * Print a HTML code with the content of the logs audited by the remote Sucuri
5414
+ * API service, this page is part of the monitoring tool.
5415
  *
5416
  * @return void
5417
  */
5418
+ function sucuriscan_auditlogs(){
5419
 
5420
+ $audit_logs = sucuriscan_get_logs();
5421
+ $max_per_page = SUCURISCAN_AUDITLOGS_PER_PAGE;
5422
+ $show_all = isset($_GET['show_all']) ? TRUE : FALSE;
5423
 
 
5424
  $template_variables = array(
5425
+ 'PageTitle' => 'Audit Logs',
5426
+ 'AuditLogs.List' => '',
5427
+ 'AuditLogs.Count' => 0,
5428
+ 'AuditLogs.NoItemsVisibility' => 'visible',
5429
+ 'AuditLogs.MaxItemsVisibility' => 'hidden',
5430
+ 'AuditLogs.MaxPerPage' => $max_per_page,
5431
  );
5432
 
5433
+ if( $audit_logs ){
5434
+ $counter_i = 0;
5435
+ $total_items = count($audit_logs->output_data);
5436
 
5437
+ $template_variables['AuditLogs.Count'] = $total_items;
5438
+ $template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
5439
 
5440
+ if( $total_items > $max_per_page && !$show_all ){
5441
+ $template_variables['AuditLogs.MaxItemsVisibility'] = 'visible';
5442
+ }
 
 
 
5443
 
5444
+ foreach( $audit_logs->output_data as $audit_log ){
5445
+ if( $counter_i > $max_per_page && !$show_all ){ break; }
 
5446
 
5447
+ $css_class = ( $counter_i % 2 == 0 ) ? '' : 'alternate';
5448
+ $snippet_data = array(
5449
+ 'AuditLog.CssClass' => $css_class,
5450
+ 'AuditLog.DateTime' => date( 'd/M/Y H:i:s', $audit_log['timestamp'] ),
5451
+ 'AuditLog.Account' => $audit_log['account'],
5452
+ 'AuditLog.Message' => $audit_log['message'],
5453
+ 'AuditLog.Extra' => '',
5454
+ );
5455
+
5456
+ // Print every extra information item in a separate table.
5457
+ if( $audit_log['extra'] ){
5458
+ $css_scrollable = $audit_log['extra_total'] > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
5459
+ $snippet_data['AuditLog.Extra'] .= '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
5460
+ foreach( $audit_log['extra'] as $log_extra ){
5461
+ $snippet_data['AuditLog.Extra'] .= '<li>' . $log_extra . '</li>';
5462
+ }
5463
+ $snippet_data['AuditLog.Extra'] .= '</ul>';
5464
+ $snippet_data['AuditLog.Extra'] .= '<small>For Mac users, this is a scrollable container</small>';
5465
  }
5466
+
5467
+ $template_variables['AuditLogs.List'] .= sucuriscan_get_snippet('integrity-auditlogs', $snippet_data);
5468
+ $counter_i += 1;
5469
  }
5470
+ }
5471
+
5472
+ return sucuriscan_get_section('integrity-auditlogs', $template_variables);
5473
+ }
5474
+
5475
+ /**
5476
+ * Check whether the WordPress version is outdated or not.
5477
+ *
5478
+ * @return string Panel with a warning advising that WordPress is outdated.
5479
+ */
5480
+ function sucuriscan_wordpress_outdated(){
5481
+ global $wp_version;
5482
+
5483
+ $cp = 0;
5484
+ $updates = get_core_updates();
5485
+
5486
+ if(
5487
+ !is_array($updates)
5488
+ || empty($updates)
5489
+ || $updates[0]->response=='latest'
5490
+ ){
5491
+ $cp = 1;
5492
+ }
5493
+
5494
+ if( strcmp($wp_version, '3.7') < 0 ){
5495
+ $cp = 0;
5496
+ }
5497
+
5498
+ $template_variables = array(
5499
+ 'WordPress.Version' => $wp_version,
5500
+ 'WordPress.UpgradeURL' => admin_url('update-core.php'),
5501
+ 'WordPress.UpdateVisibility' => 'hidden',
5502
+ );
5503
+
5504
+ $wp_version = htmlspecialchars($wp_version);
5505
 
5506
+ if( $cp == 0 ){
5507
+ $template_variables['WordPress.UpdateVisibility'] = 'visible';
5508
  }
5509
 
5510
+ return sucuriscan_get_section('integrity-wpoutdate', $template_variables);
5511
  }
5512
 
5513
  /**
5514
+ * Compare the md5sum of the core files in the current site with the hashes hosted
5515
+ * remotely in Sucuri servers. These hashes are updated every time a new version
5516
+ * of WordPress is released.
5517
  *
5518
  * @return void
5519
  */
5520
+ function sucuriscan_core_files(){
5521
+ global $wp_version;
5522
 
5523
+ $template_variables = array(
5524
+ 'CoreFiles.List' => '',
5525
+ 'CoreFiles.ListCount' => 0,
5526
+ 'CoreFiles.GoodVisibility' => 'visible',
5527
+ 'CoreFiles.BadVisibility' => 'hidden',
5528
+ );
5529
 
5530
+ if( $wp_version ){
5531
+ $latest_hashes = sucuriscan_check_wp_integrity($wp_version);
 
 
 
 
 
5532
 
5533
+ if( $latest_hashes ){
5534
+ $counter = 0;
5535
+
5536
+ foreach( $latest_hashes as $list_type => $file_list ){
5537
+ if(
5538
+ $list_type == 'stable'
5539
+ || empty($file_list)
5540
+ ){
5541
+ continue;
5542
+ }
5543
+
5544
+ foreach( $file_list as $file_path ){
5545
+ if( $file_path == '.htaccess' ){
5546
+ $file_path = sprintf(
5547
+ '%s <a href="%s" target="_blank">%s</a>',
5548
+ $file_path,
5549
+ '%%SUCURI.URL.Infosys%%#htaccess-integrity',
5550
+ '<em>(Check HTAccess Integrity)</em>'
5551
+ );
5552
+ }
 
 
 
 
 
 
 
 
5553
 
5554
+ elseif( $file_path == 'wp-config.php' ){
5555
+ $file_path = sprintf(
5556
+ '%s <a href="%s" target="_blank">%s</a>',
5557
+ $file_path,
5558
+ '%%SUCURI.URL.Infosys%%#wpconfig-rules',
5559
+ '<em>(Check WP Config Variables)</em>'
5560
+ );
5561
+ }
5562
 
 
 
 
 
 
 
 
 
 
 
 
5563
  $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
5564
+ $template_variables['CoreFiles.List'] .= sucuriscan_get_snippet('integrity-corefiles', array(
5565
+ 'CoreFiles.CssClass' => $css_class,
5566
+ 'CoreFiles.StatusType' => $list_type,
5567
+ 'CoreFiles.StatusAbbr' => substr($list_type, 0, 1),
5568
+ 'CoreFiles.FilePath' => $file_path,
5569
+ ));
5570
  $counter += 1;
5571
+ }
5572
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5573
 
5574
+ if( $counter > 0 ){
5575
+ $template_variables['CoreFiles.ListCount'] = $counter;
5576
+ $template_variables['CoreFiles.GoodVisibility'] = 'hidden';
5577
+ $template_variables['CoreFiles.BadVisibility'] = 'visible';
5578
+ }
5579
+ } else {
5580
+ sucuriscan_error( 'Error retrieving the WordPress core hashes, try again.' );
5581
+ }
5582
+ }
5583
+
5584
+ return sucuriscan_get_section('integrity-corefiles', $template_variables);
5585
+ }
5586
 
5587
  /**
5588
  * Retrieve a list with the checksums of the files in a specific version of WordPress.
5589
  *
5590
+ * @see Release Archive http://wordpress.org/download/release-archive/
5591
+ *
5592
  * @param integer $version Valid version number of the WordPress project.
5593
  * @return object Associative object with the relative filepath and the checksums of the project files.
5594
  */
5595
+ function sucuriscan_get_official_checksums( $version=0 ){
5596
  $api_url = sprintf('http://api.wordpress.org/core/checksums/1.0/?version=%s&locale=en_US', $version);
 
5597
  $request = wp_remote_get($api_url);
5598
+
5599
  if( !is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200 ){
5600
  $json_data = json_decode($request['body']);
5601
+
5602
  if( $json_data->checksums !== FALSE ){
5603
+ $checksums = $json_data->checksums;
5604
+
5605
+ // Convert the object list to an array for better handle of the data.
5606
+ if( $checksums instanceof stdClass ){
5607
+ $checksums = (array) $checksums;
5608
+ }
5609
+
5610
+ return $checksums;
5611
  }
5612
  }
5613
 
5620
  * these keys:
5621
  *
5622
  * <ul>
5623
+ * <li>modified: Files with a different checksum according to the official files of the WordPress version filtered,</li>
5624
+ * <li>stable: Files with the same checksums than the official files,</li>
5625
  * <li>removed: Official files which are not present in the local project,</li>
5626
  * <li>added: Files present in the local project but not in the official WordPress packages.</li>
5627
  * </ul>
5628
  *
5629
  * @param integer $version Valid version number of the WordPress project.
5630
+ * @return array Associative array with these keys: modified, stable, removed, added.
5631
  */
5632
+ function sucuriscan_check_wp_integrity( $version=0 ){
5633
  $latest_hashes = sucuriscan_get_official_checksums($version);
5634
 
5635
  if( !$latest_hashes ){ return FALSE; }
5636
 
5637
+ $output = array(
5638
+ 'added' => array(),
5639
+ 'removed' => array(),
5640
+ 'modified' => array(),
5641
+ 'stable' => array(),
5642
+ );
5643
 
5644
  // Get current filesystem tree.
5645
+ $wp_top_hashes = sucuriscan_get_integrity_tree( ABSPATH , false);
5646
+ $wp_admin_hashes = sucuriscan_get_integrity_tree( ABSPATH . 'wp-admin', true);
5647
+ $wp_includes_hashes = sucuriscan_get_integrity_tree( ABSPATH . 'wp-includes', true);
5648
  $wp_core_hashes = array_merge( $wp_top_hashes, $wp_admin_hashes, $wp_includes_hashes );
5649
 
5650
+ // Compare remote and local checksums and search removed files.
5651
+ foreach( $latest_hashes as $filepath => $remote_checksum ){
5652
+ if( sucuriscan_ignore_integrity_filepath($filepath) ){ continue; }
5653
+
5654
  $full_filepath = sprintf('%s/%s', ABSPATH, $filepath);
5655
+
5656
  if( file_exists($full_filepath) ){
5657
  $local_checksum = @md5_file($full_filepath);
5658
+
5659
  if( $local_checksum && $local_checksum == $remote_checksum ){
5660
+ $output['stable'][] = $filepath;
5661
+ } else {
5662
+ $output['modified'][] = $filepath;
5663
  }
5664
+ } else {
5665
  $output['removed'][] = $filepath;
5666
  }
5667
  }
5668
 
5669
  // Search added files (files not common in a normal wordpress installation).
5670
+ foreach( $wp_core_hashes as $filepath => $extra_info ){
5671
  $filepath = preg_replace('/^\.\/(.*)/', '$1', $filepath);
5672
+
5673
+ if( sucuriscan_ignore_integrity_filepath($filepath) ){ continue; }
5674
+
5675
+ if( !isset($latest_hashes[$filepath]) ){
5676
  $output['added'][] = $filepath;
5677
  }
5678
  }
5681
  }
5682
 
5683
  /**
5684
+ * Ignore irrelevant files and directories from the integrity checking.
 
 
 
5685
  *
5686
+ * @param string $filepath File path that will be compared.
5687
+ * @return boolean TRUE if the file should be ignored, FALSE otherwise.
5688
  */
5689
+ function sucuriscan_ignore_integrity_filepath( $filepath='' ){
5690
+ // List of files that will be ignored from the integrity checking.
5691
+ $ignore_files = array(
5692
+ 'favicon.ico',
5693
+ '.htaccess',
5694
+ 'wp-config.php',
5695
+ 'wp-pass.php',
5696
+ 'wp-rss.php',
5697
+ 'wp-feed.php',
5698
+ 'wp-register.php',
5699
+ 'wp-atom.php',
5700
+ 'wp-commentsrss2.php',
5701
+ 'wp-rss2.php',
5702
+ 'wp-rdf.php',
5703
+ );
5704
 
5705
+ if(
5706
+ in_array($filepath, $ignore_files)
5707
+ || strpos($filepath, 'wp-content/themes') == 0
5708
+ || strpos($filepath, 'wp-content/plugins') == 0
5709
+ ){
5710
+ return TRUE;
5711
  }
5712
 
5713
+ return FALSE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5714
  }
5715
 
5716
  /**
5717
+ * List all files inside wp-content that have been modified in the last days.
5718
  *
5719
  * @return void
5720
  */
5721
+ function sucuriscan_modified_files(){
5722
+ $valid_day_ranges = array( 1, 3, 7, 30, 60 );
5723
+ $template_variables = array(
5724
+ 'ModifiedFiles.List' => '',
5725
+ 'ModifiedFiles.SelectOptions' => '',
5726
+ 'ModifiedFiles.NoFilesVisibility' => 'visible',
5727
+ 'ModifiedFiles.Days' => 0,
5728
+ );
5729
 
5730
+ // Find files modified in the last days.
5731
+ $back_days = 7;
 
 
 
 
 
 
 
5732
 
5733
+ // Correct the ranges of the search to be between one and sixty days.
5734
+ if( sucuriscan_check_page_nonce() && isset($_POST['sucuriscan_last_days']) ){
5735
+ $back_days = intval($_POST['sucuriscan_last_days']);
5736
+ if ( $back_days <= 0 ){ $back_days = 1; }
5737
+ elseif( $back_days >= 60 ){ $back_days = 60; }
5738
+ }
 
 
 
5739
 
5740
+ // Generate the options for the select field of the page form.
5741
+ foreach( $valid_day_ranges as $day ){
5742
+ $selected_option = ($back_days == $day) ? 'selected="selected"' : '';
5743
+ $template_variables['ModifiedFiles.SelectOptions'] .= sprintf(
5744
+ '<option value="%d" %s>%d</option>',
5745
+ $day, $selected_option, $day
5746
+ );
 
 
 
 
 
 
 
 
 
 
5747
  }
5748
 
5749
+ // Scan the files of the site.
5750
+ $template_variables['ModifiedFiles.Days'] = $back_days;
5751
+ $wp_content_hashes = sucuriscan_get_integrity_tree( ABSPATH.'wp-content', true );
5752
+ $back_days = current_time('timestamp') - ( $back_days * 86400);
5753
+ $counter = 0;
5754
+
5755
+ foreach( $wp_content_hashes as $file_path => $file_info ){
5756
+ if( $file_info['filetime'] >= $back_days ){
5757
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
5758
+ $mod_date = date('d/M/Y H:i:s', $file_info['filetime']);
5759
+
5760
+ $template_variables['ModifiedFiles.List'] .= sucuriscan_get_snippet('integrity-modifiedfiles', array(
5761
+ 'ModifiedFiles.CssClass' => $css_class,
5762
+ 'ModifiedFiles.CheckSum' => $file_info['checksum'],
5763
+ 'ModifiedFiles.FilePath' => $file_path,
5764
+ 'ModifiedFiles.DateTime' => $mod_date
5765
+ ));
5766
+ $counter += 1;
5767
  }
5768
  }
5769
 
5770
+ if( $counter > 0 ){
5771
+ $template_variables['ModifiedFiles.NoFilesVisibility'] = 'hidden';
 
 
5772
  }
5773
+
5774
+ return sucuriscan_get_section('integrity-modifiedfiles', $template_variables);
5775
  }
5776
 
5777
  /**
5778
+ * Generate and print the HTML code for the Post-Hack page.
 
5779
  *
5780
  * @return void
5781
  */
5782
+ function sucuriscan_posthack_page(){
5783
+ if( !current_user_can('manage_options') ){
5784
+ wp_die(__('You do not have sufficient permissions to access this page: Sucuri Post-Hack') );
 
 
 
 
 
 
 
 
 
5785
  }
5786
 
5787
+ $process_form = sucuriscan_posthack_process_form();
 
 
 
5788
 
5789
+ // Page pseudo-variables initialization.
5790
+ $template_variables = array(
5791
+ 'PageTitle' => 'Post-Hack',
5792
+ 'UpdateSecretKeys' => sucuriscan_update_secret_keys($process_form),
5793
+ 'ResetPassword' => sucuriscan_posthack_users($process_form),
5794
+ 'DatabaseBackups' => sucuriscan_database_backups($process_form),
 
 
 
 
 
5795
  );
5796
 
5797
+ echo sucuriscan_get_template('posthack', $template_variables);
 
 
5798
  }
5799
 
5800
  /**
5801
+ * Check whether the "I understand this operation" checkbox was marked or not.
 
 
5802
  *
5803
+ * @return boolean TRUE if a form submission should be processed, FALSE otherwise.
5804
  */
5805
+ function sucuriscan_posthack_process_form(){
5806
+ if( sucuriscan_check_page_nonce() && isset($_POST['sucuriscan_process_form']) ){
5807
+ if( $_POST['sucuriscan_process_form'] == 1 ){
5808
+ return TRUE;
5809
+ } else {
5810
+ sucuriscan_error('You need to confirm that you understand the risk of this operation.');
5811
+ }
5812
+ }
 
 
 
5813
 
5814
+ return FALSE;
5815
  }
5816
 
5817
  /**
5818
+ * Update the WordPress secret keys.
 
 
 
 
5819
  *
5820
+ * @param $process_form Whether a form was submitted or not.
5821
+ * @return string HTML code with the information of the process.
5822
  */
5823
+ function sucuriscan_update_secret_keys( $process_form=FALSE ){
5824
+ $template_variables = array(
5825
+ 'WPConfigUpdate.Visibility' => 'hidden',
5826
+ 'WPConfigUpdate.NewConfig' => '',
5827
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5828
 
5829
+ // Update all WordPress secret keys.
5830
+ if( $process_form && isset($_POST['sucuriscan_update_wpconfig']) ){
5831
+ $wpconfig_process = sucuriscan_set_new_config_keys();
5832
+
5833
+ if( $wpconfig_process ){
5834
+ $template_variables['WPConfigUpdate.Visibility'] = 'visible';
5835
+
5836
+ if( $wpconfig_process['updated'] === TRUE ){
5837
+ sucuriscan_info( 'WordPress secret keys updated successfully (check bellow the summary of the operation).' );
5838
+ $template_variables['WPConfigUpdate.NewConfig'] .= "// Old Keys\n";
5839
+ $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['old_keys_string'];
5840
+ $template_variables['WPConfigUpdate.NewConfig'] .= "//\n";
5841
+ $template_variables['WPConfigUpdate.NewConfig'] .= "// New Keys\n";
5842
+ $template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['new_keys_string'];
5843
+ } else {
5844
+ sucuriscan_error( '<code>wp-config.php</code> file is not writable, replace the old configuration file with the new values shown bellow.' );
5845
+ $template_variables['WPConfigUpdate.NewConfig'] = $wpconfig_process['new_wpconfig'];
5846
  }
5847
+ } else {
5848
+ sucuriscan_error('<code>wp-config.php</code> file was not found in the default location.' );
5849
  }
5850
  }
5851
 
5852
+ return sucuriscan_get_section('posthack-updatesecretkeys', $template_variables);
 
 
 
 
 
 
5853
  }
5854
 
5855
  /**
5856
+ * Display a list of users in a table that will be used to select the accounts
5857
+ * where a password reset action will be executed.
 
 
 
5858
  *
5859
+ * @param $process_form Whether a form was submitted or not.
5860
+ * @return string HTML code for a table where a list of user accounts will be shown.
5861
  */
5862
+ function sucuriscan_posthack_users( $process_form=FALSE ){
5863
+ $template_variables = array(
5864
+ 'ResetPassword.UserList' => '',
5865
+ );
5866
 
5867
+ // Process the form submission (if any).
5868
+ sucuriscan_reset_user_password($process_form);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5869
 
5870
+ // Fill the user list for ResetPassword action.
5871
+ $user_list = get_users();
 
 
 
 
 
 
 
 
 
 
 
 
5872
 
5873
+ if( $user_list ){
5874
+ $counter = 0;
 
5875
 
5876
+ foreach( $user_list as $user ){
5877
+ $user->user_registered_timestamp = strtotime($user->user_registered);
5878
+ $user->user_registered_formatted = date('D, M/Y H:i', $user->user_registered_timestamp);
5879
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
5880
+
5881
+ $user_snippet = sucuriscan_get_snippet('resetpassword', array(
5882
+ 'ResetPassword.UserId' => $user->ID,
5883
+ 'ResetPassword.Username' => $user->user_login,
5884
+ 'ResetPassword.Displayname' => $user->display_name,
5885
+ 'ResetPassword.Email' => $user->user_email,
5886
+ 'ResetPassword.Registered' => $user->user_registered_formatted,
5887
+ 'ResetPassword.Roles' => implode(', ', $user->roles),
5888
+ 'ResetPassword.CssClass' => $css_class
5889
+ ));
5890
+
5891
+ $template_variables['ResetPassword.UserList'] .= $user_snippet;
5892
+ $counter += 1;
5893
  }
5894
  }
5895
 
5896
+ return sucuriscan_get_section('posthack-resetpassword', $template_variables);
 
 
 
 
 
 
 
 
 
 
5897
  }
5898
 
5899
  /**
5900
+ * Update the password of the user accounts specified.
 
 
 
 
 
5901
  *
5902
+ * @param $process_form Whether a form was submitted or not.
5903
  * @return void
5904
  */
5905
+ function sucuriscan_reset_user_password( $process_form=FALSE ){
5906
+ if( $process_form && isset($_POST['sucuriscan_reset_password']) ){
5907
+ $user_identifiers = isset($_POST['user_ids']) ? $_POST['user_ids'] : array();
5908
+ $pwd_changed = $pwd_not_changed = array();
5909
+
5910
+ if( is_array($user_identifiers) && !empty($user_identifiers) ){
5911
+ arsort($user_identifiers);
5912
+
5913
+ foreach( $user_identifiers as $user_id ){
5914
+ if( sucuriscan_new_password($user_id) ){
5915
+ $pwd_changed[] = $user_id;
5916
+ } else {
5917
+ $pwd_not_changed[] = $user_id;
5918
+ }
5919
+ }
5920
 
5921
+ if( !empty($pwd_changed) ){
5922
+ sucuriscan_info( 'Password changed successfully for users: ' . implode(', ',$pwd_changed) );
 
 
 
 
 
 
 
 
 
 
 
 
5923
  }
 
 
5924
 
5925
+ if( !empty($pwd_not_changed) ){
5926
+ sucuriscan_error( 'Password change failed for users: ' . implode(', ',$pwd_not_changed) );
 
 
 
 
 
 
 
 
 
 
5927
  }
5928
+ } else {
5929
+ sucuriscan_error( 'You did not select a user from the list.' );
5930
  }
5931
+ }
5932
+ }
5933
 
5934
+ /**
5935
+ * List and offer a way to download the database backups generates so far.
5936
+ *
5937
+ * @param $process_form Whether a form was submitted or not.
5938
+ * @return void
5939
+ */
5940
+ function sucuriscan_database_backups( $process_form=FALSE ){
5941
+ $template_variables = array(
5942
+ 'BackupList' => '',
5943
+ );
5944
 
5945
+ // Process the form submission (if any).
5946
+ sucuriscan_process_database_backups_form($process_form);
5947
+
5948
+ // Instantiate the database/backup library and get all files.
5949
+ $sucuri_backup = new SucuriScanBackup();
5950
+ $backup_files = $sucuri_backup->get_backup_files();
5951
+
5952
+ if( $backup_files ){
5953
+ $counter = 0;
5954
+
5955
+ foreach( $backup_files as $backup_file ){
5956
+ $backupfile_snippet = sucuriscan_get_snippet('posthack-databasebackups', array(
5957
+ 'BackupList.FileURL' => admin_url('?sucuriscan_download=' . $backup_file->filename),
5958
+ 'BackupList.Filename' => $backup_file->filename,
5959
+ 'BackupList.Filetype' => strtoupper($backup_file->fileext),
5960
+ 'BackupList.Filesize' => ( $backup_file->filesize>0 ? "{$backup_file->filesize} (~ {$backup_file->filehumansize})" : 0 ),
5961
+ 'BackupList.Filetime' => date('Y/M/d H:i', $backup_file->filetime),
5962
+ 'BackupList.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate',
5963
+ ));
5964
+
5965
+ $template_variables['BackupList'] .= $backupfile_snippet;
5966
+ $counter += 1;
5967
  }
5968
  }
5969
 
5970
+ return sucuriscan_get_section('posthack-databasebackups', $template_variables);
 
 
 
 
 
5971
  }
5972
 
5973
  /**
5974
+ * Process the form submissions of the database backups panel.
 
5975
  *
5976
+ * @param $process_form Whether a form was submitted or not.
5977
  * @return void
5978
  */
5979
+ function sucuriscan_process_database_backups_form( $process_form=FALSE ){
5980
+ if( $process_form && isset($_POST['sucuriscan_database_backup']) ){
5981
+
5982
+ if( isset($_POST['generate_dbbackup']) ){
5983
+ $sucuri_backup = new SucuriScanBackup();
5984
+ $dbbackup_filepath = $sucuri_backup->all_database();
5985
+
5986
+ if( $dbbackup_filepath ){
5987
+ $dbbackup_fileurl = admin_url( '?sucuriscan_download=' . basename($dbbackup_filepath) );
5988
+ sucuriscan_info( 'Database backup generated and stored in the plugin upload folder, you can download the
5989
+ file from here: <a href="'.$dbbackup_fileurl.'" target="_blank"><strong>Download DB Backup</strong></a>' );
5990
+ } else {
5991
+ sucuriscan_error( 'Could not generate a new database backup' );
5992
+ }
5993
+ }
5994
+
5995
+ elseif( isset($_POST['remove_dbbackup']) ){
5996
+ $sucuri_backup = new SucuriScanBackup();
5997
+ $backups_to_remove = isset($_POST['dbbackup_filenames']) ? $_POST['dbbackup_filenames'] : array();
5998
+ $sucuri_backup->remove_backup_file($backups_to_remove);
5999
+ }
6000
 
 
 
 
6001
  }
6002
+ }
6003
+
6004
+ if( !function_exists('sucuriscan_download_file') ){
6005
+ /**
6006
+ * Download the SQL or Zip file generated to hold a backup of the database.
6007
+ *
6008
+ * @return void
6009
+ */
6010
+ function sucuriscan_download_file(){
6011
+ if(
6012
+ current_user_can('manage_options')
6013
+ AND isset($_GET['sucuriscan_download'])
6014
+ ){
6015
+ $sucuri_backup = new SucuriScanBackup();
6016
+ $backup_file = $sucuri_backup->get_backup_file_from_filename($_GET['sucuriscan_download']);
6017
+
6018
+ if( $backup_file ){
6019
+ switch($backup_file->fileext){
6020
+ case 'sql':
6021
+ $download_filename = sprintf('%s.sql', DB_NAME);
6022
+ header('Content-Type: application/x-sql');
6023
+ break;
6024
+ case 'zip':
6025
+ $download_filename = sprintf('%s.sql.zip', DB_NAME);
6026
+ header('Content-Type: application/zip');
6027
+ break;
6028
+ default:
6029
+ $download_filename = $backup_file->filename;
6030
+ header('Content-Type: application/octet-stream');
6031
+ break;
6032
+ }
6033
+
6034
+ header('Content-Disposition: attachment; filename='.$download_filename);
6035
+ header('Content-Length: '.$backup_file->filesize);
6036
+ header('Cache-Control: private');
6037
+
6038
+ if( $fd=fopen($backup_file->filepath,'r') ){
6039
+ while( !feof($fd) ){ echo fread($fd, 2048); }
6040
+ }
6041
+
6042
+ fclose($fd);
6043
+ }
6044
+ }
6045
  }
6046
 
6047
+ add_action('admin_init', 'sucuriscan_download_file');
 
 
 
 
 
6048
  }
6049
 
6050
  /**
6051
+ * Generate and print the HTML code for the Last Logins page.
6052
  *
6053
+ * This page will contains information of all the logins of the registered users.
6054
+ *
6055
+ * @return string Last-logings for the administrator accounts.
6056
  */
6057
+ function sucuriscan_lastlogins_page(){
6058
+ if( !current_user_can('manage_options') ){
6059
+ wp_die(__('You do not have sufficient permissions to access this page: Sucuri Last-Logins') );
 
 
6060
  }
6061
 
6062
+ // Page pseudo-variables initialization.
6063
+ $template_variables = array(
6064
+ 'PageTitle' => 'Last Logins',
6065
+ 'LastLoginsNonce' => wp_create_nonce('sucuriscan_lastlogins_nonce'),
6066
+ 'LastLogins.Admins' => sucuriscan_lastlogins_admins(),
6067
+ 'LastLogins.AllUsers' => sucuriscan_lastlogins_all(),
6068
+ 'UserList' => '',
6069
+ 'UserListLimit' => SUCURISCAN_LASTLOGINS_USERSLIMIT,
 
6070
  );
6071
+
6072
+ echo sucuriscan_get_template('lastlogins', $template_variables);
6073
  }
6074
 
6075
  /**
6076
+ * List all the user administrator accounts.
6077
+ *
6078
+ * @see http://codex.wordpress.org/Class_Reference/WP_User_Query
6079
  *
6080
  * @return void
6081
  */
6082
+ function sucuriscan_lastlogins_admins(){
 
 
 
 
 
6083
  // Page pseudo-variables initialization.
6084
  $template_variables = array(
6085
+ 'AdminUsers.List' => ''
 
 
 
 
6086
  );
6087
 
6088
+ $user_query = new WP_User_Query(array( 'role' => 'Administrator' ));
6089
+ $admins = $user_query->get_results();
 
 
 
6090
 
6091
+ foreach( (array)$admins as $admin ){
6092
+ $admin->lastlogins = sucuriscan_get_logins(5, $admin->ID);
 
6093
 
6094
+ $user_snippet = array(
6095
+ 'AdminUsers.Username' => $admin->user_login,
6096
+ 'AdminUsers.Email' => $admin->user_email,
6097
+ 'AdminUsers.LastLogins' => '',
6098
+ 'AdminUsers.UserURL' => admin_url('user-edit.php?user_id='.$admin->ID),
6099
+ 'AdminUsers.NoLastLogins' => 'visible',
6100
+ 'AdminUsers.NoLastLoginsTable' => 'hidden',
6101
+ );
6102
 
6103
+ if( !empty($admin->lastlogins) ){
6104
+ $user_snippet['AdminUsers.NoLastLogins'] = 'hidden';
6105
+ $user_snippet['AdminUsers.NoLastLoginsTable'] = 'visible';
6106
+ $counter = 0;
6107
+
6108
+ foreach( $admin->lastlogins as $lastlogin ){
6109
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6110
+ $user_snippet['AdminUsers.LastLogins'] .= sucuriscan_get_snippet('lastlogins-admins-lastlogin', array(
6111
+ 'AdminUsers.RemoteAddr' => $lastlogin->user_remoteaddr,
6112
+ 'AdminUsers.Datetime' => $lastlogin->user_lastlogin,
6113
+ 'AdminUsers.CssClass' => $css_class,
6114
+ ));
6115
+ $counter += 1;
6116
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6117
  }
 
6118
 
6119
+ $template_variables['AdminUsers.List'] .= sucuriscan_get_snippet('lastlogins-admins', $user_snippet);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6120
  }
6121
 
6122
+ return sucuriscan_get_section('lastlogins-admins', $template_variables);
6123
  }
6124
 
6125
  /**
6126
+ * List the last-logins for all user accounts in the site.
6127
  *
6128
  * This page will contains information of all the logins of the registered users.
6129
  *
6130
+ * @return string Last-logings for all user accounts.
6131
  */
6132
+ function sucuriscan_lastlogins_all(){
 
 
 
 
 
6133
  $template_variables = array(
 
 
6134
  'UserList' => '',
6135
  'UserListLimit' => SUCURISCAN_LASTLOGINS_USERSLIMIT,
6136
  );
6137
 
6138
  if( !sucuriscan_lastlogins_datastore_is_writable() ){
6139
+ sucuriscan_error( 'Last-logins datastore file is not writable: <code>'.sucuriscan_lastlogins_datastore_filepath().'</code>' );
 
 
6140
  }
6141
 
6142
  $limit = isset($_GET['limit']) ? intval($_GET['limit']) : SUCURISCAN_LASTLOGINS_USERSLIMIT;
6144
 
6145
  $counter = 0;
6146
  $user_list = sucuriscan_get_logins($limit);
6147
+
6148
+ foreach( $user_list as $user ){
6149
  $counter += 1;
6150
+ $css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
6151
+
6152
+ $user_dataset = array(
6153
  'UserList.Number' => $counter,
6154
+ 'UserList.UserId' => $user->user_id,
6155
+ 'UserList.Username' => '<em>Unknown</em>',
6156
+ 'UserList.Displayname' => '',
6157
+ 'UserList.Email' => '',
6158
+ 'UserList.Registered' => '',
6159
  'UserList.RemoteAddr' => $user->user_remoteaddr,
6160
  'UserList.Hostname' => $user->user_hostname,
6161
  'UserList.Datetime' => $user->user_lastlogin,
6162
  'UserList.TimeAgo' => sucuriscan_time_ago($user->user_lastlogin),
6163
+ 'UserList.UserURL' => admin_url('user-edit.php?user_id='.$user->user_id),
6164
+ 'UserList.CssClass' => $css_class,
6165
+ );
6166
+
6167
+ if( $user->user_exists ){
6168
+ $user_dataset['UserList.Username'] = $user->user_login;
6169
+ $user_dataset['UserList.Displayname'] = $user->display_name;
6170
+ $user_dataset['UserList.Email'] = $user->user_email;
6171
+ $user_dataset['UserList.Registered'] = $user->user_registered;
6172
+ }
6173
+
6174
+ $template_variables['UserList'] .= sucuriscan_get_snippet('lastlogins-all', $user_dataset);
6175
  }
6176
 
6177
+ return sucuriscan_get_section('lastlogins-all', $template_variables);
6178
  }
6179
 
6180
  /**
6275
  * @param integer $user_id Optional user identifier to filter the results.
6276
  * @return array The list of all the user logins through the time until now.
6277
  */
6278
+ function sucuriscan_get_logins( $limit=10, $user_id=0 ){
6279
  $lastlogins = array();
6280
  $datastore_filepath = sucuriscan_lastlogins_datastore_is_readable();
6281
 
6282
+ if( $datastore_filepath ){
6283
  $parsed_lines = 0;
6284
  $lastlogins_lines = array_reverse(file($datastore_filepath));
6285
+
6286
+ foreach( $lastlogins_lines as $line ){
6287
  $line = str_replace("\n", '', $line);
6288
+
6289
  if( preg_match('/^a:/', $line) ){
6290
  $user_lastlogin = unserialize($line);
6291
 
6301
  }
6302
 
6303
  /* Get the WP_User object and add extra information from the last-login data */
6304
+ $user_lastlogin['user_exists'] = FALSE;
6305
  $user_account = get_userdata($user_lastlogin['user_id']);
6306
+
6307
+ if( $user_account ){
6308
+ $user_lastlogin['user_exists'] = TRUE;
6309
+
6310
+ foreach( $user_account->data as $var_name=>$var_value ){
6311
+ $user_lastlogin[$var_name] = $var_value;
6312
+ }
6313
  }
6314
+
6315
+ $lastlogins[] = (object)$user_lastlogin;
6316
  $parsed_lines += 1;
6317
  }
6318
 
6335
  * @param boolean $user WordPress user object with the information of the account involved in the operation.
6336
  * @return string URL where the browser must be redirected to.
6337
  */
6338
+ function sucuriscan_login_redirect( $redirect_to='', $request=NULL, $user=FALSE ){
6339
  $login_url = !empty($redirect_to) ? $redirect_to : admin_url();
6340
+
6341
  if( $user instanceof WP_User && $user->ID ){
6342
+ $login_url = add_query_arg( 'sucuriscan_lastlogin', 1, $login_url );
6343
  }
6344
+
6345
  return $login_url;
6346
  }
6347
+
6348
+ $lastlogin_redirection = sucuriscan_get_option('sucuriscan_lastlogin_redirection');
6349
+ if( $lastlogin_redirection == 'enabled' ){
6350
+ add_filter('login_redirect', 'sucuriscan_login_redirect', 10, 3);
6351
+ }
6352
  }
6353
 
6354
  if( !function_exists('sucuri_get_user_lastlogin') ){
6358
  * @return void
6359
  */
6360
  function sucuriscan_get_user_lastlogin(){
6361
+ if( isset($_GET['sucuriscan_lastlogin']) && current_user_can('manage_options') ){
6362
  $current_user = wp_get_current_user();
6363
 
6364
  // Select the penultimate entry, not the last one.
6369
  $message_tpl = 'The last time you logged in was: %s, from %s - %s';
6370
  $lastlogin_message = sprintf( $message_tpl, date('Y/M/d'), $row->user_remoteaddr, $row->user_hostname );
6371
  $lastlogin_message .= chr(32).'(<a href="'.site_url('wp-admin/admin.php?page='.SUCURISCAN.'_lastlogins').'">View Last-Logins</a>)';
6372
+ sucuriscan_info( $lastlogin_message );
6373
  }
6374
  }
6375
  }
6810
  }
6811
 
6812
  /**
6813
+ * Gather information from the server, database engine, and PHP interpreter.
6814
  *
6815
+ * @return array A list of pseudo-variables and values that will replace them in the HTML template.
 
6816
  */
6817
  function sucuriscan_server_info(){
6818
  global $wpdb;
6822
  $mysql_version = $wpdb->get_var('SELECT VERSION() AS version');
6823
  $mysql_info = $wpdb->get_results('SHOW VARIABLES LIKE "sql_mode"');
6824
  $sql_mode = ( is_array($mysql_info) && !empty($mysql_info[0]->Value) ) ? $mysql_info[0]->Value : 'Not set';
6825
+ $runtime_scan = sucuriscan_get_option('sucuriscan_runtime');
6826
+ $runtime_scan_human = date( 'd/M/Y H:i:s', $runtime_scan );
6827
 
6828
  $template_variables = array(
6829
+ 'PluginVersion' => SUCURISCAN_VERSION,
6830
+ 'PluginMD5' => SUCURISCAN_PLUGIN_CHECKSUM,
6831
+ 'PluginRuntimeDatetime' => $runtime_scan_human,
6832
+ 'OperatingSystem' => sprintf('%s (%d Bit)', PHP_OS, PHP_INT_SIZE*8),
6833
+ 'Server' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'Unknown',
6834
+ 'MemoryUsage' => $memory_usage,
6835
+ 'MySQLVersion' => $mysql_version,
6836
+ 'SQLMode' => $sql_mode,
6837
+ 'PHPVersion' => PHP_VERSION,
 
 
6838
  );
6839
 
6840
  $field_names = array(
6860
 
6861
 
6862
  /**
6863
+ * Global variables used by the functions bellow.
6864
+ *
6865
+ * These are lists of options allowed to use in the execution of the monitoring
6866
+ * tool, and the administrator can select among them in the settings page.
6867
+ *
6868
+ * @var array
6869
+ */
6870
+ $sucuriscan_notify_options = array(
6871
+ 'sucuriscan_notify_user_registration' => 'Enable email alerts for new user registration',
6872
+ 'sucuriscan_notify_success_login' => 'Enable email alerts for successful logins',
6873
+ 'sucuriscan_notify_failed_login' => 'Enable email alerts for failed logins',
6874
+ 'sucuriscan_notify_post_publication' => 'Enable email alerts for new site content',
6875
+ 'sucuriscan_notify_theme_editor' => 'Enable email alerts when a file is modified via the theme/plugin editor',
6876
+ 'sucuriscan_notify_website_updated' => 'Enable email alerts when your website is updated',
6877
+ 'sucuriscan_notify_settings_updated' => 'Enable email alerts when your website settings are updated',
6878
+ 'sucuriscan_notify_theme_switched' => 'Enable email alerts when the website theme is switched',
6879
+ 'sucuriscan_notify_theme_updated' => 'Enable email alerts when a theme is updated',
6880
+ 'sucuriscan_notify_widget_added' => 'Enable email alerts when a widget is added to a sidebar',
6881
+ 'sucuriscan_notify_widget_deleted' => 'Enable email alerts when a widget is deleted from a sidebar',
6882
+ 'sucuriscan_notify_plugin_change' => 'Enable email alerts for Sucuri plugin changes',
6883
+ 'sucuriscan_notify_plugin_activated' => 'Enable email alerts when a plugin is activated',
6884
+ 'sucuriscan_notify_plugin_deactivated' => 'Enable email alerts when a plugin is deactivated',
6885
+ 'sucuriscan_notify_plugin_updated' => 'Enable email alerts when a plugin is updated',
6886
+ 'sucuriscan_notify_plugin_installed' => 'Enable email alerts when a plugin is installed',
6887
+ 'sucuriscan_notify_plugin_deleted' => 'Enable email alerts when a plugin is deleted',
6888
+ 'sucuriscan_prettify_mails' => 'Enable email alerts in HTML (uncheck to get email in text/plain)',
6889
+ 'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
6890
+ );
6891
+
6892
+ $sucuriscan_schedule_allowed = array(
6893
+ 'hourly' => 'Every three hours (3 hours)',
6894
+ 'twicedaily' => 'Twice daily (12 hours)',
6895
+ 'daily' => 'Once daily (24 hours)',
6896
+ '_oneoff' => 'Never',
6897
+ );
6898
+
6899
+ $sucuriscan_interface_allowed = array(
6900
+ 'spl' => 'SPL (Standard PHP Library)',
6901
+ 'opendir' => 'OpenDir (Medium performance)',
6902
+ 'glob' => 'Glob (Low performance)',
6903
+ );
6904
+
6905
+ /**
6906
+ * Print a HTML code with the settings of the plugin.
6907
  *
6908
  * @return void
6909
  */
6910
+ function sucuriscan_settings_page(){
6911
 
6912
+ global $sucuriscan_schedule_allowed, $sucuriscan_interface_allowed, $sucuriscan_notify_options;
6913
+
6914
+ // Check the nonce here to populate the value through other functions.
6915
+ $page_nonce = sucuriscan_check_page_nonce();
6916
+
6917
+ // Process all form submissions.
6918
+ sucuriscan_settings_form_submissions($page_nonce);
6919
+
6920
+ // Register the site, get its API key, and store it locally for future usage.
6921
+ $api_registered_modal = '';
6922
+
6923
+ // Whether the form to manually add the API key should be shown or not.
6924
+ $display_manual_key_form = (bool) isset($_POST['sucuriscan_recover_api_key']);
6925
+
6926
+ if( $page_nonce && isset($_POST['sucuriscan_wordpress_apikey']) ){
6927
+ $registered = sucuriscan_register_site();
6928
+
6929
+ if( $registered ){
6930
+ $api_registered_modal = sucuriscan_get_modal('settings-apiregistered', array(
6931
+ 'Title' => 'Site registered successfully',
6932
+ 'CssClass' => 'sucuriscan-apikey-registered',
6933
+ ));
6934
+ } else {
6935
+ $display_manual_key_form = TRUE;
6936
+ }
6937
+ }
6938
+
6939
+ // Get initial variables to decide some things bellow.
6940
+ $api_key = sucuriscan_wordpress_apikey();
6941
+ $scan_freq = sucuriscan_get_option('sucuriscan_scan_frequency');
6942
+ $scan_interface = sucuriscan_get_option('sucuriscan_scan_interface');
6943
+ $runtime_scan = sucuriscan_get_option('sucuriscan_runtime');
6944
+ $runtime_scan_human = date( 'd/M/Y H:i:s', $runtime_scan );
6945
+
6946
+ // Generate HTML code to configure the scanning frequency from the plugin settings.
6947
+ $scan_freq_options = '';
6948
+ foreach( $sucuriscan_schedule_allowed as $schedule => $schedule_label ){
6949
+ $selected = ( $scan_freq==$schedule ? 'selected="selected"' : '' );
6950
+ $scan_freq_options .= sprintf(
6951
+ '<option value="%s" %s>%s</option>',
6952
+ $schedule, $selected, $schedule_label
6953
+ );
6954
+ }
6955
+
6956
+ // Generate HTML code to configure the scanning interface from the plugin settings.
6957
+ $scan_interface_options = '';
6958
+ foreach( $sucuriscan_interface_allowed as $interface_name => $interface_desc ){
6959
+ $selected = ( $scan_interface==$interface_name ? 'selected="selected"' : '' );
6960
+ $scan_interface_options .= sprintf(
6961
+ '<option value="%s" %s>%s</option>',
6962
+ $interface_name, $selected, $interface_desc
6963
+ );
6964
+ }
6965
+
6966
+ // Generate HTML code to configure the notifications of the plugin.
6967
+ $notification_options = '';
6968
+ $counter = 0;
6969
+
6970
+ foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
6971
+ $alert_value = sucuriscan_get_option($alert_type);
6972
+ $checked = ( $alert_value == 'enabled' ? 'checked="checked"' : '' );
6973
+ $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
6974
+
6975
+ $notification_options .= sucuriscan_get_snippet('settings-notification', array(
6976
+ 'Notification.CssClass' => $css_class,
6977
+ 'Notification.Name' => $alert_type,
6978
+ 'Notification.Checked' => $checked,
6979
+ 'Notification.Label' => $alert_label,
6980
+ ));
6981
+ $counter += 1;
6982
  }
6983
 
6984
  $template_variables = array(
6985
+ 'PageTitle' => 'Settings',
6986
+ 'APIKey' => $api_key,
6987
+ 'APIKey.RecoverVisibility' => ( $api_key || $display_manual_key_form ? 'hidden' : 'visible' ),
6988
+ 'APIKey.ManualKeyFormVisibility' => ( $display_manual_key_form ? 'visible' : 'hidden' ),
6989
+ 'APIKey.RemoveVisibility' => ( $api_key ? 'visible' : 'hidden' ),
6990
+ 'ScanningFrequency' => 'Undefined',
6991
+ 'ScanningFrequencyOptions' => $scan_freq_options,
6992
+ 'ScanningInterface' => ( $scan_interface ? $sucuriscan_interface_allowed[$scan_interface] : 'Undefined' ),
6993
+ 'ScanningInterfaceOptions' => $scan_interface_options,
6994
+ 'ScanningInterfaceVisibility' => ( SucuriScanFileInfo::is_spl_available() ? 'hidden' : 'visible' ),
6995
+ 'ScanningRuntime' => $runtime_scan,
6996
+ 'ScanningRuntimeHuman' => $runtime_scan_human,
6997
+ 'NotificationOptions' => $notification_options,
6998
+ 'ModalWhenAPIRegistered' => $api_registered_modal,
6999
  );
7000
 
7001
+ if( array_key_exists($scan_freq, $sucuriscan_schedule_allowed) ){
7002
+ $template_variables['ScanningFrequency'] = $sucuriscan_schedule_allowed[$scan_freq];
7003
+ }
7004
+
7005
+ echo sucuriscan_get_template('settings', $template_variables);
7006
+ }
7007
+
7008
+ /**
7009
+ * Process the requests sent by the form submissions originated in the settings
7010
+ * page, all forms must have a nonce field that will be checked against the one
7011
+ * generated in the template render function.
7012
+ *
7013
+ * @param boolean $page_nonce True if the nonce is valid, False otherwise.
7014
+ * @return void
7015
+ */
7016
+ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
7017
+
7018
+ global $sucuriscan_schedule_allowed, $sucuriscan_interface_allowed, $sucuriscan_notify_options;
7019
+
7020
+ // Use this conditional to avoid double checking.
7021
+ if( is_null($page_nonce) ){
7022
+ $page_nonce = sucuriscan_check_page_nonce();
7023
+ }
7024
+
7025
+ if( $page_nonce ){
7026
+
7027
+ // Recover API key through the email registered previously.
7028
+ if( isset($_POST['sucuriscan_recover_api_key']) ){
7029
+ sucuriscan_recover_api_key();
7030
+ }
7031
+
7032
+ // Save API key after it was recovered by the administrator.
7033
+ if( isset($_POST['sucuriscan_manual_api_key']) ){
7034
+ sucuriscan_set_api_key( $_POST['sucuriscan_manual_api_key'], TRUE );
7035
+ sucuriscan_create_scheduled_task();
7036
+ }
7037
+
7038
+ // Remove API key from the local storage.
7039
+ if( isset($_POST['sucuriscan_remove_api_key']) ){
7040
+ sucuriscan_set_api_key('');
7041
+ wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7042
+ sucuriscan_notify_event( 'plugin_change', 'Sucuri API key removed' );
7043
+ }
7044
+
7045
+ // Modify the schedule of the filesystem scanner.
7046
+ if(
7047
+ isset($_POST['sucuriscan_scan_frequency'])
7048
+ && isset($sucuriscan_schedule_allowed)
7049
+ ){
7050
+ $frequency = $_POST['sucuriscan_scan_frequency'];
7051
+ $current_frequency = sucuriscan_get_option('sucuriscan_scan_frequency');
7052
+ $allowed_frequency = array_keys($sucuriscan_schedule_allowed);
7053
+
7054
+ if( in_array($frequency, $allowed_frequency) && $current_frequency != $frequency ){
7055
+ update_option('sucuriscan_scan_frequency', $frequency);
7056
+ wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7057
+
7058
+ if( $frequency != '_oneoff' ){
7059
+ wp_schedule_event( time()+10, $frequency, 'sucuriscan_scheduled_scan' );
7060
+ }
7061
+
7062
+ sucuriscan_notify_event( 'plugin_change', 'Filesystem scanning frequency changed to: ' . $frequency );
7063
+ sucuriscan_info( 'Filesystem scan scheduled to run <code>'.$frequency.'</code>' );
7064
+ }
7065
+ }
7066
+
7067
+ // Set the method (aka. interface) that will be used to scan the site.
7068
+ if(
7069
+ isset($_POST['sucuriscan_scan_interface'])
7070
+ && isset($sucuriscan_interface_allowed)
7071
+ ){
7072
+ $interface = trim($_POST['sucuriscan_scan_interface']);
7073
+ $allowed_values = array_keys($sucuriscan_interface_allowed);
7074
+
7075
+ if( in_array($interface, $allowed_values) ){
7076
+ update_option('sucuriscan_scan_interface', $interface);
7077
+ sucuriscan_notify_event( 'plugin_change', 'Filesystem scanning interface changed to: ' . $interface );
7078
+ sucuriscan_info( 'Filesystem scan interface set to <code>'.$interface.'</code>' );
7079
+ }
7080
+ }
7081
+
7082
+ // Update the notification settings.
7083
+ if(
7084
+ isset($_POST['sucuriscan_save_notification_settings'])
7085
+ && isset($sucuriscan_notify_options)
7086
+ ){
7087
+ foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
7088
+ if( isset($_POST[$alert_type]) ){
7089
+ $option_value = ( $_POST[$alert_type] == 1 ? 'enabled' : 'disabled' );
7090
+ update_option( $alert_type, $option_value );
7091
+ sucuriscan_notify_event( 'plugin_change', 'Email notification settings changed' );
7092
+ }
7093
+ }
7094
+
7095
+ sucuriscan_info( 'Notification settings updated.' );
7096
+ }
7097
+
7098
+ // Reset all the plugin's options.
7099
+ if( isset($_POST['sucuriscan_reset_options']) ){
7100
+ // Notify the event before the API key is removed.
7101
+ $event_msg = 'All plugins options were resetted';
7102
+ sucuriscan_report_event( 1, 'core', $event_msg );
7103
+ sucuriscan_notify_event( 'plugin_change', $event_msg );
7104
+
7105
+ // Remove all plugin's options from the database.
7106
+ $options = sucuriscan_get_options_from_db('all_sucuriscan_options');
7107
+
7108
+ foreach( $options as $option ){
7109
+ delete_option( $option->option_name );
7110
+ }
7111
+
7112
+ // Remove the scheduled tasks.
7113
+ wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
7114
+
7115
+ sucuriscan_info( 'All plugin options were resetted successfully' );
7116
+ }
7117
+
7118
+ }
7119
+
7120
  }
7121