| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- package com.zsElectric.boot.common.util;
- import cn.hutool.core.util.StrUtil;
- import lombok.extern.slf4j.Slf4j;
- import java.util.Arrays;
- import java.util.HashSet;
- import java.util.Set;
- import java.util.regex.Pattern;
- /**
- * 安全防护工具类
- * <p>
- * 提供 XSS 攻击和 SQL 注入防护的核心检测方法
- *
- * @author zsElectric
- */
- @Slf4j
- public class SecurityUtils {
- /**
- * SQL 注入检测是否使用严格模式
- * 默认为宽松模式以减少误判
- */
- private static volatile boolean sqlStrictMode = false;
- /**
- * 设置 SQL 注入检测模式
- *
- * @param strictMode true 为严格模式,false 为宽松模式
- */
- public static void setSqlStrictMode(boolean strictMode) {
- sqlStrictMode = strictMode;
- }
- /**
- * XSS 攻击检测正则表达式
- */
- private static final Pattern[] XSS_PATTERNS = {
- // Script 标签
- Pattern.compile("<script[^>]*?>.*?</script>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
- Pattern.compile("<script[^>]*?>", Pattern.CASE_INSENSITIVE),
- Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
- // JavaScript 事件
- Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onerror\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onload\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onclick\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onmouseover\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onfocus\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onblur\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onsubmit\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onreset\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onselect\\s*=", Pattern.CASE_INSENSITIVE),
- Pattern.compile("onchange\\s*=", Pattern.CASE_INSENSITIVE),
- // iframe 标签
- Pattern.compile("<iframe[^>]*?>.*?</iframe>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
- Pattern.compile("<iframe[^>]*?>", Pattern.CASE_INSENSITIVE),
- // embed、object 标签
- Pattern.compile("<embed[^>]*?>", Pattern.CASE_INSENSITIVE),
- Pattern.compile("<object[^>]*?>", Pattern.CASE_INSENSITIVE),
- // eval、expression
- Pattern.compile("eval\\s*\\(", Pattern.CASE_INSENSITIVE),
- Pattern.compile("expression\\s*\\(", Pattern.CASE_INSENSITIVE),
- // vbscript
- Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
- // img 标签的 src
- Pattern.compile("<img[^>]+src[\\s]*=[\\s]*['\"]?javascript:", Pattern.CASE_INSENSITIVE),
- // style 中的 expression
- Pattern.compile("style\\s*=.*expression", Pattern.CASE_INSENSITIVE),
- // base64 编码的脚本
- Pattern.compile("data:text/html;base64", Pattern.CASE_INSENSITIVE),
- // SVG
- Pattern.compile("<svg[^>]*?>.*?</svg>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
- // Meta 标签
- Pattern.compile("<meta[^>]*?>", Pattern.CASE_INSENSITIVE),
- // Link 标签
- Pattern.compile("<link[^>]*?>", Pattern.CASE_INSENSITIVE)
- };
- /**
- * SQL 注入危险关键词
- */
- private static final Set<String> SQL_KEYWORDS = new HashSet<>(Arrays.asList(
- // DML 语句
- "select", "insert", "update", "delete",
- // DDL 语句
- "drop", "create", "alter", "truncate",
- // DCL 语句
- "grant", "revoke",
- // 联合查询
- "union", "join",
- // 系统函数和存储过程
- "exec", "execute", "xp_cmdshell", "sp_executesql",
- // 信息获取
- "information_schema", "mysql.user", "sys.",
- // 条件判断
- "case", "when", "then", "else", "end",
- // 其他危险操作
- "declare", "cast", "convert", "char", "chr",
- "concat", "load_file", "into outfile", "into dumpfile",
- "benchmark", "sleep", "waitfor", "delay",
- // 子查询
- "exists", "any", "all", "some"
- ));
- /**
- * SQL 注入检测正则表达式
- */
- private static final Pattern[] SQL_INJECTION_PATTERNS = {
- // SQL 注释
- Pattern.compile("('.+--)|(--)|(;)|(\\|{2})"),
- // SQL 函数调用
- Pattern.compile("\\bexec(ute)?\\s*\\(", Pattern.CASE_INSENSITIVE),
- // union 查询
- Pattern.compile("\\bunion\\b.*\\bselect\\b", Pattern.CASE_INSENSITIVE),
- // 多语句
- Pattern.compile(";.*?(select|insert|update|delete|drop|create|alter)", Pattern.CASE_INSENSITIVE),
- // 16 进制编码
- Pattern.compile("0x[0-9a-f]+", Pattern.CASE_INSENSITIVE),
- // 字符串拼接
- Pattern.compile("\\bconcat\\s*\\(", Pattern.CASE_INSENSITIVE),
- // sleep 函数
- Pattern.compile("\\bsleep\\s*\\(", Pattern.CASE_INSENSITIVE),
- // benchmark 函数
- Pattern.compile("\\bbenckmark\\s*\\(", Pattern.CASE_INSENSITIVE),
- // waitfor delay
- Pattern.compile("\\bwaitfor\\s+\\bdelay\\b", Pattern.CASE_INSENSITIVE),
- // 子查询
- Pattern.compile("\\bsubstr\\s*\\(", Pattern.CASE_INSENSITIVE),
- Pattern.compile("\\bsubstring\\s*\\(", Pattern.CASE_INSENSITIVE)
- };
- /**
- * 检测 XSS 攻击
- *
- * @param value 待检测的字符串
- * @return 如果检测到 XSS 攻击返回 true,否则返回 false
- */
- public static boolean containsXss(String value) {
- if (StrUtil.isBlank(value)) {
- return false;
- }
- // 解码 URL 编码
- String decodedValue = urlDecode(value);
- // 使用正则表达式检测
- for (Pattern pattern : XSS_PATTERNS) {
- if (pattern.matcher(decodedValue).find()) {
- log.warn("检测到 XSS 攻击,匹配模式: {}, 内容: {}", pattern.pattern(), value);
- return true;
- }
- }
- return false;
- }
- /**
- * 检测 SQL 注入
- *
- * @param value 待检测的字符串
- * @return 如果检测到 SQL 注入返回 true,否则返回 false
- */
- public static boolean containsSqlInjection(String value) {
- if (StrUtil.isBlank(value)) {
- return false;
- }
- String lowerValue = value.toLowerCase();
- // 检查注释符号(更严格的检测)
- if (lowerValue.contains("--") || lowerValue.contains("/*") || lowerValue.contains("*/") || lowerValue.contains("#")) {
- // 检查是否是真正的注释而不是普通文本
- if (lowerValue.matches(".*\\s(--|#).*") || lowerValue.contains("/*") || lowerValue.contains("*/")) {
- log.warn("检测到 SQL 注入注释符号: {}, 内容: {}", "--/#/*", value);
- return true;
- }
- }
- // 检查危险关键词(使用更精确的匹配规则)
- for (String keyword : SQL_KEYWORDS) {
- // 使用单词边界进行匹配,避免误判(例如:"selection" 不应匹配 "select")
- // 同时确保关键词前后不是字母数字字符
- String pattern = "([^a-zA-Z0-9]|^)" + keyword + "([^a-zA-Z0-9]|$)";
- if (Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(lowerValue).find()) {
- // 进一步检查是否为真实攻击而非正常文本
- // 例如:"select" 在 "selected" 中是正常文本,但在 "select * from" 中可能是攻击
- if (isRealSqlInjection(keyword, lowerValue)) {
- log.warn("检测到 SQL 注入关键词: {}, 内容: {}", keyword, value);
- return true;
- }
- }
- }
- // 使用正则表达式检测
- for (Pattern pattern : SQL_INJECTION_PATTERNS) {
- if (pattern.matcher(value).find()) {
- log.warn("检测到 SQL 注入,匹配模式: {}, 内容: {}", pattern.pattern(), value);
- return true;
- }
- }
- return false;
- }
- /**
- * 清理 XSS 攻击内容(转义特殊字符)
- *
- * @param value 待清理的字符串
- * @return 清理后的字符串
- */
- public static String cleanXss(String value) {
- if (StrUtil.isBlank(value)) {
- return value;
- }
- // HTML 实体编码
- value = value.replace("&", "&")
- .replace("<", "<")
- .replace(">", ">")
- .replace("\"", """)
- .replace("'", "'")
- .replace("/", "/");
- return value;
- }
- /**
- * URL 解码(支持多次编码)
- *
- * @param value 待解码的字符串
- * @return 解码后的字符串
- */
- private static String urlDecode(String value) {
- String decoded = value;
- try {
- // 最多解码 3 次,防止多重编码绕过
- for (int i = 0; i < 3; i++) {
- String temp = java.net.URLDecoder.decode(decoded, "UTF-8");
- if (temp.equals(decoded)) {
- break;
- }
- decoded = temp;
- }
- } catch (Exception e) {
- log.warn("URL 解码失败: {}", value, e);
- }
- return decoded;
- }
- /**
- * 验证输入是否安全(综合检查 XSS 和 SQL 注入)
- *
- * @param value 待验证的字符串
- * @return 如果输入安全返回 true,否则返回 false
- */
- public static boolean isSafeInput(String value) {
- return !containsXss(value) && !containsSqlInjection(value);
- }
- /**
- * 判断是否为真实的SQL注入攻击
- *
- * @param keyword 检测到的关键词
- * @param value 待检测的字符串(小写)
- * @return 如果是真实攻击返回 true,否则返回 false
- */
- private static boolean isRealSqlInjection(String keyword, String value) {
- if (!sqlStrictMode) {
- // 在宽松模式下,只对明显的攻击模式进行拦截
- switch (keyword) {
- case "select":
- // 只有当 select 后面跟着典型的 SQL 结构时才认为是攻击
- boolean isSelectAttack = value.matches(".*select\\s+[a-z0-9_*]+\\s+from\\s+[a-z0-9_]+.*") ||
- value.matches(".*select\\s+\\*\\s+from\\s+[a-z0-9_]+.*");
- if (isSelectAttack) {
- log.debug("检测到可能的 SELECT 攻击: {}", value);
- }
- return isSelectAttack;
- case "insert":
- boolean isInsertAttack = value.contains("insert into");
- if (isInsertAttack) {
- log.debug("检测到可能的 INSERT 攻击: {}", value);
- }
- return isInsertAttack;
- case "update":
- boolean isUpdateAttack = value.contains("update ") && value.contains(" set ");
- if (isUpdateAttack) {
- log.debug("检测到可能的 UPDATE 攻击: {}", value);
- }
- return isUpdateAttack;
- case "delete":
- boolean isDeleteAttack = value.contains("delete from");
- if (isDeleteAttack) {
- log.debug("检测到可能的 DELETE 攻击: {}", value);
- }
- return isDeleteAttack;
- case "drop":
- case "create":
- case "alter":
- case "truncate":
- boolean isDdlAttack = value.contains(keyword + " ");
- if (isDdlAttack) {
- log.debug("检测到可能的 DDL 攻击 ({}): {}", keyword, value);
- }
- return isDdlAttack;
- case "union":
- boolean isUnionAttack = value.contains("union select");
- if (isUnionAttack) {
- log.debug("检测到可能的 UNION 攻击: {}", value);
- }
- return isUnionAttack;
- case "exec":
- case "execute":
- boolean isExecAttack = value.contains(keyword + "(");
- if (isExecAttack) {
- log.debug("检测到可能的 EXEC 攻击 ({}): {}", keyword, value);
- }
- return isExecAttack;
- default:
- // 对于其他关键词,采用宽松策略,避免误判正常用户名等
- return false;
- }
- } else {
- // 严格模式下保持原来的逻辑
- switch (keyword) {
- case "select":
- // select 通常是攻击的一部分,后面跟着列名和 from
- boolean isSelectAttack = value.matches(".*select\\s+[a-z0-9_*]+\\s+from\\s+[a-z0-9_]+.*") ||
- value.matches(".*select\\s+\\*\\s+from\\s+[a-z0-9_]+.*");
- if (isSelectAttack) {
- log.debug("[严格模式] 检测到可能的 SELECT 攻击: {}", value);
- }
- return isSelectAttack;
- case "insert":
- // insert 通常是攻击的一部分,后面跟着 into
- boolean isInsertAttack = value.contains("insert into");
- if (isInsertAttack) {
- log.debug("[严格模式] 检测到可能的 INSERT 攻击: {}", value);
- }
- return isInsertAttack;
- case "update":
- // update 通常是攻击的一部分,后面跟着 set
- boolean isUpdateAttack = value.contains("update ") && value.contains(" set ");
- if (isUpdateAttack) {
- log.debug("[严格模式] 检测到可能的 UPDATE 攻击: {}", value);
- }
- return isUpdateAttack;
- case "delete":
- // delete 通常是攻击的一部分,后面跟着 from
- boolean isDeleteAttack = value.contains("delete from");
- if (isDeleteAttack) {
- log.debug("[严格模式] 检测到可能的 DELETE 攻击: {}", value);
- }
- return isDeleteAttack;
- case "drop":
- case "create":
- case "alter":
- case "truncate":
- // DDL 语句通常是攻击的一部分
- boolean isDdlAttack = value.contains(keyword + " ");
- if (isDdlAttack) {
- log.debug("[严格模式] 检测到可能的 DDL 攻击 ({}): {}", keyword, value);
- }
- return isDdlAttack;
- case "union":
- // union 通常是攻击的一部分,后面跟着 select
- boolean isUnionAttack = value.contains("union select");
- if (isUnionAttack) {
- log.debug("[严格模式] 检测到可能的 UNION 攻击: {}", value);
- }
- return isUnionAttack;
- default:
- // 对于其他关键词,采用宽松策略,避免误判正常用户名等
- return false;
- }
- }
- }
- }
|