如何安全实现JSON字段的动态等值、模糊与不等查询?
我有一个JSON类型的数据库列,内部包含多个字段,需要实现对该字段的等值、模糊(like)与不等查询,且列名是动态的,无法硬编码到WHERE子句中。
当前用GORM编写的查询代码生成了格式错误的MySQL语句,比如生成了log->>$'severity' = 'High',正确格式应该是log->>'$.severity'。如果用fmt.Sprintf拼接查询语句虽然能生成正确SQL,但会有SQL注入风险。相关代码如下:
func applyServerLogsQuery(db *gorm.DB, logType api.LogDestination_LogType, req *logs.GetLogsRequest) (*gorm.DB, error) { // set log type db = db.Where("log_type = ?", logType) // check for time if !req.StartTime.IsZero() { db = db.Where("time_stamp >= ?", req.StartTime) } if !req.EndTime.IsZero() { db = db.Where("time_stamp <= ?", req.EndTime) } // process filters for _, filter := range req.Filters { if len(filter.Values) == 0 { continue } property := filter.PropertyInfo.Name value := filter.Values[0] switch filter.FilterType { case logs.FilterTypeRegex: db = db.Where("log->>$? like ?", property, value) case logs.FilterTypeShould: db = db.Where("log->>$? = ?", property, value) case logs.FilterTypeExclude: db = db.Where("log->>$? != ?", property, value) } /* prefix := fmt.Sprintf("log->>'$.%s'", filter.PropertyInfo.Name) value := filter.Values[0] queryString := fmt.Sprintf("log->>'$.%s' like '%s'", filter.PropertyInfo.Name, value) log.Printf("Prefix %s value %s query %s", prefix, value, queryString) switch filter.FilterType { case logs.FilterTypeRegex: db = db.Where(queryString) } */ } // handle order by time orderStr := "time_stamp" if req.OrderDesc { orderStr = fmt.Sprintf("%s desc", orderStr) } else { orderStr = fmt.Sprintf("%s asc", orderStr) } db = db.Order(orderStr) // handle limit db = db.Limit(int(req.Limit)) return db, nil }
错误生成的MySQL查询语句示例:
SELECT * FROMserver_logsWHERE log_type = 2 AND time_stamp >= '2023-03-10 15:55:14.304' AND time_stamp <= '2023-03-10 15:55:24.441' AND log->>$'severity' = 'High' ORDER BY time_stamp desc
方法1:用GORM表达式+参数绑定构建安全查询
利用GORM的gorm.Expr构建合法的JSON路径表达式,同时保留参数化查询避免注入风险:
for _, filter := range req.Filters { if len(filter.Values) == 0 { continue } property := filter.PropertyInfo.Name value := filter.Values[0] // 通过字符串拼接生成合法JSON路径,属性名作为参数传入 jsonPathExpr := gorm.Expr("log->>'$.'||?", property) switch filter.FilterType { case logs.FilterTypeRegex: db = db.Where("? LIKE ?", jsonPathExpr, value) case logs.FilterTypeShould: db = db.Where("? = ?", jsonPathExpr, value) case logs.FilterTypeExclude: db = db.Where("? != ?", jsonPathExpr, value) } }
这里用MySQL的||操作符拼接$.和动态属性名,所有变量都通过参数绑定传入,彻底规避SQL注入,同时生成log->>'$.severity'这类正确格式的SQL片段。
方法2:利用GORM v2内置JSON查询能力
如果使用GORM v2及以上版本,可直接拼接JSON路径模板,仅将查询值作为参数绑定:
for _, filter := range req.Filters { if len(filter.Values) == 0 { continue } property := filter.PropertyInfo.Name value := filter.Values[0] // 预构建合法的JSON路径模板 jsonKey := fmt.Sprintf("log->>'$.%s'", property) switch filter.FilterType { case logs.FilterTypeRegex: db = db.Where(fmt.Sprintf("%s LIKE ?", jsonKey), value) case logs.FilterTypeShould: db = db.Where(fmt.Sprintf("%s = ?", jsonKey), value) case logs.FilterTypeExclude: db = db.Where(fmt.Sprintf("%s != ?", jsonKey), value) } }
注意:若property来自用户输入,需先做合法性校验(比如仅允许字母、下划线等字符),避免恶意构造的路径引发风险。
方法3:直接修正SQL模板占位符写法
调整WHERE子句的模板格式,让GORM正确生成JSON路径:
for _, filter := range req.Filters { if len(filter.Values) == 0 { continue } property := filter.PropertyInfo.Name value := filter.Values[0] switch filter.FilterType { case logs.FilterTypeRegex: db = db.Where("log->>'$.'? LIKE ?", property, value) case logs.FilterTypeShould: db = db.Where("log->>'$.'? = ?", property, value) case logs.FilterTypeExclude: db = db.Where("log->>'$.'? != ?", property, value) } }
这种写法直接在SQL模板中把$.和占位符组合,GORM会自动替换占位符为属性名,生成格式正确的SQL,同时查询值通过参数绑定保证安全。
内容的提问来源于stack exchange,提问作者Praveen Valtix

