API-Specific SQL Injection Vectors

API-Specific SQL Injection Vectors

REST and GraphQL APIs present unique SQL injection challenges. Unlike traditional web applications where form inputs are primary vectors, APIs accept complex JSON payloads, query parameters, and headers that all potentially interact with databases:

// Vulnerable Node.js Express API endpoint
app.post('/api/users/search', async (req, res) => {
    // Dangerous: Trusting JSON payload directly
    const { filters, sortBy, limit } = req.body;
    
    // Building dynamic query from untrusted input
    let query = 'SELECT * FROM users WHERE 1=1';
    
    // Vulnerable filter construction
    if (filters) {
        Object.keys(filters).forEach(key => {
            query += ` AND ${key} = '${filters[key]}'`;  // SQL injection vector!
        });
    }
    
    // Vulnerable sort construction
    if (sortBy) {
        query += ` ORDER BY ${sortBy}`;  // Column name injection!
    }
    
    const results = await db.query(query);
    res.json(results);
});

// Secure API implementation
app.post('/api/users/search', async (req, res) => {
    const { filters, sortBy, limit } = req.body;
    
    // Whitelist allowed fields
    const allowedFields = ['username', 'email', 'created_at', 'status'];
    const allowedSortFields = ['username', 'created_at'];
    
    // Build parameterized query safely
    const conditions = [];
    const params = [];
    
    if (filters) {
        Object.keys(filters).forEach(key => {
            if (allowedFields.includes(key)) {
                conditions.push(`${key} = $${params.length + 1}`);
                params.push(filters[key]);
            }
        });
    }
    
    let query = 'SELECT * FROM users';
    if (conditions.length > 0) {
        query += ' WHERE ' + conditions.join(' AND ');
    }
    
    // Safe sort handling
    if (sortBy && allowedSortFields.includes(sortBy)) {
        query += ` ORDER BY ${sortBy}`;
    }
    
    // Safe limit handling
    const safeLimit = Math.min(parseInt(limit) || 100, 1000);
    query += ` LIMIT ${safeLimit}`;
    
    try {
        const results = await db.query(query, params);
        res.json({ data: results.rows });
    } catch (error) {
        logger.error('Database query failed', { error, userId: req.user.id });
        res.status(500).json({ error: 'Internal server error' });
    }
});