Перейти к содержимому
Назад к пути
Теория 4 мин чтения

3. Чеклист для Code Review

Чеклист для Code Review: SQL-инъекции в Go

Этот чеклист предназначен для систематической проверки Go-кода на наличие SQL-инъекций. Используйте его при code review, аудитах безопасности и в CI/CD pipeline.

Автоматическая проверка: grep-паттерны

Перед ручным ревью запустите автоматический поиск потенциально опасных конструкций:

# Поиск конкатенации строк в SQL-запросах
grep -rn 'fmt.Sprintf.*SELECT\|fmt.Sprintf.*INSERT\|fmt.Sprintf.*UPDATE\|fmt.Sprintf.*DELETE' --include="*.go" .

# Поиск конкатенации через +
grep -rn '"SELECT.*" +\|"INSERT.*" +\|"UPDATE.*" +\|"DELETE.*" +' --include="*.go" .

# Поиск использования fmt.Sprintf с WHERE
grep -rn 'Sprintf.*WHERE' --include="*.go" .

# Поиск db.Raw() без параметров (GORM)
grep -rn 'db.Raw(".*+' --include="*.go" .

# Поиск db.Exec/db.Query с конкатенацией
grep -rn 'db\.\(Exec\|Query\|QueryRow\).*fmt\.\|db\.\(Exec\|Query\|QueryRow\).*+' --include="*.go" .

Интегрируйте эти проверки в CI:

# .gitlab-ci.yml
sql_injection_scan:
  script:
    - |
      FOUND=$(grep -rn 'fmt.Sprintf.*\(SELECT\|INSERT\|UPDATE\|DELETE\)' --include="*.go" . || true)
      if [ -n "$FOUND" ]; then
        echo "POTENTIAL SQL INJECTION FOUND:"
        echo "$FOUND"
        exit 1
      fi      

Чеклист: Уровень 1 — Запросы к БД

1.1. Все SQL-запросы параметризованы?

// FAIL: конкатенация
query := "SELECT * FROM users WHERE name = '" + name + "'"
db.Query(query)

// FAIL: fmt.Sprintf
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)
db.Query(query)

// PASS: параметризация
db.Query("SELECT * FROM users WHERE name = ?", name)
db.QueryContext(ctx, "SELECT * FROM users WHERE name = $1", name)

1.2. Динамические идентификаторы защищены whitelist?

// FAIL: пользовательский ввод в ORDER BY
sortCol := r.URL.Query().Get("sort")
query := fmt.Sprintf("SELECT * FROM products ORDER BY %s", sortCol)

// PASS: whitelist для ORDER BY
allowed := map[string]string{
    "name":  "name",
    "price": "price",
    "date":  "created_at",
}
col, ok := allowed[sortCol]
if !ok {
    col = "created_at"
}
query := fmt.Sprintf("SELECT * FROM products ORDER BY %s", col)

1.3. LIKE-запросы экранируют спецсимволы?

// FAIL: пользовательский ввод напрямую в LIKE
db.Query("SELECT * FROM products WHERE name LIKE ?", "%"+userInput+"%")
// Атакующий может ввести "%" и получить все записи

// PASS: экранирование спецсимволов LIKE
func escapeLike(s string) string {
    s = strings.ReplaceAll(s, "\\", "\\\\")
    s = strings.ReplaceAll(s, "%", "\\%")
    s = strings.ReplaceAll(s, "_", "\\_")
    return s
}
db.Query("SELECT * FROM products WHERE name LIKE ? ESCAPE '\\'",
    "%"+escapeLike(userInput)+"%")

1.4. IN-запросы безопасно формируются?

// FAIL: конкатенация списка ID
ids := strings.Join(userIDs, ",")
query := fmt.Sprintf("SELECT * FROM users WHERE id IN (%s)", ids)

// PASS: генерация плейсхолдеров
func placeholders(n int) string {
    ph := make([]string, n)
    for i := range ph {
        ph[i] = "?"
    }
    return strings.Join(ph, ",")
}

args := make([]interface{}, len(userIDs))
for i, id := range userIDs {
    args[i] = id
}
query := fmt.Sprintf("SELECT * FROM users WHERE id IN (%s)", placeholders(len(userIDs)))
db.Query(query, args...)

Чеклист: Уровень 2 — Контекст и таймауты

2.1. Все запросы используют context?

// FAIL: запрос без context
db.Query("SELECT * FROM users WHERE id = ?", id)

// PASS: запрос с context и таймаутом
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
db.QueryContext(ctx, "SELECT * FROM users WHERE id = ?", id)

2.2. Таймауты установлены на уровне соединения?

// PASS: настройка пула соединений
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(1 * time.Minute)

Чеклист: Уровень 3 — ORM и библиотеки

3.1. Raw-запросы в ORM параметризованы?

// FAIL: GORM raw без параметров
db.Raw("SELECT * FROM users WHERE name = '" + name + "'").Scan(&users)

// PASS: GORM raw с параметрами
db.Raw("SELECT * FROM users WHERE name = ?", name).Scan(&users)

// FAIL: sqlx с конкатенацией
sqlx.Get(db, &user, "SELECT * FROM users WHERE email = '"+email+"'")

// PASS: sqlx с параметрами
sqlx.Get(db, &user, "SELECT * FROM users WHERE email = ?", email)

3.2. Миграции не используют пользовательский ввод?

// FAIL: динамическое имя таблицы из конфига
tableName := config.Get("table_name")
db.Exec(fmt.Sprintf("CREATE TABLE %s (id INTEGER PRIMARY KEY)", tableName))

// PASS: статическое определение через код
db.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY)")

Чеклист: Уровень 4 — Second-Order

4.1. Данные из БД не используются в raw SQL?

// FAIL: данные из БД подставляются в запрос
var username string
db.QueryRow("SELECT username FROM users WHERE id = ?", id).Scan(&username)
db.Exec(fmt.Sprintf("INSERT INTO logs (msg) VALUES ('Action by %s')", username))

// PASS: параметризация даже для данных из БД
db.ExecContext(ctx, "INSERT INTO logs (msg) VALUES (?)",
    fmt.Sprintf("Action by %s", username))

4.2. Сохранённые процедуры не конкатенируют параметры?

При использовании PostgreSQL или MySQL функций — убедитесь, что внутри них нет EXECUTE с конкатенацией строк.

Чеклист: Уровень 5 — Инфраструктура

5.1. БД пользователь имеет минимальные привилегии?

  • Приложение НЕ использует root/postgres/admin аккаунт
  • Отдельные пользователи для read и write операций
  • Нет доступа к information_schema (если не нужно)
  • Нет привилегий DROP, ALTER, GRANT

5.2. Логирование SQL-ошибок настроено?

// PASS: логирование ошибок SQL
row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
if err := row.Scan(&user); err != nil {
    slog.Error("SQL query failed",
        "error", err,
        "endpoint", r.URL.Path,
        "remote_addr", r.RemoteAddr,
    )
}

5.3. Rate limiting настроен для публичных эндпоинтов?

  • Login/Register: max 5-10 req/min
  • Search/Filter: max 30 req/min
  • API endpoints: токен + rate limit

Сводная таблица Code Review

Пункт Критичность Автоматизируемый
Prepared statements Критическая Да (grep)
Whitelist для ORDER BY Высокая Частично
Context + таймауты Средняя Да (linter)
Second-order проверка Высокая Нет
Минимальные привилегии Высокая Нет
Rate limiting Средняя Нет
Логирование ошибок Средняя Частично

Инструменты для автоматизации

# gosec — статический анализатор безопасности Go
gosec ./...
# Ищет: G201 (SQL string formatting), G202 (SQL string concatenation)

# govulncheck — проверка зависимостей
govulncheck ./...

# staticcheck — общий анализатор
staticcheck ./...

# semgrep — правила для SQL injection
semgrep --config p/golang-sql-injection .

Используйте этот чеклист при каждом code review и интегрируйте автоматические проверки в CI/CD pipeline. Ни один PR с потенциальной SQL-инъекцией не должен пройти ревью.

Продолжить чтение

Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу

Когда закончишь — отметь раздел, чтобы продолжить.

🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru