“我只是转义了引号”:为什么你的代码仍然脆弱
\n\n如果你曾经搜索过“How can I sanitize user input with PHP?”,你并不孤单。这个问题在 Stack Overflow 上获得了数十万次浏览。这并不奇怪:安全问题是 Web 开发中最热门的话题之一。
\n\n但矛盾的是:大多数回答要么过时,要么危险。开发者仍然在使用类似 mysql_real_escape_string()(该函数已消失 10 年)或直接删除“危险”字符。剧透:这根本没用。
在本文中,我们将分析 PHP 用户输入消毒的 5 大常见错误,这些错误会让你的网站漏洞百出,并展示如何一劳永逸地修复它们。
\n\n错误 #1:盲目相信“消毒”是万能药
\n\n最大的错误是认为存在一个万能函数可以“清理”所有数据。以下是新手常见的代码:
\n\n<?php\n$name = $_POST['name'];\n$name = strip_tags($name);\n$name = addslashes($name);\n// 现在数据安全了吗?不!\n?>\n\n问题在于,不同的上下文需要不同的处理方式。验证(检查格式)和消毒(清理)不是一回事。此外,还有输出转义,这完全发生在另一个阶段。
\n\n正确做法:明确在每个阶段对数据做什么。对于数据库是一种方式,对于 HTML 是另一种,对于 JSON 又是另一种。
\n\n错误 #2:使用过时的函数(或错误地使用它们)
\n\n让我们回顾一下 PHP 安全领域的“恐龙”:
\n\n- \n
mysql_real_escape_string()— 已在 PHP 7.0 中移除。忘掉它。 \n addslashes()— 无法防御所有 SQL 注入,尤其是在多字节编码下。 \n strip_tags()— 删除标签,但不转义输出。XSS 仍可能通过属性发生。 \n
一个至今仍可见的危险代码示例:
\n\n<?php\n$id = $_GET['id'];\n$id = addslashes($id);\n$query = "SELECT * FROM users WHERE id = '$id'";\n// 如果 id = 1 OR 1=1 -- 你已被入侵\n?>\n\n正确做法:使用预处理语句(prepared statements)。这是防御 SQL 注入唯一可靠的方法。
\n\n<?php\n$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");\n$stmt->execute(['id' => $_GET['id']]);\n?>\n\n看到了吗?对于 SQL 不需要任何消毒。只需使用参数。数据库自己会区分哪些是数据,哪些是命令。
\n\n错误 #3:混淆验证与消毒
\n\n许多人认为:“我会删除所有可疑字符,这样就能保证安全。”但这会破坏应用程序的逻辑。想象一下,用户输入了密码 P@ssw0rd!,而你删除了 @ 和 ! 字符。密码变成了 Pssw0rd——用户再也无法登录。
验证——检查输入是否符合预期格式(电子邮件、数字、URL)。如果不符合,则拒绝。
\n\n消毒——删除或修改不需要的字符,但仅在确实需要时进行(例如,对于文件名)。
\n\n正确验证电子邮件的示例:
\n\n<?php\n$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);\nif ($email === false) {\n // 电子邮件无效——不保存,不输出\n echo "请输入有效的电子邮件地址";\n exit;\n}\n// 现在 $email 是用于任何目的的安全字符串\n?>\n\n黄金法则:如果你期望一个数字——请检查它是否是数字。如果是电子邮件——请检查格式。其他所有情况——拒绝。
\n\n错误 #4:忽略输出上下文(XSS)
\n\n你已经通过预处理语句将数据保存到数据库。太好了,没有 SQL 注入。但