diff --git a/bonus-framework/src/main/java/com/bonus/framework/interceptor/ParamSecureHandler.java b/bonus-framework/src/main/java/com/bonus/framework/interceptor/ParamSecureHandler.java index ece8e43..1e9875f 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/interceptor/ParamSecureHandler.java +++ b/bonus-framework/src/main/java/com/bonus/framework/interceptor/ParamSecureHandler.java @@ -5,9 +5,9 @@ import com.bonus.common.core.domain.AjaxResult; import com.bonus.common.utils.SafeUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.servlet.AsyncHandlerInterceptor; +import com.bonus.framework.interceptor.XssCheck; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -50,7 +50,7 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor { return false; } if (contentType.contains(MULTIPART_FORM_DATA_VALUE)) { - return false; + return true; } return false; } @@ -63,8 +63,14 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor { if (shouldIgnore) { return true; } - // 过滤文件上传功能 + // 过滤文件上传功能-只对参数进行校验 if (isFileUpload(request)) { + String params = request.getParameter("params"); + boolean flag = XssCheck.xssCleanNew(params); + if(!flag){ + returnJson(response, "输入值非法", 500); + return false; + } return true; } XssRequestWrapper requestWrapper = new XssRequestWrapper(request); @@ -241,4 +247,6 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor { throws Exception { // SecurityContextHolder.remove(); } + + } diff --git a/bonus-framework/src/main/java/com/bonus/framework/interceptor/XssCheck.java b/bonus-framework/src/main/java/com/bonus/framework/interceptor/XssCheck.java new file mode 100644 index 0000000..3357dd5 --- /dev/null +++ b/bonus-framework/src/main/java/com/bonus/framework/interceptor/XssCheck.java @@ -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 XSS_PATTERNS = new ArrayList<>(); + + static { + // 初始化所有XSS模式 + XSS_PATTERNS.add(Pattern.compile("", 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("", Pattern.CASE_INSENSITIVE)); + XSS_PATTERNS.add(Pattern.compile("", 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 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(); + } +} diff --git a/bonus-framework/src/main/java/com/bonus/framework/interceptor/XssRequestWrapper.java b/bonus-framework/src/main/java/com/bonus/framework/interceptor/XssRequestWrapper.java index cdf71cd..4f29bd9 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/interceptor/XssRequestWrapper.java +++ b/bonus-framework/src/main/java/com/bonus/framework/interceptor/XssRequestWrapper.java @@ -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)); // 双引号 @@ -96,16 +95,11 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { public XssRequestWrapper(HttpServletRequest request) { super(request); - // 不对查询串做篡改,保持原样,防止影响参数绑定 - queryString = request.getQueryString(); - // 先读取请求体数据 try { - // 使用 getInputStream() 而不是 getReader() 来避免冲突 - ServletInputStream inputStream = request.getInputStream(); + // 读取请求体内容 StringBuilder stringBuilder = new StringBuilder(); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding(request))); - + BufferedReader reader = request.getReader(); char[] charBuffer = new char[1024]; int bytesRead; while ((bytesRead = reader.read(charBuffer)) > 0) { @@ -115,10 +109,12 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { // 不变更请求体,只做检测 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) { log.error("读取请求体数据失败: {}", e.getLocalizedMessage(), e); @@ -126,12 +122,9 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { streamParam = ""; setChecked(false); } - } - // 辅助方法:获取字符编码 - private String getCharacterEncoding(HttpServletRequest request) { - String encoding = request.getCharacterEncoding(); - return encoding != null ? encoding : "UTF-8"; + // 不对查询串做篡改,保持原样,防止影响参数绑定 + queryString = request.getQueryString(); } @Override