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 {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// grab configuration from Config objectlog.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 requestURI 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);}
// 重新构造bodybyte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);DataBuffer bodyDataBuffer = toDataBuffer(newBytes);Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
// 重新构造headerHttpHeaders 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) {@Overridepublic Flux<DataBuffer> getBody() {return bodyFlux;}
@Overridepublic HttpHeaders getHeaders() {return headers;}};
return chain.filter(exchange.mutate().request(newRequest).build());});} else {return chain.filter(exchange);}}
@Overridepublic 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=0String 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和发烧友关注
Official Account
