【问题标题】:Is this sufficient to prevent query injection while using SQL Server?这足以防止使用 SQL Server 时的查询注入吗?
【发布时间】:2010-04-09 16:16:00
【问题描述】:

我最近接手了一个需要与 PHP/SQL Server 集成的项目。我正在寻找最快和最简单的功能来防止 SQL Server 上的 SQL 注入,因为我更喜欢 MySQL,并且预计不会有更多与 SQL Server 相关的项目。

这个功能够用吗?

$someVal = mssql_escape($_POST['someVal']);

$query = "INSERT INTO tblName SET field = $someVal";

mssql_execute($query);

function mssql_escape($str) {
    return str_replace("'", "''", $str);
}

如果没有,我应该采取哪些额外步骤?


编辑: 我在 Linux 服务器上运行 - sqlsrv_query() only works if your hosting environment is windows

【问题讨论】:

  • umm - derek,我没有看到您在防止注入攻击方面做了很多事情。正如马克所说,使用参数。
  • 如果你不得不问,答案可能是(在这种情况下,肯定是)“不”
  • 这些天我实际上并不是一个服务器端的人。我在 actionscript/jquery 上花了太多时间!

标签: sql-server sql-injection


【解决方案1】:

最好的选择:不要使用连接在一起的 SQL 语句 - 使用参数化查询。

例如不要创建类似

的东西
string stmt = "INSERT INTO dbo.MyTable(field1,field2) VALUES(" + value1 + ", " + value2 + ")"

或类似的东西,然后尝试通过替换单引号或其他东西来“清理”它 - 你永远不会抓住所有东西,总会有人找到绕过你的“安全防护”的方法。

改为:

string stmt = "INSERT INTO dbo.MyTable(field1,field2) VALUES(@value1, @value2)";

然后在执行此 INSERT 语句之前设置参数值。这确实是避免 SQL 注入的唯一可靠方法 - 使用它!

更新:如何使用来自 PHP 的参数化查询 - 我 found something here - 这有帮助吗?

$tsql = "INSERT INTO DateTimeTable (myDate, myTime,
                                    myDateTimeOffset, myDatetime2)
         VALUES (?, ?, ?, ?)";

$params = array(
            date("Y-m-d"), // Current date in Y-m-d format.
            "15:30:41.987", // Time as a string.
            date("c"), // Current date in ISO 8601 format.
            date("Y-m-d H:i:s.u") // Current date and time.
          );

$stmt = sqlsrv_query($conn, $tsql, $params);

所以看起来你不能使用像@value1、@value2 这样的“命名”参数,而是你只使用问号?对于每个参数,您基本上只需创建一个参数数组,然后将其传递给查询。

这篇文章Accessing SQL Server Databases with PHP 也可能有所帮助 - 它有一个类似的示例,说明如何使用参数化查询插入数据。

更新:在您透露您使用的是 Linux 之后,这种方法不再适用。相反,您需要使用 PHP 中的备用库来调用数据库——例如 PDO

PDO 既可以在任何 *nix 类型的操作系统上运行,也可以在各种数据库(包括 SQL Server)上运行,并且它也支持参数化查询:

$db = new PDO('your-connection-string-here');
$stmt = $db->prepare("SELECT priv FROM testUsers WHERE username=:username AND password=:password");
$stmt->bindParam(':username', $user);
$stmt->bindParam(':password', $pass);
$stmt->execute();

【讨论】:

  • 此外,它通常还有其他好处,例如查询计划缓存、可读性等。
  • 您能否举例说明这些参数的设置可能是什么样的?
  • 我认为您可以使用命名参数,例如 :value,但它可能只是用于 MySQLi 或 PDO,不完全确定。
  • 我猜 sqlsrv_query() 只适用于 windows 服务环境 =( 我正在运行 Linux
  • 我很抱歉,我不是故意冒犯或任何事情。我不知道 sqlsrv_query() 是特定于 Windows 环境的。
【解决方案2】:

不,这还不够。据我所知,一般来说,字符串替换永远都不够用(在任何平台上)。

为防止 SQL 注入,所有查询都需要参数化 - 作为参数化查询或作为带参数的存储过程。

在这些情况下,数据库调用库(即 ADO.NET 和 SQL 命令)将参数与查询分开发送,服务器应用它们,这消除了以任何方式更改实际 SQL 的能力。除了注入之外,这还有很多好处,其中包括代码页问题和日期转换问题 - 就此而言,如果服务器不希望它们按照客户端的方式完成,那么任何对字符串的转换都可能会出现问题。

【讨论】:

  • 转义引号足够,由于您提到的原因以及查询计划缓存以及如果您总是参数化,请放心,你永远不需要逃跑。
  • @richardtallent - 转义漏洞很多 - 见stackoverflow.com/questions/139199/…
  • 字符串转义的漏洞与 MySQL 的备用转义语法和潜在的 Unicode 0x02BC 的 DBMS 翻译有关,这两者都不适用于 MSSQL(是的,当我听说最初,没有雪茄)。因此,除非他使用奇怪的 QUOTED_IDENTIFIER 设置(谁这样做?),或者做一些愚蠢的事情,比如截断转义的字符串,否则他所做的是足够,只是不是最优的。您的建议很好(“使用参数”),但您的说法未经证实(“为防止 sql 注入,所有查询都需要参数化”)。
【解决方案3】:

我部分不同意其他海报。如果您通过双引号的函数运行所有参数,这应该可以防止任何可能的注入攻击。实际上,在实践中,更常见的问题不是故意破坏,而是由于值合法地包含单引号而中断的查询,例如名为“O'Hara”的客户或“不要在 9:00 之前致电 Sally”的评论字段。不管怎样,我一直都是这样逃跑的,从来没有遇到过问题。

一个警告:在某些数据库引擎上,除了单引号之外,可能还有其他危险字符。我知道的唯一例子是 Postgres,其中反斜杠很神奇。在这种情况下,您的转义函数还必须使用双反斜杠。检查文档。

我不反对使用准备好的语句,对于简单的情况,唯一改变的是参数的值,它们是一个很好的解决方案。但是我经常发现我必须根据程序中的条件分段构建查询,比如如果参数 X 不为空,那么我不仅需要将它添加到 where 子句,而且我还需要一个额外的连接才能到达值我真的需要测试。准备好的语句不能处理这个。当然,您可以分段构建 SQL,将其转换为准备好的语句,然后提供参数。但这只是没有明显收获的痛苦。

这些天,我主要使用 Java 编写允许函数重载的代码,也就是说,根据传入参数的类型有多个实现。所以我例行编写了一组函数,我通常将它们简单地命名为“q”来表示“quote”,它们返回给定的类型,并适当地引用。对于字符串,它将任何引号加倍,然后在整个事物周围加上引号。对于整数,它只返回整数的字符串表示形式。对于日期,它将转换为 JDBC (Java SQL) 标准日期格式,然后驱动程序应该将其转换为正在使用的特定数据库所需的任何内容。等等(在我当前的项目中,我什至将数组作为传入类型包含在内,我将其转换为适合在 IN 子句中使用的格式。)然后每次我想在 SQL 语句中包含一个字段时,我只写“ q(x)”。因为这是在必要时加上引号,所以我不需要额外的字符串操作来加上引号,所以它可能就像不进行转义一样容易。

例如易受攻击的方式:

String myquery="select name from customer where customercode='"+custcode+"'";

安全方式:

String myquery="select name from customer where customercode="+q(custcode);

正确的打字方式并不比错误的打字方式特别多,因此很容易养成一个好习惯。

【讨论】:

  • 我相信你知道,除了用双引号替换单引号之外,还有更多的清理方法。另一个重要方面是确保值是声明的类型。即,验证日期真的是日期,整数真的是整数,snozzberries 真的尝起来像snozzberries。 :)
  • @Thomas:我不确定你在说什么。问题是关于 SQL 注入攻击。如果有人输入“foobar”作为数值,我们创建一个查询,例如“select plugh from zork where plughid='foobar'”,在 mySQL 和 Postgres 中他只会得到一个 not-found。也许某些引擎会给出 SQL 错误。但是没有黑客可以使用它来访问数据库的危险,或者它可能导致数据库损坏。
  • @Jay - 我的意思是有更多的理由来清理您的输入,而不仅仅是防止 SQL 注入。在调用数据库之前,您应该尽早清理输入以发现问题。 完全依赖您的数据库来验证数据类型(和一般有效性)是一种不好的做法。因此,您为清理输入而构建的任何例程都应该至少检查数据类型的有效性。
  • @Thomas:我当然同意程序应该检查其输入的有效性。但这与阻止 SQL 注入是完全不同的问题。如果用户输入美元金额的“Lots”,这是一个输入错误,应该被拒绝并给用户一条消息。但是,在字符串变量中嵌入引号本身并没有什么不合法的。 “O'Hara”是一个完全有效的名字; “不要放弃这艘船”可能是一个完全有效的评论字段,等等。这样的输入不应该因为无效而被拒绝:它们应该在插入 SQL 语句时被正确地转义。
  • 续:我想您可以编写一个输入验证函数来检查输入是否符合各种合法标准,如果不符合则返回错误消息,如果它作为副作用它会加倍SQL 的引号,但我认为这是一个坏主意。它将在一个函数中混合两个不相关的任务。
【解决方案4】:

用字符串替换转义引号足以防止 SQL 注入攻击向量。

这仅适用于当 QUOTED_IDENTIFIER 为 ON 时的 SQL Server,并且当您不对转义字符串做一些愚蠢的事情时,例如截断它或在转义后将您的 Unicode 字符串转换为 8 位字符串。特别是,您需要确保 QUOTED_IDENTIFIER 设置为ON。通常这是默认设置,但它可能取决于您在 PHP 中用于访问 MSSQL 的库。

参数化是一种最佳实践,但转义引号以防止 SQL 注入本身并没有什么不安全的地方。

转义字符串的 rel 问题不是替换的效果,而是忘记每次都进行替换的可能性。

也就是说,您的代码会转义值,但不会将值括在引号中。你需要这样的东西:

function mssql_escape($str) {
  return "N'" + str_replace("'", "''", $str) + "'";
}

上面的N 允许您传递更高的Unicode 字符。如果这不是问题(即您的文本字段是varchar 而不是nvarchar),您可以删除N

现在,如果你这样做,有一些警告:

  1. 您需要确保为 每个 字符串值调用 mssql_escape。这就是问题所在。
  2. 日期和 GUID 值也需要以相同的方式转义。
  3. 您应该验证数值,或者至少也使用相同的函数对它们进行转义(MSSQL 会将字符串转换为适当的数值类型)。

同样,正如其他人所说,参数化查询更安全——不是因为转义引号不起作用(除了上面提到的情况外,它确实有效),而是因为更容易在视觉上确保您没有忘记转义某些内容。

【讨论】:

  • 我一直想知道的一个问题 - 是否有必要转义不需要用户输入的查询?像“从表中选择 *”这样简单的东西?
猜你喜欢
  • 2012-07-30
  • 2010-09-23
  • 1970-01-01
  • 2017-09-24
  • 1970-01-01
  • 1970-01-01
  • 2013-02-25
  • 1970-01-01
  • 2020-09-16
相关资源
最近更新 更多