Irbisa · Шпаргалка

Сети и безопасность для бэкенда

TCP и модель OSI, DNS, TLS, HTTP/3 и QUIC, балансировщики и прокси, WebSocket, CORS, хеширование паролей, OAuth 2.0 и JWT, SSRF, mTLS

Middle-разработчик · 19 вопросов

  1. 01

    Какие уровни есть в моделях OSI и TCP/IP и на каком уровне работают IP, TCP, TLS, HTTP и балансировщик нагрузки?

    В OSI семь уровней: физический, канальный, сетевой, транспортный, сеансовый, представления и прикладной.

  2. 02

    Что происходит шаг за шагом между вводом https://example.com/orders в браузере и первым байтом ответа?

    Браузер узнаёт IP example.com через DNS, открывает TCP-соединение на порт 443, проводит TLS-рукопожатие и отправляет по нему GET /orders.

  3. 03

    Чем HTTPS отличается от HTTP, что делает TLS-рукопожатие и когда стоит выбрать UDP вместо TCP?

    HTTPS устроен как HTTP поверх TLS: рукопожатие согласует шифр и проверяет сертификат сервера, после чего обе стороны шифруют трафик сеансовым ключом, так что он закрыт от посторонних и защищён от подмены.

  4. 04

    Что такое CORS, какие запросы вызывают preflight OPTIONS и почему CORS не защищает API от curl или другого сервера?

    CORS позволяет серверу сообщить браузеру, каким другим origin разрешено читать его ответы, а соблюдает это правило сам браузер.

  5. 05

    Как WebSocket-соединение начинается с HTTP-запроса и что должны поддерживать прокси и балансировщики перед сервисом на Go, чтобы оно не обрывалось?

    Клиент отправляет обычный GET с Upgrade: websocket и Connection: Upgrade, сервер отвечает 101 Switching Protocols, и дальше то же TCP-соединение несёт WebSocket-фреймы в обе стороны.

    // Registered as mux.HandleFunc("GET /ws", chat).func chat(w http.ResponseWriter, r *http.Request) {	// Checks the Upgrade headers and Origin, then answers 101 Switching Protocols.	c, err := websocket.Accept(w, r, &websocket.AcceptOptions{		OriginPatterns: []string{"app.example.com"}, // browsers send Origin; CORS does not apply here	})…
  6. 06

    Как TCP открывает соединение тройным рукопожатием и как закрывает его, и почему у нагруженного клиента копятся сокеты в TIME_WAIT?

    TCP открывает соединение через SYN, SYN-ACK, ACK, то есть один RTT до любых данных, и закрывает его парой FIN и ACK в каждую сторону.

  7. 07

    Как работает разрешение имён в DNS и почему сервис на Go может ещё минутами слать трафик на старый IP после смены записи?

    Рекурсивный резолвер идёт от корня к серверам TLD и дальше к авторитативным серверам домена, и каждый ответ кэшируется на время его TTL.

  8. 08

    Как TLS-клиент решает, что сертификат действительно принадлежит api.example.com, и что делать вместо InsecureSkipVerify, если CA приватный?

    Клиент строит цепочку от сертификата сервера через промежуточные до корня, которому уже доверяет, проверяет сроки действия каждого сертификата и проверяет, что имя хоста есть в Subject Alternative Names листового сертификата.

    // The billing API uses a certificate from the company CA, which is not in// the system pool, so calls fail with "certificate signed by unknown authority".func newBillingClient(companyCA []byte) (*http.Client, error) {	pool, err := x509.SystemCertPool()	if err != nil {		return nil, err…
  9. 09

    Что меняет HTTP/3 поверх QUIC по сравнению с HTTP/2 поверх TCP (рукопожатие, head-of-line blocking, миграция соединения) и нужен ли он API на Go?

    HTTP/3 работает поверх QUIC, транспорта на UDP со встроенным TLS 1.3: один RTT на подключение вместо RTT для TCP плюс RTT для TLS, потоки, которые не стопорят друг друга при потере пакета, и соединения, переживающие смену IP.

  10. 10

    Чем балансировщик L4 отличается от L7 и что каждый из них видит в запросе и может с ним сделать?

    Балансировщик L4 пересылает TCP- или UDP-соединения по IP и порту, не читая их содержимое, поэтому подходит для любого протокола, но балансирует целые соединения.

  11. 11

    Чем прямой прокси отличается от обратного и как сервису на Go за прокси узнать настоящий IP клиента, не доверяя подделанному X-Forwarded-For?

    Прямой (forward) прокси действует от имени клиентов и ходит на любые серверы, как корпоративный прокси для исходящего трафика; обратный (reverse) прокси стоит перед серверами, как nginx, ingress или балансировщик.

    // The address ranges of our own load balancers and ingress proxies.var trustedProxies = []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")} func trusted(ip netip.Addr) bool {	return slices.ContainsFunc(trustedProxies, func(p netip.Prefix) bool { return p.Contains(ip) })}…
  12. 12

    Как сервису на Go хранить пароли пользователей, почему SHA-256 здесь не подходит и чем отличаются bcrypt и argon2id из golang.org/x/crypto?

    Храните медленный хэш с солью, а не сам пароль и не его быстрый хэш.

    // bcrypt: the salt and the cost travel inside the result, "$2a$12$<salt><hash>".func hashPassword(pw string) (string, error) {	h, err := bcrypt.GenerateFromPassword([]byte(pw), 12) // ErrPasswordTooLong past 72 bytes	return string(h), err}…
  13. 13

    JWT или серверные сессии: в чём компромиссы, где браузеру хранить токен и что такое IDOR?

    Сессия представляет собой случайный ID, указывающий на состояние на сервере, поэтому её можно отозвать сразу; JWT устроен как подписанный токен, который проверяется без обращения к хранилищу, поэтому масштабируется без общего состояния, но действует до истечения срока.

  14. 14

    Найдите баг: JWT, выпущенный тестовым скриптом два года назад, до сих пор проходит аутентификацию в продакшене.

    var secret = []byte(os.Getenv("JWT_SECRET")) // userID authenticates every request to the orders API.func userID(raw string) (string, error) {	tok, err := jwt.Parse(raw, func(t *jwt.Token) (any, error) {		return secret, nil	})	if err != nil {		return "", err	}	return tok.Claims.GetSubject()}

    golang-jwt проверяет exp, только если он есть в токене: по умолчанию это поле необязательно, поэтому токен скрипта, выпущенный без exp, действует вечно.

  15. 15

    Как работает OAuth 2.0 authorization code flow с PKCE и что OpenID Connect добавляет поверх OAuth?

    Приложение отправляет пользователя к провайдеру идентификации, тот проводит вход и возвращает пользователя с одноразовым кодом; бэкенд обменивает этот код вместе с PKCE-verifier, который знает только он, на токены.

    var conf = &oauth2.Config{	ClientID:     "orders-web",	ClientSecret: "from-the-secret-store", // a backend is a confidential client	RedirectURL:  "https://app.example.com/callback",	Scopes:       []string{oidc.ScopeOpenID, "email"},	Endpoint:     oauth2.Endpoint{AuthURL: "https://id.example.com/authorize", TokenURL: "https://id.example.com/token"},…
  16. 16

    Эта функция проверяет HMAC-подпись вебхука, прежде чем хендлер доверится телу. Какие две уязвимости в ней есть и как их исправить?

    // The provider sends X-Signature: hex(HMAC-SHA256(secret, body)).func verifyWebhook(r *http.Request, secret []byte) ([]byte, error) {	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))	if err != nil {		return nil, err	}	mac := hmac.New(sha256.New, secret)	mac.Write(body)	want := hex.EncodeToString(mac.Sum(nil))	if r.Header.Get("X-Signature") != want {		return nil, errors.New("bad signature")	}	return body, nil}

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

  17. 17

    Найдите баг: эндпоинт скачивания файлов позволяет любому пользователю прочитать /etc/passwd и конфиг самого сервиса.

    // filesDir is "/srv/app/uploads"; the config lives in /srv/app/config.yaml.// Registered as mux.HandleFunc("GET /download", download).func download(w http.ResponseWriter, r *http.Request) {	name := r.URL.Query().Get("name") // "report.pdf"	http.ServeFile(w, r, filepath.Join(filesDir, name))}

    Имя из query string попадает прямо в filepath.Join, а тот разрешает ..: ?name=../../../etc/passwd превращается в /etc/passwd, а ?name=../config.yaml в конфиг сервиса.

  18. 18

    Сервис запрашивает URL вебхука, который задал пользователь. Что такое SSRF и как заблокировать 169.254.169.254 и внутренние хосты, в том числе через DNS rebinding?

    Server-side request forgery (SSRF) заставляет ваш сервер отправлять запросы, которые атакующий не может отправить напрямую: к сервису метаданных облака, админским портам на localhost или внутренним API.

    var cgnat = netip.MustParsePrefix("100.64.0.0/10") // carrier-grade NAT, not in IsPrivate // Control runs on every dial, after DNS, with the IP about to be connected,// so a rebinding answer or a redirect to 10.0.0.5 is checked as well.func publicOnly(network, address string, _ syscall.RawConn) error {	ap, err := netip.ParseAddrPort(address)…
  19. 19

    Как работает взаимный TLS (mTLS) между двумя сервисами на Go и какие поля tls.Config должна задать каждая сторона, чтобы ни одна не говорила с самозванцем?

    В mTLS сервер тоже запрашивает сертификат, и каждая сторона проверяет цепочку другой по приватному CA: сервер задаёт ClientAuth: tls.RequireAndVerifyClientCert и ClientCAs, клиент RootCAs, свои Certificates и ServerName.

    // Server: only callers with a certificate from our CA, and only the allowed ones.func serverTLS(cert tls.Certificate, clientCAs *x509.CertPool, allowed []string) *tls.Config {	return &tls.Config{		Certificates: []tls.Certificate{cert},		ClientAuth:   tls.RequireAndVerifyClientCert, // RequireAnyClientCert does not verify		ClientCAs:    clientCAs,…