package scanner import ( "net/http" "strings" ) // CheckCORS отправляет клон req с произвольным Origin-заголовком // (уникальным на каждый вызов) и смотрит, отражает ли сервер этот // Origin в Access-Control-Allow-Origin — особенно опасно в сочетании // с Access-Control-Allow-Credentials: true, что открывает межсайтовый // доступ к аутентифицированным данным с любого сайта. // // В отличие от Check-проверок, CORS не подставляется в §маркер§ — // здесь модифицируется заголовок запроса, а не тело/параметр, поэтому // это отдельная функция, а не реализация интерфейса Check. func CheckCORS(client *http.Client, req *http.Request) (*Finding, error) { evilOrigin := "https://" + randToken(6) + ".burterm-cors-check.invalid" probe := req.Clone(req.Context()) probe.Header.Set("Origin", evilOrigin) resp, err := client.Do(probe) if err != nil { return nil, err } defer resp.Body.Close() acao := resp.Header.Get("Access-Control-Allow-Origin") acac := resp.Header.Get("Access-Control-Allow-Credentials") switch { case acao == evilOrigin: sev := SeverityMedium detail := "Сервер отражает произвольный Origin в Access-Control-Allow-Origin" if strings.EqualFold(acac, "true") { sev = SeverityHigh detail += " вместе с Access-Control-Allow-Credentials: true — межсайтовый доступ к аутентифицированным данным с любого сайта" } return &Finding{Check: "CORS", Severity: sev, Detail: detail, Evidence: acao}, nil case acao == "*" && strings.EqualFold(acac, "true"): // Браузер такую комбинацию по спецификации отклонит, но раз // сервер её всё же выставляет — это сигнал небрежной настройки, // стоит перепроверить остальные CORS-заголовки вручную. return &Finding{ Check: "CORS", Severity: SeverityMedium, Detail: "Access-Control-Allow-Origin: * вместе с Allow-Credentials: true (браузеры отклонят эту комбинацию, но она сигнализирует о небрежной настройке)", Evidence: acao, }, nil } return nil, nil }