
Привет! Меня зовут Александра, я инженер по информационной безопасности в 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/
Добавить комментарий