提问人:johnnyRose 提问时间:9/18/2015 最后编辑:johnnyRose 更新时间:8/28/2020 访问量:15053
当参数不是字符串时,不参数化 SQL 查询是否安全?
Is it safe to not parameterize an SQL query when the parameter is not a string?
问:
在SQL注入方面,我完全理解参数化的必要性;这是书中最古老的技巧之一。但是什么时候可以证明不参数化是合理的?是否有任何数据类型被认为是“安全的”,可以不进行参数化?string
SqlCommand
例如:我不认为自己接近 SQL 专家,但我想不出任何可能容易受到 SQL 注入影响的情况,接受 a 或 an 并将其直接连接到查询中。bool
int
我的假设是否正确,或者这可能会在我的程序中留下巨大的安全漏洞?
为了澄清起见,这个问题被标记为 c#,这是一种强类型语言;当我说“参数”时,可以考虑类似 .public int Query(int id)
答:
"SELECT * FROM Table1 WHERE Id=" + intVariable.ToString()
安全
没关系,
攻击者不能在你键入的 int 变量中注入任何内容。
性能
不行。
最好使用参数,这样查询将编译一次并缓存以供下次使用。下次即使使用不同的参数值,查询也会被缓存,不需要在数据库服务器中编译。
编码风格
糟糕的做法。
- 参数更具可读性
- 也许它让你习惯了没有参数的查询,然后也许你犯了一次错误,以这种方式使用字符串值,然后你可能应该告别你的数据。坏习惯!
"SELECT * FROM Product WHERE Id=" + TextBox1.Text
虽然这不是你的问题,但也许对未来的读者有用:
安全
灾难!
即使字段是整数,您的查询也可能受到 SQL 注入的影响。假设您的应用程序中有一个查询。攻击者可以插入文本框,查询将是:Id
"SELECT * FROM Table1 WHERE Id=" + TextBox1.Text
1; DELETE Table1
SELECT * FROM Table1 WHERE Id=1; DELETE Table1
如果不想在此处使用参数化查询,则应使用类型化值:
string.Format("SELECT * FROM Table1 WHERE Id={0}", int.Parse(TextBox1.Text))
您的问题
我的问题之所以出现,是因为一位同事写了一堆查询 连接整数值,我想知道它是否是一个 浪费我的时间来检查和修复所有这些问题。
我认为更改这些代码不是浪费时间。确实建议改变!
如果您的同事使用 int 变量,则没有安全风险,但我认为更改这些代码不会浪费时间,确实建议更改这些代码。它使代码更具可读性、更易于维护,并使执行速度更快。
评论
.ToString()
int
ToString
实际上有两个问题合二为一。标题中的问题与OP在之后的评论中表达的担忧关系不大。
虽然我意识到对于 OP 来说,重要的是他们的特殊情况,但对于来自 Google 的读者来说,回答更普遍的问题很重要,这个问题可以表述为“如果我确保我连接的每个文字都是安全的,串联是否与准备好的语句一样安全?所以,我想集中谈谈后者。答案是
绝对不是。
解释并不像大多数读者想要的那么直接,但我会尽力而为。
我一直在思考这个问题一段时间,最终写了这篇文章(尽管基于 PHP 环境),我试图总结一切。我突然想到,在一些相关但更狭窄的主题中,保护 SQL 注入的问题经常被回避,例如字符串转义、类型转换等。虽然有些措施在单独采取时可以被认为是安全的,但没有系统,也没有简单的规则可以遵循。这使得它非常滑,过多地影响了开发人员的注意力和经验。
SQL注入的问题不能简化为某些特定的语法问题。它比普通开发人员过去认为的要广泛。这也是一个方法论问题。它不仅是“我们必须应用哪种特定格式”,而且还是“必须如何完成”。
(从这个角度来看,另一个答案中引用的 Jon Skeet 的一篇文章做得比好事要好得多,因为它再次在一些边缘情况下吹毛求疵,专注于特定的语法问题,而未能从整体上解决问题。
当你试图解决保护问题时,不是作为一个整体,而是作为一个不同的语法问题,你面临着许多问题。
- 可能的格式选择列表非常庞大。意味着人们很容易忽略一些。或者混淆它们(例如,通过使用字符串转义作为标识符)。
- 串联意味着所有保护措施必须由程序员完成,而不是程序。仅此问题就会导致以下几个后果:
- 这种格式是手动的。手动意味着极易出错。一个人可能只是忘记申请。
- 此外,有一种诱惑是将格式化过程转移到一些集中式功能中,使事情更加混乱,并破坏不会进入数据库的数据。
- 当涉及多个开发人员时,问题会乘以十倍。
- 使用串联时,无法一眼看出具有潜在危险的查询:它们都具有潜在危险!
与这种混乱不同,准备好的陈述确实是圣杯:
- 它可以用一个易于遵循的简单规则的形式来表达。
- 它本质上是不可侵犯的措施,意味着开发人员不能干涉,并且无论是否愿意,都会破坏这一过程。
- 防止注入实际上只是准备好的语句的副作用,其真正目的是生成语法正确的语句。语法正确的语句是 100% 的注入证明。然而,尽管存在任何注入的可能性,但我们需要我们的语法是正确的。
- 如果一直使用,无论开发人员的经验如何,它都可以保护应用程序。比如说,有一种叫做二阶注入的东西。还有一种非常强烈的错觉,上面写着“为了保护,转义所有用户提供的输入”。如果开发人员自由决定哪些需要保护,哪些不需要保护,那么它们结合在一起会导致注入。
(进一步思考,我发现当前的占位符集不足以满足现实生活的需求,必须进行扩展,无论是对于复杂的数据结构,如数组,甚至是 SQL 关键字或标识符,有时也必须动态添加到查询中,但开发人员在这种情况下没有武装, 并被迫回退到字符串连接,但这是另一个问题)。
有趣的是,这个问题的争议是由 Stack Overflow 非常有争议的性质引发的。该网站的想法是利用直接提问的用户的特定问题来实现拥有适合来自搜索的用户的通用答案数据库的目标。这个想法本身并不坏,但在这样的情况下它就失败了:当用户提出一个非常狭隘的问题时,特别是在与同事的争执中得到争论(或决定是否值得重构代码)。虽然大多数有经验的参与者都在尝试写出答案,但要牢记 Stack Overflow 的使命,让他们的答案对尽可能多的读者有好处,而不仅仅是 OP。
评论
我认为这是安全的......从技术上讲,但这是一个可怕的习惯。你真的想写这样的查询吗?
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE IsAlive = " + isAlive +
" AND FirstName = @firstName");
sqlCommand.Parameters.AddWithValue("firstName", "Rob");
在类型从整数变为字符串的情况下,它还会使您容易受到攻击(想想员工编号,尽管它的名字可能包含字母)。
因此,我们已将 EmployeeNumber 的类型从 更改为 ,但忘记更新我们的 sql 查询。哎呀。int
string
评论
AddWithValue
AddWithValue
AddWithValue
在我看来,如果你能保证你使用的参数永远不会包含字符串,那么它是安全的,但我无论如何都不会这样做。此外,由于您正在执行串联,您会看到性能略有下降。我想问你的问题是,你为什么不想使用参数?
评论
在您控制的计算机(如 Web 服务器)上使用强类型平台时,可以阻止对仅具有 、 、 或(和其他数值)值的查询进行代码注入。令人担忧的是,由于强制 sql server 重新编译每个查询,并阻止它获得有关哪些查询以什么频率运行的良好统计信息(这会损害缓存管理)而导致的性能问题。bool
DateTime
int
但是,“在您控制的计算机上”部分很重要,因为否则用户可以更改系统用于从这些值生成字符串的行为,以包含任意文本。
我也喜欢从长远考虑。当今天陈旧的强类型代码库通过自动翻译移植到新的热门动态语言时,您突然失去了类型检查,但还没有针对动态代码进行所有正确的单元测试,会发生什么?
实际上,没有充分的理由不对这些值使用查询参数。这是正确的方法。当值确实是常量时,继续将值硬编码到 sql 字符串中,但除此之外,为什么不直接使用参数呢?这并不难。
归根结底,我不会称它为虫子,但我会称它为气味:它本身就不是一个虫子,但强烈表明虫子就在附近,或者最终会在附近。好的代码可以避免留下异味,任何好的静态分析工具都会标记这一点。
我要补充一点,不幸的是,这不是你可以直接赢得的那种论点。这听起来像是“正确”已经不够的情况了,踩着同事的脚趾自己解决这个问题不太可能促进良好的团队动力;它最终可能弊大于利。在这种情况下,更好的方法可能是推广使用静态分析工具。这将使目标努力具有合法性和可信度,并回过头来修复现有代码。
评论
DateTime
int
我们不要只考虑安全性或类型安全的注意事项。
使用参数化查询的原因是提高数据库级别的性能。从数据库的角度来看,参数化查询是 SQL 缓冲区中的一个查询(使用 Oracle 的术语,尽管我认为所有数据库在内部都有类似的概念)。因此,数据库可以在内存中保存一定数量的查询,准备好并准备执行。这些查询不需要解析,并且速度更快。经常运行的查询通常位于缓冲区中,并且不需要在每次使用时进行分析。
除非
有人不使用参数化查询。在这种情况下,缓冲区会不断被几乎相同的查询流刷新,每个查询都需要由数据库引擎解析和运行,并且性能会受到全面影响,因为即使是频繁运行的查询最终也会在一天内重新解析多次。我以调整数据库为生,这一直是唾手可得的果实的最大来源之一。
现在
为了回答您的问题,如果您的查询具有少量不同的数值,则可能不会引起问题,实际上可能会无限地提高性能。但是,如果可能有数百个值并且查询被调用很多,您将影响系统的性能,因此不要这样做。
是的,您可以增加 SQL 缓冲区,但这最终总是以牺牲其他更关键的内存用途为代价的,例如缓存、索引或数据。道德,非常虔诚地使用参数化查询,这样你就可以优化你的数据库,并将更多的服务器内存用于重要的东西......
在某些情况下,可以使用字符串值以外的非参数化(连接)变量执行SQL注入攻击 - 请参阅Jon的这篇文章:http://codeblog.jonskeet.uk/2014/08/08/the-bobbytables-culture/。
问题是,当被调用时,一些自定义区域性提供程序可以将非字符串参数转换为其字符串表示形式,从而将一些 SQL 注入到查询中。ToString
评论
int
CultureInfo
没关系,但从来都不安全。安全性始终取决于输入,例如,如果输入对象是 TextBox,攻击者可以做一些棘手的事情,因为 TextBox 可以接受字符串,因此您必须进行某种验证/转换才能防止用户输入错误。但问题是,它并不安全。就这么简单。
评论
要向 Maciek 添加一些信息,请回答:
通过反射调用程序集的 main-function,可以很容易地更改 .NET 第三方应用的区域性信息:
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Assembly asm = Assembly.LoadFile(@"C:\BobbysApp.exe");
MethodInfo mi = asm.GetType("Test").GetMethod("Main");
mi.Invoke(null, null);
Console.ReadLine();
}
static Program()
{
InstallBobbyTablesCulture();
}
static void InstallBobbyTablesCulture()
{
CultureInfo bobby = (CultureInfo)CultureInfo.InvariantCulture.Clone();
bobby.DateTimeFormat.ShortDatePattern = @"yyyy-MM-dd'' OR ' '=''";
bobby.DateTimeFormat.LongTimePattern = "";
bobby.NumberFormat.NegativeSign = "1 OR 1=1 OR 1=";
Thread.CurrentThread.CurrentCulture = bobby;
}
}
}
仅当 BobbysApp 的 Main 函数是公共的时,这才有效。如果 Main 不是公共函数,则可以调用其他公共函数。
评论
不,你可以通过这种方式获得SQL注入攻击。我用土耳其语写了一篇旧文章,展示了这里是如何做到的。PHP 和 MySQL 中的文章示例,但概念在 C# 和 SQL Server 中工作相同。
基本上你按照方式攻击。假设您有一个页面,该页面根据整数 id 值显示信息。您没有将其参数化为值,如下所示。
http://localhost/sqlEnjeksiyon//instructors.aspx?id=24
好的,我假设您正在使用 MySQL,并且我按照以下方式进行攻击。
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII((SELECT%20DATABASE()))
请注意,此处注入的值不是字符串。我们正在使用 ASCII 函数将 char 值更改为 int。您可以使用“CAST(YourVarcharCol AS INT)”在 SQL Server 中完成相同的操作。
之后,我使用长度和子字符串函数来查找您的数据库名称。
http://localhost/sqlEnjeksiyon//instructors.aspx?id=LEN((SELECT%20DATABASE()))
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR(SELECT%20DATABASE(),1,1))
然后使用数据库名称,开始获取数据库中的表名。
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR((SELECT table_name FROM INFORMATION_SCHEMA.TABLES LIMIT 1),1,1))
当然,您必须自动执行此过程,因为每个查询只能获得一个字符。但是您可以轻松地将其自动化。我的文章展示了 watir 中的一个例子。仅使用一个页面,而不是参数化的 ID 值。我可以了解您数据库中的每个表名。在那之后,我可以寻找重要的表格。这需要时间,但这是可行的。
评论
public void QuerySomething(int id) { // this will only accept an integer }
SELECT%20DATABASE()
$id_value = 'ASCII((SELECT%20DATABASE())'; $param = (int)$id_value; $query = ... id={$param};
即使对于非字符串类型,这也是不安全的。始终使用参数。时期。
请考虑以下代码示例:
var utcNow = DateTime.UtcNow;
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE created_on <= '" + utcNow + "'");
乍一看,代码看起来很安全,但如果在 Windows 区域设置中进行一些更改并以短日期格式添加注入,一切都会改变:
现在生成的命令文本如下所示:
SELECT * FROM People WHERE created_on <= '26.09.2015' OR '1'<>' 21:21:43'
类型也可以做同样的事情,因为用户可以定义自定义负号,可以很容易地将其更改为SQL注入。int
有人可能会争辩说应该使用不变区域性而不是当前区域性,但是我已经多次看到这样的字符串连接,并且在使用 .+
评论
int
评论