108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
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
|
|
}
|