我是在网络上开发东西的新手。到目前为止,我花了很多时间(50% 左右)来尝试防止坏人将诸如 sql 注入之类的东西放入我的输入表单并在服务器端验证它。这是正常的吗?
7 回答
@Jeremy - 一些 PHP 细节
当涉及到数据库查询时,请始终尝试使用准备好的参数化查询。mysqli
和PDO
库支持这一点。这比使用 mysql_real_escape_string 等转义函数要安全得多。
是的,mysql_real_escape_string 实际上只是一个字符串转义函数。它不是灵丹妙药。它将做的只是转义危险字符,以便它们可以安全地用于单个查询字符串。但是,如果您不事先清理输入,那么您将容易受到某些攻击向量的攻击。
想象一下下面的 SQL:
$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);
您应该能够看到这很容易被利用。
想象一下id
参数包含常见的攻击向量:
1 OR 1=1
它们中没有要编码的危险字符,因此它将直接通过转义过滤器。离开我们:
SELECT fields FROM table WHERE id = 1 OR 1=1
这是一个可爱的 SQL 注入向量。
虽然这些功能很有用,但必须小心使用。您需要确保所有 Web 输入都经过一定程度的验证。在这种情况下,我们看到我们可以被利用,因为我们没有检查我们用作数字的变量是否实际上是数字。在 PHP 中,您应该广泛使用一组函数来检查输入是否为整数、浮点数、字母数字等。但是当涉及到 SQL 时,请注意大多数准备好的语句的值。如果上面的代码是一个准备好的语句,那么它是安全的,因为数据库函数会知道这1 OR 1=1
不是一个有效的文字。
至于 htmlspecialchars()。这本身就是一个雷区。
PHP 中存在一个真正的问题,因为它有一系列不同的与 html 相关的转义函数,并且没有明确指导哪些函数执行什么操作。
首先,如果您在 HTML 标记中,那么您就遇到了真正的麻烦。看着
echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';
我们已经在一个 HTML 标记中,所以我们不需要 < 或 > 来做任何危险的事情。我们的攻击向量可能只是javascript:alert(document.cookie)
现在生成的 HTML 看起来像
<img src= "javascript:alert(document.cookie)" />
攻击直接通过。
它变得更糟。为什么?因为 htmlspecialchars 只编码双引号而不是单引号。所以如果我们有
echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";
我们的邪恶攻击者现在可以注入全新的参数
pic.png' onclick='location.href=xxx' onmouseover='...
给我们
<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />
在这些情况下,没有灵丹妙药,您只需要自己清理输入即可。如果您尝试过滤掉不良字符,您肯定会失败。采取白名单方法,只让好的字符通过。查看XSS 备忘单,了解有关向量的多样性的示例
即使您在 HTML 标记之外使用 htmlspecialchars($string),您仍然容易受到多字节字符集攻击向量的攻击。
最有效的方法是使用 mb_convert_encoding 和 htmlentities 的组合,如下所示。
$str = mb_convert_encoding($str, ‘UTF-8′, ‘UTF-8′);
$str = htmlentities($str, ENT_QUOTES, ‘UTF-8′);
即使这样,IE6 也容易受到攻击,因为它处理 UTF 的方式。但是,您可以回退到更有限的编码,例如 ISO-8859-1,直到 IE6 的使用率下降。
为了防止 sql 注入攻击,只需使用准备好的语句进行查询(具体方式取决于您的平台)。一旦你这样做了,你就再也不必为这个特定的方面而烦恼了。你只需要在任何地方使用它。
至于一般的输入验证,依靠一个通用的基础来测试所需的字段、数字等总是好的。例如,ASP.Net 的验证器非常易于使用。您应该遵循的一条经验法则是不要相信客户端(javascript)会为您执行此操作,因为它很容易绕过它。始终首先在服务器端进行。
需要注意的一个特殊情况是允许引入可能包含 html/javascript 的丰富内容。这可能允许恶意用户在您的数据中注入 javascript,这将触发您在渲染时无法控制的代码。不要尝试推出自己的验证码。在网上搜索免费的、经过测试的、托管的代码,这些代码将为您完成。Jeff 在其中一个播客中对此提出了一些建议。
自动化输入验证代码后,执行此操作所花费的时间应该与业务规则的复杂性直接相关。所以作为一般规则:保持简单。
不,这不正常。也许你需要:
- 使用权限组件避免 SQL 注入(Java 中的 PreparedStatements)
- 创建一个“过滤”来自用户的消息的组件(Java 中的 servlet 过滤器)。
任何现代语言都支持这两件事。
亲切的问候
我很高兴你注意保护自己。太多人没有。
然而,正如其他人所说,更好的架构选择将使您的问题消失。使用准备好的语句(大多数语言应该支持)将使 SQL 注入攻击消失。加上许多数据库,它们将显着提高性能。处理跨站点脚本攻击更加棘手。但基本策略必须是决定如何转义用户输入,决定在哪里转义,并始终在同一个地方进行。不要陷入认为越多越好的陷阱!在一个地方以一种方式始终如一地执行它就足够了,并且将避免您不得不弄清楚多个转义级别中的哪一个导致特定的错误。
或者学习如何创建和维护一个健全的架构的课程需要经验。而且,它需要反思你的糟糕经历。因此,请注意您当前的痛点(看起来确实如此),并考虑您可以采取哪些不同的措施来避免它们。如果您有导师,请与您的导师交谈。这不会总是对你的这个项目有很大帮助,但它会在下一个项目中帮助你。
我看到了你的问题。看起来你的保护逻辑遍布代码库。并且每次编写具有潜在危险的代码时,都必须小心包含所有保护措施。每次出现新威胁时,您都必须检查所有这些声明并验证它们是否安全。
你不能以这种方式做真正的安全。
你应该有一些包装器,如果不是不可能的话,这会使生成不安全的代码变得困难。例如,准备好的语句。但是您可能想要使用 ORM,例如 Ruby on Rails 的 ActiveRecord,或者在您的框架中使用类似的东西。
对于输出和 XSS 保护,请确保默认情况下输出是 HTML 转义的。然后,如果您确实需要将生成的 HTML 输出给用户,您将明确地执行此操作,并且更容易验证。
对于 CSRF 保护,尝试也找到一个通用的解决方案。通常它应该自动完成它的职责,而不需要你显式地创建一个验证令牌,并手动验证它、丢弃它或拒绝请求。
只是对准备好的陈述的说明。首先,如果可以的话,您应该尝试使用存储过程……在大多数情况下,它们可能是更好的解决方案。
其次,它们都保护您免受 SQL 注入,只要您不使用动态 SQL,即编写更多 SQL 然后执行它的 SQL。在这种情况下,它们将无效——存储过程也是如此。
关于您花费的时间百分比:验证非常重要,它确实需要一些思考,如果不是一些时间的话。但是百分比取决于您的应用程序有多大,不是吗?在一个非常小的应用程序中,比如说,只有一个新闻通讯注册,验证很可能会占用你很大一部分时间。
在较大的应用程序中,即您有很多非展示代码的地方,这是不正常的。
您面临的问题只能通过泛化来解决。
尝试识别您需要的常见输入验证类型
- 数字/字符串值/正则表达式验证
- 范围/长度
- 转义特殊字符
- 根据黑名单检查您在特定上下文中不期望的常见关键字('script'、'select'、'drop'...)
并在处理数据之前系统地调用它们。
所有数据库访问都必须使用准备好的语句完成,而不是连接查询字符串。
所有输出都必须转义,因为您不想将所有转义的内容存储在数据库中。
一个好的带外/社交方法是:尽可能地识别你的用户。被识别的机会越高,他们对系统的欺骗就越少。获取他们的手机号码以发送代码,检查他们的信用卡等。