HIGH side channel attackbasic auth

Side Channel Attack with Basic Auth

How Side Channel Attack Manifests in Basic Auth

Side channel attacks against Basic Auth exploit timing differences and error message variations to extract sensitive information. When an attacker can observe authentication responses, they can distinguish between "invalid username" and "invalid password" errors, allowing username enumeration. This timing attack works because password verification typically takes longer than username lookup.

// Vulnerable Basic Auth timing attack example
const express = require('express');
const app = express();

app.use(express.basicAuth((username, password) => {
    // Vulnerable: different timing for username vs password failures
    const user = users.find(u => u.username === username);
    if (!user) {
        return false; // Fast response: username doesn't exist
    }
    
    // Slow response: username exists, verifying password
    return user.password === password;
}));

The fundamental issue is that Basic Auth implementations often provide different response characteristics based on authentication failure type. An attacker can measure response times to determine if a username exists in the system. This becomes more dangerous when combined with error messages that explicitly state whether the username or password was incorrect.

// Even more vulnerable: explicit error messages
app.post('/login', (req, res) => {
    const { username, password } = req.body;
    
    // Different error messages reveal account existence
    if (!users[username]) {
        return res.status(401).json({ 
            error: 'Invalid username' 
        });
    }
    
    if (users[username].password !== password) {
        return res.status(401).json({ 
            error: 'Invalid password' 
        });
    }
    
    res.json({ token: generateToken(username) });
});

Another Basic Auth-specific side channel occurs through HTTP status code variations. Some implementations return 403 Forbidden for invalid credentials while others return 401 Unauthorized. Attackers can use these variations to map out authentication logic and identify protected resources.

// Status code variation side channel
app.get('/protected', (req, res) => {
    const authHeader = req.headers.authorization;
    
    if (!authHeader) {
        return res.status(401).json({ 
            error: 'Authentication required' 
        });
    }
    
    const [scheme, credentials] = authHeader.split(' ');
    if (scheme.toLowerCase() !== 'basic') {
        return res.status(400).json({ 
            error: 'Invalid authentication scheme' 
        });
    }
    
    // Different failure paths create timing variations
    const decoded = Buffer.from(credentials, 'base64').toString();
    const [username, password] = decoded.split(':');
    
    if (!username || !password) {
        return res.status(401).json({ 
            error: 'Missing credentials' 
        });
    }
    
    // Vulnerable timing: database lookup vs. immediate failure
    const user = users.find(u => u.username === username);
    if (!user) {
        return res.status(401).json({ 
            error: 'Authentication failed' 
        });
    }
    
    if (user.password !== password) {
        return res.status(401).json({ 
            error: 'Authentication failed' 
        });
    }
    
    res.json({ message: 'Access granted' });
});

Basic Auth-Specific Detection

Detecting side channel vulnerabilities in Basic Auth requires systematic testing of timing variations and error message consistency. The key indicators are:

  • Response time differences between valid/invalid usernames
  • Distinct error messages for username vs password failures
  • HTTP status code variations based on authentication state
  • Different processing paths for authentication failures

middleBrick's Basic Auth scanning specifically tests these timing characteristics by making multiple authentication attempts with varying inputs and measuring response characteristics. The scanner uses statistical analysis to identify significant timing differences that indicate information leakage.

// middleBrick CLI scan for Basic Auth side channels
npm install -g middlebrick

# Scan a Basic Auth endpoint
middlebrick scan https://api.example.com/protected \
  --auth-type basic \
  --username admin \
  --password wrong \
  --verbose

# Output includes timing analysis and error message consistency
# Example findings:
# - Timing variance: 45ms (high risk)
# - Error message leakage: username enumeration possible
# - Status code inconsistency: 401 vs 403 variations detected

For manual testing, you can use timing analysis tools to measure authentication response variations. The following script demonstrates how to test for username enumeration vulnerabilities:

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

async function testTiming(username) {
    const start = Date.now();
    
    try {
        const response = await axios.get('https://api.example.com/protected', {
            auth: { username, password: 'invalid' },
            validateStatus: () => true
        });
        
        return {
            status: response.status,
            time: Date.now() - start,
            message: response.data?.error || 'No message'
        };
    } catch (error) {
        return {
            status: error.response?.status || 500,
            time: Date.now() - start,
            message: error.response?.data?.error || 'No message'
        };
    }
}

// Test multiple usernames to detect timing patterns
async function analyzeBasicAuth() {
    const testUsers = ['admin', 'user', 'test', 'nonexistent'];
    const results = await Promise.all(testUsers.map(testTiming));
    
    console.log('Timing Analysis:');
    results.forEach((result, i) => {
        console.log(`${testUsers[i]}: ${result.time}ms, Status: ${result.status}, Message: ${result.message}`);
    });
    
    // Analyze for timing differences
    const times = results.map(r => r.time);
    const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
    const variance = Math.max(...times) - Math.min(...times);
    
    console.log(`Average: ${avgTime}ms, Variance: ${variance}ms`);
    
    if (variance > 30) {
        console.log('⚠️  High timing variance detected - potential username enumeration');
    }
}

analyzeBasicAuth();

middleBrick's automated scanning goes further by testing across multiple dimensions simultaneously and providing severity scores based on the combination of timing, error message, and status code analysis.

Basic Auth-Specific Remediation

Fixing side channel vulnerabilities in Basic Auth requires eliminating timing differences and standardizing error responses. The most effective approach is to use constant-time comparison functions and uniform error handling.

// Secure Basic Auth implementation
const express = require('express');
const crypto = require('crypto');
const app = express();

// Constant-time string comparison to prevent timing attacks
function constantTimeCompare(val1, val2) {
    if (val1.length !== val2.length) return false;
    
    let result = 0;
    for (let i = 0; i < val1.length; i++) {
        result |= val1.charCodeAt(i) ^ val2.charCodeAt(i);
    }
    return result === 0;
}

// Uniform error response to prevent information leakage
function createErrorResponse() {
    return {
        error: 'Authentication failed',
        timestamp: Date.now()
    };
}

app.use(express.basicAuth((username, password) => {
    // Simulate constant-time database lookup
    const start = process.hrtime.bigint();
    
    // Always perform full lookup to prevent timing differences
    const user = users.find(u => u.username === username) || { password: null };
    
    // Always perform password comparison, even if user doesn't exist
    const validPassword = constantTimeCompare(password, user.password || '');
    
    // Add constant delay to normalize response times
    const elapsed = Number(process.hrtime.bigint() - start) / 1000000;
    const delay = Math.max(0, 50 - elapsed); // 50ms minimum response time
    
    // Return true only if both username exists and password matches
    return validPassword && user.password !== null;
}));

// Middleware to add uniform response headers
app.use((req, res, next) => {
    res.setHeader('X-Authentication-Method', 'Basic');
    res.setHeader('X-Content-Type-Options', 'nosniff');
    next();
});

// Standardized error handler
app.use((err, req, res, next) => {
    if (res.headersSent) {
        return next(err);
    }
    
    console.error('Authentication error:', err.message);
    
    res.status(401).json(createErrorResponse());
});

For applications using Basic Auth with JSON Web Tokens (JWT), the remediation approach includes additional safeguards:

const jwt = require('jsonwebtoken');

function authenticateJWT(req, res, next) {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Basic ')) {
        return res.status(401).json(createErrorResponse());
    }
    
    try {
        const base64Credentials = authHeader.substring(6);
        const credentials = Buffer.from(base64Credentials, 'base64').toString();
        const [username, password] = credentials.split(':');
        
        // Constant-time authentication
        const user = users.find(u => u.username === username) || { password: null };
        const valid = constantTimeCompare(password, user.password || '');
        
        if (!valid || !user.password) {
            // Simulate processing time for valid usernames
            const start = process.hrtime.bigint();
            while (process.hrtime.bigint() - start < 50000000n) {}
            
            return res.status(401).json(createErrorResponse());
        }
        
        // Create JWT token
        const token = jwt.sign(
            { userId: user.id, username: user.username },
            process.env.JWT_SECRET,
            { expiresIn: '1h' }
        );
        
        res.json({ token, user: { id: user.id, username: user.username } });
    } catch (error) {
        res.status(401).json(createErrorResponse());
    }
}

The key remediation principles are: use constant-time comparisons, standardize error messages, add uniform delays to normalize response times, and avoid conditional logic that creates different execution paths based on authentication state.

Frequently Asked Questions

How can I test if my Basic Auth implementation is vulnerable to side channel attacks?
Use timing analysis tools to measure response variations when testing with valid vs invalid usernames. Look for response time differences greater than 30ms and inconsistent error messages. middleBrick's automated scanning can identify these vulnerabilities by testing multiple authentication scenarios and analyzing timing patterns statistically.
Does adding rate limiting prevent Basic Auth side channel attacks?
No, rate limiting prevents brute force attacks but doesn't address side channel vulnerabilities. An attacker can still enumerate valid usernames by making single requests and observing timing differences or error messages. You need constant-time comparisons and uniform error handling to fix side channel issues.