diff --git a/bonus-common/src/main/java/com/bonus/common/filter/RequestCoverFilter.java b/bonus-common/src/main/java/com/bonus/common/filter/RequestCoverFilter.java index c1ba7b8..46594df 100644 --- a/bonus-common/src/main/java/com/bonus/common/filter/RequestCoverFilter.java +++ b/bonus-common/src/main/java/com/bonus/common/filter/RequestCoverFilter.java @@ -12,6 +12,7 @@ import org.springframework.util.StreamUtils; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.Part; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -66,6 +67,8 @@ public class RequestCoverFilter implements Filter { handleUrlParams(httpRequest, response, chain, integrality, encrypt); } else if (contentType.contains(MediaType.APPLICATION_JSON_VALUE)) { handleBodyRequest(httpRequest, response, chain, integrality, encrypt); + } else if (contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) { + handleMultipartRequest(httpRequest, response, chain, integrality, encrypt); } else { chain.doFilter(request, response); } @@ -76,6 +79,22 @@ public class RequestCoverFilter implements Filter { // 清理逻辑 } + /** + * 处理 multipart/form-data 请求 + */ + private void handleMultipartRequest(HttpServletRequest request, ServletResponse response, + FilterChain chain, boolean integrality, boolean encrypt) + throws IOException, ServletException { + try { + // 创建包装的请求对象,处理 multipart 请求 + MultipartRequestWrapper wrappedRequest = new MultipartRequestWrapper(request, integrality, encrypt); + chain.doFilter(wrappedRequest, response); + } catch (Exception e) { + log.error("处理 multipart 请求时发生错误: {}", e.getMessage(), e); + throw new ServletException("请求处理失败", e); + } + } + /** * 处理请求体里的参数 */ @@ -400,5 +419,116 @@ public class RequestCoverFilter implements Filter { } } -} + /** + * Multipart 请求包装类 + * 只处理 params 字段的解密(params 是 JSON 字符串),忽略文件字段 + */ + private static class MultipartRequestWrapper extends HttpServletRequestWrapper { + private final Map parameterMap; + private final boolean integrality; + private final boolean encrypt; + public MultipartRequestWrapper(HttpServletRequest request, boolean integrality, boolean encrypt) { + super(request); + this.integrality = integrality; + this.encrypt = encrypt; + + // 复制原始参数映射 + this.parameterMap = new HashMap<>(request.getParameterMap()); + + // 处理 params 参数 + processParams(); + } + + /** + * 处理 params 参数(JSON 字符串格式) + */ + private void processParams() { + String[] paramsValues = parameterMap.get("params"); + if (paramsValues != null && paramsValues.length > 0) { + String encryptedParams = paramsValues[0]; + try { + String decryptedParams = encryptedParams; + + // 解密参数 + if (encrypt) { + decryptedParams = Sm4Utils.decrypt(decryptedParams); + } + + // 校验数据完整性 + if (integrality) { + String[] parts = decryptedParams.split("\\|"); + if (parts.length != 2) { + log.error("解密后的参数格式不正确: {}", decryptedParams); + throw new CaptchaException("请求参数不正确"); + } + integrityVerification(parts[1], parts[0]); + decryptedParams = parts[0]; // 只保留原始 JSON 字符串部分 + } + + if (!ObjectUtils.isEmpty(decryptedParams)) { + // 由于 params 是 JSON 字符串,我们将其作为原始参数保留 + // 而不是尝试解析为键值对,以保持 JSON 结构完整 + parameterMap.put("params", new String[]{decryptedParams}); + } else { + // 移除空的 params 参数 + parameterMap.remove("params"); + } + + } catch (Exception e) { + log.error("处理 multipart params 参数时发生错误: {}", e.getMessage(), e); + throw new CaptchaException("请求参数不正确"); + } + } + } + + /** + * 数据完整性校验 + */ + private void integrityVerification(String providedHmac, String data) { + if (providedHmac == null) { + log.error("完整性校验哈希值为空"); + throw new CaptchaException("请求参数不正确"); + } + String calculatedHash = Sm3Util.encrypt(data); + log.info("计算出的哈希值: {}", calculatedHash); + log.info("提供的哈希值: {}", providedHmac); + if (!calculatedHash.equals(providedHmac)) { + log.error("参数完整性校验失败"); + throw new CaptchaException("请求参数不正确"); + } + } + + @Override + public String getParameter(String name) { + String[] values = parameterMap.get(name); + return (values != null && values.length > 0) ? values[0] : null; + } + + @Override + public Map getParameterMap() { + return parameterMap; + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(parameterMap.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + return parameterMap.get(name); + } + + // 重写文件相关方法,确保文件上传功能正常工作 + @Override + public Collection getParts() throws IOException, ServletException { + return ((HttpServletRequest) getRequest()).getParts(); + } + + @Override + public Part getPart(String name) throws IOException, ServletException { + return ((HttpServletRequest) getRequest()).getPart(name); + } + } +} \ No newline at end of file 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 7edbc1b..ece8e43 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 @@ -50,7 +50,7 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor { return false; } if (contentType.contains(MULTIPART_FORM_DATA_VALUE)) { - return true; + return false; } return false; } @@ -73,13 +73,13 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor { /** * 校验参数是否合法 */ - /*if (!requestWrapper.isChecked()) { + if (!requestWrapper.isChecked()) { log.error("输入值非法: queryString={}, body={}", StringUtils.defaultString(requestWrapper.getQueryString(), "null"), StringUtils.defaultString(requestWrapper.getReaderParam(), "null")); returnJson(response, "输入值非法", 500); return false; - }*/ + } // System.err.println(JSON.toJSONString(request.getParameterMap())); /** 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 e5632e4..cdf71cd 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 @@ -31,14 +31,81 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { private String streamParam; private boolean checked; + // 预编译所有正则表达式模式以提高性能 + private 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)); // 单引号 + 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)); + } + public XssRequestWrapper(HttpServletRequest request) { super(request); + // 不对查询串做篡改,保持原样,防止影响参数绑定 + queryString = request.getQueryString(); + // 先读取请求体数据 try { - // 读取请求体内容 + // 使用 getInputStream() 而不是 getReader() 来避免冲突 + ServletInputStream inputStream = request.getInputStream(); StringBuilder stringBuilder = new StringBuilder(); - BufferedReader reader = request.getReader(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding(request))); + char[] charBuffer = new char[1024]; int bytesRead; while ((bytesRead = reader.read(charBuffer)) > 0) { @@ -48,12 +115,10 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { // 不变更请求体,只做检测 streamParam = requestBody; - body = streamParam.getBytes(request.getCharacterEncoding() != null ? - request.getCharacterEncoding() : "UTF-8"); + body = streamParam.getBytes(getCharacterEncoding(request)); // 检查安全性(仅校验,不篡改) - String queryStr = request.getQueryString(); - setChecked(xssCleanNew(requestBody) && (queryStr == null || xssCleanNew(queryStr))); + setChecked(xssCleanNew(requestBody) && (queryString == null || xssCleanNew(queryString))); } catch (IOException e) { log.error("读取请求体数据失败: {}", e.getLocalizedMessage(), e); @@ -61,9 +126,12 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { streamParam = ""; setChecked(false); } + } - // 不对查询串做篡改,保持原样,防止影响参数绑定 - queryString = request.getQueryString(); + // 辅助方法:获取字符编码 + private String getCharacterEncoding(HttpServletRequest request) { + String encoding = request.getCharacterEncoding(); + return encoding != null ? encoding : "UTF-8"; } @Override @@ -154,38 +222,15 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { return xssClean(value); } - // 正则表达式模式 - private static final String regex1 = ""; - private static final String regex2 = "src[\r\n]*=[\r\n]*'(.*?)'"; - private static final String regex3 = "src[\r\n]*=[\r\n]*\"(.*?)\""; - private static final String regex4 = ""; - private static final String regex5 = ""; - private static final String regex6 = "eval\\((.*?)\\)"; - private static final String regex7 = "e-xpression\\((.*?)\\)"; - private static final String regex8 = "javascript:"; - private static final String regex9 = "vbscript:"; - private static final String regex10 = "onload(.*?)="; - - // 安全模式:移除过度严格的字符级别清理,避免破坏 JSON/参数结构 - // public static final String SAFE_SCRIPT_PATTERN = "(\\||;|\\$|'|\\'|0x0d|0x0a|\\%27|\\%3B|_|.)"; - private String xssClean(String value) { if (value == null) { return null; } - value = Pattern.compile(regex1, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll(""); - value = Pattern.compile(regex2, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll(""); - value = Pattern.compile(regex3, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll(""); - value = Pattern.compile(regex4, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll(""); - value = Pattern.compile(regex5, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll(""); - value = Pattern.compile(regex6, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll(""); - value = Pattern.compile(regex7, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll(""); - value = Pattern.compile(regex8, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll(""); - value = Pattern.compile(regex9, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll(""); - value = Pattern.compile(regex10, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll(""); - // 移除对普通字符的全量替换,防止破坏字段名/JSON结构 - // value = Pattern.compile(SAFE_SCRIPT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll(""); + // 使用预编译的模式 + for (Pattern pattern : XSS_PATTERNS) { + value = pattern.matcher(value).replaceAll(""); + } return value; } @@ -200,21 +245,8 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { return isJsonSafe(value); } - List patterns = new ArrayList<>(); - patterns.add(Pattern.compile(regex1, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex2, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex3, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex4, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex5, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex6, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex7, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex8, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex9, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex10, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - // 移除过度严格的 SAFE_SCRIPT_PATTERN 检查 - // patterns.add(Pattern.compile(SAFE_SCRIPT_PATTERN, Pattern.CASE_INSENSITIVE)); - - for (Pattern pattern : patterns) { + // 使用预编译的模式进行检查 + for (Pattern pattern : XSS_PATTERNS) { if (match(pattern, value)) { return false; // 发现XSS攻击 } @@ -224,19 +256,28 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { // 检查字符串是否为JSON格式 private boolean isJsonString(String str) { - try { - // 尝试解析为JSON对象 - new JSONObject(str); - return true; - } catch (JSONException e1) { + if (str == null || str.trim().isEmpty()) { + return false; + } + + String trimmed = str.trim(); + if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || + (trimmed.startsWith("[") && trimmed.endsWith("]"))) { try { - // 尝试解析为JSON数组 - new JSONArray(str); + // 尝试解析为JSON对象 + new JSONObject(trimmed); return true; - } catch (JSONException e2) { - return false; + } catch (JSONException e1) { + try { + // 尝试解析为JSON数组 + new JSONArray(trimmed); + return true; + } catch (JSONException e2) { + return false; + } } } + return false; } // 检查JSON字符串中的值是否安全 @@ -285,6 +326,7 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { return false; } } + // 其他类型(数字、布尔值等)视为安全 } return true; } @@ -307,6 +349,7 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { return false; } } + // 其他类型(数字、布尔值等)视为安全 } return true; } @@ -317,22 +360,7 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { return true; } - List patterns = new ArrayList<>(); - // 这里添加你的所有XSS模式... - patterns.add(Pattern.compile(regex1, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex2, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex3, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex4, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex5, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex6, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex7, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - patterns.add(Pattern.compile(regex8, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex9, Pattern.CASE_INSENSITIVE)); - patterns.add(Pattern.compile(regex10, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)); - // 移除过度严格的 SAFE_SCRIPT_PATTERN 检查 - // patterns.add(Pattern.compile(SAFE_SCRIPT_PATTERN, Pattern.CASE_INSENSITIVE)); - - for (Pattern pattern : patterns) { + for (Pattern pattern : XSS_PATTERNS) { if (match(pattern, value)) { return false; } @@ -359,7 +387,6 @@ public class XssRequestWrapper extends HttpServletRequestWrapper { this.checked = checked; } - // 添加字符编码支持 public String getCharacterEncoding() { String encoding = super.getCharacterEncoding();