Теория 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-инъекцией не должен пройти ревью.
Продолжить чтение
Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу
Когда закончишь — отметь раздел, чтобы продолжить.