首页

SQL 注入拦截过滤器GlobalSqlInjectionFilter

标签:sql注入     发布时间:2024-09-07   
package cn.herodotus.dante.gateway.filter;@b@@b@import cn.herodotus.dante.gateway.utils.WebFluxUtils;@b@import cn.herodotus.engine.assistant.core.utils.protect.SqlInjectionUtils;@b@import cn.herodotus.engine.assistant.definition.constants.ErrorCodes;@b@import cn.herodotus.engine.assistant.definition.domain.Result;@b@import io.netty.buffer.ByteBufAllocator;@b@import org.apache.commons.lang3.StringUtils;@b@import org.apache.hc.core5.http.HttpStatus;@b@import org.jetbrains.annotations.NotNull;@b@import org.slf4j.Logger;@b@import org.slf4j.LoggerFactory;@b@import org.springframework.cloud.gateway.filter.GatewayFilterChain;@b@import org.springframework.cloud.gateway.filter.GlobalFilter;@b@import org.springframework.core.Ordered;@b@import org.springframework.core.io.buffer.DataBuffer;@b@import org.springframework.core.io.buffer.DataBufferUtils;@b@import org.springframework.core.io.buffer.NettyDataBufferFactory;@b@import org.springframework.http.HttpHeaders;@b@import org.springframework.http.HttpMethod;@b@import org.springframework.http.MediaType;@b@import org.springframework.http.server.reactive.ServerHttpRequest;@b@import org.springframework.http.server.reactive.ServerHttpRequestDecorator;@b@import org.springframework.stereotype.Component;@b@import org.springframework.web.server.ServerWebExchange;@b@import reactor.core.publisher.Flux;@b@import reactor.core.publisher.Mono;@b@@b@import java.net.URI;@b@import java.nio.charset.StandardCharsets;@b@import java.util.concurrent.atomic.AtomicReference;@b@@b@/**@b@ * <p>Description: SQL 注入拦截过滤器 </p>@b@ * <p>@b@ * 对xss字符集的转换会导致会第三方平台接入的接口出现一些列问题,尤其是需要参数签名验签的接口,因为参数的变化导致验签不成功@b@ * 对于第三方平台(尤其时强势的第三方),我们往往无法要求第三方按照我们的参数规则传递参数,这类的接口会包含sql注入的关键字@b@ * 在请求重构过程,可能会改变参数的结构,会导致验签失败@b@ * 对post请求,虽然目前前后端大多交互都是通过Json,但如有特殊请求参数可能是非Json格式参数,需要多改类型参数进行兼容@b@ *@b@ * @author : gengwei.zheng@b@ * @date : 2021/9/1 8:47@b@ */@b@@Component@b@public class GlobalSqlInjectionFilter implements GlobalFilter, Ordered {@b@@b@    private static final Logger log = LoggerFactory.getLogger(GlobalSqlInjectionFilter.class);@b@@b@    @Override@b@    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {@b@@b@        log.debug("[Herodotus] |- Global SQL Injection Filter in use!");@b@@b@        ServerHttpRequest serverHttpRequest = exchange.getRequest();@b@        HttpMethod method = serverHttpRequest.getMethod();@b@        String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);@b@@b@        URI uri = exchange.getRequest().getURI();@b@@b@        if (isGetRequest(method)) {@b@            String rawQuery = uri.getRawQuery();@b@            if (StringUtils.isBlank(rawQuery)) {@b@                return chain.filter(exchange);@b@            }@b@@b@            log.debug("[Herodotus] |- The original request parameter is [{}]", rawQuery);@b@            // 执行XSS清理@b@            boolean isSQLInjection = SqlInjectionUtils.checkForGet(rawQuery);@b@@b@            // 如果存在sql注入,直接拦截请求@b@            if (isSQLInjection) {@b@                return sqlInjectionResponse(exchange, uri);@b@            }@b@            // 不对参数做任何处理@b@            return chain.filter(exchange);@b@        }@b@@b@        //post请求时,如果是文件上传之类的请求,不修改请求消息体@b@        if (isPostRequest(method, contentType)) {@b@            // 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory@b@@b@            //从请求里获取Post请求体@b@            String bodyString = resolveBodyFromRequest(serverHttpRequest);@b@@b@            if (StringUtils.isNotBlank(bodyString)) {@b@@b@                boolean isSQLInjection;@b@                if (WebFluxUtils.isJsonMediaType(contentType)) {@b@                    //如果MediaType是json才执行json方式验证@b@                    isSQLInjection = SqlInjectionUtils.checkForPost(bodyString);@b@                } else {@b@                    //form表单方式,需要走get请求@b@                    isSQLInjection = SqlInjectionUtils.checkForGet(bodyString);@b@                }@b@@b@                //  如果存在sql注入,直接拦截请求@b@                if (isSQLInjection) {@b@                    return sqlInjectionResponse(exchange, uri);@b@                }@b@@b@                ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();@b@                // 重新构造body@b@                byte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);@b@                DataBuffer bodyDataBuffer = toDataBuffer(newBytes);@b@                Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);@b@@b@                HttpHeaders headers = new HttpHeaders();@b@                headers.putAll(exchange.getRequest().getHeaders());@b@@b@@b@                // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度@b@                int length = bodyString.getBytes().length;@b@                headers.remove(HttpHeaders.CONTENT_LENGTH);@b@                headers.setContentLength(length);@b@@b@                // 设置CONTENT_TYPE@b@                if (StringUtils.isNotBlank(contentType)) {@b@                    headers.set(HttpHeaders.CONTENT_TYPE, contentType);@b@                }@b@@b@                // 由于post的body只能订阅一次,由于上面代码中已经订阅过一次body。所以要再次封装请求到request才行,不然会报错请求已经订阅过@b@                request = new ServerHttpRequestDecorator(request) {@b@                    @NotNull@b@                    @Override@b@                    public HttpHeaders getHeaders() {@b@                        return headers;@b@                    }@b@@b@                    @NotNull@b@                    @Override@b@                    public Flux<DataBuffer> getBody() {@b@                        return bodyFlux;@b@                    }@b@                };@b@@b@                //封装request,传给下一级@b@                return chain.filter(exchange.mutate().request(request).build());@b@            }@b@        }@b@@b@        return chain.filter(exchange);@b@    }@b@@b@    /**@b@     * 从Flux<DataBuffer>中获取字符串的方法@b@     *@b@     * @return 请求体@b@     */@b@    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {@b@        //获取请求体@b@        Flux<DataBuffer> body = serverHttpRequest.getBody();@b@        AtomicReference<String> bodyRef = new AtomicReference<>();@b@        body.subscribe(buffer -> {@b@            byte[] content = new byte[buffer.readableByteCount()];@b@            buffer.read(content);@b@            DataBufferUtils.release(buffer);@b@            bodyRef.set(new String(content, StandardCharsets.UTF_8));@b@        });@b@        //获取request body@b@        return bodyRef.get();@b@    }@b@@b@    /**@b@     * 字节数组转DataBuffer@b@     *@b@     * @param bytes 字节数组@b@     * @return DataBuffer@b@     */@b@    private DataBuffer toDataBuffer(byte[] bytes) {@b@        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);@b@        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);@b@        buffer.write(bytes);@b@        return buffer;@b@    }@b@@b@    private Mono<Void> sqlInjectionResponse(ServerWebExchange exchange, URI uri) {@b@        log.error("[Herodotus] |- Parameters of Request [" + uri.getRawPath() + uri.getRawQuery() + "] contain illegal SQL keyword!");@b@        return WebFluxUtils.writeJsonResponse(exchange.getResponse(), new Result<String>().type(ErrorCodes.SQL_INJECTION_REQUEST).status(HttpStatus.SC_FORBIDDEN));@b@    }@b@@b@    private boolean isGetRequest(HttpMethod method) {@b@        return method == HttpMethod.GET;@b@    }@b@@b@    private Boolean isPostRequest(HttpMethod method, String contentType) {@b@        return (method == HttpMethod.POST || method == HttpMethod.PUT) && (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType));@b@    }@b@@b@    /**@b@     * 自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行@b@     *@b@     * @return order@b@     */@b@    @Override@b@    public int getOrder() {@b@        return FilterOrder.GLOBAL_SQL_INJECTION_FILTER_ORDER;@b@    }@b@}


<<热门下载>>