golang安全防护手写SQL注入的插件库Hotcoal的使用

Golang安全防护手写SQL注入的插件库Hotcoal的使用

保护手写SQL免受注入攻击

许多Golang应用不使用ORM,而是更喜欢使用原始SQL进行数据库操作。虽然编写原始SQL有时比使用ORM更灵活、高效和简洁,但它可能使您容易受到SQL注入攻击。

虽然大多数数据库技术(PostgreSQL、MySQL、Sqlite等)和Golang SQL库(database/sqlsqlx等)允许您使用预编译语句来接收参数并对其进行清理,但存在一些限制:

  • 您可以使用参数作为值,但不能用于列名或表名
  • 对于更复杂的操作,一个好的ORM可以动态生成SQL并安全地执行;但您没有使用ORM,所以开始手工编写SQL…

这就是Hotcoal的用武之地。

Hotcoal工作原理

Hotcoal提供了一个最小化的API,帮助您保护手工编写的SQL免受SQL注入攻击。

  1. 使用Hotcoal而不是纯字符串手工编写SQL。这样所有参数都会根据您提供的允许列表进行验证。
  2. 将结果转换为纯字符串并与您的SQL库一起使用。

您可以将Hotcoal与任何您喜欢的SQL库一起使用。Hotcoal不替代带有参数的预编译查询,而是对其进行补充。

示例

我们使用以下示例表:

CREATE TABLE "users" (
	"id"	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
	"first_name"	TEXT NOT NULL,
	"middle_name"	TEXT NOT NULL,
	"last_name"	TEXT NOT NULL,
	"nickname"	TEXT,
	"email"	TEXT NOT NULL
);

验证列名

我们使用Hotcoal验证列名并创建SQL查询字符串。字符串常量可以直接使用Wrap转换为hotcoalString,无需验证。

但是字符串变量不能直接转换为hotcoalString。这有助于防止SQL注入。相反,您必须使用AllowlistValidate对字符串变量进行验证。如果尝试使用Wrap将字符串变量转换为hotcoalString而不进行验证,它将无法编译。

import (
  "database/sql"
  "github.com/motrboat/hotcoal"  
)

func queryCount(db *sql.DB, columnName string, value string) *sql.Row {

  // 验证columnName变量是否在允许列表中
  // 返回一个hotcoalString
  validatedColumnName, err := hotcoal.
    Allowlist("first_name", "middle_name", "last_name").
    Validate(columnName)

  if err != nil {
    return nil, err
  }  

  // 使用hotcoalStrings手工编写SQL
  query := hotcoal.Wrap("SELECT COUNT(*) FROM users WHERE ") +
    validatedColumnName +
    hotcoal.Wrap(" = ?;")

  // 如果尝试使用未经验证的常规字符串变量,
  // 它容易受到SQL注入攻击,因此无法编译:
  //       query := hotcoal.Wrap("SELECT COUNT(*) FROM users WHERE ") +
  //         columnName +
  //         hotcoal.Wrap(" = ?;")

  // 仅在传递给数据库库时将hotcoalString转换回常规字符串
  // 使用带有参数的预编译查询来清理值
  row := db.QueryRow(query.String(), value)

  return row
}

验证手工编写的SQL

要手工编写更复杂的SQL,您还可以使用strings函数的hotcoal等效函数JoinReplaceReplaceAll。这些可以使用hotcoalStrings或字符串常量调用。但不能使用字符串变量-必须先验证这些变量。

import (
  "database/sql"
  "github.com/motrboat/hotcoal"  
)

type Filter struct {
  ColumnName string
  Value string
}

func queryCount(db *sql.DB, tableName string, filters []Filter) (*sql.Row, error) {
  validatedTableName, err := hotcoal.
    Allowlist("users", "customers").
    Validate(tableName)
  if err != nil {
    return nil, err
  }

  allowlist := hotcoal.Allowlist("first_name", "middle_name", "last_name", "nickname")

  sqlArr := hotcoal.Slice{}
  values := []string{}

  for _, filter := range filters {
    validatedColumnName, err := allowlist.Validate(filter.ColumnName)
    if err != nil {
      return nil, err
    }

    sqlArr = append(sqlArr, validatedColumnName + hotcoal.Wrap(" = ?"))
    values = append(values, filter.Value)
  }

  query := hotcoal.Wrap("SELECT COUNT(*) FROM {{TABLE}} WHERE {{FILTERS}};").
    ReplaceAll(
      "{{TABLE}}",
      validatedTableName,
    ).
    ReplaceAll(
      "{{FILTERS}}",
      hotcoal.Join(sqlArr, " OR "),
    )

  row := db.QueryRow(query.String(), values...)

  return row
}

字符串构建器

Hotcoal还提供了使用hotcoalStringsstrings.Builder版本。它最小化了内存复制,效率更高。

import (
  "database/sql"
  "github.com/motrboat/hotcoal"  
)

type Filter struct {
  ColumnName string
  Value string
}

func queryCount(db *sql.DB, filters []Filter) *sql.Row {
  allowlist := hotcoal.Allowlist("first_name", "middle_name", "last_name", "nickname")

  builder := hotcoal.Builder{}
  values := []string{}

  builder.Write("SELECT COUNT(*) FROM users WHERE ")

  for i, filter := range filters {
    if i != 0 {
      builder.Write(" OR ")
    }

    validatedColumnName, err := allowlist.Validate(filter.ColumnName)
    if err != nil {
      return nil, err
    }

    builder.Write(validatedColumnName + hotcoal.Wrap(" = ?"))
    values = append(values, filter.Value)
  }

  builder.Write(";")

  row := db.QueryRow(builder.String(), values...)

  return row
}

免责声明

Hotcoal不提供任何保证。


更多关于golang安全防护手写SQL注入的插件库Hotcoal的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang安全防护手写SQL注入的插件库Hotcoal的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang SQL注入防护:Hotcoal库使用指南

Hotcoal是一个轻量级的Golang库,专门用于防止SQL注入攻击。它通过将字符串"烧焦"(hot coal)来安全地处理SQL查询中的参数,确保用户输入不会被解释为SQL代码。

Hotcoal核心功能

  1. 自动转义特殊字符
  2. 提供类型安全的SQL参数处理
  3. 简化安全SQL查询的构建

安装Hotcoal

go get github.com/stephenafamo/hotcoal

基本使用方法

1. 简单参数转义

package main

import (
	"fmt"
	"github.com/stephenafamo/hotcoal"
)

func main() {
	userInput := "admin'; DROP TABLE users;--"
	safeInput := hotcoal.S(userInput)
	
	query := fmt.Sprintf("SELECT * FROM users WHERE username = %s", safeInput)
	fmt.Println(query)
	// 输出: SELECT * FROM users WHERE username = 'admin''; DROP TABLE users;--'
}

2. 构建完整SQL查询

package main

import (
	"database/sql"
	"fmt"
	"github.com/stephenafamo/hotcoal"
)

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

	username := hotcoal.S("admin")
	password := hotcoal.S("password123")

	query := hotcoal.Join([]hotcoal.String{
		hotcoal.S("SELECT * FROM users WHERE username = "), username,
		hotcoal.S(" AND password = "), password,
	})

	rows, err := db.Query(string(query))
	if err != nil {
		panic(err)
	}
	defer rows.Close()

	// 处理查询结果...
}

3. 使用预编译语句

package main

import (
	"database/sql"
	"github.com/stephenafamo/hotcoal"
)

func getUser(db *sql.DB, userID int) (*User, error) {
	query := hotcoal.S("SELECT id, name, email FROM users WHERE id = ?")
	
	stmt, err := db.Prepare(string(query))
	if err != nil {
		return nil, err
	}
	defer stmt.Close()

	var user User
	err = stmt.QueryRow(userID).Scan(&user.ID, &user.Name, &user.Email)
	if err != nil {
		return nil, err
	}

	return &user, nil
}

高级用法

1. 批量处理参数

package main

import (
	"fmt"
	"github.com/stephenafamo/hotcoal"
)

func main() {
	ids := []int{1, 2, 3, 4, 5}
	safeIDs := make([]hotcoal.String, len(ids))
	
	for i, id := range ids {
		safeIDs[i] = hotcoal.S(fmt.Sprintf("%d", id))
	}
	
	query := hotcoal.S("SELECT * FROM products WHERE id IN (") +
		hotcoal.JoinWith(safeIDs, ", ") +
		hotcoal.S(")")
	
	fmt.Println(query)
}

2. 自定义安全验证

package main

import (
	"errors"
	"github.com/stephenafamo/hotcoal"
)

func safeUsername(input string) (hotcoal.String, error) {
	if len(input) > 20 {
		return "", errors.New("username too long")
	}
	
	// 添加额外的验证逻辑
	if input == "admin" {
		return "", errors.New("reserved username")
	}
	
	return hotcoal.S(input), nil
}

func main() {
	username, err := safeUsername("newuser")
	if err != nil {
		panic(err)
	}
	
	// 使用安全的username构建查询...
}

最佳实践

  1. 始终使用Hotcoal处理用户输入:即使是内部系统,也应遵循这一原则
  2. 结合预编译语句使用:Hotcoal + 预编译语句提供双重保护
  3. 验证输入长度和格式:在转换为hotcoal.String之前进行验证
  4. 限制数据库权限:应用使用的数据库账号应只有必要的最小权限
  5. 记录可疑查询:监控和记录包含特殊字符的查询

性能考虑

Hotcoal的设计考虑了性能因素,但在高并发场景下仍需注意:

  • 重用hotcoal.String对象
  • 使用预编译语句减少解析开销
  • 批量处理参数时预分配切片

Hotcoal为Golang开发者提供了一种简单有效的方式来防止SQL注入攻击,结合良好的编程实践,可以显著提高应用程序的安全性。

回到顶部