2025年PHP中的SQL注入:如何防护

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么SQL注入仍然是一个现实威胁?

\n\n

想象一下:您的网站运行顺畅,用户注册、发表评论、下单购物。突然之间——数据库落入了攻击者手中。不是因为黑客多么天才,而是因为有人在SQL查询中忘记加了一个问号。

\n\n

SQL注入并非来自2000年代的过时威胁。在2024年,OWASP仍将其列为Web应用程序最危险的三大漏洞之一。而Stack Overflow上关于"How can I prevent SQL injection in PHP?"的问题浏览量已超过150万——并且还在增长。这说明问题依然存在。

\n\n

在本文中,我们将探讨如何在2025年彻底封堵PHP中的SQL注入漏洞。没有魔法,没有复杂的框架,只有纯粹的代码和最佳实践。

\n\n

什么是SQL注入?用简单的话来说

\n\n

SQL注入是指用户输入成为SQL命令的一部分,而不仅仅是数据。生活中的例子:

\n\n
<?php\n// 糟糕的代码——永远不要这样做!\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n?>
\n\n

如果攻击者在username字段中输入字符串' OR '1'='1,查询将变成:

\n\n
SELECT * FROM users WHERE username = '' OR '1'='1'
\n\n

这将返回所有用户。而如果输入'; DROP TABLE users; --——则可能丢失整个表。可怕吗?确实如此。

\n\n

2025年PHP中防止SQL注入的最佳实践

\n\n

1. 使用PDO与预处理语句(这是标准做法)

\n\n

PDO(PHP数据对象)是一个用于数据库操作的通用接口。其主要优势在于:它将SQL逻辑与数据分离。您编写带有占位符的查询,PDO负责安全的转义。

\n\n
<?php\n// 好的代码——使用PDO\n$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';\n$pdo = new PDO($dsn, $user, $pass, [\n    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n    PDO::ATTR_EMULATE_PREPARES => false, // 禁用模拟预处理\n]);\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');\n$stmt->execute(['email' => $_POST['email']]);\n$user = $stmt->fetch();\n?>
\n\n

为什么这样有效? 数据库接收查询结构时与数据是分开的。即使用户输入了恶意的SQL,它也会被解释为普通字符串,而不是命令的一部分。

\n\n

2. 使用Mysqli与预处理语句(替代方案)

\n\n

如果您只使用MySQL,可以使用mysqli——但必须使用预处理语句,而不是通过mysqli_real_escape_string()进行转义。

\n\n
<?php\n$mysqli = new mysqli('localhost', 'user', 'pass', 'testdb');\n$stmt = $mysqli->prepare('INSERT INTO comments (user_id, text) VALUES (?, ?)');\n$stmt->bind_param('is', $userId, $commentText);\n$stmt->execute();\n?>
\n\n

重要提示: 在2025年,mysqli_real_escape_string()仍然存在,但依赖它进行防护是个坏主意。它只转义字符串,但无法防止数字字段或LIKE运算符中的注入。

\n\n

3. 过滤与验证——第二道防线

\n\n

预处理语句是基础。但在此基础上,请根据数据类型进行过滤。如果期望是数字——请验证它是数字。如果是邮箱——请检查格式。

\n\n
<?php\n$id = filter_input(INPUT_POST, 'user_id', FILTER_VALIDATE_INT);\nif ($id === false) {\n    throw new InvalidArgumentException('ID must be an integer');\n}\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');\n$stmt->execute(['id' => $id]);\n?>
\n\n

组合

博客

书籍推荐