Pull to refresh
742.24
OTUS
Цифровые навыки от ведущих экспертов

6 лучших практик безопасности для Go

Reading time8 min
Views5.5K
Original author: Christian Meléndez
Перевод статьи подготовлен специально для студентов курса «Разработчик Golang».





Популярность Golang за последние годы существенно увеличилась. Успешные проекты, такие как Docker, Kubernetes и Terraform, сделали большую ставку на этот язык программирования. В последнее время Go де-факто стал стандартом для создания инструментальных средств командной строки. Что касается безопасности, то Go успешно справляется согласно своим отчетам об уязвимостях, имея только один реестр CVE с 2002 года.

Однако отсутствие уязвимостей не означает, что язык программирования является сверхбезопасным. Мы, люди, можем создавать небезопасные приложения, если мы не следуем определенным правилам. Например, зная правила написания безопасного кода от OWASP, мы можем задуматься о том, как применять эти методы при использовании Go. И это именно то, чем я займусь в этот раз. В этом посте я покажу вам шесть практик, которые необходимо принимать во внимание при разработке с применением Go.

1. Проверяйте входные данные


Проверять входные пользовательские данные нужно не только для функциональных целей, это также помогает избежать атак злоумышленников, отправлявших нам интрузивные данные, которые могут повредить систему. Более того, вы помогаете пользователям использовать этот инструмент более уверенно, не давая им делать глупые и распространенные ошибки. Например, вы можете запретить пользователю пытаться удалить несколько записей одновременно.

Для проверки пользовательского ввода вы можете использовать нативные пакеты Go, такие как strconv для обработки преобразования строк в другие типы данных. Go также поддерживает регулярные выражения с regexp для сложных проверок. Несмотря на то, что Go предпочитает использовать нативные библиотеки, существуют сторонние пакеты, такие как validator. С помощью validator вы можете включать проверки для структур или отдельных полей намного легче. Например, следующий код проверяет, что структура User содержит действительный адрес электронной почты:

package main

import (
	"fmt"

	"gopkg.in/go-playground/validator.v9"
)

type User struct {
	Email string `json:"email" validate:"required,email"`
	Name  string `json:"name" validate:"required"`
}

func main() {
	v := validator.New()
	a := User{
		Email: "a",
	}

	err := v.Struct(a)

	for _, e := range err.(validator.ValidationErrors) {
		fmt.Println(e)
	}
}


2. Используйте HTML-шаблоны


Одной из распространенных критических уязвимостей является межсайтовый скриптинг или XSS. В основном эксплойт состоит в том, что злоумышленник может внедрить в приложение вредоносный код для изменения вывода. Например, кто-то может отправить JavaScript-код как часть строки запроса в URL. Когда приложение возвращает значение пользователя, может быть выполнен JavaScript-код. Поэтому, как разработчик, вы должны знать о такой возможности и очищать вводимые пользователем данные.

Go имеет пакет html/template для кодирования того, что приложение возвращает пользователю. Таким образом, вместо того, чтобы браузер выполнял ввод типа <script>alert(‘Вас взломали!’);</script>, он выдаст предупреждение; Вы можете закодировать ввод, и приложение будет обрабатывать ввод как типичный HTML-код, напечатанный в браузере. HTTP-сервер, который возвращает HTML-шаблон, будет выглядеть так:

package main

import (
	"html/template"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	param1 := r.URL.Query().Get("param1")
	tmpl := template.New("hello")
	tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
	tmpl.ExecuteTemplate(w, "T", param1)
}
func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}


Но есть и сторонние библиотеки, которые вы можете использовать при разработке веб-приложений на Go. Например, веб-инструментарий Gorilla, который включает библиотеки, помогающие разработчикам выполнять такие вещи, как кодирование аутентификационных значений файлов cookie. Существует также nosurf, который представляет собой пакет HTTP, который помогает предотвратить подделку межсайтовых запросов (CSRF).

3. Защититесь от SQL инъекций


Если вы уже некоторое время занимаетесь разработкой, возможно, вы знаете о SQL инъекциях, которые по-прежнему занимают первую позицию в топе OWASP. Тем не менее, есть некоторые конкретные моменты, которые вы должны учитывать при использовании Go. Первое, что вам нужно сделать, это убедиться, что у пользователя, который подключается к базе данных, есть ограниченные права. Хорошей практикой также является санация введенных пользователем данных, как я описывал в предыдущем разделе, или экранирование специальных символов и использование функции HTMLEscapeString из пакета HTML-шаблонов.

Но самый важный фрагмент кода, который вам нужно включить, — это использование параметризованных запросов. В Go вы не подготавливаете выражение (речь идет о Prepared Statement) в connection; Вы подготавливаете его в БД. Вот пример того, как использовать параметризованные запросы:

customerName := r.URL.Query().Get("name")
db.Exec("UPDATE creditcards SET name=? WHERE customerId=?", customerName, 233, 90)


Однако что, если ядро ​​базы данных не поддерживает использование подготовленных выражений? Или что, если это влияет на производительность запросов? Ну, вы можете использовать функцию db.Query(), но сначала убедитесь, что вы санируете пользовательский ввод, как показано в предыдущих разделах. Существуют также сторонние библиотеки, такие как sqlmap, для предотвращения SQL инъекций.

Несмотря на все наши усилия, иногда уязвимости проскальзывают или попадают в наши приложения через третьих лиц. Чтобы обеспечить защиту своих веб-приложений от критических атак, таких как SQL инъекции, рассмотрите вариант использования платформы управления безопасностью приложений, например, такой ​​как Sqreen.

4. Шифруйте конфиденциальную информацию


То, что строку трудно читать, например, в формате base-64, не означает, что скрытое значение является секретным. Вам нужен способ шифрования информации, который злоумышленники не смогут легко декодировать. Типичная информация, которую вы бы хотели зашифровать, это пароли базы данных, пароли пользователей или даже номера социального страхования.

В OWASP есть несколько рекомендаций о том, какие алгоритмы шифрования использовать, например, bcrypt, PDKDF2, Argon2 или scrypt. К счастью, есть пакет Go, который включает надежные реализации для шифрования информации — crypto. Следующий код является примером использования bcrypt:

package main

import (
	"database/sql"
	"context"
	"fmt"

	"golang.org/x/crypto/bcrypt"
)

func main() {
	ctx := context.Background()
	email := []byte("john.doe@somedomain.com")
	password := []byte("47;u5:B(95m72;Xq")

	hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
	if err != nil {
		panic(err)
	}

	stmt, err := db.PrepareContext(ctx, "INSERT INTO accounts SET hash=?, email=?")
	if err != nil {
		panic(err)
	}
	result, err := stmt.ExecContext(ctx, hashedPassword, email)
	if err != nil {
		panic(err)
	}
}


Обратите внимание, что вам по-прежнему необходимо соблюдать осторожность при передаче информации между службами. Вы бы не хотели отправлять данные пользователя в виде простого текста. При этом не имеет значения, шифрует ли приложение пользовательские данные перед сохранением. Предположим, что кто-то в интернете может прослушивать ваш трафик и вести журналы запросов вашей системы. Злоумышленник может использовать эту информацию для сопоставления ее с другими данными из других систем.

5. Обеспечьте HTTPS-связь


В настоящее время большинство браузеров требуют, чтобы HTTPS работал на каждом сайте. Chrome, например, покажет вам предупреждение, если сайт не использует HTTPS. Отдел информзащиты может использовать в качестве политики обеспечение транзитного шифрования для связи между службами. То есть, для обеспечения безопасности транзитного соединения в системе речь идет не только о прослушивании приложения в порту 443. Вам также необходимо использовать надлежащие сертификаты и применять HTTPS, чтобы не допустить, чтобы злоумышленники понизили протокол до HTTP.

Вот фрагмент кода для веб-приложения, которое обеспечивает и использует протокол HTTPS:

package main

import (
    "crypto/tls"
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
        w.Write([]byte("This is an example server.\n"))
    })
    cfg := &tls.Config{
        MinVersion:               tls.VersionTLS12,
        CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
        PreferServerCipherSuites: true,
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
            tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_RSA_WITH_AES_256_CBC_SHA,
        },
    }
    srv := &http.Server{
        Addr:         ":443",
        Handler:      mux,
        TLSConfig:    cfg,
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
    }
    log.Fatal(srv.ListenAndServeTLS("tls.crt", "tls.key"))
}


Обратите внимание, что приложение будет прослушивать порт 443. Следующая строка — это строка, обеспечивающая настройку HTTPS:

w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")


Вы также можете указать имя сервера в конфигурации TLS таким образом:
config := &tls.Config{ServerName: "yourSiteOrServiceName"}


Рекомендуется всегда использовать транзитное шифрование, даже если ваше веб-приложение предназначено только для внутренней связи. Представьте себе, если по какой-то причине злоумышленник может прослушать ваш внутренний трафик. Всякий раз, когда это возможно, всегда лучше поднять планку сложности для возможных будущих атакующих.

6. Обращайте внимание на ошибки и логи


И последнее, но не менее важное, это обработка ошибок и логирование в ваших приложениях Go.

Для успешного устранения неполадок в продакшене вам необходимо правильно настроить свои приложения. Вы должны принимать во внимание ошибки, которые вы показываете пользователям. Вы не хотели бы, чтобы пользователи знали, что именно пошло не так. Злоумышленники могут использовать эту информацию, чтобы определить, какие услуги и технологии вы используете. Кроме того, вы должны помнить, что, хотя логи — это замечательно, они где-то хранятся. И если логи попадают в чужие руки, их можно использовать для встраивания предстоящей атаки в систему.

Итак, первое, что вам нужно узнать или вспомнить, это то, что в Go нет исключений. Это означает, что вам нужно обрабатывать ошибки иначе, нежели в других языках. Стандарт выглядит так:

if err != nil {
    // обработка ошибки
}


Также Go предлагает встроенную библиотеку для работы с логами. Самый простой код выглядит так:

package main

import (
	"log"
)

func main() {
	log.Print("Logging in Go!")
}


Но есть и сторонние библиотеки для ведения логов. Некоторые из них — это logrus, glog и loggo. Вот небольшой фрагмент кода с использованием logrus:

package main

import (
	"os"

	log "github.com/sirupsen/logrus"
)

func main() {
	file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	log.SetOutput(file)
	log.SetFormatter(&log.JSONFormatter{})
	log.SetLevel(log.WarnLevel)

	log.WithFields(log.Fields{
		"animal": "walrus",
		"size":   10,
	}).Info("A group of walrus emerges from the ocean")
}


Наконец, убедитесь, что вы применяете все предыдущие рекомендации, такие как шифрование и санация данных, которые вы помещаете в логи.

Всегда есть куда расти


Эти рекомендации — минимум того, что должно быть свойственно вашим приложениям Go. Однако, если ваше приложение представляет собой инструмент командной строки, вам не понадобятся методы шифрования для транзита. Но остальные советы по безопасности применимы практически ко всем типам приложений. Если вы хотите узнать больше, есть книга от OWASP специально для Go, которая углубляется в некоторые темы. Также есть репозиторий, содержащий ссылки на фреймворки, библиотеки и другие инструменты для обеспечения безопасности в Go.

Независимо от того, что вы в конечном итоге делаете, помните, что вы всегда можете повысить безопасность своего приложения. Злоумышленники всегда найдут новые способы использования уязвимостей, поэтому постарайтесь постоянно работать над безопасностью своего приложения.


Эта статья была написан Кристианом Мелендесом. Кристиан — технолог, который начинал как разработчик программного обеспечения и совсем недавно стал облачным архитектором, специализирующимся на реализации конвейеров непрерывной поставки с приложениями в нескольких вариантах, включая .NET, Node.js и Java, часто с использованием контейнеров Docker.

Узнать подробнее о курсе.
Tags:
Hubs:
0
Comments11

Articles

Information

Website
otus.ru
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
OTUS