Sanitizer API ile güvenli DOM işleme

Yeni Sanitizer API'nin amacı, rastgele dizelerin sayfaya güvenli bir şekilde eklenmesi için sağlam bir işlemci geliştirmektir.

Jack J
Jack J

Uygulamalar her zaman güvenilmeyen dizelerle ilgilenir, ancak bu içeriği bir HTML dokümanının parçası olarak güvenli bir şekilde oluşturmak zor olabilir. Yeterli dikkat olmazsa yanlışlıkla siteler arası komut dosyası (XSS) için kötü amaçlı saldırganların yararlanabileceği fırsatlar oluşturabilirsiniz.

Yeni Sanitizer API teklifi, bu riski azaltmak amacıyla rastgele dizelerin sayfaya güvenli bir şekilde eklenmesi 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())

Çıkış karakterli kullanıcı girişi

Kullanıcı girişi, sorgu dizeleri, çerez içerikleri ve benzeri öğeler DOM'ye eklenirken dizelerden doğru şekilde çıkış yapılmalıdır. .innerHTML aracılığıyla DOM işlemeye özellikle dikkat edilmelidir. Çıkış karaktersiz 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ış yapar veya dizeyi .textContent kullanarak genişletirseniz alert(0) yürütülmez. Ancak, kullanıcı tarafından eklenen <em> aynı zamanda dize olarak da genişletildiğinden, bu yöntem HTML'deki metin süslemesini tutmak için kullanılamaz.

Burada yapılacak en iyi şey çıkış yapmak değil, tizasyon işlemini yapmaktır.

Kullanıcı girişini temizleme

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

Çıkış karakteri ekleme, özel HTML karakterlerinin HTML Varlıkları ile değiştirilmesini ifade eder.

Temizleme işlemi, HTML dizelerinden anlamsal olarak zararlı parçaların (komut dosyası yürütme gibi) kaldırılmasını ifade eder.

Örnek

Önceki örnekte <img onerror>, hata işleyicinin yürütülmesine neden olur, ancak onerror işleyicisi kaldırıldıysa, <em> değişmeden bırakılarak 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="">`

Temizleme işlemini doğru bir şekilde yapmak için giriş dizesini HTML olarak ayrıştırmak, zararlı olarak kabul edilen etiket ve özellikleri atlamak ve zararsız olanları tutmak gerekir.

Önerilen Sanitizer API spesifikasyonu, bu tür işlemleri tarayıcılara standart bir API olarak sunmayı amaçlamaktadır.

Dezenfektan API'sı

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. Aşağıdaki gibi olabilir.

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

setHTML() ifadesinin Element üzerinde tanımlandığını unutmayın. Bir Element yöntemi olduğundan, ayrıştırma işlemi kendinden açıklamalı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.

Temizleme işleminin sonucunu dize olarak almak için setHTML() sonuçlarından .innerHTML öğesini 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ştirin

Sanitizer API, varsayılan olarak komut dosyasının 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 alması gerektiğini belirtir.

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

blockElements: Temizleyicinin, alt öğelerini tutarken kaldırması gereken öğelerin adları.

dropElements: Dezenfektanın kaldırması gereken öğelerin, alt öğeleriyle birlikte adları.

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>

Ayrıca aşağıdaki seçenekleri kullanarak temizleyicinin belirtilen özelliklere izin verip vermeyeceğini de belirleyebilirsiniz:

  • allowAttributes
  • dropAttributes

allowAttributes ve dropAttributes özellikleri için özellik eşleşme listeleri beklenir. Bu listelerde anahtarları özellik adı, değerleri, hedef öğe listeleri veya * joker karakteri olan nesneler bulunur.

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 veriliyorsa öğe ve özellikler için 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 bilinen bir kitaplıktır. Sanitizer API ile DOMPurify arasındaki temel fark, DOMPurify'ın temizleme işleminin sonucunu bir dize olarak döndürmesidir. Bunu .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, Sanitizer API'nin tarayıcıda uygulanmadığı durumlarda 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 çift ayrıştırma işlemi, işleme süresini boşa harcar, ancak ikinci ayrıştırma işleminin sonucunun ilkinden farklı olduğu durumlardan kaynaklanan ilginç güvenlik açıklarına da yol açabilir.

HTML'nin ayrıştırılması bağlam da gerektirir. Örneğin <td>, <table> dilinde anlamlı olurken <div> için geçerli değildir. DOMPurify.sanitize(), dizeleri bağımsız değişken olarak kabul ettiğinden, ayrıştırma bağlamının tahmin edilmesi gerekiyordu.

DOMPurify yaklaşımını geliştiren Sanitizer API, ç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

Standartlaştırma sürecindeki Sanitizer API ile ilgili tartışmalar devam ederken Chrome da bu API'yi uygulama sürecindedir.

Adım Durum
1. Açıklayıcı oluşturun Tamamlandı
2. Spesifikasyon taslağı oluştur Tamamlandı
3. Geri bildirim alma ve tasarımı yineleme Tamamlandı
4. Chrome kaynak denemesi Tamamlandı
5. Lansman M105'te Gönderme Hazırlığı

Mozilla: Bu teklifi prototip oluşturmaya değer ve aktif bir şekilde uyguluyor.

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

Sanitizer API'yi etkinleştirme

Tarayıcı Desteği

  • x
  • x
  • x

Kaynak

about://flags veya KSA seçeneği ile etkinleştirme

Chrome

Chrome, Sanitizer API'yi uygulama sürecindedir. 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 kanallarının önceki sürümlerinde --enable-blink-features=SanitizerAPI adresinden bu özelliği etkinleştirip hemen deneyebilirsiniz. Chrome'un flag'lerle çalıştırılmasıyla ilgili talimatlara göz atın.

Firefox

Firefox, Sanitizer API'sını deneysel bir özellik olarak da uygulamaktadır. Bu özelliği etkinleştirmek için about:config alanında 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 bildiriminiz varsa öğrenmeyi çok isteriz. Sanitizer API GitHub sorunları hakkındaki düşüncelerinizi paylaşıp bu API ile ilgilenen spesifikasyon yazarları ve kullanıcılarla tartışın.

Chrome uygulamasında hata veya beklenmeyen bir davranış bulursanız 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.

Demografi

Sanitizer API'yi çalışırken görmek için Mike West'in Sanitizer API Playground'a göz atın:

Referanslar


Fotoğraf, Towfiqu barbhuiya tarafından Unsplash'ta yayınlandı.