Free — no signup required

Injection Attacks via APIs: SQL and Beyond

2 min read

APIs as Injection Vectors

Injection attacks are not unique to APIs, but APIs dramatically expand the attack surface. Every API parameter — URL path segments, query strings, JSON body fields, HTTP headers — is a potential injection point. An attacker doesn't need a browser form; they can craft raw HTTP requests with any content they choose.

SQL Injection remains the most impactful injection class. It occurs when user-supplied input is concatenated directly into a SQL query, allowing the attacker to alter the query's logic.

// VULNERABLE: String concatenation
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;

// Attacker sends: email = "' OR '1'='1"
// Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1'
// This returns ALL users in the database

The Fix: Parameterized Queries

The only reliable defense against SQL injection is parameterized queries (also called prepared statements). The query structure is sent to the database separately from the data, so the database engine never interprets user input as SQL syntax.

// SECURE: Parameterized query
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [req.body.email]);
// The database treats req.body.email as pure data, never as SQL

Input Validation as Defense in Depth

Parameterized queries solve SQL injection, but APIs face other injection types: NoSQL injection (MongoDB operators in JSON), command injection (if input reaches shell commands), and SSRF (Server-Side Request Forgery) (if input controls a URL the server fetches).

The defense-in-depth approach combines:
1. Allowlist validation: Reject any input that doesn't match an expected format (e.g., email must match RFC 5322 pattern, age must be an integer between 0 and 150)
2. Parameterized queries / ORMs: For database interactions
3. Output encoding: Encode data before rendering to prevent XSS
4. Schema validation libraries: Tools like zod, joi, or ajv validate the entire request body shape before it reaches business logic

// Using zod for request validation
import { z } from 'zod';

const TransferSchema = z.object({
  to_account: z.number().int().positive(),
  amount: z.number().positive().max(10000),
});

app.post('/api/transfer', (req, res) => {
  const result = TransferSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ error: result.error });
  }
  // Proceed with validated data only
});

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free