CodeAuditAgent
Tüm yazılar
  • Güvenlik
  • Web
  • Next.js

Güvenlik Başlıkları: CSP, HSTS ve Diğerleri

Nonce ve strict-dynamic ile CSP, HSTS ve preload, frame-ancestors, Referrer-Policy, Permissions-Policy ve COOP için bir Next.js yapılandırmalı pratik rehber.

· 7 dk okuma · Lina Source LLC

Güvenlik başlıkları, sunucunuzun tarayıcıya verdiği talimatlardır: yalnızca bu betikleri çalıştır, benimle yalnızca HTTPS üzerinden konuş, başka sitelerin bu sayfayı çerçeve içine almasına izin verme. Kodunuzdaki hataları düzeltmezler, ama bir saldırganın bu hatalarla yapabileceklerini sınırlarlar. Katı bir Content Security Policy arkasındaki bir cross-site scripting açığı, aynı açığın CSP olmadan var olmasından çok daha küçük bir sorundur.

Bu başlıkların çoğu tek satırlık bir yapılandırmadır. İstisna, planlama gerektiren CSP'dir. Bu rehber her başlığın ne yaptığını, tipik bir web uygulaması için makul değerleri ve bunları üretim ortamını bozmadan nasıl devreye alacağınızı anlatıyor.

Content-Security-Policy

CSP, tarayıcıya betikler, stiller, görseller, çerçeveler ve bağlantılar için hangi kaynaklara izin verildiğini söyler. Temel görevi, enjekte edilmiş betiklerin çalışmasını engellemektir. Alan adlarından oluşan bir izin listesi bariz yaklaşım gibi görünse de uzun bir atlatma geçmişine sahiptir: kullanıcı içeriği veya eski kütüphane sürümleri barındıran, izin verilmiş herhangi bir CDN, saldırganın kontrol ettiği bir betiği yüklemek için kullanılabilir.

Nonce'lar ve strict-dynamic

İşe yarayan yaklaşım, nonce tabanlı bir politikadır. Sunucu her yanıt için rastgele bir değer üretir, bunu CSP başlığına koyar ve oluşturduğu her script etiketine nonce niteliği olarak ekler. Enjekte edilmiş script etiketleri nonce'u bilmez ve engellenir. 'strict-dynamic' eklemek, güvenilir bir betiğin yüklediği betiklerin başka betikler yüklemesine izin verir; bu da her alan adını listelemeden bundler'ların ve tag manager'ların çalışmaya devam etmesini sağlar.

Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-4AEemGb0xJptoIGFP3Nd' 'strict-dynamic';
  style-src 'self' 'nonce-4AEemGb0xJptoIGFP3Nd';
  img-src 'self' data: https:;
  connect-src 'self';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
  form-action 'self';
  upgrade-insecure-requests

Başlık tek satır olarak gönderilir; burada okunabilirlik için satırlara bölünmüştür. Bu politikadaki bazı direktifler göründüğünden fazlasını yapar. object-src 'none', eski eklenti içeriklerini engeller. base-uri 'none', enjekte edilmiş bir base etiketinin göreli betik URL'lerini başka yere yönlendirmesini önler. form-action 'self', enjekte edilmiş formların kimlik bilgilerini başka bir yere göndermesini engeller. Nonce tahmin edilemez olmalı ve her yanıtta yenilenmelidir; bu da onu kullanan sayfaların statik bir önbellekten sunulamayacağı anlamına gelir.

Report-Only ile devreye alın

Körlemesine dağıtılan katı bir CSP bir şeyleri bozar: bir analitik kod parçası, satır içi bir olay işleyicisi, üçüncü taraf bir widget. Politikayı önce Content-Security-Policy-Report-Only olarak yayına alın. Tarayıcı hiçbir şeyi uygulamaz, ancak her ihlali report-to veya report-uri direktifinde belirtilen endpoint'e raporlar. Bir süre rapor toplayın, meşru olanları düzeltin veya izin verin, ardından başlık adını zorlama moduna geçirin. Zorlamaya geçtikten sonra da raporlamayı açık tutun; çünkü yeni ihlaller ya bir gerilemedir ya da bir saldırı. Sayfalara kendi betiklerini enjekte eden tarayıcı eklentilerinden gelen gürültülü raporlar bekleyin; neye izin vereceğinize karar vermeden önce bunları kaynağa göre filtreleyin.

Strict-Transport-Security

HSTS, tarayıcıya belirli bir süre boyunca alan adınız için HTTPS kullanmasını söyler; kullanıcı http:// yazsa veya eski bir bağlantıya tıklasa bile. Bu, ağdaki bir saldırganın ilk düz HTTP isteğini yakalayıp HTTPS'e yönlendirmeyi ortadan kaldırabileceği açıklığı kapatır. Tarayıcılar başlığı yalnızca HTTPS üzerinden geldiğinde dikkate alır ve HSTS uygulanan bir alan adında kullanıcıların sertifika hatalarını geçmesine de izin vermez; amaç tam olarak budur, ama bir sertifikanın süresi dolarsa risk de budur.

Bir gün gibi kısa bir max-age ile başlayın, hiçbir şeyin bozulmadığını doğrulayın, ardından bir veya iki yıla çıkarın. includeSubDomains kuralı her alt alan adına genişletir; bu yüzden önce aynı alan adındaki eski pazarlama siteleri ve dahili araçlar dahil hepsinin geçerli HTTPS sunduğunu doğrulayın.

preload direktifi, tarayıcı preload listesine yapılan bir başvuruyla birleştiğinde alan adınızı tarayıcılara gömer; böylece en ilk ziyaret bile HTTPS kullanır. En az bir yıllık max-age ve includeSubDomains gerektirir. Bunu geri dönüşü zor bir kapı olarak görün: listeden çıkarılmak mümkündür ama kullanıcılara ulaşması uzun sürer ve bu süre boyunca HTTPS sunamayan her alt alan adı erişilemez hâle gelir.

Tek satırlık başlıklar

  • X-Content-Type-Options: nosniff. Tarayıcıların, belirttiğinizden farklı bir içerik tipi tahmin etmesini engeller; bu da metin olarak sunulan yüklenmiş bir dosyanın betik olarak çalıştırılmasını önler.
  • frame-ancestors (CSP içinde) ve X-Frame-Options: DENY. Sayfalarınızı kimin bir çerçeve içine gömebileceğini kontrol eder; clickjacking'e karşı savunma budur. Modern tarayıcılar ikisi birden ayarlandığında frame-ancestors'ı kullanır ve X-Frame-Options'ı yok sayar; X-Frame-Options'ı eski istemciler için tutun. Kendi sayfalarınızı çerçeve içine alıyorsanız 'self' veya SAMEORIGIN kullanın.
  • Referrer-Policy: strict-origin-when-cross-origin. Diğer sitelere tam yolu ve sorgu dizesini değil, yalnızca origin'inizi gönderir. Bu, URL'lerdeki token'ların ve ID'lerin üçüncü taraflara sızmasını önler. Özellikle hassas sayfalar için no-referrer kullanın.
  • Permissions-Policy. camera=(), microphone=(), geolocation=() ve payment=() gibi kullanmadığınız tarayıcı özelliklerini kapatır; böylece enjekte edilmiş veya gömülü kod bunları talep edemez.
  • Cross-Origin-Opener-Policy: same-origin. Sayfanızı kendi browsing context group'una yerleştirir; böylece başka bir sitenin açtığı bir pencere sayfanıza referans tutamaz. OAuth veya ödeme açılır pencerelerine ihtiyacınız varsa same-origin-allow-popups kullanın.
  • Cross-Origin-Resource-Policy: same-origin veya same-site. Tarayıcılara, diğer origin'lerin yanıtlarınızı görsel, betik veya başka alt kaynak olarak yüklemesine izin vermemelerini söyler. Varlıkları kardeş bir alt alan adından sunuyorsanız same-site, başka yerlere gömülmesi amaçlanan varlıklar için cross-origin kullanın.

X-XSS-Protection'ı bırakabilirsiniz. Kontrol ettiği filtre modern tarayıcılardan kaldırıldı ve yerini CSP aldı. Bir güvenlik tarayıcısı ısrar ederse değerini 0 yapın. Benzer şekilde, yalnızca saldırgana bilgi sağlayan başlıklardan kaçının: X-Powered-By ve ayrıntılı Server başlıkları framework'ünüzü ve sürümünüzü bedavaya açığa çıkarır. Next.js'te ilkini kaldırmak için yapılandırmada poweredByHeader: false ayarlayın.

Bir Next.js yapılandırması

Statik başlıklar, headers() fonksiyonunun onları her route'a uyguladığı next.config.ts dosyasına aittir. Aşağıdaki değerler, kendini çerçeve içine gömmeyen, kamera veya konum kullanmayan ve kendi varlıklarını sunan bir uygulama için makul bir varsayılandır. Körü körüne kopyalamak yerine her birini uygulamanızın gerçekte yaptığı işe göre ayarlayın.

// next.config.ts
import type { NextConfig } from "next";

const securityHeaders = [
  {
    key: "Strict-Transport-Security",
    value: "max-age=63072000; includeSubDomains",
  },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  {
    key: "Permissions-Policy",
    value: "camera=(), microphone=(), geolocation=(), payment=()",
  },
  { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
  { key: "Cross-Origin-Resource-Policy", value: "same-origin" },
];

const nextConfig: NextConfig = {
  async headers() {
    return [{ source: "/(.*)", headers: securityHeaders }];
  },
};

export default nextConfig;

CSP her istek için yeni bir nonce gerektirdiğinden middleware'de ayarlanır. Next.js, nonce'u isteğin Content-Security-Policy başlığından okur ve render sırasında framework'ün kendi betiklerine uygular. Kendi script etiketleriniz için nonce'u bir server component'te headers().get("x-nonce") ile okuyabilirsiniz.

// middleware.ts (in Next.js 16 the file is proxy.ts and the function is proxy)
import { NextResponse, type NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const csp = [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'nonce-${nonce}'`,
    "img-src 'self' blob: data:",
    "object-src 'none'",
    "base-uri 'self'",
    "form-action 'self'",
    "frame-ancestors 'none'",
    "upgrade-insecure-requests",
  ].join("; ");

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-nonce", nonce);
  requestHeaders.set("Content-Security-Policy", csp);

  const response = NextResponse.next({ request: { headers: requestHeaders } });
  response.headers.set("Content-Security-Policy", csp);
  return response;
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

Nonce ile render edilen sayfalar dinamik olarak render edilmelidir; çünkü statik bir sayfa her ziyaretçi için aynı nonce'u yeniden kullanır. Devreye alma sürecinde başlık adını her iki yerde de Content-Security-Policy-Report-Only olarak değiştirin ve bir raporlama endpoint'i ekleyin. Geliştirme ortamında React, hata ayıklama özellikleri için script-src içinde 'unsafe-eval' gerektirebilir; bunu yalnızca NODE_ENV development olduğunda ekleyin.

Nasıl doğrulanır

  • Ana sayfaya, dinamik bir sayfaya, bir API route'una ve statik bir varlığa karşı curl -sI https://your-domain.example/ çalıştırın ve her birindeki başlıkları okuyun. Yalnızca HTML sayfalarında ayarlanan başlıklar yaygın bir eksikliktir.
  • Tarayıcının geliştirici araçlarını açın. CSP ihlalleri konsolda direktif ve engellenen kaynakla birlikte görünür; Network sekmesi ise gerçekte sunulan başlıkları gösterir.
  • Kontrolü hem origin'de hem de CDN'inizin veya reverse proxy'nizin arkasında yapın. Proxy'ler bazen başlıkları kaldırır, çoğaltır veya geçersiz kılar; çakışan iki CSP başlığının ikisi de uygulanır, bu yüzden etkin politika her ikisinden de daha katı olur.
  • Hızlı bir ikinci görüş için Mozilla HTTP Observatory gibi herkese açık bir denetleyici, zayıf CSP direktiflerini tespit etmek için de Google'ın CSP Evaluator'ını kullanın.
  • CI'a, önemli route'lara istek gönderip başlıkların mevcut olduğunu doğrulayan bir test ekleyin; böylece bir yapılandırma refactor'ı onları sessizce kaldıramaz.

Başlıklar yapılandırmadır ve tam da bu yüzden zamanla kayarlar: kendi yanıtını oluşturan yeni bir route handler, varsayılanları geçersiz kılan bir proxy kuralı, bir sürümün önünü açmak için 'unsafe-inline' ile gevşetilmiş bir CSP. Onları kod gibi inceleyin. CodeAuditAgent, herkese açık bir depodaki veya yapıştırılmış bir kod parçasındaki yapılandırmayı kodun geri kalanıyla birlikte okur ve eksik veya zayıflatılmış başlıkları önem derecesi, alıntılanmış yapılandırma ve önerilen bir düzeltmeyle birlikte raporlar.

Makul bir iş sırası: tek satırlık başlıklar bugün, kısa bir max-age ile HSTS bu hafta ve raporları toplayabildiğiniz ilk anda Report-Only modunda nonce tabanlı bir CSP.