【问题标题】:Is CallableStatement really immune to SQL injection?CallableStatement 真的对 SQL 注入免疫吗?
【发布时间】:2014-01-10 20:09:45
【问题描述】:

我们有一个 Java 应用程序,它与同一个数据库上的多个 SQL Server 数据库进行通信 盒子。这些数据库的数量和名称各不相同。总的来说,我们几乎只使用带有 CallableStatement 的存储过程来访问数据库。我们非常擅长避免 SQL 注入和使用绑定变量。

唯一需要关注的是数据库名称本身被连接到我们传递给 CallableStatement 的 SQL 中:

"{call [" + dbName + ".dbo." + procName + "(?, ?, ?)}"

procName 使用模板方法模式硬编码到子类中,因此可以保证字符串是安全的。

dbName 是在外部定义的。我尝试将 dbName 设置为各种模式以逃避语法并在我的开发环境中利用它,但没有成功。

我已将其设置为以下以生成以下 SQL 调用(更改表和过程名称以保护无辜者):

securitytest].nx_proc()};delete from poor_victim_table;

变成

{call [securitytest].nx_proc()};delete from poor_victim_table;].dbo.proper_proc_name()}

securitytest].nx_proc()};exec('delete from poor_victim_table');

变成

{call [securitytest].nx_proc()};exec('delete from poor_victim_table');].dbo.proper_proc_name(?,?,?,?,?,?,?)}

Incorrect syntax near ')'.poor_victim_table 中的结果仍然有行。我使用过truncate tabledrop tabledrop database,当它们不起作用时,我切换到简单的delete 以排除安全设置。

如果我使用带有绑定参数的 proc,我总是会得到预期参数的数量与提供的参数(例如 The index 1 is out of range.)之间的不匹配。

securitytest]};exec('delete from poor_victim_table');

变成

{call [securitytest]};exec('delete from poor_victim_table');].dbo.proper_proc_name(?,?,?,?,?,?,?)}

所有的道路似乎都导致运行时错误,SQL不执行。当然,这很棒。但我想确保它失败是因为它不能成功,而不是因为我没有尝试正确的组合而失败。

流行的观点/都市神话是,使用存储过程可以让您免受 SQL 注入的影响,但在安全性方面,我更愿意不相信这样的绝对语句。

在研究了一段时间后,我想出的最好的就是这个 stackoverflow 问题:SQL injection - no danger on stored procedure call (on iSeries)?。它似乎支持使用 CallableStatement,因为它可以保护您免受 SQL 注入除非您的 proc 代码本身从输入参数中生成动态 SQL

所以,我向社区提出的问题是,假设 proc 中的 SQL 代码是安全的,那么在 JDBC 中使用 CallableStatement 真的可以防止 SQL 注入吗?或者 SQL Server 驱动程序是否以阻止它的方式解析字符串,但其他驱动程序可能不会?还是我不够努力?

如果它是安全的,如何保证?是不是因为使用{ call blah(?) } 的抽象语法,它不是真正的SQL,而是被翻译成SQL?

【问题讨论】:

    标签: stored-procedures jdbc sql-injection callable-statement


    【解决方案1】:

    应该是安全的,但就像您一样,我不相信这一点,尤其是当您使用不同的 JDBC 驱动程序等连接到不同的数据库时。

    如果我是你,在你发布声明之前,我会确保检查 dbName 是否包含除字母、数字和可能的下划线之外的任何内容。这应该允许所有有效的 dbNames,并防止各种弄乱它。

    【讨论】:

    • 我刚刚看到你在帖子开头说“不同的 sql 服务器”,但没有设置任何特定的标签,所以我不确定这是否意味着“MS SQL 的不同实例” ,或“Oracle、DB2、Mysql、MS Sql 和其他一些”。如果您只有一种数据库类型,则依赖测试驱动程序会更容易一些,但您永远不知道驱动程序的未来版本会发生什么变化。对数据库名称中使用的每条输入进行完整性检查仍然是您最安全的选择。
    • 仅限 Microsoft SQL Server。 Java 代码的一次安装与多个 SQL Server 数据库通信,但它们始终位于同一个 SQL Server 实例/服务器上。
    • 从您的调用语句的语法中是这样认为的。尽管如此,从“我无法获得当前版本的驱动程序以允许 sql 注入”到“任何人都无法将 sql 注入到我的调用语句中,无论我是否使用当前的驱动程序版本或未来版本”将不允许字符串中的任何字符在数据库名称中无效,因此驱动程序无法误解。将字符串截断到一定长度也可能会有所帮助。
    • 感谢您的意见。我已经编写了错误报告来验证数据库名称是一个很好的衡量标准。
    【解决方案2】:

    检查您的数据库连接 url,我认为这是引用静态数据库,所以如果您在可调用语句中写入数据库名称,这将在数据库名称更改时产生问题,您的代码就像死掉(大多数地方要更改),所以不要在查询中使用数据库名称,但您可以为不同的数据库连接或不同的帮助类创建不同的对象。

    【讨论】:

      【解决方案3】:

      流行的观点/都市神话是使用存储过程 让你对 SQL 注入免疫,但我更喜欢不相信绝对 涉及安全性时的类似声明。

      而且你不相信这样一个笼统的陈述做得很好。但是,该语句需要是合格的/合格的。

      看,如果存储过程写得好,它就不会受到 SQL 注入的影响。它允许您控制允许使用哪种 SQL 语句。您可以配置您的生产数据库,使其不允许执行 DML,只允许执行存储过程(从而使您免于意外或故意执行可怕的笛卡尔关节的人。)

      但除了编写良好的存储过程之外,调用者还有责任验证和清理输入,而 JDBC 为您提供了一种通过 "bind" parameters 执行此操作的方法。

      CallableStatement 继承自 PreparedStatement,它提供了绑定参数的方法。绑定方法对传入的参数值进行转义,从而使注入几乎不可能。

      将存储过程(以及可调用和准备好的语句)视为一把锤子。您可以很好地使用它来敲钉子或压碎拇指。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多