这是一个存在SQL注入风险的实现。问题在于使用$1作为列名参数,这在PostgreSQL中是不安全的。列名、表名等标识符不能使用参数占位符。
不安全的原因
在PostgreSQL中,参数占位符只能用于值,不能用于标识符(列名、表名等)。当尝试将列名作为参数传递时,它会被当作字符串值处理,而不是列名引用。
安全解决方案
方案1:使用白名单验证列名
import (
"context"
"fmt"
"github.com/jackc/pgx/v4/pgxpool"
)
// 定义允许的列名白名单
var allowedColumns = map[string]bool{
"en_US": true,
"fr_FR": true,
"de_DE": true,
"es_ES": true,
}
func getTranslation(dbpool *pgxpool.Pool, language, key string) (string, error) {
// 验证列名是否在白名单中
if !allowedColumns[language] {
return "", fmt.Errorf("invalid language column: %s", language)
}
// 使用字符串拼接构造查询(因为列名已验证)
query := fmt.Sprintf("SELECT %s FROM lang WHERE key = $1", language)
var translation string
err := dbpool.QueryRow(context.Background(), query, key).Scan(&translation)
return translation, err
}
func main() {
dbpool, err := pgxpool.Connect(context.Background(), "postgres://user:password@host:port/database")
if err != nil {
panic(err)
}
defer dbpool.Close()
translation, err := getTranslation(dbpool, "en_US", "co")
if err != nil {
panic(err)
}
fmt.Println(translation)
}
方案2:使用CASE语句(更安全)
import (
"context"
"github.com/jackc/pgx/v4/pgxpool"
)
func getTranslationSafe(dbpool *pgxpool.Pool, language, key string) (string, error) {
query := `
SELECT CASE $1
WHEN 'en_US' THEN en_US
WHEN 'fr_FR' THEN fr_FR
WHEN 'de_DE' THEN de_DE
WHEN 'es_ES' THEN es_ES
ELSE NULL
END
FROM lang
WHERE key = $2`
var translation string
err := dbpool.QueryRow(context.Background(), query, language, key).Scan(&translation)
return translation, err
}
方案3:使用pgx的标识符引用(需要额外处理)
import (
"context"
"fmt"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/jackc/pgx/v4"
)
func getTranslationWithIdentifier(dbpool *pgxpool.Pool, language, key string) (string, error) {
// 使用QuoteIdentifier安全地引用列名
quotedColumn := pgx.Identifier{language}.Sanitize()
query := fmt.Sprintf("SELECT %s FROM lang WHERE key = $1", quotedColumn)
var translation string
err := dbpool.QueryRow(context.Background(), query, key).Scan(&translation)
return translation, err
}
判断查询语句是否安全的标准
- 所有用户输入必须使用参数化查询(
$1, $2等占位符)
- 标识符(列名、表名)不能作为参数传递
- 动态标识符必须通过白名单验证或安全引用函数处理
- 避免字符串拼接构造SQL语句,除非标识符已严格验证
在你的原始代码中,尝试将列名作为参数传递是无效的,会导致查询错误或返回字面字符串值,而不是预期的列数据。