This commit is contained in:
2025-12-04 21:24:42 +08:00
parent 312dfec699
commit 216d49a0af
2 changed files with 249 additions and 270 deletions

View File

@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
@@ -31,305 +32,283 @@ import java.util.regex.Pattern;
* 3. Commons-Lang 的 xml/html escape * 3. Commons-Lang 的 xml/html escape
* 4. JDK 提供的 URLEncoder * 4. JDK 提供的 URLEncoder
* 5. XSS、SQL、orderBy 过滤器 * 5. XSS、SQL、orderBy 过滤器
*
* @author calvin、ThinkGem * @author calvin、ThinkGem
* @version 2025-7-9 * @version 2025-7-9
*/ */
public class EncodeUtils { public class EncodeUtils {
public static final String UTF_8 = "UTF-8"; public static final String UTF_8 = "UTF-8";
private static final Logger logger = LoggerFactory.getLogger(EncodeUtils.class); private static final Logger logger = LoggerFactory.getLogger(EncodeUtils.class);
private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
/** /**
* Hex编码. * Hex编码.
*/ */
public static String encodeHex(byte[] input) { public static String encodeHex(byte[] input) {
return new String(Hex.encodeHex(input)); return new String(Hex.encodeHex(input));
} }
/** /**
* Hex解码. * Hex解码.
*/ */
public static byte[] decodeHex(String input) { public static byte[] decodeHex(String input) {
try { try {
return Hex.decodeHex(input.toCharArray()); return Hex.decodeHex(input.toCharArray());
} catch (DecoderException e) { } catch (DecoderException e) {
throw ExceptionUtils.unchecked(e); throw ExceptionUtils.unchecked(e);
} }
} }
/** /**
* Base64编码. * Base64编码.
*/ */
public static String encodeBase64(byte[] input) { public static String encodeBase64(byte[] input) {
return new String(Base64.encodeBase64(input)); return new String(Base64.encodeBase64(input));
} }
/** /**
* Base64编码. * Base64编码.
*/ */
public static String encodeBase64(String input) { public static String encodeBase64(String input) {
if (StringUtils.isBlank(input)){ if (StringUtils.isBlank(input)) {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
return new String(Base64.encodeBase64(input.getBytes(StandardCharsets.UTF_8))); return new String(Base64.encodeBase64(input.getBytes(StandardCharsets.UTF_8)));
} }
// /**
// * Base64编码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).
// */
// public static String encodeUrlSafeBase64(byte[] input) {
// return Base64.encodeBase64URLSafe(input);
// }
/** /**
* Base64解码. * Base64解码.
*/ */
public static byte[] decodeBase64(String input) { public static byte[] decodeBase64(String input) {
return Base64.decodeBase64(input.getBytes(StandardCharsets.UTF_8)); return Base64.decodeBase64(input.getBytes(StandardCharsets.UTF_8));
} }
/** /**
* Base64解码. * Base64解码.
*/ */
public static String decodeBase64String(String input) { public static String decodeBase64String(String input) {
if (StringUtils.isBlank(input)){ if (StringUtils.isBlank(input)) {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
return new String(Base64.decodeBase64(input.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); return new String(Base64.decodeBase64(input.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
} }
/** /**
* Base62编码。 * Base62编码。
*/ */
public static String encodeBase62(byte[] input) { public static String encodeBase62(byte[] input) {
char[] chars = new char[input.length]; char[] chars = new char[input.length];
for (int i = 0; i < input.length; i++) { for (int i = 0; i < input.length; i++) {
chars[i] = BASE62[((input[i] & 0xFF) % BASE62.length)]; chars[i] = BASE62[((input[i] & 0xFF) % BASE62.length)];
} }
return new String(chars); return new String(chars);
} }
/** /**
* Html 转码. * Html 转码.
*/ */
public static String encodeHtml(String html) { public static String encodeHtml(String html) {
return StringEscapeUtils.escapeHtml4(html); return StringEscapeUtils.escapeHtml4(html);
} }
/** /**
* Html 解码. * Html 解码.
*/ */
public static String decodeHtml(String htmlEscaped) { public static String decodeHtml(String htmlEscaped) {
return StringEscapeUtils.unescapeHtml4(htmlEscaped); return StringEscapeUtils.unescapeHtml4(htmlEscaped);
} }
/** /**
* Xml 转码. * Xml 转码.
*/ */
public static String encodeXml(String xml) { public static String encodeXml(String xml) {
return StringEscapeUtils.escapeXml10(xml); return StringEscapeUtils.escapeXml10(xml);
} }
/** /**
* Xml 解码. * Xml 解码.
*/ */
public static String decodeXml(String xmlEscaped) { public static String decodeXml(String xmlEscaped) {
return StringEscapeUtils.unescapeXml(xmlEscaped); return StringEscapeUtils.unescapeXml(xmlEscaped);
} }
/** /**
* URL 编码, Encode默认为UTF-8. * URL 编码, Encode默认为UTF-8.
*/ */
public static String encodeUrl(String part) { public static String encodeUrl(String part) {
return encodeUrl(part, EncodeUtils.UTF_8); return encodeUrl(part, EncodeUtils.UTF_8);
} }
/** /**
* URL 编码, Encode默认为UTF-8. * URL 编码, Encode默认为UTF-8.
*/ */
public static String encodeUrl(String part, String encoding) { public static String encodeUrl(String part, String encoding) {
if (part == null){ if (part == null) {
return null; return null;
} }
try { try {
return URLEncoder.encode(part, StringUtils.isNotBlank(encoding) ? encoding : EncodeUtils.UTF_8); return URLEncoder.encode(part, StringUtils.isNotBlank(encoding) ? encoding : EncodeUtils.UTF_8);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw ExceptionUtils.unchecked(e); throw ExceptionUtils.unchecked(e);
} }
} }
/** /**
* URL 解码, Encode默认为UTF-8. * URL 解码, Encode默认为UTF-8.
*/ */
public static String decodeUrl(String part) { public static String decodeUrl(String part) {
return decodeUrl(part, EncodeUtils.UTF_8); return decodeUrl(part, EncodeUtils.UTF_8);
} }
/** /**
* URL 解码, Encode默认为UTF-8. * URL 解码, Encode默认为UTF-8.
*/ */
public static String decodeUrl(String part, String encoding) { public static String decodeUrl(String part, String encoding) {
if (part == null){ if (part == null) {
return null; return null;
} }
try { try {
return URLDecoder.decode(part, StringUtils.isNotBlank(encoding) ? encoding : EncodeUtils.UTF_8); return URLDecoder.decode(part, StringUtils.isNotBlank(encoding) ? encoding : EncodeUtils.UTF_8);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw ExceptionUtils.unchecked(e); throw ExceptionUtils.unchecked(e);
} }
} }
/** /**
* URL 解码(两次), Encode默认为UTF-8. * URL 解码(两次), Encode默认为UTF-8.
*/ */
public static String decodeUrl2(String part) { public static String decodeUrl2(String part) {
return decodeUrl(decodeUrl(part)); return decodeUrl(decodeUrl(part));
} }
// 预编译XSS过滤正则表达式 // 预编译XSS过滤正则表达式
private static final List<Pattern> xssPatterns = ListUtils.newArrayList( private static final List<Pattern> xssPatterns = ListUtils.newArrayList(
Pattern.compile("(<\\s*(script|link|style|iframe)([\\s\\S]*?)(>|<\\/\\s*\\1\\s*>))|(</\\s*(script|link|style|iframe)\\s*>)", Pattern.CASE_INSENSITIVE), Pattern.compile("(<\\s*(script|link|style|iframe)([\\s\\S]*?)(>|<\\/\\s*\\1\\s*>))|(</\\s*(script|link|style|iframe)\\s*>)", Pattern.CASE_INSENSITIVE),
Pattern.compile("\\s*(href|src)\\s*=\\s*(\"\\s*(javascript|vbscript|data):[^\"]+\"|'\\s*(javascript|vbscript|data):[^']+'|(javascript|vbscript|data):[^\\s]+)\\s*(?=>)", Pattern.CASE_INSENSITIVE), Pattern.compile("\\s*(href|src)\\s*=\\s*(\"\\s*(javascript|vbscript|data):[^\"]+\"|'\\s*(javascript|vbscript|data):[^']+'|(javascript|vbscript|data):[^\\s]+)\\s*(?=>)", Pattern.CASE_INSENSITIVE),
Pattern.compile("\\s*/?\\s*on[a-zA-Z]+\\s*=\\s*(['\"]?)(.*?)\\1(?=\\s|>|/>)", Pattern.CASE_INSENSITIVE), Pattern.compile("\\s*/?\\s*on[a-zA-Z]+\\s*=\\s*(['\"]?)(.*?)\\1(?=\\s|>|/>)", Pattern.CASE_INSENSITIVE),
Pattern.compile("(eval\\((.*?)\\)|expression\\((.*?)\\))", Pattern.CASE_INSENSITIVE), Pattern.compile("(eval\\((.*?)\\)|expression\\((.*?)\\))", Pattern.CASE_INSENSITIVE),
Pattern.compile("^(javascript:|vbscript:)", Pattern.CASE_INSENSITIVE) Pattern.compile("^(javascript:|vbscript:)", Pattern.CASE_INSENSITIVE)
); );
/** /**
* XSS 非法字符过滤,内容以<!--HTML-->开头的用以下规则(保留标签) * XSS 非法字符过滤,内容以<!--HTML-->开头的用以下规则(保留标签)
* @author ThinkGem *
*/ * @author ThinkGem
public static String xssFilter(String text) { */
return xssFilter(text, null); public static String xssFilter(String text) {
} return xssFilter(text, null);
}
/** /**
* XSS 非法字符过滤,内容以<!--HTML-->开头的用以下规则(保留标签) * XSS 非法字符过滤,内容以<!--HTML-->开头的用以下规则(保留标签)
* @author ThinkGem *
*/ * @author ThinkGem
public static String xssFilter(String text, HttpServletRequest request) { */
request = (request != null ? request : ServletUtils.getRequest()); public static String xssFilter(String text, HttpServletRequest request) {
if (request != null && StringUtils.containsAny(request.getRequestURI(), ServletUtils.XSS_FILE_EXCLUDE_URI)) { // 获取当前请求对象(优先级:入参 > 上下文获取)
return text; HttpServletRequest currentRequest = (request != null) ? request : ServletUtils.getRequest();
} // 排除指定URI的过滤
String oriValue = StringUtils.trim(text); if (currentRequest != null && StringUtils.containsAny(currentRequest.getRequestURI(), ServletUtils.XSS_FILE_EXCLUDE_URI)) {
if (text != null){ return text;
String value = oriValue; }
for (Pattern pattern : xssPatterns) { // 空值处理
Matcher matcher = pattern.matcher(value); if (text == null) {
if (matcher.find()) { return null;
value = matcher.replaceAll(StringUtils.EMPTY); }
} // 去除首尾空格,保留原始值用于日志对比
} String originalValue = StringUtils.trim(text);
// 如果开始不是HTMLXMLJOSN格式则再进行HTML的 "、<、> 转码。 String filteredValue = originalValue;
if (!StringUtils.startsWithIgnoreCase(value, "<!--HTML-->") // HTML // 执行XSS正则过滤
&& !StringUtils.startsWithIgnoreCase(value, "<?xml ") // XML if (!filteredValue.isEmpty()) {
&& !(StringUtils.startsWith(value, "{") && StringUtils.endsWith(value, "}")) // JSON Object for (Pattern pattern : xssPatterns) {
&& !(StringUtils.startsWith(value, "[") && StringUtils.endsWith(value, "]")) // JSON Array Matcher matcher = pattern.matcher(filteredValue);
){ if (matcher.find()) {
StringBuilder sb = new StringBuilder(); filteredValue = matcher.replaceAll(StringUtils.EMPTY);
for (int i = 0; i < value.length(); i++) { }
char c = value.charAt(i); }
switch (c) { }
case '>': // 记录过滤日志(仅当内容有变化时)
sb.append(""); if (logger.isInfoEnabled() && !filteredValue.equals(originalValue)) {
break; String requestUrl = currentRequest != null ? currentRequest.getRequestURL().toString() : "common";
case '<': logger.info("xssFilter: {} <=<=<= {} source: {}", filteredValue, text, requestUrl);
sb.append(""); }
break; return filteredValue;
case '\'': }
sb.append("");
break;
case '\"':
sb.append("");
break;
default:
sb.append(c);
break;
}
}
value = sb.toString();
}
if (logger.isInfoEnabled() && !value.equals(oriValue)){
logger.info("xssFilter: {} <=<=<= {} source: {}", value, text,
request != null ? request.getRequestURL() : "common");
}
return value;
}
return null;
}
// 预编译SQL过滤正则表达式 // 预编译SQL过滤正则表达式
private static final Pattern sqlPattern = Pattern.compile( private static final Pattern sqlPattern = Pattern.compile(
"(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|((extractvalue|updatexml|if|mid|database|rand|user)([\\s]*?)\\()" "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|((extractvalue|updatexml|if|mid|database|rand|user)([\\s]*?)\\()"
+ "|(\\b(select|update|and|or|delete|insert|trancate|substr|ascii|declare|exec|count|master|into" + "|(\\b(select|update|and|or|delete|insert|trancate|substr|ascii|declare|exec|count|master|into"
+ "|drop|execute|case when|sleep|union|load_file)\\b)", Pattern.CASE_INSENSITIVE); + "|drop|execute|case when|sleep|union|load_file)\\b)", Pattern.CASE_INSENSITIVE);
private static final Pattern simplePattern = Pattern.compile("[a-z0-9_\\.\\, ]*", Pattern.CASE_INSENSITIVE); private static final Pattern simplePattern = Pattern.compile("[a-z0-9_\\.\\, ]*", Pattern.CASE_INSENSITIVE);
private static final Pattern columnNamePattern = Pattern.compile("[a-z0-9_\\.`\"\\[\\]]*", Pattern.CASE_INSENSITIVE); private static final Pattern columnNamePattern = Pattern.compile("[a-z0-9_\\.`\"\\[\\]]*", Pattern.CASE_INSENSITIVE);
/** /**
* SQL过滤防止注入传入参数输入有select相关代码替换空。 * SQL过滤防止注入传入参数输入有select相关代码替换空。
* @author ThinkGem *
*/ * @author ThinkGem
public static String sqlFilter(String text){ */
return sqlFilter(text, "common"); public static String sqlFilter(String text) {
} return sqlFilter(text, "common");
}
/** /**
* SQL过滤防止注入传入参数输入有select相关代码替换空。 * SQL过滤防止注入传入参数输入有select相关代码替换空。
* @author ThinkGem *
*/ * @author ThinkGem
public static String sqlFilter(String text, String source){ */
if (text != null){ public static String sqlFilter(String text, String source) {
String value = text; if (text != null) {
if (StringUtils.inString(source, "simple", "orderBy")) { String value = text;
Matcher matcher = simplePattern.matcher(value); if (StringUtils.inString(source, "simple", "orderBy")) {
if (!matcher.matches()) { Matcher matcher = simplePattern.matcher(value);
value = StringUtils.EMPTY; if (!matcher.matches()) {
} value = StringUtils.EMPTY;
} else if (StringUtils.inString(source, "columnName")) { }
Matcher matcher = columnNamePattern.matcher(value); } else if (StringUtils.inString(source, "columnName")) {
if (!matcher.matches()) { Matcher matcher = columnNamePattern.matcher(value);
value = StringUtils.EMPTY; if (!matcher.matches()) {
} value = StringUtils.EMPTY;
} else { }
Matcher matcher = sqlPattern.matcher(value); } else {
if (matcher.find()) { Matcher matcher = sqlPattern.matcher(value);
value = matcher.replaceAll(StringUtils.EMPTY); if (matcher.find()) {
} value = matcher.replaceAll(StringUtils.EMPTY);
} }
if (logger.isWarnEnabled() && !value.equals(text)){ }
logger.info("sqlFilter: {} <=<=<= {} source: {}", value, text, source); if (logger.isWarnEnabled() && !value.equals(text)) {
return StringUtils.EMPTY; logger.info("sqlFilter: {} <=<=<= {} source: {}", value, text, source);
} return StringUtils.EMPTY;
return value; }
} return value;
return null; }
} return null;
}
// 对邮箱和手机号进行安全处理 // 对邮箱和手机号进行安全处理
private static final Pattern emailPattern = Pattern.compile("([\\w\\W]?)([\\w\\W]+)([\\w\\W])(@[\\w\\W]+)"); private static final Pattern emailPattern = Pattern.compile("([\\w\\W]?)([\\w\\W]+)([\\w\\W])(@[\\w\\W]+)");
private static final Pattern mobilePattern = Pattern.compile("(\\d{3})(\\d+)(\\d{3})"); private static final Pattern mobilePattern = Pattern.compile("(\\d{3})(\\d+)(\\d{3})");
/** /**
* 手机号码进行掩码处理 * 手机号码进行掩码处理
*/ */
public static String mobileMask(String mobile){ public static String mobileMask(String mobile) {
if (StringUtils.isBlank(mobile)){ if (StringUtils.isBlank(mobile)) {
return mobile; return mobile;
} }
return mobilePattern.matcher(mobile).replaceAll("$1****$3"); return mobilePattern.matcher(mobile).replaceAll("$1****$3");
} }
/** /**
* 对电子邮箱进行掩码处理 * 对电子邮箱进行掩码处理
*/ */
public static String emailMask(String email){ public static String emailMask(String email) {
if (StringUtils.isBlank(email)){ if (StringUtils.isBlank(email)) {
return email; return email;
} }
return emailPattern.matcher(email).replaceAll("$1****$3$4"); return emailPattern.matcher(email).replaceAll("$1****$3$4");
} }
} }

View File

@@ -214,8 +214,8 @@ public class MysqlUtils {
public static ExecResult getExecResult(BizDbConfig dbConfig, String sql) { public static ExecResult getExecResult(BizDbConfig dbConfig, String sql) {
try { try {
EXEC_FILE = null;
Connection conn = getConnection(dbConfig.getDbIp(), dbConfig.getDbPort(), dbConfig.getDbUsername(), dbConfig.getDbPassword()); Connection conn = getConnection(dbConfig.getDbIp(), dbConfig.getDbPort(), dbConfig.getDbUsername(), dbConfig.getDbPassword());
Statement statement = conn.createStatement(); Statement statement = conn.createStatement();
boolean isQuery = sql.trim().toUpperCase().startsWith("SELECT"); boolean isQuery = sql.trim().toUpperCase().startsWith("SELECT");