¿Cómo protege SpringBoot contra ataques XSS?

Directorio de artículos

1. Ataque de secuencias de comandos entre sitios XSS

①: Introducción a las vulnerabilidades XSS

②: Clasificación de vulnerabilidad XSS

③: sugerencias de protección

2. Ataque de inyección SQL

①: Introducción a la vulnerabilidad de inyección SQL

②: sugerencias de protección

3. Cómo prevenir ataques XSS e inyección sql en SpringBoot

①: Crear la clase de filtro de solicitud Xss XssHttpServletRequestWraper

②: agregue la clase de filtrado de solicitudes XssHttpServletRequestWraper a Filter e inyéctela en el contenedor


1. Ataque de secuencias de comandos entre sitios XSS

①: Introducción a las vulnerabilidades XSS

El ataque de secuencias de comandos entre sitios (XSS) se refiere a un atacante que inserta un código de secuencia de comandos malicioso en una página web. Cuando un usuario navega por la página, el código de secuencia de comandos incrustado en la página web se analizará y ejecutará, logrando así el propósito de atacar maliciosamente el usuario. ¡Los ataques XSS están dirigidos a ataques a nivel de usuario!

②: Clasificación de vulnerabilidad XSS

XSS almacenado: XSS almacenado, persistencia, el código se almacena en el servidor, como en información personal o artículos publicados, inserte código, si no hay filtrado o el filtrado no es estricto, entonces estos códigos se almacenarán en el servidor, los usuarios Activa la ejecución del código cuando se accede a la página. Este tipo de XSS es más peligroso y es fácil provocar gusanos y robar cookies.

XSS reflectante: no persistente, es necesario engañar a los usuarios para que hagan clic en el enlace para activar el código XSS (no existe tal página ni contenido en el servidor), generalmente es fácil de aparecer en la página de búsqueda.

XSS tipo DOM: sin pasar por el backend, la vulnerabilidad DOM-XSS es Document Objeet Modeluna vulnerabilidad basada en el modelo de objetos de documento (, DOM). DOM-XSS se activa pasando parámetros a través de la URL para controlar el activador. De hecho, También es un XSS de tipo reflexión.

③: sugerencias de protección

  • Restringir la entrada del usuario, los datos del formulario especifican el tipo de valor, por ejemplo, la edad solo puede ser int, el nombre es una combinación de letras y números;

  • Realizar procesamiento de codificación html en los datos;

  • Filtrar o eliminar etiquetas html especiales;

  • Etiquetas para filtrar eventos de JavaScript.

2. Ataque de inyección SQL

①: Introducción a la vulnerabilidad de inyección SQL

La inyección SQL (SQLi) es un ataque de inyección que ejecuta sentencias SQL maliciosas. Le da al atacante control total sobre el servidor de base de datos detrás de una aplicación web insertando código SQL arbitrario en las consultas de la base de datos. Los atacantes pueden usar vulnerabilidades de inyección SQL para eludir las medidas de seguridad de las aplicaciones; pueden eludir la autenticación y autorización de páginas web o aplicaciones web y recuperar el contenido de toda la base de datos SQL; también pueden usar la inyección SQL para agregar, modificar y eliminar entradas en la base de datos Registro.

Las vulnerabilidades de inyección SQL pueden afectar cualquier sitio web o aplicación web que utilice una base de datos SQL como MySQL, Oracle, SQL Server u otras. Los delincuentes pueden utilizarlo para obtener acceso no autorizado a datos confidenciales de los usuarios: información de clientes, datos personales, secretos comerciales, propiedad intelectual, etc. Los ataques de inyección SQL son una de las vulnerabilidades de aplicaciones web más antiguas, populares y peligrosas.

②: sugerencias de protección

El uso de mybatis #{}puede prevenir eficazmente la inyección de SQL.

  • Cuando se utiliza #{}:

<select id="getBlogById" resultType="Blog" parameterType=”int”>
       select id,title,author,content
       from blog where id=#{id}
</select>

Imprima la declaración SQL ejecutada y verá que SQL se ve así:

select id,title,author,content from blog where id = ?

No importa qué parámetros se ingresen, el sql impreso es así. Esto se debe a que mybatis ha habilitado la función de precompilación. Antes de ejecutar el SQL, el SQL anterior se enviará a la base de datos para su compilación. Al ejecutar, utilice directamente el SQL compilado y reemplace el marcador de posición "?". Debido a que la inyección SQL solo puede funcionar en el proceso de compilación, ¿está precompilada como #{}? La forma de evitar el problema de la inyección SQL es muy buena.

¿Cómo logra mybatis la precompilación de SQL?

De hecho, en la parte inferior del marco, es la clase en jdbc la que está funcionando. Es una subclase de Statement con la que estamos muy familiarizados. Su objeto contiene la declaración SQL compilada. Este método "listo" no solo puede mejorar la seguridad, sino también mejorar la eficiencia al ejecutar un SQL varias veces, porque el SQL se ha compilado y no es necesario volver a compilarlo cuando se ejecuta nuevamente. PreparedStatement PreparedStatement

  • ${}al usar

<select id="orderBlog" resultType="Blog" parameterType=”map”>
       select id,title,author,content
       from blog order by ${orderParam}
</select>

Observe atentamente, el formato del parámetro en línea #{xxx}ha cambiado de " " ${xxx}. Si asignamos " orderParamid" al parámetro "", el sql se imprimirá así:

select id,title,author,contet from blog order by id

Obviamente, esto no puede evitar la inyección de SQL, y los parámetros participarán directamente en la compilación de SQL, por lo que no se pueden evitar los ataques de inyección. Pero cuando se trata de nombres de tablas y columnas dinámicas, solo ${}se puede utilizar el formato de parámetro " ", por lo que dichos parámetros deben procesarse manualmente en el código para evitar la inyección.

3. Cómo prevenir ataques XSS e inyección sql en SpringBoot

Sin más, vayamos al código.

Para ataques Xss e inyección Sql, podemos usar filtros para eliminar algunas solicitudes según las necesidades comerciales.

①: Crear clase de filtrado de solicitudes Xss XssHttpServletRequestWraper

El código se muestra a continuación:

public class XssHttpServletRequestWraper extends HttpServletRequestWrapper {

    Logger log = LoggerFactory.getLogger(this.getClass());

    public XssHttpServletRequestWraper() {
        super(null);
    }

    public XssHttpServletRequestWraper(HttpServletRequest httpservletrequest) {
        super(httpservletrequest);
    }

 //过滤springmvc中的 @RequestParam 注解中的参数
    public String[] getParameterValues(String s) {

        String str[] = super.getParameterValues(s);
        if (str == null) {
            return null;
        }
        int i = str.length;
        String as1[] = new String[i];
        for (int j = 0; j < i; j++) {
            //System.out.println("getParameterValues:"+str[j]);
            as1[j] = cleanXSS(cleanSQLInject(str[j]));
        }
        log.info("XssHttpServletRequestWraper净化后的请求为:==========" + as1);
        return as1;
    }

 //过滤request.getParameter的参数
    public String getParameter(String s) {
        String s1 = super.getParameter(s);
        if (s1 == null) {
            return null;
        } else {
            String s2 = cleanXSS(cleanSQLInject(s1));
            log.info("XssHttpServletRequestWraper净化后的请求为:==========" + s2);
            return s2;
        }
    }

 //过滤请求体 json 格式的
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) { }
        };
    }

    public   String inputHandlers(ServletInputStream servletInputStream){
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (servletInputStream != null) {
                try {
                    servletInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return  cleanXSS(sb.toString ());
    }

    public String cleanXSS(String src) {
        String temp = src;

        src = src.replaceAll("<", "<").replaceAll(">", ">");
        src = src.replaceAll("\\(", "(").replaceAll("\\)", ")");
        src = src.replaceAll("'", "'");
        src = src.replaceAll(";", ";");
        // 新增
        /**-----------------------start--------------------------*/
        src = src.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
        src = src.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41");
        src = src.replaceAll("eval\\((.*)\\)", "");
        src = src.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        src = src.replaceAll("script", "");
        src = src.replaceAll("link", "");
        src = src.replaceAll("frame", "");
        /**-----------------------end--------------------------*/
        Pattern pattern = Pattern.compile("(eval\\((.*)\\)|script)",
                Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(src);
        src = matcher.replaceAll("");

        pattern = Pattern.compile("[\\\"\\'][\\s]*javascript:(.*)[\\\"\\']",
                Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(src);
        src = matcher.replaceAll("\"\"");

        // 增加脚本
        src = src.replaceAll("script", "").replaceAll(";", "")
                /*.replaceAll("\"", "").replaceAll("@", "")*/
                .replaceAll("0x0d", "").replaceAll("0x0a", "");

        if (!temp.equals(src)) {
            // System.out.println("输入信息存在xss攻击!");
            // System.out.println("原始输入信息-->" + temp);
            // System.out.println("处理后信息-->" + src);

            log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
            log.error("原始输入信息-->" + temp);

            throw new CustomerException("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
        }
        return src;
    }

    //输出
    public void outputMsgByOutputStream(HttpServletResponse response, String msg) throws IOException {
        ServletOutputStream outputStream = response.getOutputStream(); //获取输出流
        response.setHeader("content-type", "text/html;charset=UTF-8"); //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
        byte[] dataByteArr = msg.getBytes("UTF-8");// 将字符转换成字节数组,指定以UTF-8编码进行转换
        outputStream.write(dataByteArr);// 使用OutputStream流向客户端输出字节数组
    }

    // 需要增加通配,过滤大小写组合
    public String cleanSQLInject(String src) {
        String lowSrc = src.toLowerCase();
        String temp = src;
        String lowSrcAfter = lowSrc.replaceAll("insert", "forbidI")
                .replaceAll("select", "forbidS")
                .replaceAll("update", "forbidU")
                .replaceAll("delete", "forbidD").replaceAll("and", "forbidA")
                .replaceAll("or", "forbidO");

        if (!lowSrcAfter.equals(lowSrc)) {
            log.error("sql注入检查:输入信息存在SQL攻击!");
            log.error("原始输入信息-->" + temp);
            log.error("处理后信息-->" + lowSrc);
            throw new CustomerException("sql注入检查:参数含有非法攻击字符,已禁止继续访问!!");
        }
        return src;
    }
}

②: agregue la clase de filtrado de solicitudes  XssHttpServletRequestWraper a Filter e inyéctela en el contenedor

@Component
public class XssFilter implements Filter {

    Logger log  = LoggerFactory.getLogger(this.getClass());

    // 忽略权限检查的url地址
    private final String[] excludeUrls = new String[]{
            "null"
    };

    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;

        String pathInfo = req.getPathInfo() == null ? "" : req.getPathInfo();
        //获取请求url的后两层
        String url = req.getServletPath() + pathInfo;
        //获取请求你ip后的全部路径
        String uri = req.getRequestURI();
        //注入xss过滤器实例
        XssHttpServletRequestWraper reqW = new XssHttpServletRequestWraper(req);

        //过滤掉不需要的Xss校验的地址
        for (String str : excludeUrls) {
            if (uri.indexOf(str) >= 0) {
                arg2.doFilter(arg0, response);
                return;
            }
        }
        //过滤
        arg2.doFilter(reqW, response);
    }
    public void destroy() {
    }
    public void init(FilterConfig filterconfig1) throws ServletException {
    }
}

El código anterior ya puede completar el filtrado de los parámetros de la solicitud y el cuerpo de la solicitud JSON, pero hay otras formas de implementar el cuerpo de la solicitud JSON. Si está interesado, consulte la extensión a continuación.

Para obtener más contenido, preste atención a la cuenta oficial [Estilo del programador] para obtener contenido más interesante.

Extensión: también puede reescribir el cuerpo de la solicitud Json en primavera  MappingJackson2HttpMessageConverter para filtrar

Debido a que el cuerpo de la solicitud pasará por una transformación cuando entre y salga de Contoroller  MappingJackson2HttpMessageConverter para convertir el cuerpo de la solicitud al formato json que necesitamos, ¡puede hacer algunas modificaciones aquí!

@Configuration
public class MyConfiguration {

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
        //自定义转换器
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

        //转换器日期格式设置
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(smt);
        converter.setObjectMapper(objectMapper);

        //转换器添加自定义Module扩展,主要是在这里做XSS过滤的!!,其他的是其他业务,不用看
        SimpleModule simpleModule = new SimpleModule();
        //添加过滤逻辑类!
        simpleModule.addDeserializer(String.class,new StringDeserializer());
        converter.getObjectMapper().registerModule(simpleModule);

        //设置中文编码格式
        List<MediaType> list = new ArrayList<>();
        list.add(MediaType.APPLICATION_JSON_UTF8);
        converter.setSupportedMediaTypes(list);

        return converter;
    }

}

La verdadera clase de lógica de filtrado : StringDeserializer

//检验请求体的参数
@Component
public class StringDeserializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        String str = jsonParser.getText().trim();
        //sql注入拦截
        if (sqlInject(str)) {
          throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!");
        }

        return xssClean(str);

    }

    public boolean sqlInject(String str) {

        if (StringUtils.isEmpty(str)) {
            return false;
        }

        //去掉'|"|;|\字符
        str = org.apache.commons.lang3.StringUtils.replace(str, "'", "");
        str = org.apache.commons.lang3.StringUtils.replace(str, "\"", "");
        str = org.apache.commons.lang3.StringUtils.replace(str, ";", "");
        str = org.apache.commons.lang3.StringUtils.replace(str, "\\", "");

        //转换成小写
        str = str.toLowerCase();

        //非法字符
        String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alert","alter", "drop"};

        //判断是否包含非法字符
        for (String keyword : keywords) {
            if (str.indexOf(keyword) != -1) {
                return true;
            }
        }
        return false;

    }

    //xss攻击拦截

    public String xssClean(String value) {
        if (value == null || "".equals(value)) {
            return value;
        }

        //非法字符
        String[] keywords = {"<", ">", "<>", "()", ")", "(", "javascript:", "script","alter", "''","'"};
        //判断是否包含非法字符
        for (String keyword : keywords) {
            if (value.indexOf(keyword) != -1) {
               throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!");
            }
        }

        return value;
    }
}

El uso de este formulario también puede completar el filtrado del cuerpo de la solicitud json, pero personalmente recomiendo usar  XssHttpServletRequestWraper el formulario para completar el filtrado xss. !

Para obtener más contenido, preste atención a la cuenta oficial [Estilo del programador] para obtener contenido más interesante. 

Supongo que te gusta

Origin blog.csdn.net/dreaming317/article/details/129787078
Recomendado
Clasificación