SQL预编译中order by后为什么不能参数化原因

SQL预编译中order by后为什么不能参数化原因

以Golang为例进行说明

go 复制代码
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql" // Import MySQL driver
)

func main() {
	// 数据库连接信息
	db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 查询的参数
	userID := 123

	// 准备查询语句,使用占位符
	query := "SELECT username, email FROM users WHERE id = ?"
	rows, err := db.Query(query, userID)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {
		var username, email string
		if err := rows.Scan(&username, &email); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("Username: %s, Email: %s\n", username, email)
	}
}

db.Query(query, userID)会自动给值加上引号。比如假设id="1",那么拼凑成的语句会是SELECT username, email FROM users WHERE id ='1'

再看order by,order by后一般是接字段名,而字段名是不能带引号的,比如 order by id;如果带上引号成了order by 'id',那username就是一个字符串不是字段名了,这就产生了语法错误。

所以order by后不能参数化的本质是:一方面预编译又只有自动加引号的db.Query()方法,没有不加引号的方法;而另一方面order by后接的字段名不能有引号。

更本质的说法是:不只order by,凡是字符串但又不能加引号的位置都不能参数化;包括sql关键字、库名表名字段名函数名等等。

不能参数化位置的防sql注入办法

不能参数化的位置,不管怎么拼接,最终都是和使用"+"号拼接字符串的功效一样:拼成了sql语句但没有防sql注入的效果。

但好在的一点是,不管是sql关键字,还是库名表名字段名函数名对于后台开发者来说他的集合都是有限的,更准确点应该说也就那么几个。

这时我们应可以使用白名单的这种针对有限集合最常用的处理办法进行处理,如果传来的参数不在白名单列表中,直接返回错误即可。

代码类似如下:

go 复制代码
package main

import (
	"database/sql"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 假设输入的排序列是由用户提供的
	userInput := "columnName" // 假设用户输入
	orderBy := "id"          // 默认排序列

	// 验证用户提供的排序列是否是允许的列,避免注入
	allowedColumns := map[string]bool{"columnName": true, "columnName2": true, "columnName3": true} // 列出所有允许的列名
	if _, ok := allowedColumns[userInput]; !ok {
		log.Fatal("Invalid input for order by")
	}

	// 使用预编译语句
	stmt, err := db.Prepare("SELECT * FROM table_name ORDER BY " + userInput)
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	// 执行查询
	rows, err := stmt.Query()
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	// 处理结果
	for rows.Next() {
		// 处理每一行的数据
	}
	if err = rows.Err(); err != nil {
		log.Fatal(err)
	}
}
相关推荐
smile_life_1 分钟前
MySQL 设置 防止账户被暴力破解
数据库·mysql
ShineWinsu18 分钟前
对于Qt:QRadioButton常见属性以及QButtonGroup的解析
数据库·c++·qt·microsoft·gui·qradiobutton·qbuttongroup
测试盐39 分钟前
oracle数据库卡顿排查
数据库·oracle
滕州市燕猫虎计算机科技工作室个体工商户1 小时前
MySQL、MVCC、多版本并发控制
数据库·mysql·mvcc·数据库事务
明航咨询-陈老师1 小时前
ITSS运行维护服务能力成熟度认证技术纵深:标准模型、等级路径、2026审核趋势与CS/CMMI对比
服务器·数据库·cmmi
缓慢更新2 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能
AI小百科2 小时前
向量数据库:AI 的「记忆仓库」是怎么工作的
数据库·人工智能·oracle
煎饼皮皮侠2 小时前
【设计】设计一个Web版数据库管理平台(之七):多数据库类型易扩展
数据库
Nturmoils2 小时前
MongoDB迁移之后,Spring Data 和 Hibernate 各自接哪类请求
数据库
画中有画2 小时前
在自动化脚本中操作本地数据库
数据库·oracle·自动化