/** * Security Test Framework * Comprehensive security testing utilities for WordPress HVAC plugin */ const { getBrowserManager } = require('../../framework/browser/BrowserManager'); const { getAuthManager } = require('../../framework/authentication/AuthManager'); const BasePage = require('../../framework/base/BasePage'); class SecurityTestFramework { constructor(config = {}) { this.config = { baseUrl: 'https://upskill-staging.measurequick.com', timeout: 30000, screenshotOnFailure: true, ...config }; this.browserManager = getBrowserManager(); this.authManager = getAuthManager(); this.testResults = []; } /** * Initialize security test framework * @param {Object} config - Configuration object * @returns {Promise} */ async initialize(config = {}) { this.config = { ...this.config, ...config }; await this.browserManager.initialize(this.config); await this.authManager.initialize(this.config); } /** * Test authentication redirects for protected URLs * @param {string[]} protectedUrls - Array of URLs that should require authentication * @returns {Promise} */ async testAuthenticationRedirects(protectedUrls) { console.log('🔒 Testing authentication redirects...'); const results = { passed: 0, failed: 0, tests: [] }; for (const url of protectedUrls) { const testResult = await this.testSingleUrlAuthRedirect(url); results.tests.push(testResult); if (testResult.passed) { results.passed++; } else { results.failed++; } } console.log(` ✅ Passed: ${results.passed}, ❌ Failed: ${results.failed}`); return results; } /** * Test single URL for proper authentication redirect * @param {string} url - URL to test * @returns {Promise} * @private */ async testSingleUrlAuthRedirect(url) { const fullUrl = url.startsWith('http') ? url : this.config.baseUrl + url; const testResult = { url: url, fullUrl: fullUrl, passed: false, statusCode: null, redirectUrl: null, error: null }; try { console.log(` 🔍 Testing: ${url}`); const page = this.browserManager.getCurrentPage(); // Navigate to protected URL without authentication const response = await page.goto(fullUrl, { waitUntil: 'networkidle', timeout: this.config.timeout }); testResult.statusCode = response.status(); testResult.redirectUrl = page.url(); // Check if properly redirected to login const isRedirected = this.isRedirectedToLogin(testResult.redirectUrl, fullUrl); // Check for proper status codes const hasValidStatus = response.status() === 302 || response.status() === 200 && isRedirected; testResult.passed = isRedirected && hasValidStatus; if (testResult.passed) { console.log(` ✅ Properly redirected to login`); } else { console.log(` ❌ Not properly protected - Status: ${response.status()}, URL: ${page.url()}`); if (this.config.screenshotOnFailure) { await this.takeSecurityScreenshot(`auth-fail-${url.replace(/[^a-zA-Z0-9]/g, '-')}.png`); } } } catch (error) { testResult.error = error.message; console.log(` ❌ Error testing ${url}: ${error.message}`); } return testResult; } /** * Check if URL is redirected to login page * @param {string} currentUrl - Current page URL * @param {string} originalUrl - Original requested URL * @returns {boolean} * @private */ isRedirectedToLogin(currentUrl, originalUrl) { // If still on the same URL, not redirected if (currentUrl === originalUrl) { return false; } // Check for login page indicators const loginIndicators = [ 'community-login', 'wp-login.php', 'login', 'auth', 'signin' ]; return loginIndicators.some(indicator => currentUrl.toLowerCase().includes(indicator.toLowerCase()) ); } /** * Test role-based access control * @param {Object} testScenarios - Test scenarios with roles and expected access * @returns {Promise} */ async testRoleBasedAccess(testScenarios) { console.log('👥 Testing role-based access control...'); const results = { passed: 0, failed: 0, tests: [] }; for (const scenario of testScenarios) { const testResult = await this.testRoleAccess(scenario); results.tests.push(testResult); if (testResult.passed) { results.passed++; } else { results.failed++; } } console.log(` ✅ Passed: ${results.passed}, ❌ Failed: ${results.failed}`); return results; } /** * Test access control for specific role * @param {Object} scenario - Test scenario * @returns {Promise} * @private */ async testRoleAccess(scenario) { const { user, url, expectedAccess } = scenario; const testResult = { user: user.username, role: user.role, url: url, expectedAccess: expectedAccess, actualAccess: false, passed: false, error: null }; try { console.log(` 🔍 Testing ${user.role} access to ${url}`); // Login as specified user await this.authManager.login(user); const page = this.browserManager.getCurrentPage(); const fullUrl = url.startsWith('http') ? url : this.config.baseUrl + url; // Navigate to URL const response = await page.goto(fullUrl, { waitUntil: 'networkidle', timeout: this.config.timeout }); // Determine if user has access testResult.actualAccess = this.determineAccess(response, page.url(), fullUrl); testResult.passed = testResult.actualAccess === expectedAccess; if (testResult.passed) { console.log(` ✅ Access control working correctly`); } else { console.log(` ❌ Access control failed - Expected: ${expectedAccess}, Actual: ${testResult.actualAccess}`); if (this.config.screenshotOnFailure) { await this.takeSecurityScreenshot(`role-fail-${user.role}-${url.replace(/[^a-zA-Z0-9]/g, '-')}.png`); } } // Logout await this.authManager.logout(); } catch (error) { testResult.error = error.message; console.log(` ❌ Error testing role access: ${error.message}`); } return testResult; } /** * Determine if user has access based on response and URL * @param {Response} response - Page response * @param {string} currentUrl - Current page URL * @param {string} requestedUrl - Originally requested URL * @returns {boolean} * @private */ determineAccess(response, currentUrl, requestedUrl) { // If redirected to login, no access if (this.isRedirectedToLogin(currentUrl, requestedUrl)) { return false; } // If status is 403 (Forbidden), no access if (response.status() === 403) { return false; } // If status is 404 (Not Found), might indicate access control if (response.status() === 404) { return false; } // If status is 200 and on the same URL, has access if (response.status() === 200 && currentUrl === requestedUrl) { return true; } // Default to no access for safety return false; } /** * Test for common WordPress security vulnerabilities * @returns {Promise} */ async testCommonVulnerabilities() { console.log('🛡️ Testing common security vulnerabilities...'); const results = { passed: 0, failed: 0, tests: [] }; // Test directory browsing const directoryTest = await this.testDirectoryBrowsing(); results.tests.push(directoryTest); // Test file inclusion vulnerabilities const fileInclusionTest = await this.testFileInclusion(); results.tests.push(fileInclusionTest); // Test WordPress version disclosure const versionDisclosureTest = await this.testVersionDisclosure(); results.tests.push(versionDisclosureTest); // Test admin access without authentication const adminAccessTest = await this.testUnauthenticatedAdminAccess(); results.tests.push(adminAccessTest); // Count results results.tests.forEach(test => { if (test.passed) results.passed++; else results.failed++; }); console.log(` ✅ Passed: ${results.passed}, ❌ Failed: ${results.failed}`); return results; } /** * Test directory browsing vulnerabilities * @returns {Promise} * @private */ async testDirectoryBrowsing() { const testResult = { testName: 'Directory Browsing', passed: true, vulnerabilities: [] }; const directoriesToTest = [ '/wp-content/', '/wp-content/plugins/', '/wp-content/themes/', '/wp-content/uploads/', '/wp-includes/', '/wp-admin/' ]; for (const dir of directoriesToTest) { try { const page = this.browserManager.getCurrentPage(); const response = await page.goto(this.config.baseUrl + dir, { timeout: 10000 }); if (response.status() === 200) { const content = await page.content(); if (content.includes('Index of') || content.includes('Directory listing')) { testResult.passed = false; testResult.vulnerabilities.push(`Directory browsing enabled for ${dir}`); } } } catch (error) { // Directory might be properly protected - continue } } return testResult; } /** * Test file inclusion vulnerabilities * @returns {Promise} * @private */ async testFileInclusion() { const testResult = { testName: 'File Inclusion', passed: true, vulnerabilities: [] }; const payloads = [ '../../../../etc/passwd', '..\\..\\..\\..\\windows\\system32\\drivers\\etc\\hosts', 'php://filter/read=convert.base64-encode/resource=wp-config.php' ]; for (const payload of payloads) { try { const page = this.browserManager.getCurrentPage(); const testUrl = `${this.config.baseUrl}/?file=${encodeURIComponent(payload)}`; const response = await page.goto(testUrl, { timeout: 10000 }); if (response.status() === 200) { const content = await page.content(); if (content.includes('root:') || content.includes('# localhost')) { testResult.passed = false; testResult.vulnerabilities.push(`File inclusion vulnerability with payload: ${payload}`); } } } catch (error) { // Error might indicate protection - continue } } return testResult; } /** * Test WordPress version disclosure * @returns {Promise} * @private */ async testVersionDisclosure() { const testResult = { testName: 'Version Disclosure', passed: true, disclosures: [] }; try { const page = this.browserManager.getCurrentPage(); await page.goto(this.config.baseUrl, { timeout: 10000 }); const content = await page.content(); // Check for WordPress version in meta tags const versionRegex = /} * @private */ async testUnauthenticatedAdminAccess() { const testResult = { testName: 'Unauthenticated Admin Access', passed: true, vulnerabilities: [] }; try { const page = this.browserManager.getCurrentPage(); const response = await page.goto(`${this.config.baseUrl}/wp-admin/`, { timeout: 10000 }); // Should be redirected to login if (!this.isRedirectedToLogin(page.url(), `${this.config.baseUrl}/wp-admin/`)) { testResult.passed = false; testResult.vulnerabilities.push('Admin area accessible without authentication'); } } catch (error) { console.warn(`Admin access test error: ${error.message}`); } return testResult; } /** * Test CSRF protection * @param {Object} formData - Form data to test * @returns {Promise} */ async testCSRFProtection(formData) { console.log('🔐 Testing CSRF protection...'); const testResult = { testName: 'CSRF Protection', passed: false, error: null }; try { // Login first await this.authManager.loginAsMasterTrainer(); const page = this.browserManager.getCurrentPage(); await page.goto(formData.formUrl); // Try to submit form without CSRF token const response = await page.evaluate(async (data) => { const form = new FormData(); Object.keys(data.fields).forEach(key => { form.append(key, data.fields[key]); }); return await fetch(data.submitUrl, { method: 'POST', body: form }); }, formData); // If request is rejected (403/400), CSRF protection is working testResult.passed = response.status === 403 || response.status === 400; } catch (error) { testResult.error = error.message; } return testResult; } /** * Take security-related screenshot * @param {string} filename - Screenshot filename * @returns {Promise} * @private */ async takeSecurityScreenshot(filename) { try { return await this.browserManager.takeScreenshot(`security-${filename}`); } catch (error) { console.warn(`Failed to take security screenshot: ${error.message}`); return null; } } /** * Generate comprehensive security test report * @param {Object} allTestResults - All test results * @returns {Object} */ generateSecurityReport(allTestResults) { const report = { timestamp: new Date().toISOString(), baseUrl: this.config.baseUrl, totalTests: 0, totalPassed: 0, totalFailed: 0, securityScore: 0, categories: {}, recommendations: [], criticalIssues: [] }; // Process results by category Object.keys(allTestResults).forEach(category => { const categoryResults = allTestResults[category]; report.categories[category] = { tests: categoryResults.tests || [categoryResults], passed: categoryResults.passed || (categoryResults.passed ? 1 : 0), failed: categoryResults.failed || (categoryResults.passed ? 0 : 1) }; report.totalTests += report.categories[category].tests.length; report.totalPassed += report.categories[category].passed; report.totalFailed += report.categories[category].failed; }); // Calculate security score (0-100) report.securityScore = report.totalTests > 0 ? Math.round((report.totalPassed / report.totalTests) * 100) : 0; // Generate recommendations based on failures this.generateSecurityRecommendations(report, allTestResults); return report; } /** * Generate security recommendations * @param {Object} report - Security report * @param {Object} testResults - Test results * @private */ generateSecurityRecommendations(report, testResults) { if (report.securityScore < 100) { report.recommendations.push('Review and strengthen security configurations'); } if (testResults.authRedirects && testResults.authRedirects.failed > 0) { report.criticalIssues.push('Some protected URLs are not properly secured'); report.recommendations.push('Implement proper authentication checks for all protected pages'); } if (testResults.roleAccess && testResults.roleAccess.failed > 0) { report.criticalIssues.push('Role-based access control has issues'); report.recommendations.push('Review and fix user role permissions'); } if (testResults.vulnerabilities) { testResults.vulnerabilities.tests.forEach(test => { if (!test.passed && test.vulnerabilities) { test.vulnerabilities.forEach(vuln => { report.criticalIssues.push(vuln); }); } }); } } /** * Clean up security test framework * @returns {Promise} */ async cleanup() { await this.authManager.cleanup(); await this.browserManager.cleanup(); } } module.exports = SecurityTestFramework;