← back to Handbag Auth Nextjs

auction-viewer/security-test.js

327 lines

#!/usr/bin/env node

/**
 * Security Test Suite for LUXVAULT Auction API
 * Tests various security measures and vulnerabilities
 */

const axios = require('axios');
const colors = require('colors');

const BASE_URL = 'http://45.61.58.125:7500';

class SecurityTester {
  constructor() {
    this.results = [];
    this.passed = 0;
    this.failed = 0;
  }

  async test(name, fn) {
    process.stdout.write(`Testing ${name}... `);
    try {
      await fn();
      console.log('✅ PASSED'.green);
      this.passed++;
      this.results.push({ test: name, status: 'PASSED' });
    } catch (error) {
      console.log(`❌ FAILED: ${error.message}`.red);
      this.failed++;
      this.results.push({ test: name, status: 'FAILED', error: error.message });
    }
  }

  async runTests() {
    console.log('='.repeat(60));
    console.log('LUXVAULT Security Test Suite'.bold);
    console.log('='.repeat(60));
    console.log();

    // Test 1: SQL Injection Protection
    await this.test('SQL Injection Protection', async () => {
      const maliciousInputs = [
        "'; DROP TABLE auctions; --",
        "1 OR 1=1",
        "' UNION SELECT * FROM users --",
        "'; DELETE FROM auctions WHERE '1'='1"
      ];

      for (const input of maliciousInputs) {
        const response = await axios.get(`${BASE_URL}/api/auctions`, {
          params: { search: input },
          validateStatus: () => true
        });

        if (response.status === 500 || !response.data.success) {
          // Good - the injection was blocked
          continue;
        }

        // Check if data is still intact
        if (response.data.data && response.data.data.length > 0) {
          // Data exists, injection didn't work
          continue;
        }

        throw new Error(`SQL injection not blocked for: ${input}`);
      }
    });

    // Test 2: XSS Protection
    await this.test('XSS Protection Headers', async () => {
      const response = await axios.get(`${BASE_URL}/api/health`, {
        validateStatus: () => true
      });

      const headers = response.headers;

      if (!headers['x-content-type-options']) {
        throw new Error('Missing X-Content-Type-Options header');
      }
      if (!headers['x-frame-options']) {
        throw new Error('Missing X-Frame-Options header');
      }
      if (!headers['x-xss-protection']) {
        throw new Error('Missing X-XSS-Protection header');
      }
      if (!headers['content-security-policy']) {
        throw new Error('Missing Content-Security-Policy header');
      }
    });

    // Test 3: Rate Limiting
    await this.test('Rate Limiting', async () => {
      const requests = [];
      const limit = 110; // Should exceed the 100 request limit

      for (let i = 0; i < limit; i++) {
        requests.push(
          axios.get(`${BASE_URL}/api/health`, {
            validateStatus: () => true
          })
        );
      }

      const responses = await Promise.all(requests);
      const rateLimited = responses.some(r => r.status === 429);

      if (!rateLimited) {
        throw new Error('Rate limiting not working');
      }
    });

    // Test 4: Input Validation
    await this.test('Input Validation', async () => {
      const invalidInputs = [
        { limit: -1 },
        { limit: 10000 },
        { offset: -100 },
        { sort: 'invalid_column' }
      ];

      for (const input of invalidInputs) {
        const response = await axios.get(`${BASE_URL}/api/auctions`, {
          params: input,
          validateStatus: () => true
        });

        if (response.status === 200 && response.data.success) {
          // Check if the input was properly validated
          if (input.limit && (response.data.data.length > 1000 || response.data.data.length < 0)) {
            throw new Error(`Invalid limit not validated: ${input.limit}`);
          }
        }
      }
    });

    // Test 5: Path Traversal Protection
    await this.test('Path Traversal Protection', async () => {
      const maliciousPaths = [
        '../../../etc/passwd',
        '..\\..\\..\\windows\\system32\\config\\sam',
        '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'
      ];

      for (const path of maliciousPaths) {
        try {
          const response = await axios.get(`${BASE_URL}/api/logs`, {
            params: { file: path },
            validateStatus: () => true,
            headers: {
              'Authorization': 'Bearer test_token'
            }
          });

          if (response.status === 200) {
            // Check if sensitive data was exposed
            const data = JSON.stringify(response.data);
            if (data.includes('root:') || data.includes('Administrator')) {
              throw new Error(`Path traversal vulnerability with: ${path}`);
            }
          }
        } catch (error) {
          // Network error is fine, means the request was blocked
        }
      }
    });

    // Test 6: CORS Configuration
    await this.test('CORS Configuration', async () => {
      const response = await axios.options(`${BASE_URL}/api/health`, {
        headers: {
          'Origin': 'http://evil.com',
          'Access-Control-Request-Method': 'GET'
        },
        validateStatus: () => true
      });

      const allowOrigin = response.headers['access-control-allow-origin'];

      if (allowOrigin === '*' || allowOrigin === 'http://evil.com') {
        throw new Error('CORS is too permissive');
      }
    });

    // Test 7: Information Disclosure
    await this.test('Information Disclosure', async () => {
      // Try to trigger an error
      const response = await axios.get(`${BASE_URL}/api/auctions`, {
        params: { limit: 'not_a_number' },
        validateStatus: () => true
      });

      if (response.status === 500 || response.status === 400) {
        const errorMessage = JSON.stringify(response.data);

        // Check for sensitive information
        if (errorMessage.includes('stack') ||
            errorMessage.includes('node_modules') ||
            errorMessage.includes('/root/') ||
            errorMessage.includes('SQL') ||
            errorMessage.includes('Database')) {
          throw new Error('Sensitive information disclosed in error message');
        }
      }
    });

    // Test 8: Authentication on Sensitive Endpoints
    await this.test('Authentication Required for Logs', async () => {
      const response = await axios.get(`${BASE_URL}/api/logs`, {
        validateStatus: () => true
      });

      if (response.status === 200) {
        throw new Error('Logs endpoint accessible without authentication');
      }

      if (response.status !== 401) {
        throw new Error(`Expected 401, got ${response.status}`);
      }
    });

    // Test 9: Request Size Limits
    await this.test('Request Size Limits', async () => {
      const largePayload = 'x'.repeat(2 * 1024 * 1024); // 2MB

      try {
        const response = await axios.post(`${BASE_URL}/api/test`, {
          data: largePayload
        }, {
          validateStatus: () => true,
          maxContentLength: Infinity,
          maxBodyLength: Infinity
        });

        if (response.status === 200) {
          throw new Error('Large payload accepted');
        }
      } catch (error) {
        // Error is expected for oversized payload
      }
    });

    // Test 10: HTTP Security Headers
    await this.test('HSTS Header Present', async () => {
      const response = await axios.get(`${BASE_URL}/api/health`, {
        validateStatus: () => true
      });

      const hsts = response.headers['strict-transport-security'];

      if (!hsts) {
        throw new Error('HSTS header missing');
      }

      if (!hsts.includes('max-age=31536000')) {
        throw new Error('HSTS max-age too short');
      }
    });

    // Print summary
    console.log();
    console.log('='.repeat(60));
    console.log('Test Results Summary'.bold);
    console.log('='.repeat(60));
    console.log(`✅ Passed: ${this.passed}`.green);
    console.log(`❌ Failed: ${this.failed}`.red);
    console.log(`📊 Total: ${this.passed + this.failed}`);
    console.log();

    // Security score
    const score = Math.round((this.passed / (this.passed + this.failed)) * 100);
    let rating;
    let color;

    if (score >= 90) {
      rating = 'EXCELLENT';
      color = 'green';
    } else if (score >= 70) {
      rating = 'GOOD';
      color = 'yellow';
    } else if (score >= 50) {
      rating = 'FAIR';
      color = 'orange';
    } else {
      rating = 'POOR';
      color = 'red';
    }

    console.log(`Security Score: ${score}% - ${rating}`[color].bold);
    console.log();

    // Detailed results
    if (this.failed > 0) {
      console.log('Failed Tests:'.red.bold);
      this.results
        .filter(r => r.status === 'FAILED')
        .forEach(r => {
          console.log(`  ❌ ${r.test}: ${r.error}`);
        });
    }

    return score;
  }
}

// Run tests
async function main() {
  const tester = new SecurityTester();

  try {
    const score = await tester.runTests();
    process.exit(score >= 70 ? 0 : 1);
  } catch (error) {
    console.error('Test suite error:', error);
    process.exit(1);
  }
}

// Check if colors module is installed
try {
  require('colors');
} catch (e) {
  console.log('Installing required module: colors');
  require('child_process').execSync('npm install colors', { stdio: 'inherit' });
}

main();