Проверяем защищённость приложения на Go: с чего начать

от автора

Привет! Меня зовут Александра, я инженер по информационной безопасности в Delivery Club. Мы используем Go в качестве основного языка для разработки Web-API и представляем вашему вниманию краткое руководство по быстрой проверке сервиса на соответствие базовым требованиям безопасности. Представленную ниже информацию можно адаптировать под проекты, написанные и на других языках.

Код

Проверка пользовательского ввода

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

  • заголовки HTTP-запросов;

  • тела HTTP-запросов;

  • URI-запросы, отвечающие за роутинг и/или маппинг ресурсов, при условии, что такой запрос каким-либо образом обрабатывается приложением;

  • параметры GET и POST;

  • содержимое форм.

Исключения

Исключениями из перечня данных пользовательского ввода будут параметры, использующие стойкую цифровую подпись с секретом, например, хранящиеся на сервере JWS (RFC 7515), подписанные и проверяемые алгоритмами на основе HMAC/RSA/ECDSA. Однако подобные параметры требуют особого внимания в связи с существованием атак на схемы шифрования и подписи, например:

  • signature removal – удаление цифровой подписи и модификация поля с указанием алгоритма;

  • crypto oracle – подпись произвольных данных.

Если в коде есть собственная реализация криптографии, малоизвестные алгоритмы или алгоритмы, криптографическая стойкость которых не доказана либо исследований которой не существует, то можно:

  • обратиться за консультацией к специалистам по криптографии;

  • провести собственное исследование на соответствие алгоритма требованиям информационной безопасности компании;

  • отказаться от использования подобных алгоритмов.

Как может выглядеть плохая обработка входных данных в псевдокоде:

http.HandleFunc("/bar", func(w HTTP.ResponseWriter, r *HTTP.Request) {     fmt.Fprintf(w, "Hello %q!", r.Url.Query.Get("name")) // XSS     ref := r.Headers.Get("Referrer") // недоверенный заголовок     if ref != "" {         resp, err := HTTP.Get(ref+"/?utm_source=backend") // SSRF         if err != nil {             fmt.Error(err)             return         }         defer resp.Body.Close()         body, err := ioutil.ReadAll(resp.Body)         if err != nil {             fmt.Error(err)             return         }         addToLogFile(string(body)) // запись недоверенных данных     } }) log.Fatal(HTTP.ListenAndServe(":8080", nil))

Санитизация пользовательского ввода

Для защиты от вредоносной нагрузки чаще всего применяют санитизацию — удаление и/или экранирование неправильных или небезопасных символов из пользовательского ввода.

Задача первичной обработки входных данных лежит на HTTP-библиотеке. Во время проверки кода стоит обращать внимание на использование или отсутствие следующих методов санитизации:

  • белыx списков параметров (whitelisting), которые помогут отфильтровать невалидные или служебные параметры;

  • проверок границ значений (boundary checking) при конвертации строковых данных в числовые, и проверок ошибок конвертации;

  • проверок корректности конвертации строковых данных (Character escaping), например, проверок так называемого расширенного набора символов UTF-8, графически совпадающих с латинскими ASCII-символами.

  • проверок нуль-байтов;

  • проверок символов path-control: \ и \ \;

  • использование пакета «html/template» для безопасного отображения пользовательского ввода;

  • использование функции Escape* из пакета «template/html” для отображения спецсимволов.

Примечание: если валидация и/или санитизация входных данных невозможна, то HTTP-запрос должен быть полностью отклонен.

Пароли

Для реализации корректного механизма хранения и проверки паролей следует:

  • избегать использования устаревших алгоритмов, таких как SHA-1 и MD5;

  • использовать криптографически стойкий генератор псевдослучайных чисел.

Пример верной обработки пользовательских паролей:

package main import (    "crypto/rand"   "crypto/sha256"   "database/sql"   "context" "fmt" ) const saltSize = 32 func main() {   ctx := context.Background()   email := []byte("john.doe@somedomain.com")   password := []byte("47;u5:B(95m72;Xq")   // создать случайное слово   salt := make([]byte, saltSize)    _, err := rand.Read(salt)    if err!=nil {     panic(err)    } // SHA256(salt+password) hash := sha256.New() hash.Write(salt) hash.Write(password) h := hash.Sum(nil) // fmt.Printf("email : %s\n", string(email)) // fmt.Printf("password: %s\n", string(password)) // fmt.Printf("salt : %x\n", salt) // fmt.Printf("hash : %x\n", h) // использовать при подключении к БД stmt, err := db.PrepareContext(ctx, "INSERT INTO accounts SET hash=?, salt=?, email=?")  if err != nil {     panic(err)  } result, err := stmt.ExecContext(ctx, h, salt, email)  if err != nil {     panic(err) } }

Примечание: идеальным вариантом является использование способов аутентификации, отличных от аутентификации по паролям.

Обработка ошибок

Необходимо правильно и своевременно обрабатывать ошибки по мере их появления, чтобы избежать ошибок бизнес-логики.

Пример обработки ошибок:

func initialize(i int) {    ...   //Сбой   if i<2 {     fmt.Printf("Var %d - initialized\n", i)   } else {     //Завершаем нашу программу.     log.Fatal("Init failure - Terminating.") }  } func main() {    i:=1   for i<3 {      initialize(i)     i++    } fmt.Println("Initialized all variables successfully")  }

Журналирование

Не допускайте включения чувствительных данных в журналы.

Передача данных между сервисами

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

  • Легитимность передачи информации между двумя сервисами (наличие доверительных отношений).

  • Целостность информации, передаваемой между сервисами.

Легитимность передачи информации между двумя сервисами достигается:

  • наличием информации об отправителе в белом списке получателя;

  • реализацией проверки доверия отношений между сервисами.

Целостность информации можно обеспечить с помощью алгоритмов цифровой подписи и имитовставки (DSA и HMAC). Для этой задачи может быть использован mTLS или любой другой алгоритм (IKE, SSH) двусторонней аутентификации.

Swagger

При проектировании микросервисов на основе Swagger рекомендуется проверять следующие критерии:

  • Соответствие схемы авторизации требованиям информационной безопасности — блок описания securityDefinitions (Swagger v2), components/securitySchemes (Swagger v3) и провайдеров авторизации (Basic, OAuth2, Bearer).

  • Наличие в блоке security информации о точках и методах описываемого API, требующих авторизацию, например:

security:  - basicAuth: ['/admin']  - apiKey: ['/v1/']
  • Наличие информации о блоке security в каждой отдельной точке или методе API, например:

/ping:     get:       summary: Checks if the server is running       security: []   # No security

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

TLS

Если при работе сервиса требуется использовать TLS, например, для связи с другими сервисами, то необходимо учитывать следующие критерии:

  • TLS Certificate Verification — в коде не должна использоваться конфигурация, игнорирующая проверку сертификатов. Пример неправильной конфигурации:

config := &tls.Config{InsecureSkipVerify: true}
  • TLS Version — в коде должны использоваться только безопасные и актуальные версии TLS. Пример использования нежелательных версий TLS 1.0 или TLS 1.1:

config := &tls.Config{MinVersion:0, MaxVersion:1}
  • TLS ServerName — в случае конфигурации TLS HostName необходимо убедиться, что tls.ServerName совпадает с именем, указанным в сертификате:

config := &tls.Config{ServerName: "test-foo.com"}
  • Unverified TLS Library — рекомендуется использовать стандартную библиотеку для работы с TLS «crypto/tls». Если вы выбрали альтернативную библиотеку, убедитесь в её безопасности.

import "crypto/tls"

Источники

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


ссылка на оригинал статьи https://habr.com/ru/company/deliveryclub/blog/658569/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *