简体   繁体   English

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

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

I need to implement a fuzzy search for two fields, businessName and businessAddress .我需要对两个字段businessNamebusinessAddress实现模糊搜索。 Both of them can be null .它们都可以是null If one field is null , search should be based on the other field.如果一个字段是null ,则应基于另一字段进行搜索。

To be specific,再具体一点,

  • if businessName="name" and businessAddress="address" then execute select * from business where businessName like '%name%' and businessAddress like '%address%'如果businessName="name"businessAddress="address"然后执行select * from business where businessName like '%name%' and businessAddress like '%address%'
  • if businessName=null and businessAddress="address" then execute select * from business where businessAddress like '%address%'如果businessName=nullbusinessAddress="address"然后执行select * from business where businessAddress like '%address%'
  • if businessName=null and businessAddress=null then execute select * from business如果businessName=nullbusinessAddress=null然后select * from business

My code:我的代码:

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();
} ...

Apparently it's in danger of SQL-injection attack.显然它有受到 SQL 注入攻击的危险。 I know method prepareStatement.setString() can avoid attack, but number of fields is uncertain before verification.我知道方法prepareStatement.setString()可以避免攻击,但是在验证之前字段的数量是不确定的。

How can I modify it?我该如何修改它? Separate method for each case or code like below seem ugly.每个案例的单独方法或如下代码看起来很难看。

if(businessName!=null){
    if(businessAddress!=null){
        sql = ...;
    }else {
        sql = ...;
    }
else{
...

Never, ever, concatenate values into a query string like that.永远不要将值连接到这样的查询字符串中。 Always use prepared statements with parameters when executing queries, especially with user-sourced values.执行查询时,始终使用带参数的预处理语句,尤其是使用用户来源的值。

A simple solution for your case is to use a list of values for each parameter you add, and then set the values collected for those parameters before execute:对于您的情况,一个简单的解决方案是为您添加的每个参数使用值列表,然后在执行之前设置为这些参数收集的值:

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()) {
        // ...
    }
}

If you have parameters of varying types, use a List<Object> and setObject instead.如果您有不同类型的参数,请改用List<Object>setObject


The solution in the answer by MT0 also works, but not all database systems optimize that type of query well (especially if you have a lot of such conditions), which might affect performance. MT0 答案中的解决方案也有效,但并非所有数据库系统都能很好地优化该类型的查询(特别是如果您有很多这样的条件),这可能会影响性能。 For only a few conditions, the solution by MT0 is more readable, while having same/similar performance.仅在少数情况下,MT0 的解决方案更具可读性,同时具有相同/相似的性能。

You do not need dynamic SQL and can use bind variables in the query:您不需要动态 SQL 并且可以在查询中使用绑定变量:

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

(Assuming || is the string concatenation operator for your SQL dialect.) (假设||是您的 SQL 方言的字符串连接运算符。)

And then use a prepared statement and bind businessName and businessAddress to the variables.然后使用准备好的语句并将businessNamebusinessAddress绑定到变量。

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

(Add exception handling.) (添加异常处理。)


Or, if your SQL dialect requires you to handle NULL separately from the LIKE then:或者,如果您的 SQL 方言要求您将NULLLIKE分开处理,则:

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

and use named bind variables :name and :addr (or use ? pass the values twice).并使用命名绑定变量:name:addr (或使用?传递值两次)。

Except Mark Rotteveel' answer, I think there are 2 additional methods to solve your question.除了 Mark Rotteveel 的回答,我认为还有另外两种方法可以解决您的问题。

  1. Escape user's input string to avoid sql-injection.转义用户的输入字符串以避免 sql 注入。 But this way is not 100% secure .You can use ESAPI to do this, please refer to SQL_Injection_Prevention_Cheat_Sheet .但是这种方式不是 100% 安全的。您可以使用 ESAPI 来执行此操作,请参阅SQL_Injection_Prevention_Cheat_Sheet

  2. You can use ORM framework,like MyBatis.您可以使用 ORM 框架,如 MyBatis。 For example, MyBatis have dynamic-sql ,it allows you generate different sql according to different conditions.比如MyBatis有dynamic-sql ,它可以让你根据不同的条件生成不同的sql。 In addition, use #{xxx} not ${xxx} to keep sql-injection away.此外,使用#{xxx}而不是${xxx}来防止 sql 注入。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM