Skip to content

🛡️ HIGH: Missing Security Headers - Vulnerability to Common Web Attacks #3587

@ryota-murakami

Description

@ryota-murakami

🛡️ HIGH: Missing Security Headers - Vulnerability to Common Web Attacks

Problem Description

The NSX application is missing essential security headers that protect against common web vulnerabilities. Currently, the Express server only disables the x-powered-by header but lacks comprehensive security middleware.

Current state:

// server/index.ts:34
app.disable('x-powered-by')
// Missing: Security headers, CSRF protection, etc.

Why This Is Dangerous

Missing security headers expose the application to:

  1. XSS Attacks: No Content Security Policy (CSP) to prevent script injection
  2. Clickjacking: No X-Frame-Options to prevent iframe embedding
  3. MIME Sniffing: No X-Content-Type-Options to prevent MIME confusion
  4. Information Disclosure: Server version and technology stack exposed
  5. HTTPS Issues: No Strict-Transport-Security for HTTPS enforcement
  6. Referrer Leakage: No Referrer-Policy to control referrer information

Current Impact

  • Severity: High
  • Attack Vectors: XSS, Clickjacking, MIME confusion, Information disclosure
  • Risk Level: High - Easy to exploit

Comprehensive Solution

1. Install Security Dependencies

pnpm add helmet
pnpm add -D @types/helmet

2. Implement Helmet.js Security Headers

// server/index.ts
import helmet from 'helmet'

// Basic security headers
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      imgSrc: ["'self'", "data:", "https:"],
      scriptSrc: ["'self'"],
      connectSrc: ["'self'"],
      frameSrc: ["'none'"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
    },
  },
  crossOriginEmbedderPolicy: false, // Disable if causing issues with external resources
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}))

// Additional security headers
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }))
app.use(helmet.noSniff())
app.use(helmet.frameguard({ action: 'deny' }))

3. Environment-Specific Configuration

// server/lib/security.ts
import helmet from 'helmet'
import type { Request, Response, NextFunction } from 'express'

export const securityMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const isProduction = process.env.NODE_ENV === 'production'
  const isDevelopment = process.env.NODE_ENV === 'development'
  
  if (isProduction) {
    // Strict security for production
    helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
          fontSrc: ["'self'", "https://fonts.gstatic.com"],
          imgSrc: ["'self'", "data:", "https:"],
          scriptSrc: ["'self'"],
          connectSrc: ["'self'"],
          frameSrc: ["'none'"],
          objectSrc: ["'none'"],
          upgradeInsecureRequests: [],
        },
      },
      hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
      }
    })(req, res, next)
  } else if (isDevelopment) {
    // More permissive for development
    helmet({
      contentSecurityPolicy: false, // Disable CSP in development for easier debugging
      hsts: false // Disable HSTS in development
    })(req, res, next)
  } else {
    next()
  }
}

// Usage in server/index.ts
app.use(securityMiddleware)

4. Custom Security Headers

// server/lib/security.ts
export const customSecurityHeaders = (req: Request, res: Response, next: NextFunction) => {
  // Remove server information
  res.removeHeader('X-Powered-By')
  
  // Add custom security headers
  res.setHeader('X-Content-Type-Options', 'nosniff')
  res.setHeader('X-Frame-Options', 'DENY')
  res.setHeader('X-XSS-Protection', '1; mode=block')
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin')
  
  // Add security headers for API responses
  if (req.path.startsWith('/api')) {
    res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate')
    res.setHeader('Pragma', 'no-cache')
    res.setHeader('Expires', '0')
  }
  
  next()
}

5. Content Security Policy (CSP) Configuration

// server/lib/csp.ts
export const cspConfig = {
  development: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      imgSrc: ["'self'", "data:", "https:", "blob:"],
      scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"], // Allow eval in development
      connectSrc: ["'self'", "ws:", "wss:"],
      frameSrc: ["'none'"],
      objectSrc: ["'none'"],
    },
  },
  production: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      imgSrc: ["'self'", "data:", "https:"],
      scriptSrc: ["'self'"],
      connectSrc: ["'self'"],
      frameSrc: ["'none'"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
      blockAllMixedContent: true,
    },
  }
}

Testing the Implementation

1. Security Header Tests

// tests/security-headers.test.ts
import request from 'supertest'
import app from '../server/index'

describe('Security Headers', () => {
  it('should include essential security headers', async () => {
    const response = await request(app)
      .get('/')
      .expect(200)
    
    expect(response.headers['x-content-type-options']).toBe('nosniff')
    expect(response.headers['x-frame-options']).toBe('DENY')
    expect(response.headers['x-xss-protection']).toBe('1; mode=block')
    expect(response.headers['referrer-policy']).toBe('strict-origin-when-cross-origin')
  })
  
  it('should include Content Security Policy header', async () => {
    const response = await request(app)
      .get('/')
      .expect(200)
    
    expect(response.headers['content-security-policy']).toBeDefined()
  })
  
  it('should include HSTS header in production', async () => {
    process.env.NODE_ENV = 'production'
    
    const response = await request(app)
      .get('/')
      .expect(200)
    
    expect(response.headers['strict-transport-security']).toBeDefined()
  })
})

2. Security Testing Tools

# Install security testing tools
pnpm add -D @types/supertest supertest

# Test with security scanning tools
npx helmet-test http://localhost:3000
npx security-headers https://nsx.malloc.tokyo

3. Manual Testing Checklist

  • Test CSP with browser dev tools
  • Verify X-Frame-Options prevents iframe embedding
  • Check HSTS header in production
  • Test XSS protection with malicious scripts
  • Verify MIME sniffing protection

Monitoring and Alerting

1. Security Header Monitoring

// server/lib/security-monitor.ts
export const securityHeaderMonitor = (req: Request, res: Response, next: NextFunction) => {
  const requiredHeaders = [
    'x-content-type-options',
    'x-frame-options',
    'x-xss-protection',
    'content-security-policy'
  ]
  
  const missingHeaders = requiredHeaders.filter(header => !res.getHeader(header))
  
  if (missingHeaders.length > 0) {
    Logger.warn(`Missing security headers: ${missingHeaders.join(', ')}`)
  }
  
  next()
}

2. CSP Violation Reporting

// Add CSP reporting endpoint
app.post('/api/csp-violation', (req: Request, res: Response) => {
  Logger.warn('CSP Violation:', req.body)
  res.status(204).send()
})

Migration Strategy

Phase 1: Basic Security Headers

  1. Install helmet.js
  2. Add basic security headers
  3. Test in development

Phase 2: CSP Implementation

  1. Configure Content Security Policy
  2. Test with real application
  3. Fix any CSP violations

Phase 3: Production Hardening

  1. Enable HSTS
  2. Configure strict CSP
  3. Monitor for violations

Phase 4: Monitoring

  1. Add security header monitoring
  2. Set up CSP violation reporting
  3. Create alerting for security issues

Common Issues and Solutions

1. CSP Violations with External Resources

// Add specific domains to CSP
imgSrc: ["'self'", "data:", "https:", "https://example.com"]

2. Inline Scripts and Styles

// Use nonces for inline scripts
scriptSrc: ["'self'", "'nonce-{random-nonce}'"]

3. Development vs Production Differences

// Use environment-specific configurations
const cspConfig = process.env.NODE_ENV === 'production' 
  ? productionCSP 
  : developmentCSP

References


Priority: 🟠 High
Estimated Effort: 4-6 hours
Risk if not fixed: High - Multiple attack vectors

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions