Sanitizer API ile güvenli DOM işleme

Yeni Sanitizer API, sayfaya güvenli bir şekilde eklenecek rastgele dizeler için sağlam bir işlemci geliştirmeyi amaçlar.

Jack J
Jack J

Uygulamalar her zaman güvenilmeyen dizelerle çalışır ancak bu içeriği bir HTML belgesinin parçası olarak güvenli bir şekilde oluşturmak zor olabilir. Yeterli özen olmadan, yanlışlıkla kötü amaçlı saldırganların istismar edebileceği siteler arası komut dosyası (XSS) fırsatları oluşturabilirsiniz.

Yeni Sanitizer API teklifi, bu riski azaltmak amacıyla sayfaya güvenli bir şekilde eklenecek rastgele dizeler için sağlam bir işlemci oluşturmayı amaçlar. Bu makalede API tanıtılmakta ve kullanımı açıklanmaktadır.

// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())

Kullanıcı girişine çıkış karakteri ekleme

Kullanıcı girişi, sorgu dizeleri, çerez içerikleri vb. DOM'ye eklenirken dizeler doğru şekilde kod dışına alınmalıdır. .innerHTML aracılığıyla DOM manipülasyonuna özel olarak dikkat edilmelidir. Burada, kod dışına alınmamış dizeler tipik bir XSS kaynağıdır.

const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input

Yukarıdaki giriş dizesinde HTML özel karakterlerinden çıkış yaparsanız veya bunu .textContent kullanarak genişletirseniz alert(0) yürütülmez. Ancak, kullanıcı tarafından eklenen <em> aynı zamanda olduğu gibi dize olarak da genişletildiğinden, HTML'deki metin süslemesini korumak için bu yöntem kullanılamaz.

Burada yapılacak en iyi şey çıkış yapmak değil, temizleştirmek.

Kullanıcı girişi temizleniyor

Çıkış yapma ve temizleme arasındaki fark

Çıkış yöntemi, özel HTML karakterlerinin HTML Öğeleri ile değiştirilmesini ifade eder.

Arındırma, anlam açısından zararlı olan kısımların (komut dosyası yürütme gibi) HTML dizelerinden kaldırılmasını ifade eder.

Örnek

Yukarıdaki örnekte <img onerror>, hata işleyicinin yürütülmesine neden olur, ancak onerror işleyici kaldırılırsa <em> sağlam şekilde kalarak DOM'de güvenli bir şekilde genişletilebilir.

// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`

Doğru şekilde temizleme işlemi için giriş dizesini HTML olarak ayrıştırmak, zararlı kabul edilen etiket ve özellikleri çıkarmak ve zararsız olanları tutmak gerekir.

Önerilen Sanitizer API spesifikasyonu, tarayıcılar için standart bir API gibi işleme imkanı sunmayı amaçlar.

Sanitizer API'si

Sanitizer API aşağıdaki şekilde kullanılır:

const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>

Ancak { sanitizer: new Sanitizer() } varsayılan bağımsız değişkendir. Bu, aşağıdaki gibi olabilir.

$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>

setHTML() öğesinin Element üzerinde tanımlandığını belirtmek isteriz. Element yöntemi olduğu için ayrıştırılacak bağlam yeterince açıklayıcıdır (bu durumda <div>), ayrıştırma işlemi dahili olarak bir kez yapılır ve sonuç doğrudan DOM'ye genişletilir.

Temizlik işleminin sonucunu dize olarak almak için setHTML() sonuçlarında .innerHTML kullanabilirsiniz.

const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">

Yapılandırma aracılığıyla özelleştir

Sanitizer API, varsayılan olarak komut dosyası yürütülmesini tetikleyecek dizeleri kaldıracak şekilde yapılandırılmıştır. Bununla birlikte, bir yapılandırma nesnesi aracılığıyla temizleme işlemine kendi özelleştirmelerinizi de ekleyebilirsiniz.

const config = {
  allowElements: [],
  blockElements: [],
  dropElements: [],
  allowAttributes: {},
  dropAttributes: {},
  allowCustomElements: true,
  allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)

Aşağıdaki seçenekler, temizleme sonucunun belirtilen öğeyi nasıl ele alacağını belirtir.

allowElements: Dezenfektanın tutması gereken öğelerin adları.

blockElements: Dezenfektanın kaldırması ve alt öğelerini tutması gereken öğelerin adları.

dropElements: Dezenfektanın kaldırılması gereken öğelerin adları ve alt öğeleri.

const str = `hello <b><i>world</i></b>`

$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>

$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>

Dezenfektanın, belirtilen özelliklere izin verip vermeyeceğini aşağıdaki seçeneklerle de kontrol edebilirsiniz:

  • allowAttributes
  • dropAttributes

allowAttributes ve dropAttributes mülklerinde özellik eşleşme listeleri beklenir. Bu listeler, anahtarları özellik adı, değerler ise hedef öğelerin listeleri veya * joker karakteridir.

const str = `<span id=foo class=bar style="color: red">hello</span>`

$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>

allowCustomElements, özel öğelere izin verme veya reddetme seçeneğidir. İzin verildiği takdirde, öğeler ve özelliklerle ilgili diğer yapılandırmalar geçerli olmaya devam eder.

const str = `<custom-elem>hello</custom-elem>`

$div.setHTML(str)
// <div></div>

const sanitizer = new Sanitizer({
  allowCustomElements: true,
  allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>

API yüzeyi

DomPurify ile karşılaştırma

DOMPurify, temizlik işlevi sunan meşhur bir kitaplıktır. Sanitizer API ile DOMPurify arasındaki temel fark, DOMPurify'ın temizleme işleminin sonucunu bir dize olarak döndürmesidir. Bu sonucu, .innerHTML aracılığıyla bir DOM öğesine yazmanız gerekir.

const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`

DOMPurify, tarayıcıda Sanitizer API kullanılmadığında yedek olarak kullanılabilir.

DOMPurify uygulamasının bazı dezavantajları vardır. Bir dize döndürülürse giriş dizesi DOMPurify ve .innerHTML tarafından iki kez ayrıştırılır. Bu iki kez ayrıştırma işlemi, işleme süresini kaybetse de ikinci ayrıştırmanın sonucunun ilkinden farklı olduğu durumlarda ortaya çıkan ilginç güvenlik açıklarına da yol açabilir.

HTML'nin bağlamın da ayrıştırılması gerekir. Örneğin, <td>, <table> dilinde anlam ifade ederken <div> dilinde anlamlı değildir. DOMPurify.sanitize(), dizeleri yalnızca bağımsız değişken olarak aldığı için ayrıştırma bağlamının tahmin edilmesi gerekiyordu.

Sanitizer API, DOMPurify yaklaşımından daha iyi bir şekilde gelişmiş hâle gelmiştir. Aynı zamanda, çift ayrıştırma ihtiyacını ortadan kaldırmak ve ayrıştırma bağlamını netleştirmek için tasarlanmıştır.

API durumu ve tarayıcı desteği

Sanitizer API, şu anda standart hale getiriliyor ve Chrome da bu API'yi uygulama aşamasında.

Step Durum
1. Açıklayıcı oluşturun Tamamlandı
2. Spesifikasyon taslağı oluştur Tamamlandı
3. Geri bildirim alma ve tasarımda yineleme yapma Tamamlandı
4. Chrome kaynak denemesi Tamamlandı
5. Başlat M105 sürümünde gönderim yapma amacı

Mozilla: Bu teklifi prototip oluşturmaya değer ve etkin bir şekilde uygulamaktadır.

WebKit: Yanıtı WebKit posta listesinde görebilirsiniz.

Sanitizer API'yi etkinleştirme

Tarayıcı Desteği

  • Chrome: Desteklenmez..
  • Edge: Desteklenmez..
  • Firefox: Bir bayrağın arkasında.
  • Safari: desteklenmez..

Kaynak

about://flags veya KSA seçeneği aracılığıyla etkinleştirme

Chrome

Chrome, Sanitizer API'yi uygulama sürecinde. Chrome 93 veya sonraki sürümlerde about://flags/#enable-experimental-web-platform-features işaretini etkinleştirerek bu davranışı deneyebilirsiniz. Chrome Canary ve Yeni geliştirilenler kanalının önceki sürümlerinde bu özelliği --enable-blink-features=SanitizerAPI aracılığıyla etkinleştirebilir ve hemen deneyebilirsiniz. Chrome'u işaretlerle çalıştırma talimatlarına göz atın.

Firefox

Firefox da deneysel bir özellik olarak Sanitizer API'yi uyguluyor. Etkinleştirmek için about:config içinde dom.security.sanitizer.enabled işaretini true olarak ayarlayın.

Özellik algılama

if (window.Sanitizer) {
  // Sanitizer API is enabled
}

Geri bildirim

Bu API'yi deneyip geri bildirimlerinizi bizimle paylaşırsanız seviniriz. Sanitizer API GitHub sorunları hakkındaki düşüncelerinizi paylaşın ve bu API'yle ilgilenen spesifikasyon yazarları ve diğer kişilerle konuşun.

Chrome'un uygulamasında hatalar veya beklenmedik davranışlar bulursanız bunu bildirmek için hata bildiriminde bulunun. Blink>SecurityFeature>SanitizerAPI bileşenlerini seçin ve uygulayıcıların sorunu izlemesine yardımcı olmak için ayrıntıları paylaşın.

Demo

Sanitizer API'nin nasıl çalıştığını görmek için Mike West'in Sanitizer API Playground'a göz atın:

Referanslar


Fotoğraf: Towfiqu barbhuiya tarafından Unsplash'te yapıldı.