Bootstrap

SpringCloud gateway 防止XSS漏洞_springcloud项目添加xss防护(1)

Flux cachedFlux = Flux
.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}

}
return chain.filter(exchange);
}

@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

2.XssRequestGlobalFilter.java

自定义防XSS攻击网关全局过滤器。

package com.xxx.gateway.filter;

import io.netty.buffer.ByteBufAllocator;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.http.server.reactive.ServerHttpResponse;
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.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
/**
* @Author:
* @Description: 自定义防XSS攻击网关全局过滤器
* @Date:
*/

@Component
public class XssRequestGlobalFilter implements GlobalFilter, Ordered {

private Logger logger = LoggerFactory.getLogger(XssRequestGlobalFilter.class);
/**
*
* @param exchange
* @param chain
* @return
*
* get请求参考spring cloud gateway自带过滤器:
* @see org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory
*
* post请求参考spring cloud gateway自带过滤器:
* @see org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
*/
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain){
// grab configuration from Config object
logger.info(“----自定义防XSS攻击网关全局过滤器生效----”);
String path = exchange.getRequest().getPath().toString();
ServerHttpRequest serverHttpRequest = exchange.getRequest();
HttpMethod method = serverHttpRequest.getMethod();
String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);

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.equals(contentType));

// get 请求, 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory
if (method == HttpMethod.GET) {
URI uri = exchange.getRequest().getURI();

String rawQuery = uri.getRawQuery();
if (StringUtils.isBlank(rawQuery)){
return chain.filter(exchange);
}
rawQuery = XssCleanRuleUtils.xssClean(rawQuery);
try {
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) {
logger.error(“get请求清理xss攻击异常”, e);
throw new IllegalStateException(“Invalid URI query: “” + rawQuery + “””);
}
}
//post请求时,如果是文件上传之类的请求,不修改请求消息体
else if (postFlag){
// 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory

//从请求里获取Post请求体
String bodyStr = resolveBodyFromRequest(serverHttpRequest);
// 这种处理方式,必须保证post请求时,原始post表单必须有数据过来,不然会报错
if (StringUtils.isBlank(bodyStr)) {
logger.error(“请求异常:{} POST请求必须传递参数”, serverHttpRequest.getURI().getRawPath());
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.BAD_REQUEST);
byte[] bytes = “{“code”:400,“msg”:“post data error”}”.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}
//白名单处理(看业务需求)
boolean containsTarget = WhiteListUtils.richTextUrls.stream().anyMatch(s -> path.contains(s));
if (containsTarget) {
//bodyStr = XssCleanRuleUtils.xssRichTextClean(bodyStr);
bodyStr = XssCleanRuleUtils.xssClean2(bodyStr);
} else {
bodyStr = XssCleanRuleUtils.xssClean(bodyStr);
}

URI uri = serverHttpRequest.getURI();
URI newUri = UriComponentsBuilder.fromUri(uri).build(true).toUri();
ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
Flux bodyFlux = Flux.just(bodyDataBuffer);

// 定义新的消息头
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());

// 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
int length = bodyStr.getBytes().length;
headers.remove(HttpHeaders.CONTENT_LENGTH);
headers.setContentLength(length);

// 设置CONTENT_TYPE
if (StringUtils.isNotBlank(contentType)) {
headers.set(HttpHeaders.CONTENT_TYPE, contentType);
}

// 由于post的body只能订阅一次,由于上面代码中已经订阅过一次body。所以要再次封装请求到request才行,不然会报错请求已经订阅过
request = new ServerHttpRequestDecorator(request) {
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
// this causes a ‘HTTP/1.1 411 Length Required’ on httpbin.org
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, “chunked”);
}
return httpHeaders;
}

@Override
public Flux getBody() {
return bodyFlux;
}
};

//封装request,传给下一级
request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(bodyStr.length()));
return chain.filter(exchange.mutate().request(request).build());
} else {
return chain.filter(exchange);
}

}

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

/**
* 从Flux中获取字符串的方法
* @return 请求体
*/
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
//获取请求体
Flux body = serverHttpRequest.getBody();
AtomicReference bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
//获取request body
return bodyRef.get();
}

/**
* 字符串转DataBuffer
* @param value
* @return
*/
private DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}

}

3.XssResponseGlobalFilter.java

重写Response,防止xss攻击。

package com.xxx.gateway.filter;

import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;

/**
* @Author:
* @Description: 重写Response,防止xss攻击
* @Date:
*/
@Component
public class XssResponseGlobalFilter implements Ordered, GlobalFilter {

private Logger logger = LoggerFactory.getLogger(XssResponseGlobalFilter.class);

@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求url
String path = exchange.getRequest().getPath().toString();

ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono writeWith(Publisher<? extends DataBuffer> body) {
String contentType = getDelegate().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
Boolean flag = MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType);
if (body instanceof Flux && flag) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
//如果响应过大,会进行截断,出现乱码,
//然后看api DefaultDataBufferFactory有个join方法可以合并所有的流,乱码的问题解决
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = null;
try {
join = dataBufferFactory.join(dataBuffer);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
//释放掉内存
DataBufferUtils.release(join);
String result = new String(content, Charset.forName(“UTF-8”));
//logger.info(“result:”+result);
//若为带有富文本的接口,走富文本xss过滤
boolean containsTarget = WhiteListUtils.richTextUrls.stream().anyMatch(s -> path.contains(s));
if (containsTarget) {
//result = XssCleanRuleUtils.xssRichTextClean(result);
result = XssCleanRuleUtils.xssClean2(result);
} else {
//result就是response的值,对result进行去XSS
result = XssCleanRuleUtils.xssClean(result);
}
byte[] uppedContent = new String(result.getBytes(), Charset.forName(“UTF-8”)).getBytes();
return bufferFactory.wrap(uppedContent);
} catch (Exception e) {
// 处理异常,记录日志等
throw e;
} finally {
if (join != null) {
//释放掉内存
DataBufferUtils.release(join);
}
}
}));
}
// if body is not a flux. never got there.
return super.writeWith(body);
}
};
// replace response with decorator
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}

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

4.XssCleanRuleUtils.java (工具类)

上面几个过滤器使用的工具类

后续我自己这边没有用Jsoup了(感觉有坑,就放弃了)

package com.xxx.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.regex.Pattern;

/**
* @Author:
* @Description: xss过滤工具
* @Date:
*/
public class XssCleanRuleUtils {

//xss过滤规则(对于script、src及加载事件和弹窗事件的代码块)
private final static Pattern[] scriptPatterns = {
Pattern.compile(“”, 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)
};

//非富文本的
public static String xssClean(String value) {
if (value != null) {
value = value.replaceAll(“\0|\n|\r”, “”);
for (Pattern pattern : scriptPatterns) {
value = pattern.matcher(value).replaceAll(“”);
}
value = value.replaceAll(“<”, “<”).replaceAll(“>”, “>”);
}
return value;

}

//富文本的
public static String xssClean2(String value) {
if (value != null) {
value = value.replaceAll(“\0|\n|\r”, “”);
for (Pattern pattern : scriptPatterns) {
value = pattern.matcher(value).replaceAll(“”);
}
}
return value;
}

//自定义的json白名单
private static final ClassPathResource jsoupWhiteListPathRes = new ClassPathResource(“/json/xssWhiteList.json”);
//配置过滤化参数, 不对代码进行格式化
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
//富文本的(使用了Jsoup)
public static String xssRichTextClean(String value) {
// 创建一个自定义的白名单,基于Jsoup的默认白名单
Whitelist customWhitelist = Whitelist.basic();
InputStream whiteConfig = null;
try {
whiteConfig = jsoupWhiteListPathRes.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
if (whiteConfig == null) {
throw new RuntimeException(“读取jsoup xss 白名单文件失败”);
} else {
try {
JSONObject whiteListJson = JSON.parseObject(whiteConfig, JSONObject.class);

//添加标签 addTags
JSONArray addTagsJsonArr = whiteListJson.getJSONArray(“addTags”);
String[] addTagsArr = addTagsJsonArr.toArray(new String[0]);
customWhitelist.addTags(addTagsArr);

//添加属性 addAttributes
JSONArray addAttrJsonArr = whiteListJson.getJSONArray(“addAttributes”);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
img

写在最后

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源私我

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

)**
[外链图片转存中…(img-jp9uHNtC-1712717528977)]

写在最后

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源私我

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-auVTJJkH-1712717528977)]

;