Injeção de sql Mybatis-plus e prevenção de injeção de sql

Injeção de sql Mybatis-plus e prevenção de injeção de sql

1. O que é injeção de SQL?

A injeção de SQL é uma técnica de injeção de código usada para atacar aplicativos orientados a dados. Instruções SQL maliciosas são inseridas em instruções SQL executadas para alterar os resultados da consulta, por exemplo: OR 1=1 ou; drop table sys_user; etc.

2. Como o mybatis impede a injeção de SQL

As instruções sql que escrevemos em mybatis só podem ser concluídas em xml. Ao escrever sql, usaremos as duas expressões #{} e ${}. Qual é a diferença entre #{} e ${}? Abaixo, usarei dois exemplos de instruções SQL para ilustrar.

<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>
  • #{}No primeiro método usado na instrução SQL , #{}quando os dados de entrada forem uma cadeia de caracteres, " "serão usadas aspas duplas para delimitar o valor. Exemplo: Por exemplo, se
    o valor passado por userName for buscado, o resultado obtido é que mesmo que o comando para excluir a tabela seja passado, ele não será executado, pois será considerado como uma string completa para correspondência de valor . 9;DROP TABLE SYS_USER;#{}USER_NAME="9;DROP TABLE SYS_USER;"9;DROP TABLE SYS_USER;
  • O segundo ${}método SQL para obter o valor se torna USER_NAME=9;DROP TABLE SYS_USER; , porque ${}o valor é emendado diretamente atrás da instrução SQL para torná-lo SQL, portanto, o valor é emendado diretamente atrás da instrução SQL, portanto, ${}há risco de injeção de SQL. atenção ao processamento manual ao usá-lo.

1. A diferença entre #{} e ${}

  • #{}: é analisado como uma instrução JDBC pré-compilada e a #{}é analisado como um espaço reservado para parâmetro? , #{}que trata os dados recebidos como um 字符串e adiciona um aos dados recebidos automaticamente 双引号. Por exemplo: WHERE USER_NAME =#{username}, se o valor passado é 9, então o valor quando analisado em sql WHERE USER_NAME =“9”é, se o valor passado é 12345678, o sql analisado é WHERE USER_NAME =“12345678”,
  • ${}Apenas para uma substituição de string pura, ${}as variáveis ​​passadas por via são emendadas diretamente no sql. Por exemplo: WHERE USER_NAME = ${username}, se o valor passado é 9, então o valor quando ele é analisado em sql WHERE USER_NAME =9;Se o valor passado é ;DROP TABLE SYS_USER;, então o sql analisado é: SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER WHERE USER_NAME="9;DROP TABLE SYS_USER;Assim como ORDER BY ou GROUP BY, você pode usar ${}.
  • #{}A camada inferior do método adota o método pré-compilado PreparedStatement, que pode impedir amplamente a injeção de SQL, porque a injeção de SQL ocorre no tempo de compilação; a ${}camada inferior do método é apenas a instrução, que não pode impedir a injeção de SQL.
    $O método geralmente é usado para passar objetos de banco de dados, como passar nomes de tabelas

2. A diferença entre PreparedStatement e Statement

① Quando PreparedStatement executa um comando sql, o comando será analisado e compilado primeiro pelo banco de dados e, em seguida, colocado no buffer de comando. Então, quando cada comando sql for executado, se um comando de compilação for emitido no buffer, ele será be Não será analisado e compilado novamente, para que possa ser reutilizado. Durante a compilação, PreparedStatement analisará cada símbolo de marca #{} em um espaço reservado de parâmetro de parâmetro?, e a variável passada será usada como um parâmetro, e a instrução sql não será modificada para evitar ataques de injeção de SQL. ''
②Statement envia comandos Sql diretamente para o banco de dados para operação e não pode interceptar ataques de injeção SQL, porque a injeção SQL ocorre em tempo de execução. A instrução analisa e compila comandos SQL todas as vezes, o que aumenta a sobrecarga de um banco de dados grande, portanto, não é tão eficiente quanto PreparedStatement.

3. O que é pré-compilação

A pré-compilação é fazer algum trabalho de substituição de texto de código. É o primeiro trabalho de todo o processo de compilação. O processamento de instruções começando com #, como copiar o código do arquivo contido em #include, substituir #define definição de macro, compilação condicional etc., é a etapa do trabalho preparatório para a compilação. Ele lida principalmente com as instruções pré-compiladas começando com #. As instruções pré-compiladas indicam as operações realizadas pelo compilador antes que o programa seja oficialmente compilado e podem ser colocadas em qualquer lugar do programa. E a injeção de SQL só pode acontecer em tempo de execução.

4. Razões para injeção de mybaits-plus sql

O PaginationInterceptor no Mybatisplus é usado principalmente para lidar com a paginação física do banco de dados e evitar a paginação da memória.
Analisando o código-fonte do PaginationInterceptor pode ser encontrado

SQL Injection no Cenário Orderby
Conforme mencionado acima, Orderby será usado na paginação, porque a consulta dinâmica Orderby não pode ser pré-compilada, então haverá um risco de injeção se ela não passar na verificação de segurança. PaginationInnerInterceptor implementa principalmente orderby definindo as propriedades no objeto com.baomidou.mybatisplus.extension.plugins.pagination.page. É principalmente a chamada das seguintes funções. Como a emenda SQL é usada diretamente, é necessário proteger os nomes das colunas para classificação, examine:

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

Código fonte:

Pode-se ver que a paginação é feita através de emenda de strings, então existe o risco de injeção de SQL

 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. Como o Mybatis-plus impede a injeção de SQL

Ao usar o controlador de paginação, verifique os ascs e descs do plug-in de paginação de entrada para determinar se há caracteres ilegais. Se houver, ele solicitará que o parâmetro contenha um nome de coluna ilegal: create_time aaaa Exemplo
:

O util do campo de verificação:

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


}

O controlador verifica os campos na página:

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

resultado:

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
}

Acho que você gosta

Origin blog.csdn.net/sunrj_niu/article/details/124984994
Recomendado
Clasificación