代码之家  ›  专栏  ›  技术社区  ›  Arunabh

在不使用预处理语句的情况下摆脱SQL注入

  •  2
  • Arunabh  · 技术社区  · 7 年前

    我的应用程序有许多使用语句编写的JDBC查询,因此容易受到SQL注入的攻击。不幸的是,这个应用程序是大约10年前开发的,开发人员可能不知道准备好的声明。

    我知道解决这个问题的最好方法是使用PreparedStatement,但在整个应用程序中转换它非常繁琐。此外,为SQL注入编写任何类型的模式匹配都可能非常复杂,因为Select、insert、union等关键字都是英文单词,也可能出现在用户键入的文本字段中。

    有没有更智能的方法来避免SQL注入而不使用预处理语句。如果这是一个重复的问题,请给我一个问题的链接,有一个很好的答案。谢谢你的帮助。

    1 回复  |  直到 7 年前
        1
  •  7
  •   Community CDub    7 年前

    哈。不幸的是,它几乎总是取决于货币和管理决策的适当性,但“它非常乏味”通常被认为是无效的 工程 担忧——这只是适当重构代码的借口。

    同时,问题要求使用非预处理语句方法:简而言之,如果您无法将工作卸载到库中(例如预处理语句),那么唯一的另一种方法是为每个“注入”项自己执行。无论哪种方式,都必须执行检查输入的工作。唯一的问题是它是在哪里完成的,以及程序员的专业知识是如何生成输入验证代码的。

    例如,考虑一个简单的SELECT语句:

        sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;
    

    为了完整性,我们可以假设注入示例,其中 untrustedVar 是一根弦吗 1 OR 1 1; DROP TABLE mytable; 显然,这将导致不必要的行为,相对于返回给调用者的所有行,或者现在缺少的数据库表:

    SELECT * FROM mytable WHERE id = 1;
    DROP mytable;
    

    Obligatory XKCD reference

    在这种情况下,您可以让语言语义至少确保 unstrustedVar 是一个整数,可能在函数定义中:

    String[] selectRowById ( int untrustedVar ) { ...
    

    或者,如果它是一个字符串,您可以使用正则表达式这样做:

    Pattern valid_id_re = Pattern.compile('^\d{1,10}$');  // ensure id is between 1 and 10 billion
    Matcher m = valid_id_re.matcher( unstrustedVar );
    if ( ! m.matches() )
        return null;
    

    但是,如果您有没有任何语法或结构保证的较长输入(例如web表单textarea),则需要进行较低级别的字符替换,以避开潜在的坏字符。根据声明。每个变量。每种数据库风格(PostgreSQL、Oracle、MySQL、SQLite等)。这是一罐虫子。

    当然,其好处是,如果您不使用预处理语句,并且还没有人做过其他工作来避免应用程序的SQL注入攻击,那么您就别无选择了。

    同时,我敦促,敦促, 你必须重新考虑你的立场,即“由于种种原因,我们不能使用事先准备好的声明。”更重要的是,正如戈尔·汤普森在下面的评论中正确指出的那样,“无论如何,这将是一项相当大的工作,为什么不把它做好并加以利用呢?”


    写了以上内容后,我突然想到,有些人可能会认为,仅仅写一份准备好的声明就意味着更好的安全性。实际上,这是事先准备好的声明 带绑定参数 这提高了安全性。例如,可以这样写:

    String sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;
    PreparedStatment pstmt = dbh.prepareStatement( sql );
    return pstmt.executeQuery();
    

    在这一点上,您所做的仅仅是准备一个已经注入恶意代码的语句。相反,请考虑参数的实际绑定:

    String sql = "SELECT * FROM mytable WHERE id = ?";  // Raw string; never touched by "tainted" variable
    PreparedStatment pstmt = dbh.prepareStatement( sql );
    pstmt.setObject(1, p);  // Perform the actual binding.
    return pstmt.executeQuery();
    

    后一个例子做了两件事。它首先创建一个已知的安全格式,并发送 那个 至配电盘进行准备。然后 DB已经返回了一个预处理语句的句柄,我们是否绑定了变量,并最终执行了该语句。