Mybatis-plus sql injection and prevention of sql injection

Mybatis-plus sql injection and prevention of sql injection

1. What is SQL injection?

SQL injection is a code injection technique used to attack data-driven applications. Malicious SQL statements are inserted into executed SQL statements to change query results, for example: OR 1=1 or; drop table sys_user; etc.

2. How does mybatis prevent sql injection

The sql statements we write in mybatis can only be completed in xml. When we write sql, we will use the two expressions #{} and ${}. What is the difference between #{} and ${}? Below I will use two SQL statement examples to illustrate.

<select id="selectUserByUserName" parameterType="java.lang.String" resultType="com.domain.UserInfo">
	SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER 
	<where>
		USER_ID= #{userName,jdbcType=VARCHAR} 
	</where> 
</select>
<select id="selectUserByUserName" parameterType="java.lang.String" resultType="com.domain.UserInfo">
	SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER 
	<where>
		USER_NAME= ${userName,jdbcType=VARCHAR} 
	</where> 
</select>
  • #{}In the first method used in the SQL statement , #{}when the incoming data is a character string, " "double quotes will be used to enclose the value. Example: For example, if
    the value passed in by userName is fetched, the result obtained is that even if the command to delete the table is passed in, it will not be executed, because it will be regarded as a completed string for value matching. 9;DROP TABLE SYS_USER;#{}USER_NAME="9;DROP TABLE SYS_USER;"9;DROP TABLE SYS_USER;
  • The second SQL ${}method to get the value becomes USER_NAME=9;DROP TABLE SYS_USER; , because ${}the value is directly spliced ​​behind the SQL statement to make it SQL, so the value is directly spliced ​​behind the SQL statement, so ${}there is a risk of SQL injection. Pay attention to manual processing when using it.

1. The difference between #{} and ${}

  • #{}: It is parsed as a JDBC precompiled statement, and a #{}is parsed as a parameter placeholder? , #{}which treats the incoming data as one 字符串and adds one to the automatically incoming data 双引号. For example: WHERE USER_NAME =#{username}, if the value passed in is 9, then the value when parsed into sql WHERE USER_NAME =“9”is, if the value passed in is 12345678, the sql parsed into is WHERE USER_NAME =“12345678”,
  • ${}Only for a pure string replacement, ${}the variables passed in by way are directly spliced ​​in sql. For example: WHERE USER_NAME = ${username}, if the value passed in is 9, then the value when it is parsed into sql WHERE USER_NAME =9;If the value passed in is ;DROP TABLE SYS_USER;, then the sql parsed into is: SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER WHERE USER_NAME="9;DROP TABLE SYS_USER;So like ORDER BY or GROUP BY, you can use ${}.
  • #{}The bottom layer of the method adopts the precompiled method PreparedStatement, which can largely prevent SQL injection, because SQL injection occurs at compile time; the ${}bottom layer of the method is only Statement, which cannot prevent Sql injection.
    $The method is generally used to pass in database objects, such as passing in table names

2. The difference between PreparedStatement and Statement

① When PreparedStatement executes a sql command, the command will be parsed and compiled by the database first, and then put into the command buffer. Then, when each same sql command is executed, if a compile command is issued in the buffer, it will be It will not be parsed and compiled again, so that it can be reused. During compilation, PreparedStatement will parse each #{} mark symbol into a parameter parameter placeholder?, and the variable passed in is used as a parameter, and the sql statement will not be modified, so as to prevent SQL injection attacks. ''
②Statement directly sends Sql commands to the database for operation, and cannot intercept SQL injection attacks, because SQL injection occurs at runtime. Statement parses and compiles SQL commands every time, which increases the overhead of a large database, so it is not as efficient as PreparedStatement.

3. What is precompilation

Precompilation is to do some code text replacement work. It is the first work of the whole compilation process. Processing instructions starting with #, such as copying the file code contained in #include, replacing #define macro definition, conditional compilation, etc., is the stage of preparatory work for compilation. It mainly deals with the precompiled instructions starting with #. The precompiled instructions indicate the operations performed by the compiler before the program is officially compiled, and can be placed anywhere in the program. And SQL injection can only happen at runtime.

4. Reasons for mybaits-plus sql injection

The PaginationInterceptor in Mybatisplus is mainly used to handle the physical paging of the database and avoid memory paging.
Analyzing the source code of PaginationInterceptor can be found

SQL Injection in the Orderby Scenario
As mentioned above, Orderby will be used in pagination, because Orderby dynamic query cannot be precompiled, so there will be an injection risk if it does not pass the security check. PaginationInnerInterceptor mainly implements orderby by setting the properties in the com.baomidou.mybatisplus.extension.plugins.pagination.page object. It is mainly the call of the following functions. Because SQL splicing is used directly, it is necessary to secure the column names for sorting examine:

page.setAscs();
page.setDescs();

source code:

It can be seen that pagination is done through string splicing, so there is a risk of SQL injection

 public static String concatOrderBy(String originalSql, IPage<?> page, boolean orderBy) {
    
    
        if (!orderBy || !ArrayUtils.isNotEmpty(page.ascs()) && !ArrayUtils.isNotEmpty(page.descs())) {
    
    
            return originalSql;
        } else {
    
    
            StringBuilder buildSql = new StringBuilder(originalSql);
            String ascStr = concatOrderBuilder(page.ascs(), " ASC");
            String descStr = concatOrderBuilder(page.descs(), " DESC");
            if (StringUtils.isNotEmpty(ascStr) && StringUtils.isNotEmpty(descStr)) {
    
    
                ascStr = ascStr + ", ";
            }

            if (StringUtils.isNotEmpty(ascStr) || StringUtils.isNotEmpty(descStr)) {
    
    
                buildSql.append(" ORDER BY ").append(ascStr).append(descStr);
            }

            return buildSql.toString();
        }
    }

3. How does Mybatis-plus prevent sql injection

When using the paging controller, check the ascs and descs of the incoming paging plug-in to determine whether there are illegal characters. If so, it will prompt that the parameter contains an illegal column name: create_time aaaa Example
:

The util of the check field:

package com.koal.util;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.koal.exception.BizException;
import com.koal.web.ErrorCode;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Pattern;

/**
 * @author sunrj
 */
public class RegexUtils {
    
    

    /**
     * 对Page校验防止sql注入
     *
     * @param
     */
    public static void verifyPageFileld(Page page) {
    
    

        //asc校验
        Optional.ofNullable(page.ascs()).ifPresent(ascs ->  {
    
    
            Arrays.asList(ascs).forEach(asc -> {
    
    
                boolean rightfulString = RegexUtils.isRightfulString(asc);
                if (!rightfulString) {
    
    
                    throw new BizException(ErrorCode.COMMON_VERIFY_ERROR.getCode(), "ascs参数中含有非法的列名:" + asc);
                }
            });
        });
        //desc校验
        Optional.ofNullable(page.descs()).ifPresent(descs ->  {
    
    
            Arrays.asList(descs).forEach(desc -> {
    
    
                boolean rightfulString = RegexUtils.isRightfulString(desc);
                if (!rightfulString) {
    
    
                    throw new BizException("10011", "desc参数中含有非法的列名:" + desc);
                }
            });
        });
    }


    /**
     * 判断是否为合法字符(a-zA-Z0-9-_)
     *
     * @param text
     * @return
     */
    public static boolean isRightfulString(String text) {
    
    
        return match(text, "^[A-Za-z0-9_-]+$");
    }

    /**
     * 正则表达式匹配
     *
     * @param text 待匹配的文本
     * @param reg  正则表达式
     * @return
     */
    private static boolean match(String text, String reg) {
    
    
        if (StringUtils.isBlank(text) || StringUtils.isBlank(reg)) {
    
    
            return false;
        }
        return Pattern.compile(reg).matcher(text).matches();
    }


}

The controller checks the fields in the page:

@GetMapping
	@ApiOperation(value = "查询用户列表", notes = "查询用户列表")
	public ServerResponse<IPage<Account>> queryAccount(Page<Account> page) {
    
    
	    //校验page中的字段,防止sql注入
		RegexUtils.verifyPageFileld(page);
		return ServerResponse.successMethod(accountService.query(page));
	}

result:

POST http://127.0.0.1:8080/account?current=1&size=10&ascs=create_time;DROP TABLE tb_account;


结果:
{
    
    
    "code": "10011",
    "msg": "ascs参数中含有非法的列名:create_time;DROP TABLE ag_account_info;",
    "timestamp": 1653547051505
}

Guess you like

Origin blog.csdn.net/sunrj_niu/article/details/124984994