package db import ( "regexp" "strings" ) // 危险语句规则:词边界匹配,作用于 StripComments 之后的纯代码文本。 // 词表:DROP/TRUNCATE/ALTER/GRANT/REVOKE/CREATE USER|ROLE/RENAME TABLE/ // REPLACE INTO/LOAD DATA/INTO OUTFILE|DUMPFILE/xp_cmdshell/COPY..PROGRAM/EXEC/CALL。 var dangerPatterns = []struct { re *regexp.Regexp desc string }{ {regexp.MustCompile(`\bDROP\b`), "DROP"}, {regexp.MustCompile(`\bTRUNCATE\b`), "TRUNCATE"}, {regexp.MustCompile(`\bALTER\b`), "ALTER"}, {regexp.MustCompile(`\bGRANT\b`), "GRANT"}, {regexp.MustCompile(`\bREVOKE\b`), "REVOKE"}, {regexp.MustCompile(`\bCREATE\s+(USER|ROLE)\b`), "CREATE USER/ROLE"}, {regexp.MustCompile(`\bRENAME\s+TABLE\b`), "RENAME TABLE"}, {regexp.MustCompile(`\bREPLACE\s+INTO\b`), "REPLACE INTO"}, {regexp.MustCompile(`\bLOAD\s+DATA\b`), "LOAD DATA"}, {regexp.MustCompile(`\bINTO\s+(OUTFILE|DUMPFILE)\b`), "INTO OUTFILE/DUMPFILE"}, {regexp.MustCompile(`(?i)\bxp_cmdshell\b`), "xp_cmdshell"}, {regexp.MustCompile(`\bCOPY\b[^;]*\b(FROM|TO)\s+PROGRAM\b`), "COPY FROM/TO PROGRAM"}, } var ( reWriteNoWhere = regexp.MustCompile(`\b(DELETE|UPDATE)\b`) reWhere = regexp.MustCompile(`\bWHERE\b`) reExecCallHead = regexp.MustCompile(`^(EXEC|EXECUTE|CALL)\b`) ) // CheckDangerousSQL 服务端权威的危险语句判定(前端预检仅为 UX)。 // 逐语句检查;注释/字符串内的关键字不计;MySQL /*! */ 可执行注释内容参与检查; // WITH 包裹的写操作因全文扫描 CTE 体同样命中。 // 返回命中的危险原因清单(去重)。 func CheckDangerousSQL(driver, sqlText string) []string { _ = driver // 词表当前各驱动通用,保留参数便于后续按方言细化 seen := map[string]struct{}{} var hits []string add := func(desc, stmt string) { if _, ok := seen[desc]; ok { return } seen[desc] = struct{}{} hits = append(hits, desc+": "+truncateSQL(stmt, 80)) } for _, stmt := range SplitStatements(sqlText) { code := strings.ToUpper(StripComments(stmt)) code = strings.TrimSpace(code) if code == "" { continue } for _, p := range dangerPatterns { if p.re.MatchString(code) { add(p.desc, stmt) } } // DELETE/UPDATE 无 WHERE(含 WITH CTE 体内的写操作) if reWriteNoWhere.MatchString(code) && !reWhere.MatchString(code) { kw := reWriteNoWhere.FindString(code) add(kw+" 无 WHERE", stmt) } // 存储过程/动态执行仅匹配语句开头,降低误报 if m := reExecCallHead.FindString(code); m != "" { add(m, stmt) } } return hits } // IsDangerousSQL 兼容旧调用:命中任意危险规则即 true。 func IsDangerousSQL(sql string) bool { return len(CheckDangerousSQL("", sql)) > 0 } func truncateSQL(s string, n int) string { s = strings.Join(strings.Fields(s), " ") if len(s) <= n { return s } return s[:n] + "…" } func stripSQLLead(sql string) string { s := strings.TrimSpace(sql) for { if strings.HasPrefix(s, "--") { if i := strings.IndexByte(s, '\n'); i >= 0 { s = strings.TrimSpace(s[i+1:]) continue } return "" } if strings.HasPrefix(s, "/*") { if i := strings.Index(s, "*/"); i >= 0 { s = strings.TrimSpace(s[i+2:]) continue } return "" } break } return s }