check-sql-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SQL Injection Security Check

SQL注入安全检查

Analyze PHP code for SQL injection vulnerabilities.
分析PHP代码中的SQL注入漏洞。

Detection Patterns

检测模式

1. String Concatenation in Queries

1. 查询中的字符串拼接

php
// CRITICAL: Direct concatenation
$sql = "SELECT * FROM users WHERE id = " . $id;
$sql = "SELECT * FROM users WHERE email = '" . $email . "'";
$sql = "DELETE FROM posts WHERE id = $id";

// CRITICAL: In method
public function findByEmail(string $email): ?User
{
    $sql = "SELECT * FROM users WHERE email = '$email'";
    return $this->query($sql);
}
php
// CRITICAL: Direct concatenation
$sql = "SELECT * FROM users WHERE id = " . $id;
$sql = "SELECT * FROM users WHERE email = '" . $email . "'";
$sql = "DELETE FROM posts WHERE id = $id";

// CRITICAL: In method
public function findByEmail(string $email): ?User
{
    $sql = "SELECT * FROM users WHERE email = '$email'";
    return $this->query($sql);
}

2. Variable Interpolation

2. 变量插值

php
// CRITICAL: Double-quoted strings
$sql = "SELECT * FROM $table WHERE $column = '$value'";
$sql = "SELECT * FROM users WHERE name LIKE '%{$search}%'";

// CRITICAL: Heredoc/Nowdoc
$sql = <<<SQL
    SELECT * FROM users WHERE id = $id
SQL;
php
// CRITICAL: Double-quoted strings
$sql = "SELECT * FROM $table WHERE $column = '$value'";
$sql = "SELECT * FROM users WHERE name LIKE '%{$search}%'";

// CRITICAL: Heredoc/Nowdoc
$sql = <<<SQL
    SELECT * FROM users WHERE id = $id
SQL;

3. Dynamic Table/Column Names

3. 动态表/列名

php
// CRITICAL: User-controlled identifiers
$table = $_GET['table'];
$query = "SELECT * FROM $table";

$column = $request->get('sort');
$query = "SELECT * FROM users ORDER BY $column";

// Even with prepared statements:
$stmt = $pdo->prepare("SELECT * FROM $table WHERE id = ?");
// Table name is still injectable!
php
// CRITICAL: User-controlled identifiers
$table = $_GET['table'];
$query = "SELECT * FROM $table";

$column = $request->get('sort');
$query = "SELECT * FROM users ORDER BY $column";

// Even with prepared statements:
$stmt = $pdo->prepare("SELECT * FROM $table WHERE id = ?");
// Table name is still injectable!

4. ORM Raw Queries

4. ORM原生查询

php
// CRITICAL: Doctrine raw DQL
$dql = "SELECT u FROM User u WHERE u.email = '$email'";
$query = $em->createQuery($dql);

// CRITICAL: Query builder with raw
$qb->where("u.status = $status");
$qb->orderBy($userInput);

// CRITICAL: Native query
$sql = "SELECT * FROM users WHERE name = '$name'";
$em->getConnection()->executeQuery($sql);
php
// CRITICAL: Doctrine raw DQL
$dql = "SELECT u FROM User u WHERE u.email = '$email'";
$query = $em->createQuery($dql);

// CRITICAL: Query builder with raw
$qb->where("u.status = $status");
$qb->orderBy($userInput);

// CRITICAL: Native query
$sql = "SELECT * FROM users WHERE name = '$name'";
$em->getConnection()->executeQuery($sql);

5. Eloquent/Laravel Raw

5. Eloquent/Laravel原生查询

php
// CRITICAL: Raw methods
User::whereRaw("email = '$email'")->get();
DB::select("SELECT * FROM users WHERE id = $id");
DB::raw("COUNT(*) as count WHERE status = '$status'");

// VULNERABLE: Order by
User::orderBy($request->input('sort'))->get();
User::orderByRaw($request->input('order'))->get();
php
// CRITICAL: Raw methods
User::whereRaw("email = '$email'")->get();
DB::select("SELECT * FROM users WHERE id = $id");
DB::raw("COUNT(*) as count WHERE status = '$status'");

// VULNERABLE: Order by
User::orderBy($request->input('sort'))->get();
User::orderByRaw($request->input('order'))->get();

6. LIKE Clause Injection

6. LIKE子句注入

php
// CRITICAL: Unescaped in LIKE
$sql = "SELECT * FROM products WHERE name LIKE '%$search%'";

// VULNERABLE: Wildcards not escaped
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE ?");
$stmt->execute(["%$search%"]);
// User can input: %' OR '1'='1

// CORRECT: Escape wildcards
$search = addcslashes($search, '%_');
$stmt->execute(["%{$search}%"]);
php
// CRITICAL: Unescaped in LIKE
$sql = "SELECT * FROM products WHERE name LIKE '%$search%'";

// VULNERABLE: Wildcards not escaped
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE ?");
$stmt->execute(["%$search%"]);
// User can input: %' OR '1'='1

// CORRECT: Escape wildcards
$search = addcslashes($search, '%_');
$stmt->execute(["%{$search}%"]);

7. IN Clause Vulnerabilities

7. IN子句漏洞

php
// CRITICAL: Building IN unsafely
$ids = implode(',', $_GET['ids']);
$sql = "SELECT * FROM users WHERE id IN ($ids)";

// VULNERABLE: Array could contain non-integers
$ids = array_map('intval', $_GET['ids']);
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $ids) . ")";
// Empty array results in "IN ()" - SQL error

// CORRECT: Use parameter binding
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);
php
// CRITICAL: Building IN unsafely
$ids = implode(',', $_GET['ids']);
$sql = "SELECT * FROM users WHERE id IN ($ids)";

// VULNERABLE: Array could contain non-integers
$ids = array_map('intval', $_GET['ids']);
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $ids) . ")";
// Empty array results in "IN ()" - SQL error

// CORRECT: Use parameter binding
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);

8. Second-Order Injection

8. 二阶注入

php
// CRITICAL: Data from database used unsafely
$user = $this->getUserFromDatabase($id);
$sql = "SELECT * FROM orders WHERE user_name = '" . $user->name . "'";
// If name was originally injected: O'Malley' OR '1'='1
php
// CRITICAL: Data from database used unsafely
$user = $this->getUserFromDatabase($id);
$sql = "SELECT * FROM orders WHERE user_name = '" . $user->name . "'";
// If name was originally injected: O'Malley' OR '1'='1

9. Prepared Statement Bypasses

9. 预处理语句绕过

php
// VULNERABLE: prepare() but not using parameters
$sql = "SELECT * FROM users WHERE id = $id";
$stmt = $pdo->prepare($sql);
$stmt->execute(); // No binding!

// VULNERABLE: Mixing bound and unbound
$stmt = $pdo->prepare("SELECT * FROM $table WHERE id = ?");
$stmt->execute([$id]); // Table still injectable
php
// VULNERABLE: prepare() but not using parameters
$sql = "SELECT * FROM users WHERE id = $id";
$stmt = $pdo->prepare($sql);
$stmt->execute(); // No binding!

// VULNERABLE: Mixing bound and unbound
$stmt = $pdo->prepare("SELECT * FROM $table WHERE id = ?");
$stmt->execute([$id]); // Table still injectable

10. Stored Procedure Injection

10. 存储过程注入

php
// CRITICAL: Procedure call with user input
$sql = "CALL process_order($orderId, '$status')";
$pdo->query($sql);

// CRITICAL: Even with prepare
$stmt = $pdo->prepare("CALL get_user_data('$userId')");
// Parameter inside string literal is not bound
php
// CRITICAL: Procedure call with user input
$sql = "CALL process_order($orderId, '$status')";
$pdo->query($sql);

// CRITICAL: Even with prepare
$stmt = $pdo->prepare("CALL get_user_data('$userId')");
// Parameter inside string literal is not bound

Grep Patterns

Grep检测模式

bash
undefined
bash
undefined

Variable in SQL

Variable in SQL

Grep: '(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).$\w+' -i --glob "**/.php"
Grep: '(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).$\w+' -i --glob "**/.php"

Concatenation in query methods

Concatenation in query methods

Grep: "(query|execute|prepare)\s*([^)].\s\$" --glob "**/*.php"
Grep: "(query|execute|prepare)\s*([^)].\s\$" --glob "**/*.php"

Raw ORM methods

Raw ORM methods

Grep: "(whereRaw|orderByRaw|selectRaw|DB::raw)\s*(" --glob "**/*.php"
Grep: "(whereRaw|orderByRaw|selectRaw|DB::raw)\s*(" --glob "**/*.php"

DQL with variable

DQL with variable

Grep: "createQuery\s*([^)]\$" --glob "**/.php"
Grep: "createQuery\s*([^)]\$" --glob "**/.php"

LIKE without escaping

LIKE without escaping

Grep: "LIKE.'%.\$.%'" --glob "**/.php"
undefined
Grep: "LIKE.'%.\$.%'" --glob "**/.php"
undefined

Severity Classification

严重程度分类

PatternSeverity
Direct concatenation in SQL🔴 Critical
$_GET/$_POST in query🔴 Critical
Dynamic table/column name🔴 Critical
ORM raw with variable🔴 Critical
LIKE without wildcard escape🟠 Major
Second-order injection risk🟠 Major
模式严重程度
SQL中的直接拼接🔴 Critical
查询中使用$_GET/$_POST🔴 Critical
动态表/列名🔴 Critical
ORM原生查询中包含变量🔴 Critical
LIKE子句未转义通配符🟠 Major
存在二阶注入风险🟠 Major

Secure Patterns

安全模式

PDO Prepared Statements

PDO预处理语句

php
// Positional parameters
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ? AND status = ?');
$stmt->execute([$id, $status]);

// Named parameters
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
php
// Positional parameters
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ? AND status = ?');
$stmt->execute([$id, $status]);

// Named parameters
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);

Doctrine Query Builder

Doctrine查询构建器

php
$qb = $em->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u')
    ->where('u.email = :email')
    ->setParameter('email', $email);
php
$qb = $em->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u')
    ->where('u.email = :email')
    ->setParameter('email', $email);

Safe Dynamic Identifiers

安全的动态标识符

php
// Whitelist for table/column names
$allowedTables = ['users', 'orders', 'products'];
if (!in_array($table, $allowedTables, true)) {
    throw new InvalidArgumentException('Invalid table');
}

$allowedColumns = ['name', 'email', 'created_at'];
$column = in_array($sort, $allowedColumns, true) ? $sort : 'id';
php
// 表/列名白名单
$allowedTables = ['users', 'orders', 'products'];
if (!in_array($table, $allowedTables, true)) {
    throw new InvalidArgumentException('Invalid table');
}

$allowedColumns = ['name', 'email', 'created_at'];
$column = in_array($sort, $allowedColumns, true) ? $sort : 'id';

Laravel Eloquent

Laravel Eloquent

php
// Safe binding
User::where('email', $email)->first();

// With array
User::whereIn('id', $ids)->get();

// Order by validation
$allowed = ['name', 'created_at'];
$sort = in_array($input, $allowed) ? $input : 'id';
User::orderBy($sort)->get();
php
// 安全绑定
User::where('email', $email)->first();

// 使用数组
User::whereIn('id', $ids)->get();

// 排序验证
$allowed = ['name', 'created_at'];
$sort = in_array($input, $allowed) ? $input : 'id';
User::orderBy($sort)->get();

Output Format

输出格式

markdown
undefined
markdown
undefined

SQL Injection: [Description]

SQL Injection: [Description]

Severity: 🔴 Critical Location:
file.php:line
CWE: CWE-89 (SQL Injection)
Issue: User input is included in SQL without parameterization.
Attack Vector: Input:
' OR '1'='1' --
Result: Query returns all rows, bypassing conditions.
Code:
php
// Vulnerable code
Fix:
php
// Parameterized query
undefined
Severity: 🔴 Critical Location:
file.php:line
CWE: CWE-89 (SQL Injection)
Issue: User input is included in SQL without parameterization.
Attack Vector: Input:
' OR '1'='1' --
Result: Query returns all rows, bypassing conditions.
Code:
php
// Vulnerable code
Fix:
php
// Parameterized query
undefined

When This Is Acceptable

以下情况可视为安全

  • ORM query builders — Doctrine/Eloquent query builders use parameter binding internally; they are safe by design
  • Named parameters in DQL/HQL
    :paramName
    syntax with
    setParameter()
    is not vulnerable
  • Schema migrations — DDL statements in migrations don't process user input
  • ORM查询构建器 — Doctrine/Eloquent查询构建器内部使用参数绑定,设计上是安全的
  • DQL/HQL中的命名参数 — 使用
    :paramName
    语法并配合
    setParameter()
    不存在漏洞
  • Schema迁移 — 迁移中的DDL语句不处理用户输入

False Positive Indicators

误报标识

  • Code uses
    $qb->where('field = :value')->setParameter('value', $input)
  • Code uses Doctrine DQL with bound parameters
  • SQL is in a migration file with no user input
  • 代码使用
    $qb->where('field = :value')->setParameter('value', $input)
  • 代码使用带绑定参数的Doctrine DQL
  • SQL位于无用户输入的迁移文件中