如果使用下拉菜单,是否必须防止SQL注入?


96

我知道您绝不应该信任表单中的用户输入,这主要是由于SQL注入的机会。

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

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

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

<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>

112
是。没有什么可以阻止攻击者在您的<select>输入中提交他们想要的任何值。实际上,即使是稍微技术的用户也可以使用浏览器控制台添加其他选项。如果您保留可用值的数组白名单并与输入进行比较,则可以减轻这种情况(并且应该这样做,因为它可以防止出现不必要的值)
Michael Berkowski 2014年

13
您应该了解基本的请求/响应内容以及与如何根据请求构建前端无关紧要的内容,在这种情况下为下拉列表
Royal Bg 2014年

13
@YourCommonSense因为这是一个好问题。并非所有人都意识到客户的可操纵性。这将为该网站带来非常有价值的答案。
Cruncher 2014年

8
@Cruncher,我明白了。对于普通的stackoverflowian来说,这是他们新听说过的火箭科学。即使在PHP标签下有最受争议的问题。
您的常识2014年

9
“我知道您永远不要信任用户输入”。没有例外。
Prinzhorn

Answers:


69

您可以像以下示例一样简单地进行操作,以确保发布的大小符合您的期望。

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

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

如果要使用的PHP> = 5.3.0版本,请使用mysqli_ *来保存结果。如果正确使用,这将有助于sql注入。


这是“白名单” OliverBS的简写版本吗?
Tatters 2014年

不仅仅是一个非常基本的版本,您可以将值添加到数据库中进行检查以使其更易于使用和重用,或者可以为白名单类创建一个具有特定方法的白名单类,以供每个白名单进行检查。如果您不想使用数据库,则白名单属性可以位于白名单类的array属性内。
奥利弗·贝叶斯·谢尔顿2014年

9
但是,您必须使用Prepared Statements(推荐)或mysqli_real_escape_string。在输出它们时也要正确地转义这些值(例如,在HTML文档中使用htmlspecialchars())。
ComFreek

4
我建议设置的第三个参数in_arraytrue进行严格比较。我不确定会出什么问题,但是松散的比较很古怪。
Brilliand

1
@OliverBS $ _POST值也可以是数组。数字字符串比较为数字('5'=='05')。我认为您的特定示例中没有安全漏洞,但是规则很复杂,由于我什至不了解的原因,漏洞可能会打开。严格的比较易于推理,因此更易于安全使用。
Brilliand14年

195

是的,您需要对此加以保护。

让我向您展示为什么使用Firefox的开发者控制台:

我已将下拉列表中的值之一编辑为下拉表语句

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

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

如果您试图在页面上进一步限制使用行为,那么我的选择包括禁用该行为,或者只是向服务器编写自定义HTTP请求,以模仿该表单提交。确实有一个叫做curl的工具,我认为无论如何要提交此SQL注入的命令如下所示:

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

(这可能不是完全有效的curl命令,但是我希望我已经明白了。)

因此,我重申:

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

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


24
更不用说使用制作自定义有效负载了curl总是清理输入服务器端
recursion.ninja 2014年

14
图像说出一千多个单词。
davidkonrad 2014年

4
这应该是默认答案。还应该包括一些有关curl。人们不理解您可以使用任何格式和传递任何值从任何地方向任何地方发送HTTP请求,这取决于服务器在处理请求之前确保该请求有效。
retrohacker 2014年

2
多么可爱!这是小鲍比表!
光环

45

由于这个问题被标记为 ,这是有关这种特殊攻击的答案:

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

无论任何HTML内容!
必须理解,无论任何外部因素,无论是HTML输入还是其他任何因素,SQL查询都必须正确设置格式。

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

在这里,您可能会找到详尽的解释,为何必须准备好的语句,如何正确使用它们以及在哪些情况下不适合使用它们以及在这种情况下应采取的措施:《 Hitchhiker SQL注入保护指南》

另外,这个问题被标记为 。我想大多数情况是偶然的,但是无论如何,我必须警告您,原始mysqli 不足以替代旧的mysq_ *函数。仅仅是因为如果以旧样式使用,它根本不会增加安全性。尽管对准备好的语句的支持是痛苦且麻烦的,但以至于普通的PHP用户根本无法使用它们。因此,如果没有ORM或某种抽象库可供选择,那么PDO是您唯一的选择。


5
i = random(0,15); //一些使用i的查询 我还需要在这里准备陈述吗?
Cruncher 2014年

2
什么实现random
Slicedpan 2014年

9
@YourCommonSense狭narrow?对我来说,“总是做X,我将不提供任何理由”。
Cruncher 2014年

5
@Cruncher我想不出任何理由不总是每次使用准备好的语句。你能告诉我一个吗?
Wesley Murch 2014年

3
@Cruncher我同意,好像您在扮演Devil's Advocate。答案中的规定也“涉及任何可变数据”。可能有几种情况,例如PHP int强制转换,但最好只使用准备好的语句。告诉(没有经验的)人们,小小的性能“提升”比安全性更重要。答案是“不完整”,但它发出了一个强烈的信息,其他人正在忽略。我会休息的
Wesley Murch 2014年

12

是。

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

因此,对于验证下拉菜单,您只需检查以确保您正在使用的值在下拉菜单中-像这样的方式将是最好的(最疯狂的偏执)方式:

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

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

} else {

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

}

5
这个答案是不完整的,另外,您应该使用准备好的语句,这样无论值设置为什么,都无法注入
Slicedpan 2014年

7
@Slicedpan真的吗?听起来您好像不知为何就跳上了潮流……如果我在查询中有少量可能的输入,以便可以验证它们一切都很好(我知道,因为我做了这些),那么您使用预先准备好的语句不会带来额外的安全优势
Cruncher 2014年

3
除了强制使用预处理语句作为约定外,它还可以防止您将来引入SQL注入漏洞。
Slicedpan 2014年

1
@Cruncher做出“在下拉列表中的所有值始终是安全的”的承诺是非常不安全的承诺。值已更改,代码不一定更新。一个更新值的人甚至可能都不知道什么是不安全的值!特别是在充满各种安全问题的Web编程领域中,跳过这样的事情简直是不负责任的(以及其他不太友好的话)。
海德2014年

1
@Cruncher如果正确处理了SQL内容,则可以接受任何输入而不会影响SQL的正常工作。SQL部分应该准备就绪并且可以接受任何内容,无论它来自何处。其他一切都容易出错。
glglgl 2014年

8

防止用户使用控制台更改下拉菜单的一种方法是仅在其中使用整数值。然后,您可以验证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>';

然后,您就可以$size在查询中使用,知道它只会包含FALSE一个或一个整数。


2
@OliverBS filter_input如果不检查服务器端该怎么办?除了整数,没有人可以发布任何东西。
斯蒂芬2014年

感谢Styphon,所以这取代了OP中的表格?这是否适用于具有多个下拉菜单的大型表格?如果我添加另一个下拉菜单以说“颜色”?
Tatters 2014年

@SamuelTattersfield是的,是的。您可以将其用于任意数量的下拉菜单,以及任意数量的选项。您只需为每个下拉列表创建一个新数组,然后在该数组中放入该下拉列表的所有选项。
斯蒂芬2014年

真好!我喜欢。我会试一试,看看我在测试中得到了什么。
Tatters 2014年

@SamuelTattersfield很好,只需确保也使用该filter_input零件进行验证即可,否则它可以像巧克力茶壶一样安全使用。
斯蒂芬2014年

7

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

两件事情你需要做的:

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注入。

即使根本没有任何形式的输入,您仍然可以从各种来源获得字符串:从文件系统读取,从Internet抓取等。没有人可以保证这些字符串是安全的。


既不是太长的数据也不是数字字段中的字符串都不会导致任何崩溃
您的常识

嗯,现在就尝试这样做,的确显示警告而不是错误。我认为是PDO将其变成错误。我确定我在drupal.org上讨论了许多问题,其中一些字符串对于varchar来说太长了。
donquixote 2014年

6

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


6

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


5

最好使用参数化查询,以确保防止SQL注入。在这种情况下,查询的外观将是这样的:

SELECT * FROM table WHERE size = ?

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

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

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


4

表单中提交的所有内容都会以文本形式发送到您的服务器。并没有阻止任何人创建机器人来模仿客户端或在终端机中键入(如果他们愿意的话)的机器人。永远不要以为,因为您对客户端进行了编程,所以它将像您认为的那样运行。这真的很容易欺骗。

当您信任客户时会发生什么事的示例


4

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.