首先执行哪个?清洗还是验证?

9
我可以帮您进行翻译。以下是需要翻译的内容:

在我的注册表单中,有一个包含例如名称字段的字段,它将存储在名为user_name varchar(20)的数据库字段中。很明显,我应该验证用户输入。如果我首先使用下面的代码验证此字段:

<?php
 if(emptiy($_pos['name']) || strlen($_post['name'])>20)
 //send an not valid input error
 else{
 $name=htmlspcialchars($_post['name']);
 //check for sql injection;
 //insert name into database;}
?>

如果用户输入类似于<i> some one </i>这样的名称,字符串长度为17,那么else部分将执行,名称将变为&lt;i&gt some one &lt;/i&gt;,长度为28,这将在插入到数据库时产生错误。此时,如果我向用户发送一个错误,告诉他/她的输入太长了,他/她会感到困惑。我该怎么办?最好的方法是什么?

6
在存储数据之前,您不应对其进行编码。应该以原始格式存储(使用适当的转义,例如mysqli_real_escape_string或类似方法),并在输出时进行编码。这是因为如果您将其作为HTML或JSON或其他任何内容输出,则需要不同的编码。 - Niet the Dark Absol
3
防止 SQL 注入的最佳方法是使用 mysqli 或 PDO 准备语句将数据插入数据库。@Niet the Dark Absol 是正确的,但 mysqli_real_escape_string() 函数已经被弃用。 - nurakantech
出于安全考虑,我永远不会使用像 mysqli_real_escape_string() 这样的函数,我使用 PDO,它更加安全。 - naazanin
我一直遵循“先清洗,再验证”的方法。 - asprin
如果一个人输入了 'some one',我应该将 'some one' 插入到数据库中还是先去除空格再存储到数据库中? - naazanin
有一些空格在 "some" 和 "one" 之间。 - naazanin
2个回答

9
通常应该先进行净化 - "为了您的保护和他们的保护。" 这包括去除任何无效的字符 (当然是与字符编码相关的)。如果一个字段只包含字符和空格,那么首先剥离不属于这些字符的东西。
完成这个步骤之后,你需要验证结果 - 名称是否已经被使用 (对于唯一字段),长度是否正确,是否为空?
你所给出的原因是完全正确的 - 最大化用户体验。如果可以避免混淆用户,请不要混淆。这有助于防止愚蠢的复制和粘贴行为,但你必须小心 - 如果我想把我的名字记录为 "Ke$h@", 我可能会同意将它改为 "Keh"。
其次,这也是为了防止错误。
当你想创建不允许特殊字符的用户名时会发生什么?如果我输入 "Brian",而你的系统将其拒绝,因为名称已经在使用中,然后我提交 "Brian$"?首先你验证它,发现没有使用,然后剥离特殊字符,你只剩下 "Brian" 了。糟糕了 - 现在你必须再次验证,否则你就会得到一个奇怪的错误,要么账户创建失败 (如果你的数据库设置要求唯一用户名, 例如),要么更糟的是它会成功,导致用户账户被覆盖/损坏。
另一个例子是最小字段长度:如果你要求名称至少为 3 个字母长,只接受字母,而我输入 "no",你会拒绝它;但是如果我输入 "no@#$%",你可能会说它是有效的 (足够长),清洁一下它,现在它就不再有效了,以此类推。
避免这种情况的简单方法是先进行净化,这样你就不必反复思考验证。
然而,Niet 关于不在存储之前对数据进行编码是正确的;通常来讲将输出设置为 HTML 编码时会更容易些,然后当需要纯文本时记得解码即可 (用于输入文本框、JSON 字符串等)。你使用的大多数测试案例都不会包含带有 HTML 实体的数据,因此很容易引入不容易发现的荒谬错误。
大问题在于,一旦出现这样的错误,就可能很快导致数据损坏,而这种损坏并不容易解决。例如:你有纯文本,将其错误地输出到文本字段中作为 HTML 实体,表单被提交回来并重新编码...每次打开/重新提交时都会重新编码。在繁忙的站点/表单中,你可能会得到数千个不同的编码条目,并没有明确的方法来确定哪些是意图进行 HTML 编码的,哪些不是。
保护免受注入是好的,但 HTML 编码不是设计用于此目的 (也不能依赖它)。

好的,假设您输入了Brian$,首先对其进行清理,结果应为Brian,并验证其是否唯一。好的,现在您已经注册并想要登录,您输入了Brian,在登录表单中我应该再次清理输入吗?如果是的话,输出“你好,Brian”,但是您可能会感到困惑,因为您输入了Brian$。 - naazanin
2
你应该告知用户输入已被清理 - 在这种情况下,我甚至会建议当用户输入无效时给予错误提示。 - Deniz Zoeteman
@naazanin 我同意gdscei的观点,但通常我会将这样的提示留给客户端表单验证之前。在那里,我更加温柔地提示用户输入无效内容,而在服务器端,我更有可能选择以下两种模型之一:1)让它正常工作,如果用户不需要知道就不要打扰他们,或者2)拒绝无效输入并让用户自行解决。这将取决于您的用例,我无法提供全局建议。您的应用程序越国际化,越需要小心禁止潜在有效的字符。 - BrianH
1
如果我想要将我的名字记录为“Ke$h@”,我可能会同意将其更改为“Keh”,也可能不同意。这就是为什么我喜欢先进行数据清洗,验证一切是否正常,然后再检查原始未经触碰的版本是否等于已被清理过的版本。如果它们不相同,则会返回适当的错误信息以及已被清理过的输入表单。 - Ilyes512

3
不,您应该先进行验证。清理是用于处理数据存储级别的最后一步。如果业务规则没有通过验证阶段,则没有必要接近数据存储级别。如果您需要一个数字,而您得到了一个字符串,那么这是一个错误,因此您将其发送回表单。
除非需要,否则清理不是必需的(自5.4以来不再必要),如果您使用准备好的语句和SQL,则实际上会破坏输入。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接