vlambda博客
学习文章列表

Java web防护xss/sql注入的正确姿势

OURDREAM
Java web防护xss/sql注入的正确姿势

这里以springboot搭建的微服务为例,可以在网关中自定义全局拦截器,对入参进行过滤。防护的方法有很多,这里以黑名单为例,暂定项目中只存在POST和GET两种传参


自定义防XSS/SQL注入攻击网关全局过滤器


package com.javee.getway.filter;


import com.javee.getway.common.constant.WebBaseConstant;import com.javee.getway.common.model.ResponseData;import com.javee.getway.utils.CommonJsonUtil;import com.javee.getway.utils.CommonStringUtil;import com.javee.getway.utils.MonoUtil;import com.javee.getway.utils.XssSqlCleanRuleUtils;import io.netty.buffer.ByteBufAllocator;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.core.io.buffer.NettyDataBufferFactory;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpMethod;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpRequestDecorator;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import org.springframework.web.util.UriComponentsBuilder;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;


import java.net.URI;import java.nio.charset.StandardCharsets;import java.util.Optional;


/** * @Author: Javee * @Date: 2021/8/15 16:41 * @Description: 自定义防XSS/SQL注入攻击网关全局过滤器 */@Slf4j@Componentpublic class O9_XssSqlRequestGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // grab configuration from Config object        log.debug("----自定义防XSS攻击网关全局过滤器生效----"); ServerHttpRequest serverHttpRequest = exchange.getRequest(); HttpMethod method = serverHttpRequest.getMethod(); String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);        URI uri = exchange.getRequest().getURI();


Boolean postFlag = (method == HttpMethod.POST || method == HttpMethod.PUT) && (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.toLowerCase().equalsIgnoreCase(contentType));


//过滤get请求 if (method == HttpMethod.GET) {


String rawQuery = uri.getRawQuery(); if (CommonStringUtil.isEmpty(rawQuery)){ return chain.filter(exchange); }


           log.debug("原请求参数为:{}", rawQuery); // 执行XSS清理            rawQuery = XssSqlCleanRuleUtils.xssClean(rawQuery, true);            log.debug("修改后参数为:{}", rawQuery);


//    如果存在sql注入,直接拦截请求 if (rawQuery.contains("forbid")) {                log.error("请求【" + uri.getRawPath() + uri.getRawQuery() + "】参数中包含不允许sql的关键词, 请求拒绝"); String msg = CommonJsonUtil.toJson(ResponseData.instance(WebBaseConstant.ResponseCode.FAILD, "系统检测到恶意攻击,已将你的信息进行固定!!!")); return MonoUtil.generateMono(exchange.getResponse(),msg, HttpStatus.OK); }


try { //重新构造get request                URI newUri = UriComponentsBuilder.fromUri(uri) .replaceQuery(rawQuery) .build(true) .toUri();


ServerHttpRequest request = exchange.getRequest().mutate() .uri(newUri).build(); return chain.filter(exchange.mutate().request(request).build()); } catch (Exception e) {                log.error("get请求清理xss攻击异常", e); String msg = CommonJsonUtil.toJson(ResponseData.instance(WebBaseConstant.ResponseCode.FAILD, "系统检测到恶意攻击,已将你的信息进行固定!!!")); return MonoUtil.generateMono(exchange.getResponse(),msg, HttpStatus.OK); } } //post请求时,如果是文件上传之类的请求,不修改请求消息体 else if (postFlag){


return DataBufferUtils.join(serverHttpRequest.getBody()).flatMap(d -> Mono.just(Optional.of(d))).defaultIfEmpty( Optional.empty()) .flatMap(optional -> { // 取出body中的参数 String bodyString = ""; if (optional.isPresent()) { byte[] oldBytes = new byte[optional.get().readableByteCount()];                            optional.get().read(oldBytes);                            bodyString = new String(oldBytes, StandardCharsets.UTF_8); } HttpHeaders httpHeaders = serverHttpRequest.getHeaders(); ServerHttpRequest newRequest = serverHttpRequest.mutate().uri(uri).build(); // 执行XSS清理                        log.debug("{} - XSS处理前 [{}:{}]", method, uri.getPath(), bodyString);                        bodyString = XssSqlCleanRuleUtils.xssClean(bodyString, false);                        log.info("{} - XSS处理后 [{}:{}] ", method, uri.getPath(), bodyString);


//  如果存在sql注入,直接拦截请求 if (bodyString.contains("forbid")) {                            log.error("{} - [{}:{}] 参数:{}, 包含不允许sql的关键词,请求拒绝", method, uri.getPath(), bodyString); // 这里可以自由发挥,ip拉黑之类的都可以 String msg = CommonJsonUtil.toJson(ResponseData.instance(WebBaseConstant.ResponseCode.FAILD, "系统检测到恶意攻击,已将你的信息进行固定!!!")); return MonoUtil.generateMono(exchange.getResponse(),msg, HttpStatus.OK); }


// 重新构造body byte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8); DataBuffer bodyDataBuffer = toDataBuffer(newBytes); Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);


// 重新构造header HttpHeaders headers = new HttpHeaders();                        headers.putAll(httpHeaders); // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度 int length = newBytes.length;                        headers.remove(HttpHeaders.CONTENT_LENGTH);                        headers.setContentLength(length);                        headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf8"); // 重写ServerHttpRequestDecorator,修改了body和header,重写getBody和getHeaders方法                        newRequest = new ServerHttpRequestDecorator(newRequest) { @Override public Flux<DataBuffer> getBody() { return bodyFlux; }


@Override public HttpHeaders getHeaders() { return headers; } };


return chain.filter(exchange.mutate().request(newRequest).build()); }); } else { return chain.filter(exchange); } }


@Override public int getOrder() { return -90; }


/** * 字节数组转DataBuffer * * @param bytes 字节数组 * @return DataBuffer */ private DataBuffer toDataBuffer(byte[] bytes) { NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);        buffer.write(bytes); return buffer; }


}



xss过滤/sql注入监测工具类:


package com.javee.getway.utils;


import lombok.extern.slf4j.Slf4j;


import java.io.UnsupportedEncodingException;import java.net.URLDecoder;import java.util.regex.Pattern;import java.util.stream.Stream;


/** * @Author: Javee * @Date: 2021/8/15 16:44 * @Description: xss过滤/sql注入监测工具 */@Slf4jpublic class XssSqlCleanRuleUtils { private final static Pattern[] scriptPatterns = { Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE), Pattern.compile("target[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("</script>", Pattern.CASE_INSENSITIVE), Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE), Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE), Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onabort(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onblur(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onchange(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onclick(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("ondbclick(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onerror(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onfocus(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onmousedown(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onmouseup(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onmousemove(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onmouseout(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onmouseover(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onselect(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("onunload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL) };


private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";


private static Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);//整体都忽略大小写


/** * GET请求参数过滤 * @param value * @return */ public static String xssClean(String value, boolean isGet) {


//过滤xss字符集 if (value != null) { value = value.replaceAll("\0|\n|\r", ""); for (Pattern pattern : scriptPatterns) { value = pattern.matcher(value).replaceAll(""); } }


return cleanSqlKeyWords(value, isGet);


}


/** * 解析参数SQL关键字 * @param value * @return */ private static String cleanSqlKeyWords(String value, boolean isGet) {


//参数需要url编码 //这里需要将参数转换为小写来处理 //不改变原值 //value示例 order=asc&pageNum=1&pageSize=100&parentId=0 String splitChar = isGet ? "=" : ":"; String lowerValue = null; try {            lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase(); } catch (UnsupportedEncodingException e) {            e.printStackTrace(); }


//获取到请求中所有参数值-取每个key=value组合第一个等号后面的值 boolean isContains = Stream.of(lowerValue.split("\\&")) .map(kp -> kp.substring(kp.indexOf(splitChar) + 1)) .parallel() .anyMatch(param -> { if (sqlPattern.matcher(param).find()) {                        log.error("参数中包含不允许sql的关键词"); return true; } return false; });


return isContains ? "forbid" : value; }


}



效果解读:入参中如果存在xss黑名单中的关键词,会直接替换为空字符串,如果入参的sql正则校验不通过(存在sql注入敏感词),则返回给前端,并进行警告(可自定义操作)


点击阅读原文跳转至作者csdn

抖音/Javee

菜鸟小v
主要分享编程知识和小技巧,偶尔分享数码科技,欢迎各位coder和发烧友关注
9篇原创内容
Official Account