integrity özniteliği, bir <script> veya <link> ile yüklenen dosyanın içeriğinin tam olarak sizin beklediğiniz içerik olduğunu tarayıcıya doğrulatır. Değeri dosyanın kriptografik özetidir (sha384-... gibi); tarayıcı dosyayı indirdikten sonra kendi özetini hesaplar ve eşleşmezse dosyayı yüklemeyi reddeder. Bu mekanizmaya Subresource Integrity (SRI) denir. crossorigin özniteliği ise başka bir kökenden yapılan isteğin CORS ile yapılıp yapılmayacağını ve çerez gibi kimlik bilgilerinin gönderilip gönderilmeyeceğini belirler. CDN'den yüklenen bir dosyada ikisi genellikle birlikte yazılır.

Bu yazıda önce integrity değerinin biçimini, tarayıcının birden fazla özeti nasıl seçtiğini ve özetin nasıl hesaplandığını, sonra crossorigin değerlerini ve CORS olmadan yüklenen görsellerin canvas üzerinde neden "kirli" (tainted) sayıldığını anlatıyoruz. İçerik Güvenlik Politikası (CSP) ve XSS'e karşı korunma XSS ve CSP yazısının konusudur.

integrity neye karşı korur?

MDN'e göre integrity, tedarik zinciri saldırısına (supply chain attack) karşı bir savunmadır. Bu senaryoda saldırgan, betiği veya stil sayfasını sunan alan adına erişim elde eder ve beklenen dosyayı kötü amaçlı bir kopyayla değiştirir. Sayfanız dosyayı her zamanki adresten istemeye devam eder; integrity olmadan tarayıcı yeni içeriği sorgusuz çalıştırır. integrity varsa tarayıcı içeriğin özetini hesaplar, değerle karşılaştırır ve eşleşmezse kaynağı yüklemez.

Bu yüzden SRI özellikle üçüncü taraf bir CDN'den yüklediğiniz, içeriğini sizin denetlemediğiniz dosyalar için anlamlıdır. Kendi sunucunuzdan sunduğunuz dosyalarda da kullanılabilir, ama dosyayı her güncellediğinizde özeti de güncellemeniz gerekir.

Hangi öğelerde kullanılır?

Hangi öğelerde kullanılır? script, link, import map
  • script: integrity yalnızca src varken yazılabilir; satır içi betiklerde belirtilmemelidir.
  • link: yalnızca rel değeri stylesheet, preload veya modulepreload olduğunda belirtilmelidir. Simge (icon) veya manifest bağlantılarında kullanılmaz.
  • import map: modüller için <script type="importmap"> içindeki integrity anahtarıyla URL başına özet tanımlanabilir. Import map öğesinin kendisine ise integrity özniteliği yazılmaz.
html
<script
  src="https://cdn.ornek.com/kutuphane/3.2.0/kutuphane.min.js"
  integrity="sha384-2Cz6poNjdtPViEA3+0wR364FPQ9Qw/2oXAhyRyXTajqYBqlBKFaGsvxP27y9j82Z"
  crossorigin="anonymous"></script>
<link
  rel="stylesheet"
  href="https://cdn.ornek.com/tema/1.4.0/tema.min.css"
  integrity="sha384-OeuMIzn1HCuViQXAerK9AcUuyDRqJa+8gye189FHgTfE1roJTSqoa3eaIUz0GEwD"
  crossorigin="anonymous" />

Değerin biçimi

Değer, boşlukla ayrılmış bir veya daha fazla bileşenden oluşur. Her bileşen algoritma-ÖZET biçimindedir: tirenin solunda özet fonksiyonu, sağında dosya içeriğinin o fonksiyonla hesaplanmış özetinin base64 kodlaması bulunur. Desteklenen üç fonksiyon vardır; güç sırasına göre sha256, sha384 ve sha512. MD5 veya SHA-1 gibi başka algoritmalar bu listede yer almaz.

BileşenÖrnek başlangıçNot
sha256-...sha256-NmUxNTFi...Üç fonksiyonun en zayıfı
sha384-...sha384-Tk2Yjg3Y...Orta
sha512-...sha512-OGUwYThk...En güçlüsü

Tarayıcı dosyayı indirince iki adım uygular. Önce, değerdeki bileşenler arasından en güçlü özet fonksiyonuyla üretilmiş olanların kümesini seçer ve diğerlerini dikkate almaz. Örneğin değerde hem SHA-256 hem SHA-384 özetleri varsa yalnızca SHA-384 özetleri kullanılır. Sonra dosyanın özetini bu fonksiyonla hesaplar ve seçilen değerlerin herhangi biriyle eşleşip eşleşmediğine bakar. Biri eşleşirse kaynak yüklenir, hiçbiri eşleşmezse yüklenmez.

Bu kural iki kullanım sağlar. Farklı algoritmalarla üretilmiş özetleri birlikte yazmak, her özet fonksiyonunu desteklemeyen tarayıcılarla da çalışmayı sağlar. Aynı algoritmayla üretilmiş iki farklı özet yazmak ise sunucunun dosyanın iki farklı sürümünden birini döndürmesine izin verir; MDN'in örneğinde CDN'in iki sürümden birini göndermesi bu yolla kabul edilir. Değer çok satıra bölünebilir, çünkü bileşenler boşlukla ayrılır.

html
<script
  src="https://cdn.ornek.com/betik.js"
  integrity="
    sha384-2Cz6poNjdtPViEA3+0wR364FPQ9Qw/2oXAhyRyXTajqYBqlBKFaGsvxP27y9j82Z
    sha384-OeuMIzn1HCuViQXAerK9AcUuyDRqJa+8gye189FHgTfE1roJTSqoa3eaIUz0GEwD"
  crossorigin="anonymous"></script>

Özeti hesaplamak

Özet, dosyanın baytları üzerinden hesaplanır ve base64 ile kodlanır. Elinizde Python varsa standart kitaplıktaki hashlib ve base64 modülleriyle bunu tek satırda yapabilirsiniz. Aşağıdaki komut, içinde yalnızca console.log("merhaba"); satırı bulunan bir dosya üzerinde Python 3.12 ile çalıştırıldı ve yorumda gerçek çıktısı gösteriliyor:

bash
python -c "import hashlib,base64;print('sha384-'+base64.b64encode(hashlib.sha384(open('kutuphane.js','rb').read()).digest()).decode())"
# sha384-2Cz6poNjdtPViEA3+0wR364FPQ9Qw/2oXAhyRyXTajqYBqlBKFaGsvxP27y9j82Z

Dosyaya tek bir ünlem işareti eklemek (console.log("merhaba!");) özeti tamamen değiştirdi: sha384-OeuMIzn1HCuViQXAerK9AcUuyDRqJa+8gye189FHgTfE1roJTSqoa3eaIUz0GEwD. Tarayıcının yaptığı karşılaştırma da budur: tek bir bayt farkı bile eşleşmeyi bozar. Bu nedenle dosyayı küçültme, sıkıştırma ya da satır sonu dönüştürme gibi işlemlerden sonra, yani sunucunun göndereceği son hâli üzerinden özet hesaplamanız gerekir.

Adım adım: CDN kitaplığını SRI ile eklemek

Adım adım: CDN kitaplığını SRI ile eklemek: Sabit bir adres seçin., Dosyanın son hâlini alın., Özeti hesaplayın., Etiketi yazın.
  • Sabit bir adres seçin. Adresinde sürüm numarası bulunan bir dosya kullanın (/kutuphane/3.2.0/kutuphane.min.js gibi). "En son sürüm" anlamına gelen bir adresin içeriği zamanla değişeceği için özet bir gün mutlaka tutmaz.
  • Dosyanın son hâlini alın. Sunucunun tarayıcıya göndereceği baytların aynısını indirin; özet bu baytlardan hesaplanacak.
  • Özeti hesaplayın. Yukarıdaki komutla ya da yayıncının sunduğu değerle sha384-... biçiminde bir değer elde edin.
  • Etiketi yazın. src veya href yanına integrity ve crossorigin="anonymous" ekleyin. Stil sayfasında rel="stylesheet" olduğundan emin olun.
  • Güncellemeyi planlayın. Kitaplığı yeni sürüme geçirirken adresi ve özeti aynı anda değiştirin. Aynı adresin bir süre iki farklı içerik döndürebileceği bir geçiş dönemi varsa, MDN'in iki sürümlü örneğindeki gibi aynı algoritmayla hesaplanmış iki özeti birlikte yazabilirsiniz; tarayıcı ikisinden biri eşleşirse dosyayı yükler.

Bu sırayı izlediğinizde sayfanızın davranışı öngörülebilir olur: ya beklediğiniz dosya yüklenir ya da hiçbir şey yüklenmez. Değiştirilmiş bir dosyanın sessizce çalışması ihtimali ortadan kalkar. Doğrulama başarısız olduğunda sayfanın o kaynak olmadan da kullanılabilir kalması için temel içeriği HTML'de vermek, betiği yalnızca ek işlevler için kullanmak iyi bir alışkanlıktır.

crossorigin özniteliği

crossorigin, <audio>, <img>, <link>, <script> ve <video> öğelerinde geçerlidir ve öğenin başka kökenden yaptığı isteklerin CORS ile nasıl yapılacağını ayarlar. Belirli anahtar sözcüklerden birini alan (enumerated) bir özniteliktir; değerleri şunlardır:

Değerİstek moduKimlik bilgileri
anonymouscorssame-origin: çerez, istemci TLS sertifikası ve HTTP kimlik doğrulaması yalnızca aynı kökene gönderilir
use-credentialscorsinclude: kimlik bilgileri başka kökene de her zaman gönderilir
boş değer (crossorigin veya crossorigin="")corsanonymous ile aynı
geçersiz değercorsBoş değer gibi, yani anonymous işlenir
öznitelik yokCORS kullanılmaz (modül betikleri hariç)Öğeye göre kısıtlamalar uygulanır

Öznitelik yazılmazsa kaynaklar genellikle CORS olmadan alınır. İstisna modül betikleridir: modüller her zaman CORS ile alınır, bu yüzden onlarda yalnızca crossorigin="use-credentials" bir fark yaratır. CORS olmadan yapılan başka köken isteklerinde tarayıcı kaynağa tam erişim izni istemez ve öğeye göre kısıtlamalar uygular:

ÖğeCORS olmadan başka köken kaynağında kısıtlama
img, audio, videoKaynak canvas üzerine çizilirse canvas kirli (tainted) sayılır
scriptwindow.onerror ile hata kaydına erişim sınırlanır
linkUygun crossorigin başlığı olmayan istek atılabilir

MDN'e göre Chromium tabanlı tarayıcılar rel="icon" üzerinde crossorigin'i desteklemez. Kimlik bilgisi isteyen bir web uygulaması manifesti ise aynı kökenden gelse bile crossorigin="use-credentials" ile alınmalıdır.

html
<script src="https://statik.ornek.com/cerceve.js" crossorigin="anonymous"></script>
<link rel="manifest" href="uygulama.webmanifest" crossorigin="use-credentials" />

integrity ve crossorigin neden birlikte yazılır?

MDN'in integrity sayfasındaki tüm örneklerde başka bir kökenden (cdn.example.com) yüklenen dosyalar crossorigin="anonymous" ile birlikte yazılır. Bu kalıp, isteğin CORS ile ve kimlik bilgisi gönderilmeden yapılmasını sağlar; CDN de dosyayı uygun Access-Control-Allow-Origin başlığıyla sunmalıdır. Kendi sitenizde başka kökenden integrity ile dosya yüklerken bu kalıbı koruyun. Kuralın tüm ayrıntıları için MDN'in Subresource Integrity rehberine bakabilirsiniz.

Önyüklemede de aynı uyum gerekir. rel="preload" sayfasına göre crossorigin değeri, istek aynı kökenden olsa bile kaynağın sonradan yapılacak gerçek isteğinin CORS ve kimlik bilgisi moduyla eşleşmelidir; yazı tipleri anonim CORS ile alındığı için önyüklemeleri crossorigin taşır. modulepreload isteklerinin modu ise her zaman cors'tur ve crossorigin yalnızca kimlik bilgisi modunu belirler. Bu ayrıntılar preload ve modulepreload yazısında.

html
<link rel="preload" href="https://cdn.ornek.com/fonts/govde.woff2" as="font" type="font/woff2" crossorigin />
<link rel="modulepreload" href="js/ana.js"
      integrity="sha384-2Cz6poNjdtPViEA3+0wR364FPQ9Qw/2oXAhyRyXTajqYBqlBKFaGsvxP27y9j82Z" />

Görseller, canvas ve tainted durumu

Görseller, canvas ve tainted durumu: Bağlamda getImageData() çağırmak,, canvas öğesinde toBlob(), toDataURL() veya captureStream() çağırmak kirli...

Bir canvas'ın piksellerini başka sunuculardan alınan görsel ve videolar oluşturabildiği için güvenlik sorunları kaçınılmazdır. MDN'e göre başka bir kökenden CORS onayı olmadan yüklenmiş herhangi bir veriyi canvas'a çizdiğiniz anda canvas kirli (tainted) olur. Kirli canvas artık güvenli sayılmaz ve ondan görüntü verisi okuma girişimleri SecurityError fırlatır. Bu, görsellerin izinsiz olarak başka sitelerden özel veri sızdırmak için kullanılmasını önler.

  • Bağlamda getImageData() çağırmak,
  • canvas öğesinde toBlob(), toDataURL() veya captureStream() çağırmak kirli canvas'ta hata verir.

<img> öğesinde üç durum vardır. crossorigin yazılmazsa istek Origin başlığı olmadan, CORS'suz gönderilir; görsel görünür ama tarayıcı onu kirli sayar ve canvas'ta kullanımını kısıtlar. crossorigin yazılırsa istek Origin başlığıyla CORS isteği olarak gider; sunucu Access-Control-Allow-Origin başlığını hiç göndermezse ya da gönderdiği başlıkta sitenizin kökeni yoksa tarayıcı görselin yüklenmesini engeller ve geliştirici araçları konsoluna bir CORS hatası yazar. use-credentials kullanılırsa sunucunun ayrıca Access-Control-Allow-Credentials: true göndermesi gerekir; göndermezse görsel kirli sayılır.

html
<img
  src="https://gorsel.ornek.com/urunler/canta.jpg"
  crossorigin="anonymous"
  alt="Kahverengi deri sırt çantası"
  width="600"
  height="400" />

Sunucu tarafında, görsel dosyalarının Access-Control-Allow-Origin başlığıyla sunulması gerekir. MDN'in örneği, Apache için HTML5 Boilerplate'in CORS yapılandırmasını kullanır: Origin başlığı içeren isteklerde görsel uzantılı dosyalara Access-Control-Allow-Origin: * başlığı eklenir. Bu, görsellerin internetteki her yerden başka köken erişimine açılması demektir; yalnızca belirli sitelere izin vermek istiyorsanız başlığa * yerine o kökeni yazarsınız. <video> ve <audio> için de kural aynıdır: öznitelik yoksa kaynak CORS olmadan alınır ve canvas'ta kirli kullanılır. Bu öğelerin diğer öznitelikleri img etiketi ve video ve audio etiketi yazılarında.

Uygulama: CDN kullanan bir sayfa

Aşağıdaki sayfada üçüncü taraf CDN'den gelen stil ve betik integrity ile doğrulanıyor, ikisi de crossorigin="anonymous" ile kimlik bilgisi göndermeden isteniyor. Ürün görseli canvas üzerinde işleneceği için crossorigin taşıyor; kendi kökenimizden gelen uygulama betiği ise ek bir öznitelik gerektirmiyor.

html
<!doctype html>
<html lang="tr">
  <head>
    <meta charset="utf-8" />
    <title>Ürün Önizleme</title>
    <link
      rel="stylesheet"
      href="https://cdn.ornek.com/tema/1.4.0/tema.min.css"
      integrity="sha384-OeuMIzn1HCuViQXAerK9AcUuyDRqJa+8gye189FHgTfE1roJTSqoa3eaIUz0GEwD"
      crossorigin="anonymous" />
    <script
      defer
      src="https://cdn.ornek.com/kutuphane/3.2.0/kutuphane.min.js"
      integrity="sha384-2Cz6poNjdtPViEA3+0wR364FPQ9Qw/2oXAhyRyXTajqYBqlBKFaGsvxP27y9j82Z"
      crossorigin="anonymous"></script>
    <script defer src="js/onizleme.js"></script>
  </head>
  <body>
    <h1>Ürün önizleme</h1>
    <img id="urun" src="https://gorsel.ornek.com/urunler/canta.jpg" crossorigin="anonymous"
         alt="Kahverengi deri sırt çantası" width="600" height="400" />
    <canvas id="tuval" width="600" height="400">Çantanın düzenlenebilir önizlemesi</canvas>
  </body>
</html>

Sık yapılan hatalar

  • Dosyayı değiştirip özeti güncellememek: tarayıcı dosyayı reddeder; betik veya stil çalışmaz.
  • Satır içi betiğe integrity yazmak: src yoksa integrity belirtilmemelidir.
  • integrity'yi link rel="icon" gibi değerlerde kullanmak: yalnızca stylesheet, preload ve modulepreload ile geçerlidir.
  • Desteklenmeyen algoritma yazmak: yalnızca sha256, sha384 ve sha512 desteklenir.
  • Özeti küçültmeden önce hesaplamak: özet, sunucunun gönderdiği son baytlardan hesaplanmalıdır.
  • crossorigin'i görselde unutup canvas'tan veri okumak: canvas kirli olur ve SecurityError alınır.
  • Sunucuyu yapılandırmadan crossorigin eklemek: Access-Control-Allow-Origin yoksa görsel hiç yüklenmez.
  • Gereksiz yere use-credentials kullanmak: çerezler başka kökene de gider ve sunucunun Access-Control-Allow-Credentials göndermesi gerekir.

Sık Sorulan Sorular

Kendi sunucumdaki dosyalara integrity eklemeli miyim?

Zorunlu değildir. integrity'nin asıl amacı, dosyayı sunan alan adı ele geçirilse bile değiştirilmiş içeriğin çalışmasını önlemektir; bu risk en çok üçüncü taraf CDN'lerde söz konusudur. Kendi dosyalarınıza eklerseniz her dağıtımda özetleri yeniden üretecek bir süreç kurmanız gerekir.

Hangi algoritmayı seçmeliyim?

Desteklenen üç algoritmadan biri: sha256, sha384 veya sha512. Birden fazlasını yazarsanız tarayıcı en güçlü olanı kullanır. MDN'in örneklerinde en sık sha384 ve sha512 değerleri görülür.

crossorigin ile crossorigin="anonymous" aynı mı?

Evet. Boş değer (crossorigin veya crossorigin="") anonymous ile aynıdır; geçersiz değerler de boş değer gibi, yani anonymous olarak işlenir.

integrity, CSP yerine geçer mi?

Hayır, farklı sorunları çözerler. integrity belirli bir dosyanın içeriğini doğrular; CSP hangi kaynaklardan ve hangi koşullarda kod yüklenebileceğini sınırlar. İkisi birlikte kullanılabilir; CSP için XSS ve CSP yazısına bakın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: HTML öğrenme rehberi · script etiketi · link etiketi · preload ve modulepreload · importmap · img etiketi · XSS ve CSP