diff --git a/api/entrance.go b/api/entrance.go index b03330c..4f8dba4 100644 --- a/api/entrance.go +++ b/api/entrance.go @@ -45,6 +45,7 @@ type APIGroup struct { WafFileApi WafSystemMonitorApi WafCaServerInfoApi + WafSqlQueryApi } var APIGroupAPP = new(APIGroup) @@ -105,4 +106,6 @@ var ( wafMonitorService = waf_service.WafSystemMonitorServiceApp wafCaServerInfoService = waf_service.WafCaServerInfoServiceApp + + wafSqlQueryService = waf_service.WafSqlQueryServiceApp ) diff --git a/api/waf_sql_query.go b/api/waf_sql_query.go new file mode 100644 index 0000000..d35de30 --- /dev/null +++ b/api/waf_sql_query.go @@ -0,0 +1,26 @@ +package api + +import ( + "SamWaf/model/common/response" + "SamWaf/model/request" + + "github.com/gin-gonic/gin" +) + +type WafSqlQueryApi struct { +} + +func (w *WafSqlQueryApi) ExecuteQueryApi(c *gin.Context) { + var req request.WafSqlQueryReq + err := c.ShouldBindJSON(&req) + if err == nil { + result, err := wafSqlQueryService.ExecuteQuery(req) + if err != nil { + response.FailWithMessage("查询失败: "+err.Error(), c) + } else { + response.OkWithDetailed(result, "查询成功", c) + } + } else { + response.FailWithMessage("解析失败: "+err.Error(), c) + } +} diff --git a/model/request/waf_sql_query_req.go b/model/request/waf_sql_query_req.go new file mode 100644 index 0000000..f31c163 --- /dev/null +++ b/model/request/waf_sql_query_req.go @@ -0,0 +1,7 @@ +package request + +type WafSqlQueryReq struct { + DbType string `json:"db_type" form:"db_type"` // 数据库类型:local, log, stats + Sql string `json:"sql" form:"sql"` // SQL 查询语句 + Limit int `json:"limit" form:"limit"` // 返回记录数限制,默认 1000 +} diff --git a/model/response/waf_sql_query_resp.go b/model/response/waf_sql_query_resp.go new file mode 100644 index 0000000..24efc9d --- /dev/null +++ b/model/response/waf_sql_query_resp.go @@ -0,0 +1,7 @@ +package response + +type WafSqlQueryResp struct { + Columns []string `json:"columns"` // 列名列表 + Data []map[string]interface{} `json:"data"` // 数据行列表 + Total int `json:"total"` // 总记录数 +} diff --git a/router/entrance.go b/router/entrance.go index 3073d82..deb3fca 100644 --- a/router/entrance.go +++ b/router/entrance.go @@ -43,6 +43,7 @@ type ApiGroup struct { WafFileRouter WafSystemMonitorRouter WafCaServerInfoRouter + SqlQueryRouter } type PublicApiGroup struct { LoginRouter diff --git a/router/waf_sql_query.go b/router/waf_sql_query.go new file mode 100644 index 0000000..918f825 --- /dev/null +++ b/router/waf_sql_query.go @@ -0,0 +1,15 @@ +package router + +import ( + "SamWaf/api" + "github.com/gin-gonic/gin" +) + +type SqlQueryRouter struct { +} + +func (receiver *SqlQueryRouter) InitSqlQueryRouter(group *gin.RouterGroup) { + api := api.APIGroupAPP.WafSqlQueryApi + router := group.Group("") + router.POST("/samwaf/sql_query/execute", api.ExecuteQueryApi) +} diff --git a/service/waf_service/waf_sql_query.go b/service/waf_service/waf_sql_query.go new file mode 100644 index 0000000..699d67c --- /dev/null +++ b/service/waf_service/waf_sql_query.go @@ -0,0 +1,110 @@ +package waf_service + +import ( + "SamWaf/global" + "SamWaf/model/request" + "SamWaf/model/response" + "errors" + "fmt" + "regexp" + "strings" + + "gorm.io/gorm" +) + +type WafSqlQueryService struct{} + +var WafSqlQueryServiceApp = new(WafSqlQueryService) + +func (receiver *WafSqlQueryService) ExecuteQuery(req request.WafSqlQueryReq) (response.WafSqlQueryResp, error) { + var result response.WafSqlQueryResp + var db *gorm.DB + + // 验证并选择数据库 + switch req.DbType { + case "local": + db = global.GWAF_LOCAL_DB + case "log": + db = global.GWAF_LOCAL_LOG_DB + case "stats": + db = global.GWAF_LOCAL_STATS_DB + default: + return result, errors.New("无效的数据库类型") + } + + // 验证 SQL 语句,只允许 SELECT 查询 + sqlLower := strings.ToLower(strings.TrimSpace(req.Sql)) + if !strings.HasPrefix(sqlLower, "select") { + return result, errors.New("仅允许执行 SELECT 查询") + } + + // 检查是否包含危险操作(使用单词边界匹配,避免误判字段名如 create_time、update_time) + dangerousKeywords := []string{ + `\bdrop\b`, `\bdelete\b`, `\bupdate\b`, `\binsert\b`, + `\btruncate\b`, `\balter\b`, `\bcreate\b`, `\bexec\b`, + `\bexecute\b`, `\bpragma\b`, `\battach\b`, + } + for _, pattern := range dangerousKeywords { + matched, err := regexp.MatchString(pattern, sqlLower) + if err == nil && matched { + return result, errors.New("查询包含不允许的操作: " + pattern) + } + } + + // 设置默认限制 + if req.Limit <= 0 || req.Limit > 1000 { + req.Limit = 1000 + } + + // 添加 LIMIT 限制 + sql := req.Sql + if !strings.Contains(sqlLower, "limit") { + sql = sql + fmt.Sprintf(" LIMIT %d", req.Limit) + } + + // 执行查询 + rows, err := db.Raw(sql).Rows() + if err != nil { + return result, err + } + defer rows.Close() + + // 获取列名 + columns, err := rows.Columns() + if err != nil { + return result, err + } + result.Columns = columns + + // 读取数据 + result.Data = make([]map[string]interface{}, 0) + for rows.Next() { + // 创建一个切片来存储当前行的值 + values := make([]interface{}, len(columns)) + valuePtrs := make([]interface{}, len(columns)) + for i := range columns { + valuePtrs[i] = &values[i] + } + + // 扫描当前行 + if err := rows.Scan(valuePtrs...); err != nil { + return result, err + } + + // 构建 map + rowMap := make(map[string]interface{}) + for i, col := range columns { + val := values[i] + // 处理 []byte 类型,转换为 string + if b, ok := val.([]byte); ok { + rowMap[col] = string(b) + } else { + rowMap[col] = val + } + } + result.Data = append(result.Data, rowMap) + } + + result.Total = len(result.Data) + return result, nil +} diff --git a/wafmangeweb/localserver.go b/wafmangeweb/localserver.go index e9225fd..28e2b47 100644 --- a/wafmangeweb/localserver.go +++ b/wafmangeweb/localserver.go @@ -77,6 +77,7 @@ func (web *WafWebManager) initRouter(r *gin.Engine) { router.ApiGroupApp.InitWafFileRouter(RouterGroup) router.ApiGroupApp.InitWafSystemMonitorRouter(RouterGroup) router.ApiGroupApp.InitWafCaServerInfoRouter(RouterGroup) + router.ApiGroupApp.InitSqlQueryRouter(RouterGroup) } if global.GWAF_RELEASE == "true" {