check-sql-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSQL 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'='1php
// 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'='19. 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 injectablephp
// 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 injectable10. 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 boundphp
// 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 boundGrep Patterns
Grep检测模式
bash
undefinedbash
undefinedVariable 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"
undefinedGrep: "LIKE.'%.\$.%'" --glob "**/.php"
undefinedSeverity Classification
严重程度分类
| Pattern | Severity |
|---|---|
| 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
undefinedmarkdown
undefinedSQL Injection: [Description]
SQL Injection: [Description]
Severity: 🔴 Critical
Location:
CWE: CWE-89 (SQL Injection)
file.php:lineIssue:
User input is included in SQL without parameterization.
Attack Vector:
Input:
Result: Query returns all rows, bypassing conditions.
' OR '1'='1' --Code:
php
// Vulnerable codeFix:
php
// Parameterized queryundefinedSeverity: 🔴 Critical
Location:
CWE: CWE-89 (SQL Injection)
file.php:lineIssue:
User input is included in SQL without parameterization.
Attack Vector:
Input:
Result: Query returns all rows, bypassing conditions.
' OR '1'='1' --Code:
php
// Vulnerable codeFix:
php
// Parameterized queryundefinedWhen This Is Acceptable
以下情况可视为安全
- ORM query builders — Doctrine/Eloquent query builders use parameter binding internally; they are safe by design
- Named parameters in DQL/HQL — syntax with
:paramNameis not vulnerablesetParameter() - 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位于无用户输入的迁移文件中