如果我使用下拉列表,我是否必须防止 SQL 注入?

Do I have to guard against SQL injection if I used a dropdown?

提问人:Tatters 提问时间:3/20/2014 最后编辑:doppelgreenerTatters 更新时间:6/3/2019 访问量:12294

问:

我知道你永远不应该相信来自表单的用户输入,主要是因为SQL注入的机会。

但是,这是否也适用于唯一输入来自下拉列表的表单(见下文)?

我正在将其保存到一个会话中,然后在整个站点中使用该会话来查询各种数据库(使用选择查询),任何 SQL 注入肯定会损害(可能删除)它们。$_POST['size']mysqli

没有用于查询数据库的类型化用户输入区域,只有下拉列表。

<form action="welcome.php" method="post">
<select name="size">
  <option value="All">Select Size</option> 
  <option value="Large">Large</option>
  <option value="Medium">Medium</option>
  <option value="Small">Small</option>
</select>
<input type="submit">
</form>
php mysql mysqli sql 注入

评论

113赞 Michael Berkowski 3/20/2014
是的。没有什么能阻止攻击者在您的输入中提交他们想要的任何值。事实上,即使是稍微技术含量较低的用户也可以使用浏览器控制台添加其他选项。如果您保留可用值的数组白名单并将输入与其进行比较,则可以减轻这种情况(您应该这样做,因为它可以防止不需要的值)<select>
13赞 Royal Bg 3/20/2014
您应该了解基本的请求/响应事物,以及前端如何基于请求构建无关紧要的事情,即在这种情况下,下拉列表
13赞 Cruncher 3/20/2014
@YourCommonSense 因为这是个好问题。不是每个人都意识到客户是多么容易纵。这将为这个网站带来非常有价值的答案。
8赞 Your Common Sense 3/20/2014
@Cruncher我明白了。对于一个普通的堆栈溢出者来说,这是他们以前听说过的火箭科学。即使PHP标签下有最受好评的问题。
9赞 Prinzhorn 3/21/2014
“我知道你永远不应该相信用户输入”。没有例外。

答:

13赞 rm-vanda 3/20/2014 #1

是的。

任何人都可以欺骗任何实际发送的值 --

因此,为了验证下拉菜单,您可以检查以确保您正在使用的值在下拉列表中 - 像这样的东西将是最好的(最理智的偏执)方法:

if(in_array($_POST['ddMenu'], $dropDownValues){

    $valueYouUseLaterInPDO = $dropDownValues[array_search("two", $arr)];

} else {

    die("effin h4x0rs! Keep off my LAMP!!"); 

}

评论

5赞 Slicedpan 3/20/2014
此答案不完整,此外,您应该使用预准备语句,以便无论将值设置为什么,都无法进行注入
7赞 Cruncher 3/20/2014
@Slicedpan真的吗?这听起来像是你跳上了潮流,却不知道为什么......如果我在查询中有一些可能的输入,以便我可以验证它们是否都正常(我知道,因为我做了它们),那么使用准备好的语句不会产生额外的安全优势
3赞 Slicedpan 3/20/2014
除了强制使用预准备语句作为约定可以防止将来引入 SQL 注入漏洞之外。
1赞 Your Common Sense 3/20/2014
@Cruncher事实上,你必须学会将责任分开。在构建/执行查询时,程序应该完全不知道输入数据的来源或性质(除了它在查询中的作用)。因此,您可以自由地以您喜欢的方式验证您的 HTML 数据,但它绝不应影响任何 SQL 交互。
1赞 hyde 3/21/2014
@Cruncher 做出“下拉列表中的所有值将始终是安全的”的承诺是一个非常不安全的承诺。值已更改,代码不一定更新。更新值的人甚至可能不知道什么是不安全的值!特别是在充斥着各种安全问题的 Web 编程领域,吝啬这样的事情简直是不负责任的(以及其他不太好听的话)。
69赞 Oliver Bayes-Shelton 3/20/2014 #2

您可以执行以下示例中简单的操作,以确保发布的大小符合您的预期。

$possibleOptions = array('All', 'Large', 'Medium', 'Small');

if(in_array($_POST['size'], $possibleOptions)) {
    // Expected
} else {
    // Not Expected
}

如果您使用的是 php >= 5.3.0 版本,请使用 mysqli_* 来保存结果。如果使用得当,这将有助于 sql 注入。

评论

0赞 Tatters 3/20/2014
这是“白名单”OliverBS的简写版本吗?
0赞 Oliver Bayes-Shelton 3/20/2014
实际上不仅仅是一个非常基本的版本,您可以将值添加到数据库中以进行检查,以使其更易于和可重用。或者,也许可以创建一个白名单类,其中包含要检查的每个白名单的特定方法。如果您不想使用数据库,白名单属性可能位于白名单类的数组属性内。
9赞 ComFreek 3/21/2014
尽管如此,您必须使用预准备语句(推荐)或mysqli_real_escape_string。在输出值时也要正确转义它们(例如,在 HTML 文档中使用 htmlspecialchars())。
4赞 Brilliand 3/21/2014
我建议将第三个参数设置为 to 以进行严格比较。我不确定会出什么问题,但松散的比较非常古怪。in_arraytrue
1赞 Brilliand 3/22/2014
@OliverBS $_POST 值也可以是数组。数字字符串作为数字进行比较 ('5'=='05')。我不认为你的具体例子中有安全漏洞,但规则很复杂,漏洞可能会因为我什至不明白的原因而打开。严格的比较更容易推理,因此更容易安全使用。
8赞 Styphon 3/20/2014 #3

防止用户使用控制台更改下拉列表的一种方法是仅在其中使用整数值。然后,您可以验证 POST 值是否包含整数,并在需要时使用数组将其转换为文本。例如:

<?php
// No, you don't need to specify the numbers in the array but as we're using them I always find having them visually there helpful.
$sizes = array(0 => 'All', 1 => 'Large', 2 => 'Medium', 3 => 'Small');
$size = filter_input(INPUT_POST, "size", FILTER_VALIDATE_INT);

echo '<select name="size">';
foreach($sizes as $i => $s) {
    echo '<option value="' . $i . '"' . ($i == $size ? ' selected' : '') . '>' . $s . '</option>';
}
echo '</select>';

然后,您可以在知道它只包含整数的情况下在查询中使用。$sizeFALSE

评论

2赞 Styphon 3/20/2014
@OliverBS 如果不是服务器端的检查,那是什么?除了整数之外,任何人都不能发布任何内容。filter_input
0赞 Tatters 3/20/2014
谢谢 Styphon,所以这取代了 OP 中的形式?这是否适用于具有多个下拉列表的较大表单?如果我为它添加另一个下拉列表,比如“颜色”?
0赞 Styphon 3/20/2014
@SamuelTattersfield 是的,是的。您可以将其用于任意数量的下拉列表,并具有任意数量的选项。您只需为每个下拉列表创建一个新数组,并在数组中输入下拉列表的所有选项。
0赞 Tatters 3/20/2014
好!我喜欢。我会试一试,看看我在测试中得到了什么。
0赞 Styphon 3/20/2014
@SamuelTattersfield 太好了,只要确保也使用该部件进行验证,否则它就像巧克力茶壶一样有用。filter_input
46赞 Your Common Sense 3/20/2014 #4

由于这个问题被标记为,以下是有关这种特定类型攻击的答案:

正如您在评论中被告知的那样,您必须对涉及任何变量数据的每个查询使用准备好的语句,没有例外

不管任何 HTML 的东西!
必须了解,无论任何外部因素(无论是 HTML 输入还是其他任何因素)如何,都必须正确格式化 SQL 查询。

尽管您可以使用其他答案中建议的白名单进行输入验证,但它不会影响任何与 SQL 相关的操作 - 无论您是否验证了 HTML 输入,它们都必须保持不变。这意味着在向查询中添加任何变量时,您仍然必须使用预准备语句。

在这里,您可以找到详尽的解释,为什么必须准备好的语句,以及如何正确使用它们,它们在哪些地方不适用,以及在这种情况下该怎么做: SQL注入保护的漫游指南

此外,这个问题被标记为。我猜这主要是偶然的,但无论如何,我必须警告你,原始 mysqli 并不能充分替代旧的 mysq_* 函数。仅仅因为如果以旧样式使用,它根本不会增加安全性。虽然它对准备好的语句的支持是痛苦和麻烦的,但普通PHP用户根本无法尝试它们。因此,如果没有 ORM 或某种抽象库是选项,那么 PDO 是您唯一的选择。

评论

5赞 Cruncher 3/20/2014
i = 随机(0, 15);使用 i 进行一些查询。我还需要在这里准备声明吗?
9赞 Cruncher 3/20/2014
@YourCommonSense狭隘的眼光?对我来说,“总是做X,我不会为它提供任何理由”是狭隘的。
5赞 Wesley Murch 3/20/2014
@Cruncher我想不出任何理由不总是使用准备好的语句,每次,总是对所有事情,总是。你能告诉我一个吗?
3赞 Wesley Murch 3/20/2014
@Cruncher 我同意,你似乎在玩魔鬼的代言人。答案中的规定也是“涉及任何可变数据”。有一些情况,比如 PHP int cast,但似乎最好只使用准备好的语句。告诉(没有经验的)人,轻微的性能“提升”比安全性更重要,这还很遥远。答案是“不完整”,但它发出了其他人忽略的强烈信息。我会休息我的案子。
3赞 Your Common Sense 3/21/2014
@AnonymousPi不,没有丝毫想法。在这个问题进入“热门”或其他东西之后 - 是的,这是可以理解的。但这是一个问题——它是如何到达那里的。它与数十万个其他被忽视的问题没有什么不同。
6赞 Nameless One 3/20/2014 #5

你的 Web 浏览器并不“知道”它正在接收来自 php 的页面,它看到的只是 html。而 http 层知道的甚至比这还要少。你需要能够处理几乎任何类型的输入,这些输入可以跨越http层(幸运的是,对于大多数输入来说,php已经给出了一个错误)。 如果你试图防止恶意请求弄乱你的数据库,那么你需要假设另一端的人知道他在做什么,并且他不仅限于你在正常情况下可以在浏览器中看到的内容(更不用说你可以摆弄浏览器的开发人员工具了)。 所以是的,你需要满足下拉列表中的任何输入,但对于大多数输入,你可能会给出一个错误。

6赞 Jason Higgins 3/21/2014 #6

您限制用户只能使用某个下拉列表中的值这一事实无关紧要。技术用户可以在服务器离开其网络之前捕获发送到服务器的 http 请求,使用本地代理服务器等工具对其进行更改,然后继续执行。使用更改的请求,他们可以发送参数值,这些参数值不是您在下拉列表中指定的参数值。开发人员必须有这样一种心态,即客户端限制通常毫无意义,因为客户端上的任何内容都可以更改。在客户端数据输入的每个点都需要进行服务器验证。攻击者仅依靠开发人员在这方面的天真。

5赞 Dan Smith 3/21/2014 #7

最好使用参数化查询,以确保不会出现 SQL 注入。在这种情况下,查询的外观如下:

SELECT * FROM table WHERE size = ?

当您提供如上所述的查询时,如果文本的完整性未经验证(输入未在服务器上验证),并且其中包含 SQL 注入代码,则将正确处理该查询。换句话说,该请求将导致数据库层发生类似的事情:

SELECT * FROM table WHERE size = 'DROP table;'

这将在返回时简单地选择 0 个结果,这将使查询在实际对数据库造成损害方面无效,而无需白名单、验证检查或其他技术。请注意,负责任的程序员将分层进行安全保护,并且除了参数化查询外,还经常进行验证。但是,从性能的角度来看,没有理由不对查询进行参数化,并且这种做法增加的安全性是熟悉参数化查询的一个很好的理由。

198赞 doppelgreener 3/21/2014 #8

是的,您需要防止这种情况。

让我告诉你为什么,使用Firefox的开发者控制台:

i've edited one of the values in the dropdown to be a drop table statement

如果不清除此数据,数据库将被销毁。(这可能不是一个完全有效的 SQL 语句,但我希望我已经明白了我的观点。

仅仅因为您限制了下拉列表中可用的选项并不意味着您限制了我可以发送给您的服务器的数据。

如果您尝试使用页面上的行为进一步限制这种情况,我的选择包括禁用该行为,或者只是向您的服务器编写一个自定义 HTTP 请求,无论如何都会模仿此表单提交。有一个名为 curl 的工具用于此目的,我认为无论如何提交此 SQL 注入的命令将如下所示:

curl --data "size=%27%29%3B%20DROP%20TABLE%20*%3B%20--"  http://www.example.com/profile/save

(这可能不是一个完全有效的 curl 命令,但同样,我希望我已经表达了我的观点。

所以,我要重申:

永远不要相信用户输入。始终保护自己。

不要假设任何用户输入都是安全的。即使它通过表格以外的其他方式到达,它也可能不安全。没有一个值得信赖,以至于放弃保护自己免受SQL注入的侵害。

评论

24赞 recursion.ninja 3/22/2014
更不用说用 制作自定义有效载荷了。始终清理输入服务器端curl
14赞 davidkonrad 3/22/2014
一张图片胜过千言万语。
4赞 retrohacker 3/26/2014
这应该是默认答案。还应该包括一些关于 .人们不明白您可以使用任何格式和传递任何值从任何地方向任何地方发送 HTTP 请求,服务器在处理请求之前要确保该请求有效。curl
3赞 xubia 5/23/2014
多么可爱!这是小鲍比桌!
7赞 donquixote 3/22/2014 #9

其他答案已经涵盖了您需要了解的内容。但也许它有助于澄清更多:

您需要做两件事

1. 验证表单数据。

正如乔纳森·霍布斯(Jonathan Hobbs)的回答非常清楚地表明的那样,为表单输入选择html元素并不能为您进行任何可靠的过滤。

验证通常以不更改数据的方式完成,但会再次显示表单,字段标记为“请更正此项”。

大多数框架和 CMS 都有表单构建器来帮助您完成此任务。不仅如此,它们还有助于对抗 CSRF(或“XSRF”),这是另一种形式的攻击。

2. 清理/转义 SQL 语句中的变量。

..或者让准备好的语句为您完成这项工作。

如果使用任何变量(无论是否由用户提供)构建 (My)SQL 语句,则需要转义并引用这些变量。

通常,您插入到MySQL语句中的任何此类变量都应该是字符串,或者PHP可以可靠地转换为MySQL可以消化的字符串。如数字。

对于字符串,您需要选择几种方法之一来转义字符串,这意味着,替换任何在MySQL中会产生副作用的字符。

  • 在老式的 MySQL + PHP 中,mysql_real_escape_string() 可以完成这项工作。问题是它太容易忘记了,所以你绝对应该使用准备好的语句或查询构建器。
  • 在 MySQLi 中,您可以使用预准备语句。
  • 大多数框架和 CMS 都提供查询生成器来帮助您完成此任务。

如果要处理一个数字,则可以省略转义和引号(这就是预准备语句允许指定类型的原因)。

需要指出的是,您转义了 SQL 语句的变量,而不是数据库本身的变量。数据库将存储原始字符串,但语句需要转义版本。

如果省略其中之一会怎样?

如果你不使用表单验证,但你确实清理了你的 SQL 输入,你可能会看到各种不好的事情发生,但你不会看到 SQL 注入!(*)

首先,它可以将您的应用程序带入您未计划的状态。例如,如果要计算所有用户的平均年龄,但有一个用户为年龄提供了“aljkdfaqer”,则计算将失败。

其次,您可能需要考虑各种其他注入攻击:例如,用户输入可能包含 javascript 或其他内容。

数据库可能仍然存在问题:例如,如果一个字段(数据库表列)限制为 255 个字符,并且字符串比该长度长。或者,如果该字段只接受数字,而您尝试保存非数字字符串。但这不是“注入”,它只是“使应用程序崩溃”。

但是,即使您有一个自由文本字段,您可以在其中允许任何完全不进行验证的输入,您仍然可以像这样将其保存到数据库中,前提是当它转到数据库语句时正确转义它。当您想在某处使用此字符串时,问题就来了。

(*) 否则这将是非常奇特的东西。

如果您没有对 SQL 语句的变量进行转义,但您确实验证了表单输入,那么您仍然可以看到不好的事情发生。

首先,当您将数据保存到数据库并再次加载时,它不再是相同的数据,“在翻译中丢失”。

其次,它可能导致无效的 SQL 语句,从而使您的应用程序崩溃。例如,如果任何变量包含引号或双引号字符,则根据您使用的引号类型,您将获得无效的MySQL语句。

第三,它仍然会导致SQL注入。

如果您的用户从表单输入已经过过滤/验证,则如果您的输入被简化为硬编码的选项列表,或者如果它仅限于数字,则有意的 SQl 注入的可能性可能会降低。但是,如果您没有正确转义 SQL 语句中的变量,则任何自由文本输入都可以用于 SQL 注入。

即使你根本没有表单输入,你仍然可以有来自各种来源的字符串:从文件系统中读取,从互联网上抓取,等等。没有人能保证这些字符串是安全的。

评论

0赞 Your Common Sense 3/22/2014
太长的数据,而不是数字字段中的字符串都不会使任何东西崩溃
0赞 donquixote 3/22/2014
嗯,现在刚刚尝试过,确实它显示警告而不是错误。我认为是PDO把它变成了一个错误。我敢肯定,我在 drupal.org 的十几个问题中讨论过,其中某些字符串对于 varchar 来说太长了。
4赞 GenericJam 3/23/2014 #10

从您的表单中提交的任何内容都会以文本的形式通过网络发送到您的服务器。没有什么能阻止任何人创建一个机器人来模仿客户端,或者如果他们愿意的话,可以从终端输入它。永远不要以为因为你对客户端进行了编程,它就会像你想象的那样运行。这真的很容易被欺骗。

当您信任客户端时可能发生和将要发生的情况示例

4赞 user1452686 3/26/2014 #11

黑客可以通过使用 Telnet 发送请求来完全绕过浏览器,包括 Javascript 表单检查。当然,他会查看你的html页面的代码,以获得他必须使用的字段名称,但从那时起,对他来说就是“一切正常”。因此,您必须检查服务器上提交的所有值,就好像它们不是来自您的 html 页面一样。