请求解密修改
This commit is contained in:
parent
a7547bf00f
commit
866f8be845
|
|
@ -5,9 +5,9 @@ import com.bonus.common.core.domain.AjaxResult;
|
||||||
import com.bonus.common.utils.SafeUtil;
|
import com.bonus.common.utils.SafeUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||||
|
import com.bonus.framework.interceptor.XssCheck;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
@ -50,7 +50,7 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (contentType.contains(MULTIPART_FORM_DATA_VALUE)) {
|
if (contentType.contains(MULTIPART_FORM_DATA_VALUE)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -63,8 +63,14 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
if (shouldIgnore) {
|
if (shouldIgnore) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 过滤文件上传功能
|
// 过滤文件上传功能-只对参数进行校验
|
||||||
if (isFileUpload(request)) {
|
if (isFileUpload(request)) {
|
||||||
|
String params = request.getParameter("params");
|
||||||
|
boolean flag = XssCheck.xssCleanNew(params);
|
||||||
|
if(!flag){
|
||||||
|
returnJson(response, "输入值非法", 500);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
XssRequestWrapper requestWrapper = new XssRequestWrapper(request);
|
XssRequestWrapper requestWrapper = new XssRequestWrapper(request);
|
||||||
|
|
@ -241,4 +247,6 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// SecurityContextHolder.remove();
|
// SecurityContextHolder.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
package com.bonus.framework.interceptor;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:XxlCheck
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2025-09-17-16:33
|
||||||
|
* @version:1.0
|
||||||
|
* @description:
|
||||||
|
*/
|
||||||
|
public class XssCheck {
|
||||||
|
|
||||||
|
// 预编译所有正则表达式模式以提高性能
|
||||||
|
public static final List<Pattern> XSS_PATTERNS = new ArrayList<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 初始化所有XSS模式
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("src[\r\n]*=[\r\n]*'(.*?)'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("src[\r\n]*=[\r\n]*\"(.*?)\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("</script>", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("e-xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
// 添加更多常见XSS模式
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("onerror(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("onclick(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("onmouseover(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("alert\\((.*?)\\)", Pattern.CASE_INSENSITIVE));
|
||||||
|
|
||||||
|
// 添加特殊字符和编码模式
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("<.*>", Pattern.CASE_INSENSITIVE)); // 尖括号
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\\[.*\\]", Pattern.CASE_INSENSITIVE)); // 方括号
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\\(.*\\)", Pattern.CASE_INSENSITIVE)); // 圆括号
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("'.*'", Pattern.CASE_INSENSITIVE)); // 单引号
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\".*\"", Pattern.CASE_INSENSITIVE)); // 双引号
|
||||||
|
|
||||||
|
// URL编码模式
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%3c", Pattern.CASE_INSENSITIVE)); // < 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%3e", Pattern.CASE_INSENSITIVE)); // > 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%2f", Pattern.CASE_INSENSITIVE)); // / 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%27", Pattern.CASE_INSENSITIVE)); // ' 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%22", Pattern.CASE_INSENSITIVE)); // " 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%2b", Pattern.CASE_INSENSITIVE)); // + 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%3b", Pattern.CASE_INSENSITIVE)); // ; 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%28", Pattern.CASE_INSENSITIVE)); // ( 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%29", Pattern.CASE_INSENSITIVE)); // ) 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%5b", Pattern.CASE_INSENSITIVE)); // [ 的URL编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("%5d", Pattern.CASE_INSENSITIVE)); // ] 的URL编码
|
||||||
|
|
||||||
|
// 其他特殊字符
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("@.*", Pattern.CASE_INSENSITIVE)); // @符号
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("!.*", Pattern.CASE_INSENSITIVE)); // 感叹号
|
||||||
|
|
||||||
|
// 十六进制编码
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\\\\x3c", Pattern.CASE_INSENSITIVE)); // < 的十六进制
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\\\\x3e", Pattern.CASE_INSENSITIVE)); // > 的十六进制
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\\\\x2f", Pattern.CASE_INSENSITIVE)); // / 的十六进制
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\\\\x27", Pattern.CASE_INSENSITIVE)); // ' 的十六进制
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("\\\\x22", Pattern.CASE_INSENSITIVE)); // " 的十六进制
|
||||||
|
|
||||||
|
// 添加更多常见的XSS绕过尝试
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("data:text/html", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("base64", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("document\\.", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("window\\.", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("location\\.", Pattern.CASE_INSENSITIVE));
|
||||||
|
XSS_PATTERNS.add(Pattern.compile("cookie", Pattern.CASE_INSENSITIVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean xssCleanNew(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首先检查是否为JSON格式
|
||||||
|
if (isJsonString(value)) {
|
||||||
|
// 对JSON字符串进行特殊处理:检查值部分是否包含XSS
|
||||||
|
return isJsonSafe(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用预编译的模式进行检查
|
||||||
|
for (Pattern pattern : XSS_PATTERNS) {
|
||||||
|
if (match(pattern, value)) {
|
||||||
|
return false; // 发现XSS攻击
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // 安全
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查字符串是否为JSON格式
|
||||||
|
public static boolean isJsonString(String str) {
|
||||||
|
if (str == null || str.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String trimmed = str.trim();
|
||||||
|
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
||||||
|
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
||||||
|
try {
|
||||||
|
// 尝试解析为JSON对象
|
||||||
|
new JSONObject(trimmed);
|
||||||
|
return true;
|
||||||
|
} catch (JSONException e1) {
|
||||||
|
try {
|
||||||
|
// 尝试解析为JSON数组
|
||||||
|
new JSONArray(trimmed);
|
||||||
|
return true;
|
||||||
|
} catch (JSONException e2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查JSON字符串中的值是否安全
|
||||||
|
public static boolean isJsonSafe(String jsonStr) {
|
||||||
|
try {
|
||||||
|
// 处理JSON对象
|
||||||
|
if (jsonStr.trim().startsWith("{")) {
|
||||||
|
JSONObject jsonObject = new JSONObject(jsonStr);
|
||||||
|
return isJsonObjectSafe(jsonObject);
|
||||||
|
}
|
||||||
|
// 处理JSON数组
|
||||||
|
else if (jsonStr.trim().startsWith("[")) {
|
||||||
|
JSONArray jsonArray = new JSONArray(jsonStr);
|
||||||
|
return isJsonArraySafe(jsonArray);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// 解析失败,回退到普通字符串检查
|
||||||
|
return xssCleanNewFallback(jsonStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归检查JSON对象的安全性
|
||||||
|
public static boolean isJsonObjectSafe(JSONObject jsonObject) throws JSONException {
|
||||||
|
Iterator<String> keys = jsonObject.keys();
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
String key = keys.next();
|
||||||
|
Object value = jsonObject.get(key);
|
||||||
|
|
||||||
|
// 检查key的安全性
|
||||||
|
if (!xssCleanNewFallback(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查value的安全性
|
||||||
|
if (value instanceof String) {
|
||||||
|
if (!xssCleanNewFallback((String) value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (value instanceof JSONObject) {
|
||||||
|
if (!isJsonObjectSafe((JSONObject) value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (value instanceof JSONArray) {
|
||||||
|
if (!isJsonArraySafe((JSONArray) value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 其他类型(数字、布尔值等)视为安全
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归检查JSON数组的安全性
|
||||||
|
public static boolean isJsonArraySafe(JSONArray jsonArray) throws JSONException {
|
||||||
|
for (int i = 0; i < jsonArray.length(); i++) {
|
||||||
|
Object value = jsonArray.get(i);
|
||||||
|
|
||||||
|
if (value instanceof String) {
|
||||||
|
if (!xssCleanNewFallback((String) value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (value instanceof JSONObject) {
|
||||||
|
if (!isJsonObjectSafe((JSONObject) value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (value instanceof JSONArray) {
|
||||||
|
if (!isJsonArraySafe((JSONArray) value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 其他类型(数字、布尔值等)视为安全
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到原始的模式匹配(避免递归调用)
|
||||||
|
public static boolean xssCleanNewFallback(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Pattern pattern : XSS_PATTERNS) {
|
||||||
|
if (match(pattern, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行正则表达式匹配
|
||||||
|
*/
|
||||||
|
public static boolean match(Pattern pattern, String str) {
|
||||||
|
return pattern.matcher(str).find();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,6 @@ public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
XSS_PATTERNS.add(Pattern.compile("<.*>", Pattern.CASE_INSENSITIVE)); // 尖括号
|
XSS_PATTERNS.add(Pattern.compile("<.*>", Pattern.CASE_INSENSITIVE)); // 尖括号
|
||||||
XSS_PATTERNS.add(Pattern.compile("\\[.*\\]", Pattern.CASE_INSENSITIVE)); // 方括号
|
XSS_PATTERNS.add(Pattern.compile("\\[.*\\]", Pattern.CASE_INSENSITIVE)); // 方括号
|
||||||
XSS_PATTERNS.add(Pattern.compile("\\(.*\\)", Pattern.CASE_INSENSITIVE)); // 圆括号
|
XSS_PATTERNS.add(Pattern.compile("\\(.*\\)", Pattern.CASE_INSENSITIVE)); // 圆括号
|
||||||
XSS_PATTERNS.add(Pattern.compile("/.*/", Pattern.CASE_INSENSITIVE)); // 斜杠
|
|
||||||
XSS_PATTERNS.add(Pattern.compile("'.*'", Pattern.CASE_INSENSITIVE)); // 单引号
|
XSS_PATTERNS.add(Pattern.compile("'.*'", Pattern.CASE_INSENSITIVE)); // 单引号
|
||||||
XSS_PATTERNS.add(Pattern.compile("\".*\"", Pattern.CASE_INSENSITIVE)); // 双引号
|
XSS_PATTERNS.add(Pattern.compile("\".*\"", Pattern.CASE_INSENSITIVE)); // 双引号
|
||||||
|
|
||||||
|
|
@ -96,16 +95,11 @@ public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
public XssRequestWrapper(HttpServletRequest request) {
|
public XssRequestWrapper(HttpServletRequest request) {
|
||||||
super(request);
|
super(request);
|
||||||
|
|
||||||
// 不对查询串做篡改,保持原样,防止影响参数绑定
|
|
||||||
queryString = request.getQueryString();
|
|
||||||
|
|
||||||
// 先读取请求体数据
|
// 先读取请求体数据
|
||||||
try {
|
try {
|
||||||
// 使用 getInputStream() 而不是 getReader() 来避免冲突
|
// 读取请求体内容
|
||||||
ServletInputStream inputStream = request.getInputStream();
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding(request)));
|
BufferedReader reader = request.getReader();
|
||||||
|
|
||||||
char[] charBuffer = new char[1024];
|
char[] charBuffer = new char[1024];
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
while ((bytesRead = reader.read(charBuffer)) > 0) {
|
while ((bytesRead = reader.read(charBuffer)) > 0) {
|
||||||
|
|
@ -115,10 +109,12 @@ public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
// 不变更请求体,只做检测
|
// 不变更请求体,只做检测
|
||||||
streamParam = requestBody;
|
streamParam = requestBody;
|
||||||
body = streamParam.getBytes(getCharacterEncoding(request));
|
body = streamParam.getBytes(request.getCharacterEncoding() != null ?
|
||||||
|
request.getCharacterEncoding() : "UTF-8");
|
||||||
|
|
||||||
// 检查安全性(仅校验,不篡改)
|
// 检查安全性(仅校验,不篡改)
|
||||||
setChecked(xssCleanNew(requestBody) && (queryString == null || xssCleanNew(queryString)));
|
String queryStr = request.getQueryString();
|
||||||
|
setChecked(xssCleanNew(requestBody) && (queryStr == null || xssCleanNew(queryStr)));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("读取请求体数据失败: {}", e.getLocalizedMessage(), e);
|
log.error("读取请求体数据失败: {}", e.getLocalizedMessage(), e);
|
||||||
|
|
@ -126,12 +122,9 @@ public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
streamParam = "";
|
streamParam = "";
|
||||||
setChecked(false);
|
setChecked(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助方法:获取字符编码
|
// 不对查询串做篡改,保持原样,防止影响参数绑定
|
||||||
private String getCharacterEncoding(HttpServletRequest request) {
|
queryString = request.getQueryString();
|
||||||
String encoding = request.getCharacterEncoding();
|
|
||||||
return encoding != null ? encoding : "UTF-8";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue