SecurityUtils.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. package com.zsElectric.boot.common.util;
  2. import cn.hutool.core.util.StrUtil;
  3. import lombok.extern.slf4j.Slf4j;
  4. import java.util.Arrays;
  5. import java.util.HashSet;
  6. import java.util.Set;
  7. import java.util.regex.Pattern;
  8. /**
  9. * 安全防护工具类
  10. * <p>
  11. * 提供 XSS 攻击和 SQL 注入防护的核心检测方法
  12. *
  13. * @author zsElectric
  14. */
  15. @Slf4j
  16. public class SecurityUtils {
  17. /**
  18. * SQL 注入检测是否使用严格模式
  19. * 默认为宽松模式以减少误判
  20. */
  21. private static volatile boolean sqlStrictMode = false;
  22. /**
  23. * 设置 SQL 注入检测模式
  24. *
  25. * @param strictMode true 为严格模式,false 为宽松模式
  26. */
  27. public static void setSqlStrictMode(boolean strictMode) {
  28. sqlStrictMode = strictMode;
  29. }
  30. /**
  31. * XSS 攻击检测正则表达式
  32. */
  33. private static final Pattern[] XSS_PATTERNS = {
  34. // Script 标签
  35. Pattern.compile("<script[^>]*?>.*?</script>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
  36. Pattern.compile("<script[^>]*?>", Pattern.CASE_INSENSITIVE),
  37. Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
  38. // JavaScript 事件
  39. Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
  40. Pattern.compile("onerror\\s*=", Pattern.CASE_INSENSITIVE),
  41. Pattern.compile("onload\\s*=", Pattern.CASE_INSENSITIVE),
  42. Pattern.compile("onclick\\s*=", Pattern.CASE_INSENSITIVE),
  43. Pattern.compile("onmouseover\\s*=", Pattern.CASE_INSENSITIVE),
  44. Pattern.compile("onfocus\\s*=", Pattern.CASE_INSENSITIVE),
  45. Pattern.compile("onblur\\s*=", Pattern.CASE_INSENSITIVE),
  46. Pattern.compile("onsubmit\\s*=", Pattern.CASE_INSENSITIVE),
  47. Pattern.compile("onreset\\s*=", Pattern.CASE_INSENSITIVE),
  48. Pattern.compile("onselect\\s*=", Pattern.CASE_INSENSITIVE),
  49. Pattern.compile("onchange\\s*=", Pattern.CASE_INSENSITIVE),
  50. // iframe 标签
  51. Pattern.compile("<iframe[^>]*?>.*?</iframe>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
  52. Pattern.compile("<iframe[^>]*?>", Pattern.CASE_INSENSITIVE),
  53. // embed、object 标签
  54. Pattern.compile("<embed[^>]*?>", Pattern.CASE_INSENSITIVE),
  55. Pattern.compile("<object[^>]*?>", Pattern.CASE_INSENSITIVE),
  56. // eval、expression
  57. Pattern.compile("eval\\s*\\(", Pattern.CASE_INSENSITIVE),
  58. Pattern.compile("expression\\s*\\(", Pattern.CASE_INSENSITIVE),
  59. // vbscript
  60. Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
  61. // img 标签的 src
  62. Pattern.compile("<img[^>]+src[\\s]*=[\\s]*['\"]?javascript:", Pattern.CASE_INSENSITIVE),
  63. // style 中的 expression
  64. Pattern.compile("style\\s*=.*expression", Pattern.CASE_INSENSITIVE),
  65. // base64 编码的脚本
  66. Pattern.compile("data:text/html;base64", Pattern.CASE_INSENSITIVE),
  67. // SVG
  68. Pattern.compile("<svg[^>]*?>.*?</svg>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
  69. // Meta 标签
  70. Pattern.compile("<meta[^>]*?>", Pattern.CASE_INSENSITIVE),
  71. // Link 标签
  72. Pattern.compile("<link[^>]*?>", Pattern.CASE_INSENSITIVE)
  73. };
  74. /**
  75. * SQL 注入危险关键词
  76. */
  77. private static final Set<String> SQL_KEYWORDS = new HashSet<>(Arrays.asList(
  78. // DML 语句
  79. "select", "insert", "update", "delete",
  80. // DDL 语句
  81. "drop", "create", "alter", "truncate",
  82. // DCL 语句
  83. "grant", "revoke",
  84. // 联合查询
  85. "union", "join",
  86. // 系统函数和存储过程
  87. "exec", "execute", "xp_cmdshell", "sp_executesql",
  88. // 信息获取
  89. "information_schema", "mysql.user", "sys.",
  90. // 条件判断
  91. "case", "when", "then", "else", "end",
  92. // 其他危险操作
  93. "declare", "cast", "convert", "char", "chr",
  94. "concat", "load_file", "into outfile", "into dumpfile",
  95. "benchmark", "sleep", "waitfor", "delay",
  96. // 子查询
  97. "exists", "any", "all", "some"
  98. ));
  99. /**
  100. * SQL 注入检测正则表达式
  101. */
  102. private static final Pattern[] SQL_INJECTION_PATTERNS = {
  103. // SQL 注释
  104. Pattern.compile("('.+--)|(--)|(;)|(\\|{2})"),
  105. // SQL 函数调用
  106. Pattern.compile("\\bexec(ute)?\\s*\\(", Pattern.CASE_INSENSITIVE),
  107. // union 查询
  108. Pattern.compile("\\bunion\\b.*\\bselect\\b", Pattern.CASE_INSENSITIVE),
  109. // 多语句
  110. Pattern.compile(";.*?(select|insert|update|delete|drop|create|alter)", Pattern.CASE_INSENSITIVE),
  111. // 16 进制编码
  112. Pattern.compile("0x[0-9a-f]+", Pattern.CASE_INSENSITIVE),
  113. // 字符串拼接
  114. Pattern.compile("\\bconcat\\s*\\(", Pattern.CASE_INSENSITIVE),
  115. // sleep 函数
  116. Pattern.compile("\\bsleep\\s*\\(", Pattern.CASE_INSENSITIVE),
  117. // benchmark 函数
  118. Pattern.compile("\\bbenckmark\\s*\\(", Pattern.CASE_INSENSITIVE),
  119. // waitfor delay
  120. Pattern.compile("\\bwaitfor\\s+\\bdelay\\b", Pattern.CASE_INSENSITIVE),
  121. // 子查询
  122. Pattern.compile("\\bsubstr\\s*\\(", Pattern.CASE_INSENSITIVE),
  123. Pattern.compile("\\bsubstring\\s*\\(", Pattern.CASE_INSENSITIVE)
  124. };
  125. /**
  126. * 检测 XSS 攻击
  127. *
  128. * @param value 待检测的字符串
  129. * @return 如果检测到 XSS 攻击返回 true,否则返回 false
  130. */
  131. public static boolean containsXss(String value) {
  132. if (StrUtil.isBlank(value)) {
  133. return false;
  134. }
  135. // 解码 URL 编码
  136. String decodedValue = urlDecode(value);
  137. // 使用正则表达式检测
  138. for (Pattern pattern : XSS_PATTERNS) {
  139. if (pattern.matcher(decodedValue).find()) {
  140. log.warn("检测到 XSS 攻击,匹配模式: {}, 内容: {}", pattern.pattern(), value);
  141. return true;
  142. }
  143. }
  144. return false;
  145. }
  146. /**
  147. * 检测 SQL 注入
  148. *
  149. * @param value 待检测的字符串
  150. * @return 如果检测到 SQL 注入返回 true,否则返回 false
  151. */
  152. public static boolean containsSqlInjection(String value) {
  153. if (StrUtil.isBlank(value)) {
  154. return false;
  155. }
  156. String lowerValue = value.toLowerCase();
  157. // 检查注释符号(更严格的检测)
  158. if (lowerValue.contains("--") || lowerValue.contains("/*") || lowerValue.contains("*/") || lowerValue.contains("#")) {
  159. // 检查是否是真正的注释而不是普通文本
  160. if (lowerValue.matches(".*\\s(--|#).*") || lowerValue.contains("/*") || lowerValue.contains("*/")) {
  161. log.warn("检测到 SQL 注入注释符号: {}, 内容: {}", "--/#/*", value);
  162. return true;
  163. }
  164. }
  165. // 检查危险关键词(使用更精确的匹配规则)
  166. for (String keyword : SQL_KEYWORDS) {
  167. // 使用单词边界进行匹配,避免误判(例如:"selection" 不应匹配 "select")
  168. // 同时确保关键词前后不是字母数字字符
  169. String pattern = "([^a-zA-Z0-9]|^)" + keyword + "([^a-zA-Z0-9]|$)";
  170. if (Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(lowerValue).find()) {
  171. // 进一步检查是否为真实攻击而非正常文本
  172. // 例如:"select" 在 "selected" 中是正常文本,但在 "select * from" 中可能是攻击
  173. if (isRealSqlInjection(keyword, lowerValue)) {
  174. log.warn("检测到 SQL 注入关键词: {}, 内容: {}", keyword, value);
  175. return true;
  176. }
  177. }
  178. }
  179. // 使用正则表达式检测
  180. for (Pattern pattern : SQL_INJECTION_PATTERNS) {
  181. if (pattern.matcher(value).find()) {
  182. log.warn("检测到 SQL 注入,匹配模式: {}, 内容: {}", pattern.pattern(), value);
  183. return true;
  184. }
  185. }
  186. return false;
  187. }
  188. /**
  189. * 清理 XSS 攻击内容(转义特殊字符)
  190. *
  191. * @param value 待清理的字符串
  192. * @return 清理后的字符串
  193. */
  194. public static String cleanXss(String value) {
  195. if (StrUtil.isBlank(value)) {
  196. return value;
  197. }
  198. // HTML 实体编码
  199. value = value.replace("&", "&amp;")
  200. .replace("<", "&lt;")
  201. .replace(">", "&gt;")
  202. .replace("\"", "&quot;")
  203. .replace("'", "&#x27;")
  204. .replace("/", "&#x2F;");
  205. return value;
  206. }
  207. /**
  208. * URL 解码(支持多次编码)
  209. *
  210. * @param value 待解码的字符串
  211. * @return 解码后的字符串
  212. */
  213. private static String urlDecode(String value) {
  214. String decoded = value;
  215. try {
  216. // 最多解码 3 次,防止多重编码绕过
  217. for (int i = 0; i < 3; i++) {
  218. String temp = java.net.URLDecoder.decode(decoded, "UTF-8");
  219. if (temp.equals(decoded)) {
  220. break;
  221. }
  222. decoded = temp;
  223. }
  224. } catch (Exception e) {
  225. log.warn("URL 解码失败: {}", value, e);
  226. }
  227. return decoded;
  228. }
  229. /**
  230. * 验证输入是否安全(综合检查 XSS 和 SQL 注入)
  231. *
  232. * @param value 待验证的字符串
  233. * @return 如果输入安全返回 true,否则返回 false
  234. */
  235. public static boolean isSafeInput(String value) {
  236. return !containsXss(value) && !containsSqlInjection(value);
  237. }
  238. /**
  239. * 判断是否为真实的SQL注入攻击
  240. *
  241. * @param keyword 检测到的关键词
  242. * @param value 待检测的字符串(小写)
  243. * @return 如果是真实攻击返回 true,否则返回 false
  244. */
  245. private static boolean isRealSqlInjection(String keyword, String value) {
  246. if (!sqlStrictMode) {
  247. // 在宽松模式下,只对明显的攻击模式进行拦截
  248. switch (keyword) {
  249. case "select":
  250. // 只有当 select 后面跟着典型的 SQL 结构时才认为是攻击
  251. boolean isSelectAttack = value.matches(".*select\\s+[a-z0-9_*]+\\s+from\\s+[a-z0-9_]+.*") ||
  252. value.matches(".*select\\s+\\*\\s+from\\s+[a-z0-9_]+.*");
  253. if (isSelectAttack) {
  254. log.debug("检测到可能的 SELECT 攻击: {}", value);
  255. }
  256. return isSelectAttack;
  257. case "insert":
  258. boolean isInsertAttack = value.contains("insert into");
  259. if (isInsertAttack) {
  260. log.debug("检测到可能的 INSERT 攻击: {}", value);
  261. }
  262. return isInsertAttack;
  263. case "update":
  264. boolean isUpdateAttack = value.contains("update ") && value.contains(" set ");
  265. if (isUpdateAttack) {
  266. log.debug("检测到可能的 UPDATE 攻击: {}", value);
  267. }
  268. return isUpdateAttack;
  269. case "delete":
  270. boolean isDeleteAttack = value.contains("delete from");
  271. if (isDeleteAttack) {
  272. log.debug("检测到可能的 DELETE 攻击: {}", value);
  273. }
  274. return isDeleteAttack;
  275. case "drop":
  276. case "create":
  277. case "alter":
  278. case "truncate":
  279. boolean isDdlAttack = value.contains(keyword + " ");
  280. if (isDdlAttack) {
  281. log.debug("检测到可能的 DDL 攻击 ({}): {}", keyword, value);
  282. }
  283. return isDdlAttack;
  284. case "union":
  285. boolean isUnionAttack = value.contains("union select");
  286. if (isUnionAttack) {
  287. log.debug("检测到可能的 UNION 攻击: {}", value);
  288. }
  289. return isUnionAttack;
  290. case "exec":
  291. case "execute":
  292. boolean isExecAttack = value.contains(keyword + "(");
  293. if (isExecAttack) {
  294. log.debug("检测到可能的 EXEC 攻击 ({}): {}", keyword, value);
  295. }
  296. return isExecAttack;
  297. default:
  298. // 对于其他关键词,采用宽松策略,避免误判正常用户名等
  299. return false;
  300. }
  301. } else {
  302. // 严格模式下保持原来的逻辑
  303. switch (keyword) {
  304. case "select":
  305. // select 通常是攻击的一部分,后面跟着列名和 from
  306. boolean isSelectAttack = value.matches(".*select\\s+[a-z0-9_*]+\\s+from\\s+[a-z0-9_]+.*") ||
  307. value.matches(".*select\\s+\\*\\s+from\\s+[a-z0-9_]+.*");
  308. if (isSelectAttack) {
  309. log.debug("[严格模式] 检测到可能的 SELECT 攻击: {}", value);
  310. }
  311. return isSelectAttack;
  312. case "insert":
  313. // insert 通常是攻击的一部分,后面跟着 into
  314. boolean isInsertAttack = value.contains("insert into");
  315. if (isInsertAttack) {
  316. log.debug("[严格模式] 检测到可能的 INSERT 攻击: {}", value);
  317. }
  318. return isInsertAttack;
  319. case "update":
  320. // update 通常是攻击的一部分,后面跟着 set
  321. boolean isUpdateAttack = value.contains("update ") && value.contains(" set ");
  322. if (isUpdateAttack) {
  323. log.debug("[严格模式] 检测到可能的 UPDATE 攻击: {}", value);
  324. }
  325. return isUpdateAttack;
  326. case "delete":
  327. // delete 通常是攻击的一部分,后面跟着 from
  328. boolean isDeleteAttack = value.contains("delete from");
  329. if (isDeleteAttack) {
  330. log.debug("[严格模式] 检测到可能的 DELETE 攻击: {}", value);
  331. }
  332. return isDeleteAttack;
  333. case "drop":
  334. case "create":
  335. case "alter":
  336. case "truncate":
  337. // DDL 语句通常是攻击的一部分
  338. boolean isDdlAttack = value.contains(keyword + " ");
  339. if (isDdlAttack) {
  340. log.debug("[严格模式] 检测到可能的 DDL 攻击 ({}): {}", keyword, value);
  341. }
  342. return isDdlAttack;
  343. case "union":
  344. // union 通常是攻击的一部分,后面跟着 select
  345. boolean isUnionAttack = value.contains("union select");
  346. if (isUnionAttack) {
  347. log.debug("[严格模式] 检测到可能的 UNION 攻击: {}", value);
  348. }
  349. return isUnionAttack;
  350. default:
  351. // 对于其他关键词,采用宽松策略,避免误判正常用户名等
  352. return false;
  353. }
  354. }
  355. }
  356. }