flawopen.com/sql-injection/Javascript
SQL Injection di JavaScript (mysql2, pg, Prisma, Knex)
Template literal atau + di dalam pool.query() membuat parameter request bisa menulis ulang SQL Anda dan mengembalikan setiap baris (CWE-89). Pemanggilan yang aman di mysql2, node-postgres, Prisma, dan Knex, alasan ${} aman di satu metode Prisma tetapi berbahaya di metode sebelahnya, dan cara mengurutkan tanpa orderByRaw.[1], [2], [3], [4], [5], [6]
Bayangkan pusat sortir paket: rute dicetak dan dikunci sebelum paket tiba. Nama pada label masuk ke kolom yang disediakan; nama itu tidak bisa menambahkan pemberhentian ke rute. Parameter SQL bekerja serupa: mysql2 execute(sql, [userId]) dan pg query(sql, [status]) mengikat nilai, sedangkan $queryRaw bertag milik Prisma mengikat setiap posisi ${}. Jika SQL dirangkai lebih dulu dengan template literal atau +, atau memakai $queryRawUnsafe, teks pelanggan tertulis langsung pada rute. Knex orderByRaw memiliki risiko yang sama untuk instruksi pengurutan; petakan kunci permintaan ke kolom tetap lalu panggil orderBy.[1], [2], [3], [4], [5], [6]
Konsep Kunci & Istilah
Template literals in query()- String backtick yang berisi
${userId}dievaluasi JavaScript sebelum driver dipanggil, sehingga driver menerima SQL jadi dengan nilai yang sudah ada di dalamnya. Sintaksnya mirip placeholder, tetapi tidak mem-bind apa pun.[7], [1], [6] mysql2 execute() vs query()execute(sql, values)memakai prepared statement di sisi server.query(sql, values)meng-escape nilai di sisi klien lalu mengisinya ke penanda?. Keduanya aman selama nilai ada di array; tidak ada yang membantu jika string SQL disusun dengan+atau template literal. mysql2 menonaktifkanmultipleStatementssecara bawaan.[3], [6], [9]pg query(text, values)- node-postgres mengirim
valuesterpisah daritext, jadi$1diisi oleh PostgreSQL. Tanpa nilai, pg memakai protokol kueri sederhana, yang juga menerima beberapa perintah yang dipisahkan titik koma dalam satu panggilan.[2], [6] Prisma $queryRaw vs $queryRawUnsafe$queryRawadalah tagged template: Prisma mengubah setiap${}menjadi parameter bind.$queryRawUnsafe(string)menerima string jadi, sehingga${}yang sama di dalam template literal biasa ditempelkan ke SQL. Namanya hanya beda satu kata, tetapi risikonya berkebalikan.[4], [6]
Pemetaan kerangka kerja
- Referensi CWE untuk kelemahan yang dijelaskan dalam panduan ini: CWE-89
- OWASP Top 10:2025 — A05:2025 Injection
Alur Serangan Langkah demi Langkah
Kode Sumber: Rentan vs Aman
// users.js: setiap kueri di bawah menyusun teks SQL dari data request
// mysql2: template literal dievaluasi sebelum driver dipanggil
export async function getUser(pool, userId) {
const [rows] = await pool.query(`SELECT id, email FROM users WHERE id = ${userId}`);
return rows[0];
}
// pg: penggabungan string, dan tidak ada array nilai untuk di-bind
export async function findOrders(pg, status) {
const { rows } = await pg.query("SELECT id FROM orders WHERE status = '" + status + "'");
return rows;
}
// Prisma: varian Unsafe menerima string jadi
export function findByEmail(prisma, email) {
return prisma.$queryRawUnsafe(`SELECT id FROM "User" WHERE email = '${email}'`);
}
// Knex: orderByRaw menyisipkan nilai request sebagai SQL mentah
export function listUsers(knex, sortColumn) {
return knex('users').select('id', 'email').orderByRaw(sortColumn);
}
// users.js: nilai masuk ke array nilai, kolom berasal dari allowlist
const SORTABLE = new Map([['id', 'id'], ['email', 'email'], ['created', 'created_at']]);
// mysql2: prepared statement di server, nilai di-bind ke ?
export async function getUser(pool, userId) {
const [rows] = await pool.execute('SELECT id, email FROM users WHERE id = ?', [userId]);
return rows[0];
}
// pg: PostgreSQL mengisi $1 dari array nilai
export async function findOrders(pg, status) {
const { rows } = await pg.query('SELECT id FROM orders WHERE status = $1', [status]);
return rows;
}
// Prisma: tagged template, setiap ${} menjadi parameter bind
export function findByEmail(prisma, email) {
return prisma.$queryRaw`SELECT id FROM "User" WHERE email = ${email}`;
}
// Knex: orderBy mengutip kolom dari allowlist sebagai identifier
export function listUsers(knex, sortKey) {
const column = SORTABLE.get(sortKey) ?? 'id';
return knex('users').select('id', 'email').orderBy(column);
}
Daftar Periksa Penguatan Sistem Rekayasa
- ✓Larang
$queryRawUnsafe,$executeRawUnsafe,whereRaw/orderByRawdengan interpolasi, serta template literal atau+di argumen pertamaquery()/execute()dengan aturan ESLintno-restricted-syntaxatau Semgrep di CI.[1], [3], [2], [4], [5], [6] - ✓Kirim setiap nilai di array nilai milik driver (
?untuk mysql2,$1untuk pg) atau sebagai${}di$queryRawbertag milik Prisma.[3], [2], [4], [6] - ✓Validasi input dengan skema (zod, Joi) di route: ID diubah menjadi angka, kunci yang tidak dikenal ditolak, dan array tidak diterima di tempat yang mengharapkan string.[8], [6]
- ✓Petakan kunci pengurutan lewat allowlist
Mapdan berikan hasilnya keorderBy()Knex atauorderByPrisma; jangan pernah memberikanreq.queryke ORDER BY mentah.[5], [4], [6] - ✓Biarkan
multipleStatementsmysql2 nonaktif dan terhubung dengan pengguna database tanpa hak DDL.[3], [9], [6]
Sumber
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command
- node-postgres — Parameterized query
- mysql2 — Using prepared statements
- Prisma — Raw queries and SQL injection
- Knex.js — Raw parameter binding
- OWASP SQL Injection Prevention Cheat Sheet
- MDN — Template literals (JavaScript)
- Express 4 API — req.query
- MySQL C API — Multiple statement execution support