Inyección mybatis-plus sql y prevención de la inyección sql

Inyección mybatis-plus sql y prevención de la inyección sql

1. ¿Qué es la inyección SQL?

La inyección SQL es una técnica de inyección de código que se utiliza para atacar aplicaciones basadas en datos. Se insertan sentencias SQL maliciosas en sentencias SQL ejecutadas para cambiar los resultados de la consulta, por ejemplo: OR 1=1 o; drop table sys_user; etc.

2. ¿Cómo evita mybatis la inyección de sql?

Las sentencias sql que escribimos en mybatis solo se pueden completar en xml, cuando escribamos sql, usaremos las dos expresiones #{} y ${}. ¿Cuál es la diferencia entre #{} y ${}? A continuación, usaré dos ejemplos de instrucciones 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>
  • #{}En el primer método utilizado en la instrucción SQL , #{}cuando los datos entrantes son una cadena de caracteres, " "se utilizarán comillas dobles para encerrar el valor. Ejemplo: por ejemplo, si
    se recupera el valor pasado por nombre de usuario, el resultado obtenido es que incluso si se pasa el comando para eliminar la tabla, no se ejecutará, porque se considerará como una cadena completa para la coincidencia de valores . 9;DROP TABLE SYS_USER;#{}USER_NAME="9;DROP TABLE SYS_USER;"9;DROP TABLE SYS_USER;
  • El segundo método SQL ${}para obtener el valor se convierte en USER_NAME=9;DROP TABLE SYS_USER; , porque ${}el valor se empalma directamente detrás de la declaración SQL para convertirlo en SQL, por lo que el valor se empalma directamente detrás de la declaración SQL, por lo que ${}existe el riesgo de una inyección SQL. atención al procesamiento manual al usarlo.

1. La diferencia entre #{} y ${}

  • #{}: ¿Se analiza como una declaración precompilada de JDBC, y se #{}analiza como un marcador de posición de parámetro? , #{}que trata los datos entrantes como uno 字符串y agrega uno a los datos entrantes automáticamente 双引号. Por ejemplo: WHERE USER_NAME =#{username}si el valor pasado es 9, entonces el valor cuando se analiza en sql WHERE USER_NAME =“9”es, si el valor pasado es 12345678, el sql analizado es WHERE USER_NAME =“12345678”,
  • ${}Solo para un reemplazo de cadena pura, ${}las variables pasadas por medio se empalman directamente en sql. Por ejemplo: WHERE USER_NAME = ${username}, si el valor pasado es 9, entonces el valor cuando se analiza en sql WHERE USER_NAME =9;Si el valor pasado es ;DROP TABLE SYS_USER;, entonces el sql analizado es: SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER WHERE USER_NAME="9;DROP TABLE SYS_USER;Entonces, como ORDER BY o GROUP BY, puede usar ${}.
  • #{}La capa inferior del método adopta el método precompilado PreparedStatement, que puede evitar en gran medida la inyección SQL, porque la inyección SQL se produce en el momento de la compilación; la capa ${}inferior del método es solo Sentencia, que no puede evitar la inyección Sql.
    $El método generalmente se usa para pasar objetos de la base de datos, como pasar nombres de tablas.

2. La diferencia entre Declaración Preparada y Declaración

① Cuando PreparedStatement ejecuta un comando sql, la base de datos analizará y compilará el comando primero, y luego lo colocará en el búfer de comandos. Luego, cuando se ejecute cada mismo comando sql, si se emite un comando de compilación en el búfer, be No será analizado y compilado de nuevo, para que pueda ser reutilizado. Durante la compilación, PreparedStatement analizará cada símbolo de marca #{} en un marcador de posición de parámetro de parámetro?, y la variable pasada se usa como parámetro, y la instrucción sql no se modificará, para evitar ataques de inyección SQL. ''
②Statement envía directamente comandos Sql a la base de datos para la operación y no puede interceptar ataques de inyección SQL, porque la inyección SQL ocurre en tiempo de ejecución. La declaración analiza y compila los comandos SQL cada vez, lo que aumenta la sobrecarga de una base de datos grande, por lo que no es tan eficiente como PreparedStatement.

3. ¿Qué es la precompilación?

La precompilación es para hacer algún trabajo de reemplazo de texto de código. Es el primer trabajo de todo el proceso recopilatorio. Las instrucciones de procesamiento que comienzan con #, como copiar el código de archivo contenido en #include, reemplazar #define definición de macro, compilación condicional, etc., es la etapa del trabajo preparatorio para la compilación. Se ocupa principalmente de las instrucciones precompiladas que comienzan con #. Las instrucciones precompiladas indican las operaciones realizadas por el compilador antes de que el programa se compile oficialmente y se pueden colocar en cualquier parte del programa. Y la inyección de SQL solo puede ocurrir en tiempo de ejecución.

4. Razones para la inyección mybaits-plus sql

PaginationInterceptor en Mybatisplus se usa principalmente para manejar la paginación física de la base de datos y evitar la paginación de memoria.
Analizando el código fuente de PaginationInterceptor se puede encontrar

Inyección SQL en el escenario Orderby
Como se mencionó anteriormente, Orderby se usará en la paginación, porque la consulta dinámica Orderby no se puede precompilar, por lo que habrá un riesgo de inyección si no pasa la verificación de seguridad. PaginationInnerInterceptor implementa principalmente orderby configurando las propiedades en el objeto com.baomidou.mybatisplus.extension.plugins.pagination.page. Es principalmente la llamada de las siguientes funciones. Debido a que el empalme de SQL se usa directamente, es necesario asegurar los nombres de columna para clasificar examinar:

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

código fuente:

Se puede ver que la paginación se realiza a través del empalme de cadenas, por lo que existe el riesgo de inyección 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. ¿Cómo previene Mybatis-plus la inyección de sql?

Cuando utilice el controlador de paginación, verifique los ascs y descs del complemento de paginación entrante para determinar si hay caracteres no válidos. Si es así, le indicará que el parámetro contiene un nombre de columna no válido: create_time aaaa Ejemplo
:

La utilidad del campo de verificación:

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


}

El controlador verifica los campos en la 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
}

Supongo que te gusta

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