SyntaxStudy
Sign Up
Web Security ORM Usage, Stored Procedures, and Defence Layers
Web Security Beginner 1 min read

ORM Usage, Stored Procedures, and Defence Layers

ORMs such as Laravel's Eloquent, Django ORM, and Hibernate generate parameterised queries by default, providing strong SQL injection protection without requiring developers to write raw SQL. The risk re-emerges when developers use raw query escape hatches — Eloquent's `DB::statement()`, Django's `raw()`, or Hibernate's `createNativeQuery()` — with unsanitised user input. Code review policies should flag any raw SQL usage for security review. Stored procedures can reduce injection risk by encapsulating SQL logic inside the database, where the query structure is fixed at definition time. However, stored procedures that build dynamic SQL internally using EXEC or sp_executesql with concatenated strings reintroduce the same vulnerability. The stored procedure must also use parameterisation internally to be safe. A layered defence against SQL injection combines parameterised queries as the primary control with additional layers: a Web Application Firewall (WAF) that detects common SQLi patterns, least-privilege database accounts that limit damage if injection succeeds, database activity monitoring that alerts on anomalous queries, and regular DAST scans using tools like sqlmap or OWASP ZAP as part of the CI/CD pipeline.
Example
<?php
// Laravel Eloquent — safe by default, dangerous raw escapes

use App\Models\User;
use Illuminate\Support\Facades\DB;

// SAFE: Eloquent query builder — parameterised automatically
$users = User::where('username', $username)
             ->where('active', true)
             ->get();

// SAFE: query builder with whereIn
$ids   = [1, 2, 3];
$users = User::whereIn('id', $ids)->get();

// DANGEROUS: raw DB statement with interpolation — avoid
// DB::statement("DELETE FROM users WHERE id = $id");  // BAD

// SAFE: raw statement with bindings
DB::statement('UPDATE users SET login_at = NOW() WHERE id = ?', [$userId]);

// SAFE: raw select with bindings
$results = DB::select(
    'SELECT * FROM audit_log WHERE user_id = ? AND action = ?',
    [$userId, $action]
);

// Stored procedure (MySQL) — parameterised internally
/*
CREATE PROCEDURE GetUserByUsername(IN p_username VARCHAR(255))
BEGIN
    -- Uses parameter, not concatenation — safe
    SELECT id, email FROM users WHERE username = p_username AND active = 1;
END;
*/

// Call stored procedure via PDO
$stmt = $pdo->prepare('CALL GetUserByUsername(:username)');
$stmt->execute(['username' => $username]);
$user = $stmt->fetch();