Merge pull request #535 from samwafgo/feat_sqlquery

feat:add sql query
This commit is contained in:
samwafgo
2025-11-12 16:37:31 +08:00
committed by GitHub
8 changed files with 170 additions and 0 deletions

View File

@@ -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
)

26
api/waf_sql_query.go Normal file
View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
package response
type WafSqlQueryResp struct {
Columns []string `json:"columns"` // 列名列表
Data []map[string]interface{} `json:"data"` // 数据行列表
Total int `json:"total"` // 总记录数
}

View File

@@ -43,6 +43,7 @@ type ApiGroup struct {
WafFileRouter
WafSystemMonitorRouter
WafCaServerInfoRouter
SqlQueryRouter
}
type PublicApiGroup struct {
LoginRouter

15
router/waf_sql_query.go Normal file
View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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" {