PluginProbe ʕ •ᴥ•ʔ
Advanced Access Manager – Access Governance for WordPress / trunk
Advanced Access Manager – Access Governance for WordPress vtrunk
6.8.4 6.8.5 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13 6.9.14 6.9.15 6.9.16 6.9.17 6.9.18 6.9.19 6.9.2 6.9.20 6.9.21 6.9.22 6.9.23 6.9.24 6.9.25 6.9.26 6.9.27 6.9.28 6.9.29 6.9.3 6.9.30 6.9.31 6.9.32 6.9.33 6.9.34 6.9.35 6.9.36 6.9.37 6.9.38 6.9.39 6.9.4 6.9.41 6.9.42 6.9.43 6.9.44 6.9.45 6.9.46 6.9.47 6.9.48 6.9.49 6.9.5 6.9.51 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.0-alpha.6 7.0.0-alpha.7 7.0.0-beta.1 7.0.0-rc1 7.0.0-rc2 7.0.0-rc3 7.0.1 7.0.10 7.0.11 7.0.2 7.0.3 7.0.4 7.0.5 7.0.6 7.0.7 7.0.8 7.0.9 7.1.0 7.1.1 trunk 3.0 4.0 4.0.1 4.1 4.2 4.3 4.4 4.4.1 4.5 4.6 4.6.1 4.6.2 4.7 4.7.1 4.7.2 4.7.5 4.7.6 4.8 4.8.1 4.9 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.5.1 4.9.5.2 5.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1 5.1.1 5.10 5.11 5.2 5.2.1 5.2.5 5.2.6 5.2.7 5.3 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.4 5.4.1 5.4.2 5.4.3 5.4.3.1 5.4.3.2 5.5 5.5.1 5.5.2 5.6 5.6.1 5.6.1.1 5.7 5.7.1 5.7.2 5.7.3 5.8 5.8.1 5.8.2 5.8.3 5.9 5.9.1 5.9.1.1 5.9.2 5.9.2.1 5.9.3 5.9.4 5.9.5 5.9.6 5.9.6.1 5.9.6.2 5.9.6.3 5.9.7 5.9.7.1 5.9.7.2 5.9.7.3 5.9.8 5.9.8.1 5.9.9 5.9.9.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.2.0 6.2.1 6.2.2 6.3.0 6.3.1 6.3.2 6.3.3 6.4.0 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.6.0 6.6.1 6.6.2 6.6.3 6.6.4 6.7.0 6.7.1 6.7.2 6.7.3 6.7.4 6.7.5 6.7.6 6.7.7 6.7.8 6.7.9 6.8.0 6.8.1 6.8.2 6.8.3
advanced-access-manager / application / Service / SecurityAudit.php
advanced-access-manager / application / Service Last commit date
Shortcode 5 months ago AccessDeniedRedirect.php 1 year ago AdminToolbar.php 1 year ago ApiRoute.php 1 year ago BackendMenu.php 1 year ago BaseTrait.php 1 year ago Capability.php 1 year ago Content.php 5 months ago Core.php 10 months ago Hooks.php 1 year ago Identity.php 3 months ago Jwt.php 3 months ago LoginRedirect.php 1 year ago LogoutRedirect.php 3 months ago Metaboxes.php 1 year ago NotFoundRedirect.php 1 year ago Policies.php 1 year ago SecureLogin.php 1 year ago SecurityAudit.php 1 year ago Shortcodes.php 5 months ago Urls.php 1 year ago Welcome.php 1 year ago Widgets.php 1 year ago
SecurityAudit.php
433 lines
1 <?php
2
3 /**
4 * ======================================================================
5 * LICENSE: This file is subject to the terms and conditions defined in *
6 * file 'license.txt', which is part of this source code package. *
7 * ======================================================================
8 */
9
10 /**
11 * Security Audit service
12 *
13 * @package AAM
14 * @version 7.0.0
15 */
16 class AAM_Service_SecurityAudit
17 {
18 use AAM_Service_BaseTrait;
19
20 /**
21 * Security audit result
22 *
23 * @version 7.0.0
24 */
25 const DB_OPTION = 'aam_security_audit_report';
26
27 /**
28 * Security audit last score
29 *
30 * @version 7.0.0
31 */
32 const DB_SCOPE_OPTION = 'aam_security_audit_score';
33
34 /**
35 * Executive summary for the audit report
36 *
37 * @version 7.0.0
38 */
39 const DB_SUMMARY_OPTION = 'aam_audit_executive_summary';
40
41 /**
42 * Issue weights
43 *
44 * @version 7.0.0
45 */
46 const ISSUE_WEIGHT = [
47 'critical' => 10,
48 'warning' => 5,
49 'notice' => 2
50 ];
51
52 /**
53 * Constructor
54 *
55 * @return void
56 * @access protected
57 *
58 * @version 7.0.4
59 */
60 protected function __construct()
61 {
62 // Keep the support RESTful service enabled at all times because it is used
63 // by issue reporting feature as well
64 AAM_Restful_SecurityAudit::bootstrap();
65
66 add_action('init', function() {
67 $this->initialize_hooks();
68 }, PHP_INT_MAX);
69 }
70
71 /**
72 * Initialize service hooks
73 *
74 * @return void
75 * @access protected
76 *
77 * @version 7.0.4
78 */
79 protected function initialize_hooks()
80 {
81 add_filter('aam_security_scan_enabled_filter', function() {
82 return AAM::api()->config->get(AAM::SERVICES[__CLASS__], true);
83 });
84
85 // Register cron-job
86 if (wp_next_scheduled('aam_security_audit_cron') === false) {
87 wp_schedule_event(time(), 'daily', 'aam_security_audit_cron');
88 }
89
90 add_action('aam_security_audit_cron', function() {
91 $this->_run_audit();
92 });
93
94 add_action('aam_uninstall_action', function() {
95 wp_unschedule_event(
96 wp_next_scheduled('aam_security_audit_cron'),
97 'aam_security_audit_cron'
98 );
99 });
100 }
101
102 /**
103 * Reset last audit results
104 *
105 * @return bool
106 * @access public
107 *
108 * @version 7.0.0
109 */
110 public function reset()
111 {
112 return AAM::api()->db->delete(self::DB_OPTION)
113 && AAM::api()->db->delete(self::DB_SCOPE_OPTION)
114 && AAM::api()->db->delete(self::DB_SUMMARY_OPTION);
115 }
116
117 /**
118 * Read last audit report
119 *
120 * @return array
121 * @access public
122 *
123 * @version 7.0.0
124 */
125 public function read()
126 {
127 return AAM::api()->db->read(self::DB_OPTION, []);
128 }
129
130 /**
131 * Execute security audit check
132 *
133 * @param string $check
134 * @param bool $reset
135 *
136 * @return void
137 * @access public
138 *
139 * @version 7.0.0
140 */
141 public function execute($check, $reset = false)
142 {
143 $checks = $this->get_steps();
144 $report = [];
145
146 if ($reset) {
147 $this->reset();
148 } else {
149 $report = $this->read();
150 }
151
152 if (array_key_exists($check, $report)) {
153 $current_result = $report[$check];
154 } else {
155 $current_result = [];
156 }
157
158 if (array_key_exists($check, $checks)) {
159 $executor = $checks[$check]['executor'];
160
161 // Exclude already captures list of issues
162 $result = call_user_func(
163 $executor . '::run',
164 array_filter($current_result, function($k) {
165 return $k !== 'issues';
166 }, ARRAY_FILTER_USE_KEY)
167 );
168
169 // Merge the array of issues first
170 $issues = [];
171
172 if (isset($current_result['issues'])) {
173 $issues = $current_result['issues'];
174 }
175
176 if (isset($result['issues'])) {
177 $issues = array_merge($issues, $result['issues']);
178 }
179
180 // Storing results in db
181 $report[$check] = array_merge($current_result, $result);
182 $report[$check]['issues'] = $issues;
183
184 AAM::api()->db->write(self::DB_OPTION, $report, false);
185
186 // Recalculate the score
187 $score = 100;
188 $detected = [];
189
190 foreach($report as $check => $results) {
191 if (isset($results['issues'])) {
192 foreach($results['issues'] as $issue) {
193 $detected[$issue['code']] = $issue['type'];
194 }
195 }
196 }
197
198 foreach($detected as $type) {
199 $score -= self::ISSUE_WEIGHT[$type];
200 }
201
202 AAM::api()->db->write(
203 self::DB_SCOPE_OPTION, $score > 0 ? $score : 0
204 );
205 }
206
207 return $report[$check];
208 }
209
210 /**
211 * Get security audit steps (checks)
212 *
213 * @return array
214 * @access public
215 *
216 * @version 7.0.0
217 */
218 public function get_steps()
219 {
220 return apply_filters('aam_security_audit_checks_filter', [
221 AAM_Audit_RoleIntegrityCheck::ID => [
222 'title' => __('Verify WordPress Core Roles Integrity', 'advanced-access-manager'),
223 'step' => AAM_Audit_RoleIntegrityCheck::ID,
224 'category' => 'Roles & Capabilities',
225 'executor' => AAM_Audit_RoleIntegrityCheck::class,
226 'description' => __('Maintaining the integrity of WordPress core roles is essential. Altering or removing default roles can cause conflicts with plugins, lead to failed user registrations, and introduce integrity risks. This check ensures that the core roles remain intact, keeping your website stable and secure.', 'advanced-access-manager'),
227 'article' => 'https://aamportal.com/article/preserving-wordpress-core-roles-avoid-conflicts'
228 ],
229 AAM_Audit_CoreUserRoleOptionIntegrityCheck::ID => [
230 'title' => __('Validate WordPress Roles & Capabilities Core Option Integrity', 'advanced-access-manager'),
231 'step' => AAM_Audit_CoreUserRoleOptionIntegrityCheck::ID,
232 'category' => 'Roles & Capabilities',
233 'executor' => AAM_Audit_CoreUserRoleOptionIntegrityCheck::class,
234 'description' => __('The core "_user_roles" option contains WordPress roles and capabilities. Altering its structure can break functionality, introduce vulnerabilities, and complicate updates. This check ensures that role modifications adhere to WordPress built-in APIs and do not compromise the underlying structure.', 'advanced-access-manager'),
235 'article' => 'https://aamportal.com/article/wordpress-user-role-security-and-integrity-warning'
236 ],
237 AAM_Audit_RoleCapabilityNamingConventionCheck::ID => array(
238 'title' => __('Verify Roles & Capabilities Naming Standards', 'advanced-access-manager'),
239 'step' => AAM_Audit_RoleCapabilityNamingConventionCheck::ID,
240 'category' => 'Roles & Capabilities',
241 'executor' => AAM_Audit_RoleCapabilityNamingConventionCheck::class,
242 'description' => __('Adhering to WordPress naming conventions for roles and capabilities is vital for maintaining consistency, reducing errors, and ensuring compatibility across plugins and themes. This check promotes best practices in naming to help improve security and collaboration.', 'advanced-access-manager'),
243 'article' => 'https://aamportal.com/article/wordpress-role-capability-naming-conventions'
244 ),
245 AAM_Audit_RoleTransparencyCheck::ID => array(
246 'title' => __('Verify Roles Transparency', 'advanced-access-manager'),
247 'step' => AAM_Audit_RoleTransparencyCheck::ID,
248 'category' => 'Roles & Capabilities',
249 'executor' => AAM_Audit_RoleTransparencyCheck::class,
250 'description' => __('Hidden roles can obscure access controls and create security concerns by making it difficult to audit permissions. This check flags hidden roles, helping you ensure full transparency and control over user access.', 'advanced-access-manager'),
251 'article' => 'https://aamportal.com/article/hidden-wordpress-roles-website-access-management'
252 ),
253 AAM_Audit_EmptyUnusedRoleCheck::ID => array(
254 'title' => __('Identify Empty or Unused Roles', 'advanced-access-manager'),
255 'step' => AAM_Audit_EmptyUnusedRoleCheck::ID,
256 'category' => 'Roles & Capabilities',
257 'executor' => AAM_Audit_EmptyUnusedRoleCheck::class,
258 'description' => __('Empty roles, which lack any assigned capabilities, or unused custom roles can pose some concerns if misused by plugins or themes. This check identifies such roles, enabling administrators to audit and remove them to avoid confusion.', 'advanced-access-manager'),
259 'article' => 'https://aamportal.com/article/risks-registered-empty-roles-wordpress'
260 ),
261 AAM_Audit_HighPrivilegeRoleCheck::ID => array(
262 'title' => __('Detect High-Privilege Roles', 'advanced-access-manager'),
263 'step' => AAM_Audit_HighPrivilegeRoleCheck::ID,
264 'category' => 'Access Strategy',
265 'executor' => AAM_Audit_HighPrivilegeRoleCheck::class,
266 'description' => __('Roles with high-level privileges carry significant risks. Users with access to core settings, file uploads, or theme/plugin management can introduce vulnerabilities or disrupt site functionality. This check flags high-privilege roles that may need review.', 'advanced-access-manager'),
267 'article' => 'https://aamportal.com/article/misuse-high-privilege-capabilities-wordpress'
268 ),
269 AAM_Audit_HighPrivilegeOrElevatedUserCheck::ID => array(
270 'title' => __('Identify High-Privilege Users & Elevated Access', 'advanced-access-manager'),
271 'step' => AAM_Audit_HighPrivilegeOrElevatedUserCheck::ID,
272 'category' => 'Access Strategy',
273 'executor' => AAM_Audit_HighPrivilegeOrElevatedUserCheck::class,
274 'description' => __('Assigning high-privilege capabilities directly to users or expanding the number of users in high-privilege roles increases the potential attack surface. This check identifies users with elevated access to ensure that roles and capabilities are properly managed, minimizing security risks.', 'advanced-access-manager'),
275 'article' => 'https://aamportal.com/article/security-risks-elevated-user-access-high-privilege-wordpress'
276 ),
277 AAM_Audit_HighPrivilegeContentModeratorCheck::ID => array(
278 'title' => __('Identify High-Privilege Content Moderator Roles', 'advanced-access-manager'),
279 'step' => AAM_Audit_HighPrivilegeContentModeratorCheck::ID,
280 'category' => 'Access Strategy',
281 'executor' => AAM_Audit_HighPrivilegeContentModeratorCheck::class,
282 'description' => __('Assigning high-privilege content moderation capabilities in WordPress, poses significant security risks if granted to untrusted roles. These capabilities allow users to manipulate or delete live content, inject malware, and harm SEO performance, potentially leading to data loss and compromised site integrity. By carefully managing user roles and permissions, you can protect your website from potential cyber threats while ensuring content integrity.', 'advanced-access-manager'),
283 'article' => 'https://aamportal.com/article/wordpress-security-risks-high-privilege-roles-content-moderation'
284 ),
285 AAM_Audit_HighPrivilegeUserCountCheck::ID => array(
286 'title' => __('Identify Elevated Number of High-Privilege Users', 'advanced-access-manager'),
287 'step' => AAM_Audit_HighPrivilegeUserCountCheck::ID,
288 'category' => 'Access Strategy',
289 'executor' => AAM_Audit_HighPrivilegeUserCountCheck::class,
290 'description' => __('Having too many Administrator or high-privilege content moderation accounts on a WordPress site can seriously compromise security, as such account increases the risk of unauthorized access. Administrator accounts, with unrestricted control over the site, pose a significant threat if compromised, enabling attackers to install malware, alter site content, or hijack accounts. Even Editor accounts, though less powerful, allow users to modify and publish all posts, insert HTML and JavaScript, and upload files, which could lead to vulnerabilities like Cross-Site Scripting (XSS) or malware injection if an account is breached.', 'advanced-access-manager'),
291 'article' => 'https://aamportal.com/article/wordpress-security-risks-too-many-admin-editor-accounts'
292 ),
293 AAM_Audit_ElevatedCoreRoleCheck::ID => array(
294 'title' => __('Flag Elevated Privileges for Core Roles', 'advanced-access-manager'),
295 'step' => AAM_Audit_ElevatedCoreRoleCheck::ID,
296 'category' => 'Access Strategy',
297 'executor' => AAM_Audit_ElevatedCoreRoleCheck::class,
298 'description' => __('Modifying core WordPress roles like Editor or Subscriber by granting extra capabilities can lead to security vulnerabilities. This check ensures that core roles remain as intended and recommends creating custom roles for extended functionality.', 'advanced-access-manager'),
299 'article' => 'https://aamportal.com/article/dangers-modifying-default-wordpress-core-roles'
300 ),
301 AAM_Audit_RestfulAutoDiscoverEndpointCheck::ID => array(
302 'title' => __('Audit RESTful API Discovery Endpoint', 'advanced-access-manager'),
303 'step' => AAM_Audit_RestfulAutoDiscoverEndpointCheck::ID,
304 'category' => 'General Security Consideration',
305 'executor' => AAM_Audit_RestfulAutoDiscoverEndpointCheck::class,
306 'description' => __('The "/wp-json/" endpoint, part of WordPress RESTful API, can expose sensitive information to unauthorized users. This check audits the API endpoint to ensure that access is properly restricted to minimize the risk of exploitation.', 'advanced-access-manager'),
307 'article' => 'https://aamportal.com/article/protect-wordpress-restful-api-auto-discover-endpoint'
308 ),
309 AAM_Audit_XmlRpcEndpointCheck::ID => array(
310 'title' => __('Audit XML-RPC Endpoint Access', 'advanced-access-manager'),
311 'step' => AAM_Audit_XmlRpcEndpointCheck::ID,
312 'category' => 'General Security Consideration',
313 'executor' => AAM_Audit_XmlRpcEndpointCheck::class,
314 'description' => __('The outdated XML-RPC endpoint can be a target for brute-force attacks and other exploits. This check assesses whether the endpoint is still enabled and recommends disabling it to strengthen site security.', 'advanced-access-manager'),
315 'article' => 'https://aamportal.com/article/disable-wordpress-xml-rpc-endpoint-security'
316 ),
317 AAM_Audit_EditableFileSystemCheck::ID => array(
318 'title' => __('Check Editable File System Permissions', 'advanced-access-manager'),
319 'step' => AAM_Audit_EditableFileSystemCheck::ID,
320 'category' => 'General Security Consideration',
321 'executor' => AAM_Audit_EditableFileSystemCheck::class,
322 'description' => __('Writable WordPress file systems can lead to malware injection or backdoor installation. This check ensures that critical directories, like "/wp-content/plugins" and "/wp-content/themes", have read-only permissions to prevent unauthorized modifications.', 'advanced-access-manager'),
323 'article' => 'https://aamportal.com/article/risks-no-read-only-wordpress-file-system'
324 )
325 ]);
326 }
327
328 /**
329 * Check if report exists
330 *
331 * @return boolean
332 * @access public
333 *
334 * @version 7.0.0
335 */
336 public function has_report()
337 {
338 $report = AAM::api()->db->read(self::DB_SCOPE_OPTION);
339
340 return !empty($report);
341 }
342
343 /**
344 * Check if there is an executive summary
345 *
346 * @return boolean
347 * @access public
348 *
349 * @version 7.0.0
350 */
351 public function has_summary()
352 {
353 $summary = $this->get_summary();
354
355 return !empty($summary);
356 }
357
358 /**
359 * Get executive summary
360 *
361 * @return array|null
362 * @access public
363 *
364 * @version 7.0.0
365 */
366 public function get_summary()
367 {
368 return AAM::api()->db->read(self::DB_SUMMARY_OPTION);
369 }
370
371 /**
372 * Read the latest score
373 *
374 * @return int|null
375 * @access public
376 *
377 * @version 7.0.0
378 */
379 public function get_score()
380 {
381 return AAM::api()->db->read(self::DB_SCOPE_OPTION);
382 }
383
384 /**
385 * Get score grade
386 *
387 * @return string
388 * @access public
389 *
390 * @version 7.0.0
391 */
392 public function get_score_grade()
393 {
394 $score = $this->get_score();
395 $result = __('Excellent', 'advanced-access-manager');
396
397 if (empty($score)) {
398 $result = '';
399 } elseif ($score < 75) {
400 $result = __('Poor', 'advanced-access-manager');
401 } elseif ($score <= 90) {
402 $result = __('Moderate', 'advanced-access-manager');
403 }
404
405 return $result;
406 }
407
408 /**
409 * This is a cron job that runs audit on a background
410 *
411 * @return void
412 * @access private
413 *
414 * @version 7.0.0
415 */
416 private function _run_audit()
417 {
418 $first = true;
419 $steps = array_keys($this->get_steps());
420
421 do {
422 $result = $this->execute($steps[0], $first === true);
423
424 // No need to reset results anymore
425 $first = false;
426
427 if ($result['is_completed']) {
428 array_shift($steps);
429 }
430 } while (!empty($steps));
431 }
432
433 }