Skip to main content
โšก Calmops

Security Testing in JavaScript

Security Testing in JavaScript

Security testing is essential for identifying vulnerabilities. This article covers security testing techniques, tools, and best practices.

Introduction

Security testing provides:

  • Vulnerability detection
  • Risk identification
  • Compliance verification
  • Quality assurance
  • Proactive defense

Understanding security testing helps you:

  • Identify vulnerabilities
  • Test security controls
  • Automate security checks
  • Verify compliance
  • Build secure applications

Security Testing Types

Static Application Security Testing (SAST)

// โœ… Good: Static security analysis
class StaticSecurityAnalyzer {
  static async analyzeCode(filePath) {
    const fs = require('fs').promises;
    const content = await fs.readFile(filePath, 'utf-8');

    const issues = [];

    // Check for hardcoded secrets
    if (/password\s*=\s*['"][^'"]+['"]/i.test(content)) {
      issues.push({
        type: 'hardcoded_secret',
        severity: 'critical',
        message: 'Hardcoded password detected'
      });
    }

    // Check for eval usage
    if (/eval\s*\(/.test(content)) {
      issues.push({
        type: 'eval_usage',
        severity: 'high',
        message: 'eval() usage detected'
      });
    }

    // Check for innerHTML with variables
    if (/\.innerHTML\s*=\s*\w+/.test(content)) {
      issues.push({
        type: 'innerHTML_injection',
        severity: 'high',
        message: 'innerHTML assignment with variable detected'
      });
    }

    // Check for SQL injection patterns
    if (/query\s*\(\s*`.*\$\{/.test(content)) {
      issues.push({
        type: 'sql_injection',
        severity: 'critical',
        message: 'Potential SQL injection detected'
      });
    }

    return issues;
  }

  static async analyzeProject(projectPath) {
    const fs = require('fs').promises;
    const path = require('path');

    const allIssues = [];

    async function scanDirectory(dir) {
      const files = await fs.readdir(dir);

      for (const file of files) {
        const filePath = path.join(dir, file);
        const stat = await fs.stat(filePath);

        if (stat.isDirectory()) {
          if (!file.startsWith('.') && file !== 'node_modules') {
            await scanDirectory(filePath);
          }
        } else if (file.endsWith('.js')) {
          const issues = await this.analyzeCode(filePath);
          allIssues.push(...issues.map(i => ({ file: filePath, ...i })));
        }
      }
    }

    await scanDirectory(projectPath);
    return allIssues;
  }
}

// Usage
const issues = await StaticSecurityAnalyzer.analyzeProject('./src');
console.log(issues);

Dynamic Application Security Testing (DAST)

// โœ… Good: Dynamic security testing
class DynamicSecurityTester {
  static async testXSSVulnerability(url, payload) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        body: JSON.stringify({ input: payload })
      });

      const html = await response.text();

      // Check if payload is reflected without encoding
      if (html.includes(payload) && !html.includes(this.encodeHTML(payload))) {
        return {
          vulnerable: true,
          type: 'XSS',
          payload
        };
      }

      return { vulnerable: false };
    } catch (error) {
      console.error('Error testing XSS:', error);
      return null;
    }
  }

  static async testSQLInjection(url, payload) {
    try {
      const response = await fetch(`${url}?id=${encodeURIComponent(payload)}`);

      // Check for SQL error messages
      const html = await response.text();

      if (/SQL|syntax|database/i.test(html)) {
        return {
          vulnerable: true,
          type: 'SQL Injection',
          payload
        };
      }

      return { vulnerable: false };
    } catch (error) {
      console.error('Error testing SQL injection:', error);
      return null;
    }
  }

  static async testCSRFProtection(url) {
    try {
      const response = await fetch(url);
      const html = await response.text();

      // Check for CSRF token
      const hasToken = /csrf|token/i.test(html);

      return {
        protected: hasToken,
        type: 'CSRF'
      };
    } catch (error) {
      console.error('Error testing CSRF:', error);
      return null;
    }
  }

  static encodeHTML(text) {
    const map = {
      '&': '&',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    };
    return text.replace(/[&<>"']/g, char => map[char]);
  }
}

// Usage
const xssTest = await DynamicSecurityTester.testXSSVulnerability(
  'http://localhost:3000/search',
  '<script>alert("XSS")</script>'
);
console.log(xssTest);

Security Test Automation

Automated Security Tests

// โœ… Good: Automated security tests
describe('Security Tests', () => {
  describe('Authentication', () => {
    test('should reject requests without token', async () => {
      const response = await fetch('/api/protected');
      expect(response.status).toBe(401);
    });

    test('should reject invalid tokens', async () => {
      const response = await fetch('/api/protected', {
        headers: { 'Authorization': 'Bearer invalid_token' }
      });
      expect(response.status).toBe(403);
    });

    test('should accept valid tokens', async () => {
      const token = generateValidToken();
      const response = await fetch('/api/protected', {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      expect(response.status).toBe(200);
    });
  });

  describe('Input Validation', () => {
    test('should reject XSS payloads', async () => {
      const response = await fetch('/api/comments', {
        method: 'POST',
        body: JSON.stringify({
          text: '<script>alert("XSS")</script>'
        })
      });

      const data = await response.json();
      expect(data.text).not.toContain('<script>');
    });

    test('should validate email format', async () => {
      const response = await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify({
          email: 'invalid-email'
        })
      });

      expect(response.status).toBe(400);
    });

    test('should enforce password requirements', async () => {
      const response = await fetch('/api/auth/register', {
        method: 'POST',
        body: JSON.stringify({
          password: 'weak'
        })
      });

      expect(response.status).toBe(400);
    });
  });

  describe('Authorization', () => {
    test('should deny access to unauthorized users', async () => {
      const userToken = generateUserToken();
      const response = await fetch('/api/admin/users', {
        headers: { 'Authorization': `Bearer ${userToken}` }
      });

      expect(response.status).toBe(403);
    });

    test('should allow access to authorized users', async () => {
      const adminToken = generateAdminToken();
      const response = await fetch('/api/admin/users', {
        headers: { 'Authorization': `Bearer ${adminToken}` }
      });

      expect(response.status).toBe(200);
    });
  });

  describe('CSRF Protection', () => {
    test('should require CSRF token for state-changing requests', async () => {
      const response = await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify({ name: 'John' })
      });

      expect(response.status).toBe(403);
    });

    test('should accept valid CSRF token', async () => {
      const token = getCsrfToken();
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'X-CSRF-Token': token },
        body: JSON.stringify({ name: 'John' })
      });

      expect(response.status).toBe(201);
    });
  });

  describe('Security Headers', () => {
    test('should set Content-Security-Policy header', async () => {
      const response = await fetch('/');
      expect(response.headers.get('Content-Security-Policy')).toBeDefined();
    });

    test('should set X-Content-Type-Options header', async () => {
      const response = await fetch('/');
      expect(response.headers.get('X-Content-Type-Options')).toBe('nosniff');
    });

    test('should set X-Frame-Options header', async () => {
      const response = await fetch('/');
      expect(response.headers.get('X-Frame-Options')).toBe('DENY');
    });
  });
});

OWASP Testing

OWASP Top 10 Testing

// โœ… Good: Test for OWASP Top 10 vulnerabilities
class OWASPTester {
  static async testAll(baseURL) {
    const results = {
      'A01:2021 - Broken Access Control': await this.testAccessControl(baseURL),
      'A02:2021 - Cryptographic Failures': await this.testCryptography(baseURL),
      'A03:2021 - Injection': await this.testInjection(baseURL),
      'A04:2021 - Insecure Design': await this.testDesign(baseURL),
      'A05:2021 - Security Misconfiguration': await this.testMisconfiguration(baseURL),
      'A06:2021 - Vulnerable Components': await this.testComponents(baseURL),
      'A07:2021 - Authentication Failures': await this.testAuthentication(baseURL),
      'A08:2021 - Data Integrity Failures': await this.testDataIntegrity(baseURL),
      'A09:2021 - Logging Failures': await this.testLogging(baseURL),
      'A10:2021 - SSRF': await this.testSSRF(baseURL)
    };

    return results;
  }

  static async testAccessControl(baseURL) {
    // Test for broken access control
    const userToken = generateUserToken();
    const adminToken = generateAdminToken();

    const response = await fetch(`${baseURL}/api/admin/users`, {
      headers: { 'Authorization': `Bearer ${userToken}` }
    });

    return {
      vulnerable: response.status === 200,
      description: 'User can access admin endpoints'
    };
  }

  static async testCryptography(baseURL) {
    // Test for weak cryptography
    const response = await fetch(baseURL);

    const hasHTTPS = response.url.startsWith('https://');
    const hasSecureHeaders = response.headers.get('Strict-Transport-Security');

    return {
      vulnerable: !hasHTTPS || !hasSecureHeaders,
      description: 'Weak or missing cryptographic controls'
    };
  }

  static async testInjection(baseURL) {
    // Test for injection vulnerabilities
    const payload = "' OR '1'='1";
    const response = await fetch(`${baseURL}/api/search?q=${encodeURIComponent(payload)}`);

    const html = await response.text();

    return {
      vulnerable: /SQL|syntax|error/i.test(html),
      description: 'Potential injection vulnerability'
    };
  }

  static async testDesign(baseURL) {
    // Test for insecure design
    return {
      vulnerable: false,
      description: 'Manual review required'
    };
  }

  static async testMisconfiguration(baseURL) {
    // Test for security misconfiguration
    const response = await fetch(baseURL);

    const missingHeaders = [
      'Content-Security-Policy',
      'X-Content-Type-Options',
      'X-Frame-Options'
    ].filter(header => !response.headers.get(header));

    return {
      vulnerable: missingHeaders.length > 0,
      description: `Missing security headers: ${missingHeaders.join(', ')}`
    };
  }

  static async testComponents(baseURL) {
    // Test for vulnerable components
    // Use npm audit or Snyk
    return {
      vulnerable: false,
      description: 'Run npm audit'
    };
  }

  static async testAuthentication(baseURL) {
    // Test for authentication failures
    const response = await fetch(`${baseURL}/api/protected`);

    return {
      vulnerable: response.status === 200,
      description: 'Unauthenticated access to protected resource'
    };
  }

  static async testDataIntegrity(baseURL) {
    // Test for data integrity failures
    return {
      vulnerable: false,
      description: 'Manual review required'
    };
  }

  static async testLogging(baseURL) {
    // Test for logging failures
    return {
      vulnerable: false,
      description: 'Manual review required'
    };
  }

  static async testSSRF(baseURL) {
    // Test for SSRF vulnerabilities
    return {
      vulnerable: false,
      description: 'Manual review required'
    };
  }
}

// Usage
const results = await OWASPTester.testAll('http://localhost:3000');
console.log(results);

Penetration Testing

Penetration Test Scenarios

// โœ… Good: Penetration testing scenarios
class PenetrationTester {
  static async runFullTest(baseURL) {
    const scenarios = [
      this.testAuthenticationBypass(baseURL),
      this.testAuthorizationBypass(baseURL),
      this.testDataExfiltration(baseURL),
      this.testPrivilegeEscalation(baseURL),
      this.testSessionHijacking(baseURL)
    ];

    const results = await Promise.all(scenarios);

    return {
      timestamp: new Date(),
      baseURL,
      results,
      summary: this.generateSummary(results)
    };
  }

  static async testAuthenticationBypass(baseURL) {
    // Try common bypass techniques
    const attempts = [
      { username: 'admin', password: 'admin' },
      { username: 'admin', password: '' },
      { username: '', password: '' },
      { username: 'admin', password: "' OR '1'='1" }
    ];

    for (const attempt of attempts) {
      const response = await fetch(`${baseURL}/api/auth/login`, {
        method: 'POST',
        body: JSON.stringify(attempt)
      });

      if (response.status === 200) {
        return {
          vulnerable: true,
          type: 'Authentication Bypass',
          payload: attempt
        };
      }
    }

    return { vulnerable: false, type: 'Authentication Bypass' };
  }

  static async testAuthorizationBypass(baseURL) {
    // Try to access resources with different roles
    const userToken = generateUserToken();

    const response = await fetch(`${baseURL}/api/admin/settings`, {
      headers: { 'Authorization': `Bearer ${userToken}` }
    });

    return {
      vulnerable: response.status === 200,
      type: 'Authorization Bypass'
    };
  }

  static async testDataExfiltration(baseURL) {
    // Try to extract sensitive data
    const response = await fetch(`${baseURL}/api/users`);
    const data = await response.json();

    const hasSensitiveData = data.some(user =>
      user.password || user.ssn || user.creditCard
    );

    return {
      vulnerable: hasSensitiveData,
      type: 'Data Exfiltration'
    };
  }

  static async testPrivilegeEscalation(baseURL) {
    // Try to escalate privileges
    const userToken = generateUserToken();

    const response = await fetch(`${baseURL}/api/users/promote`, {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${userToken}` },
      body: JSON.stringify({ userId: 1, role: 'admin' })
    });

    return {
      vulnerable: response.status === 200,
      type: 'Privilege Escalation'
    };
  }

  static async testSessionHijacking(baseURL) {
    // Try to use expired or invalid sessions
    const expiredToken = generateExpiredToken();

    const response = await fetch(`${baseURL}/api/protected`, {
      headers: { 'Authorization': `Bearer ${expiredToken}` }
    });

    return {
      vulnerable: response.status === 200,
      type: 'Session Hijacking'
    };
  }

  static generateSummary(results) {
    const vulnerabilities = results.filter(r => r.vulnerable);

    return {
      totalTests: results.length,
      vulnerabilitiesFound: vulnerabilities.length,
      severity: vulnerabilities.length > 0 ? 'HIGH' : 'LOW'
    };
  }
}

// Usage
const penTest = await PenetrationTester.runFullTest('http://localhost:3000');
console.log(penTest);

Security Test Reporting

Generate Security Report

// โœ… Good: Generate security test report
class SecurityReportGenerator {
  static generateReport(testResults) {
    const report = {
      title: 'Security Test Report',
      timestamp: new Date().toISOString(),
      summary: this.generateSummary(testResults),
      findings: this.categorizeFindings(testResults),
      recommendations: this.generateRecommendations(testResults)
    };

    return report;
  }

  static generateSummary(results) {
    const vulnerabilities = results.filter(r => r.vulnerable);

    return {
      totalTests: results.length,
      passed: results.length - vulnerabilities.length,
      failed: vulnerabilities.length,
      passRate: `${((results.length - vulnerabilities.length) / results.length * 100).toFixed(2)}%`
    };
  }

  static categorizeFindings(results) {
    const categories = {
      critical: [],
      high: [],
      medium: [],
      low: []
    };

    results.forEach(result => {
      if (result.vulnerable) {
        const severity = this.determineSeverity(result);
        categories[severity].push(result);
      }
    });

    return categories;
  }

  static determineSeverity(finding) {
    if (finding.type.includes('Authentication') || finding.type.includes('Authorization')) {
      return 'critical';
    }
    if (finding.type.includes('Injection') || finding.type.includes('XSS')) {
      return 'high';
    }
    if (finding.type.includes('Header') || finding.type.includes('Configuration')) {
      return 'medium';
    }
    return 'low';
  }

  static generateRecommendations(results) {
    const recommendations = [];

    results.forEach(result => {
      if (result.vulnerable) {
        recommendations.push({
          finding: result.type,
          recommendation: this.getRecommendation(result.type)
        });
      }
    });

    return recommendations;
  }

  static getRecommendation(type) {
    const recommendations = {
      'Authentication Bypass': 'Implement strong authentication mechanisms',
      'Authorization Bypass': 'Implement proper access control checks',
      'XSS': 'Sanitize and encode all user input',
      'SQL Injection': 'Use parameterized queries',
      'CSRF': 'Implement CSRF token validation'
    };

    return recommendations[type] || 'Review and fix the vulnerability';
  }

  static exportJSON(report) {
    return JSON.stringify(report, null, 2);
  }

  static exportHTML(report) {
    // Generate HTML report
    return `
      <html>
        <head><title>${report.title}</title></head>
        <body>
          <h1>${report.title}</h1>
          <p>Generated: ${report.timestamp}</p>
          <h2>Summary</h2>
          <p>Pass Rate: ${report.summary.passRate}</p>
          <h2>Findings</h2>
          ${Object.entries(report.findings).map(([severity, findings]) =>
            `<h3>${severity.toUpperCase()}</h3><ul>${findings.map(f =>
              `<li>${f.type}</li>`
            ).join('')}</ul>`
          ).join('')}
        </body>
      </html>
    `;
  }
}

// Usage
const report = SecurityReportGenerator.generateReport(testResults);
console.log(SecurityReportGenerator.exportJSON(report));

Best Practices

  1. Test regularly:

    // โœ… Good
    // Run security tests in CI/CD pipeline
    
    // โŒ Bad
    // Never run security tests
    
  2. Use multiple tools:

    // โœ… Good
    // npm audit + Snyk + SAST + DAST
    
    // โŒ Bad
    // Only one security tool
    
  3. Document findings:

    // โœ… Good
    // Generate detailed reports
    
    // โŒ Bad
    // No documentation
    

Common Mistakes

  1. Not testing in production-like environment:

    // โŒ Bad
    // Test only in development
    
    // โœ… Good
    // Test in staging environment
    
  2. Ignoring low-severity findings:

    // โŒ Bad
    // Only fix critical issues
    
    // โœ… Good
    // Fix all vulnerabilities
    

Summary

Security testing is essential. Key takeaways:

  • Use SAST and DAST
  • Automate security tests
  • Test for OWASP Top 10
  • Conduct penetration testing
  • Generate reports
  • Test regularly
  • Use multiple tools
  • Document findings

Next Steps

Comments