如何修改代码以避免SQL注入攻击?

How can I modify the code to avoid SQL-injection attack?

提问人:sakura 提问时间:8/23/2022 最后编辑:Mark Rotteveelsakura 更新时间:8/26/2022 访问量:407

问:

我需要对两个字段和 .它们都可以是 .如果一个字段是 ,则搜索应基于另一个字段。businessNamebusinessAddressnullnull

具体来说,

  • if 然后执行businessName="name"businessAddress="address"select * from business where businessName like '%name%' and businessAddress like '%address%'
  • if 然后执行businessName=nullbusinessAddress="address"select * from business where businessAddress like '%address%'
  • if 然后执行businessName=nullbusinessAddress=nullselect * from business

我的代码:

StringBuilder sb = new StringBuilder("select * from business where 1=1 ");

if (businessName != null) {
    sb.append("and businessName like '%" + businessName + "%' ");
}
if (businessAddress != null) {
    sb.append("and businessAddress like '%" + businessAddress + "%' ");
}

try {
    con = DBUtil.getConnection();
    pst = con.prepareStatement(sb.toString());
    rs = pst.executeQuery();
} ...

显然,它有受到SQL注入攻击的危险。我知道方法可以避免攻击,但是在验证之前,字段的数量是不确定的。prepareStatement.setString()

如何修改它?每种情况或代码的单独方法(如下所示)似乎很丑陋。

if(businessName!=null){
    if(businessAddress!=null){
        sql = ...;
    }else {
        sql = ...;
    }
else{
...
java sql jdbc sql 注入

评论

1赞 8/23/2022
“我知道方法prepareStatement.setString()可以避免攻击,但是在验证之前字段数量不确定。” < - 在您的示例中,它只是 4 种不同的情况,而不是数百种组合。检查哪些传递的值为 null 并准备 3 个不同的预准备语句,然后使用适当的方法设置参数确实没有太多代码或麻烦。
0赞 Psi 8/23/2022
如果字段可能为空,但在编译时是已知的,则可以将所有字段添加到查询中,并测试“输入 = 列或缺少输入”
0赞 Your Common Sense 8/23/2022
这是针对PHP的,但你可以得到这个想法。将参数收集到数组中,然后像 pst.setStr(i,param) 一样循环遍历这个数组;此外,还有一个基于 SQL 的解决方案,但您必须将每个参数发送两次

答:

1赞 MT0 8/23/2022 #1

您不需要动态 SQL,可以在查询中使用绑定变量:

String query = "select * from business where businessName LIKE '%' || ? || '%' AND businessAddress LIKE '%' || ? || '%'";

(假设 || 是 SQL 方言的字符串连接运算符。

然后使用准备好的语句并将 和 绑定到变量。businessNamebusinessAddress

PreparedStatement st = con.prepareStatement(query);
st.setString(1,businessName);
st.setString(2,businessAddress);
ResultSet rs = st.executeQuery();

(添加异常处理。


或者,如果您的 SQL 方言要求您与 then 分开处理:NULLLIKE

String query = "select * from business
                where (:name IS NULL OR businessName LIKE '%' || :name || '%')
                AND   (:addr IS NULL OR businessAddress LIKE '%' || :addr || '%')";

并使用命名绑定变量 和 (或使用传递两次值)。:name:addr?

评论

0赞 Mark Rotteveel 8/23/2022
这并不能解决 OP 描述的创建动态查询的问题。
0赞 MT0 8/23/2022
@MarkRotteveel 无需使用动态查询。 将计算为并匹配所有字符串。'%' || NULL || '%''%%'
0赞 Mark Rotteveel 8/23/2022
在大多数 SQL 方言(遵循 SQL 标准的方言)中,将计算结果为 ,使 like 表达式的计算结果为 ,导致整个 where 条件的计算结果为 。您可能会想到 Oracle,其中等同于空字符串。'%' || NULL || '%'NULLUNKNOWNFALSENULL
0赞 MT0 8/23/2022
@MarkRotteveel 您仍然不需要使用动态 SQL,因为您可以使用WHERE (:name IS NULL OR businessName LIKE '%' || :name || '%') ...
0赞 Mark Rotteveel 8/23/2022
没错,但它并不总是导致性能良好的查询。
3赞 Mark Rotteveel 8/23/2022 #2

永远不要将值连接到这样的查询字符串中。执行查询时,请始终使用带参数的预准备语句,尤其是对于用户源值。

对于您的情况,一个简单的解决方案是为您添加的每个参数使用值列表,然后在执行之前设置为这些参数收集的值:

StringBuilder sb = new StringBuilder("select * from business where 1=1 ");
List<String> parameters = new ArrayList<>();
if (businessName != null) {
    sb.append("and businessName like '%' || ? || '%' ");
    parameters.add(businessName);
}
if (businessAddress != null) {
    sb.append("and businessAddress like '%' || ? || '%' ");
    parameters.add(businessAddress)
}

try (Connection con = DBUtil.getConnection();
     PreparedStatement pst = con.prepareStatement(sb.toString())) {
    int index = 1;
    for (String parameter : parameters) {
        pst.setString(index++, parameter);
    }
    
    try (ResultSet rs = pst.executeQuery()) {
        // ...
    }
}

如果您有不同类型的参数,请改用 and。List<Object>setObject


MT0 答案中的解决方案也有效,但并非所有数据库系统都能很好地优化该类型的查询(特别是如果您有很多此类条件),这可能会影响性能。仅在少数情况下,MT0 的解决方案更具可读性,同时具有相同/相似的性能。

评论

0赞 Eric 8/23/2022
嗨,马克,将通配符添加到变量中之间有区别吗,然后简单地使用 .我以前从未使用过(或见过)连接器......'%' || ? || '%'?||
1赞 Mark Rotteveel 8/23/2022
@Eric是SQL标准的串联运算符(有些SQL方言使用,或者要求你使用类似),它相当于Java。所以,是的,你可以在你的 Java 代码中使用裸露并执行串联。||+CONCAT"%" + someVariable + "%"?
0赞 Eric 8/23/2022
谢谢,我只是有点不确定是否有一些隐藏的技术原因不添加通配符 Java 端。
0赞 Mark Rotteveel 8/23/2022
@Eric 这主要是意见/品味
0赞 xylsh 8/23/2022 #3

除了 Mark Rotteveel 的回答之外,我认为还有其他方法可以解决您的问题。

1.转义用户的输入字符串,避免SQL注入。但这种方式并不是 100% 安全的。您可以使用 ESAPI 执行此操作,请参阅SQL_Injection_Prevention_Cheat_Sheet

2.你可以使用 ORM 框架,比如 MyBatis。例如,MyBatis 有 dynamic-sql,它允许你根据不同的条件生成不同的 sql。此外,不要使用,不要将 sql 注入拒之门外。#{xxx}${xxx}

评论

0赞 Your Common Sense 8/23/2022
为什么你会推荐一种不是100%安全的方法?
0赞 xylsh 8/24/2022
@Your常识 因为在某些受限的场景中,比如旧代码或开发时间很紧,所以更安全(虽然不是 100%)也更有意义。