Sanitizer API ile güvenli DOM işleme

Yeni Sanitizer API, rastgele dizelerin bir sayfaya güvenli bir şekilde eklenmesi için güçlü bir işlemci oluşturmayı amaçlamaktadır.

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 kötü amaçla kullanabileceğ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şinden kaçınma

Kullanıcı girişi, sorgu dizeleri, çerez içerikleri vb. DOM'a eklenirken dizeler düzgün bir şekilde kod dışına alınmalıdır. Kaçış karakteri eklenmemiş dizelerin tipik bir XSS kaynağı olduğu .innerHTML aracılığıyla DOM'de değişiklik yapılmasına özellikle dikkat edilmelidir.

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

Yukarıdaki giriş dizesindeki HTML özel karakterlerini kod dışında bırakırsanız veya .textContent kullanarak genişletirseniz alert(0) yürütülmez. Ancak kullanıcı tarafından eklenen <em> de olduğu gibi bir dize olarak genişletildiğinden, metin süslemesini HTML'de tutmak için bu yöntem kullanılamaz.

Burada yapılacak en iyi şey kaçış karakteri eklemek değil, temizlik yapmaktır.

Kullanıcı girişini temizleme

Kaçış ve temizleme arasındaki fark

Koddan kaçınma, özel HTML karakterlerinin HTML varlıkları ile değiştirilmesini ifade eder.

Sanitasyon, semantik olarak zararlı bölümlerin (ör. komut dosyası yürütme) HTML dizelerinden kaldırılmasını ifade eder.

Örnek

Önceki örnekte <img onerror>, hata işleyicinin yürütülmesine neden olur ancak onerror işleyici kaldırılırsa <em>'yi olduğu gibi bırakarak 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 temizlemek için giriş dizesini HTML olarak ayrıştırmak, zararlı olarak kabul edilen etiketleri ve özellikleri çıkarmak ve zararsız olanları korumak gerekir.

Önerilen Sanitizer API spesifikasyonu, bu tür işlemleri tarayıcılar için standart bir API olarak sağlamayı amaçlamaktadır.

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 varsayılan bağımsız değişken { sanitizer: new Sanitizer() }'tür. Bu, aşağıdaki gibi olabilir.

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

setHTML()'nin Element üzerinde tanımlandığını belirtmek isteriz. Element yöntemi olduğundan, ayrıştırılacak bağlam açıktır (bu durumda <div>). Ayrıştırma işlemi dahili olarak bir kez yapılır ve sonuç doğrudan DOM'da 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ırmayı kullanarak özelleştirme

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 işlemesi gerektiğini belirtir.

allowElements: Sanitatör'ün koruması gereken öğe 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 öğeleri reddetme seçeneğidir. İzin verilirse öğeler 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 meşhur bir kitaplıktır. Sanitizer API ile DOMPurify arasındaki temel fark, DOMPurify'in temizleme sonucunu bir dize olarak döndürmesidir. Bu dizeyi .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="">`

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

DOMPurify uygulamasının birkaç dezavantajı 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şleme süresini boşa harcar, ancak 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 ayrıştırılması için bağlam da gerekir. Örneğin, <td>, <table> dilinde anlam ifade ederken <div> dilinde anlamlı değildir. DOMPurify.sanitize() bağımsız değişkeni yalnızca bir dize aldığından, ayrıştırma bağlamının tahmin edilmesi gerekiyordu.

Sanitizer API, DOMPurify yaklaşımından daha iyi bir süreçti. Aynı zamanda iki kez 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, standartlaştırma sürecinde tartışılmakta ve Chrome'da kullanıma sunulma sürecindedir.

Step Durum
1. Açıklayıcı oluşturma Tamamlandı
2. Spesifikasyon taslağı oluştur Tamamlandı
3. Geri bildirim toplayın ve tasarımda iterasyon yapın Tamamlandı
4. Chrome kaynak denemesi Tamamlandı
5. Başlat M105'te Gönderim Niyeti

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

about://flags veya CLI seçeneğiyle 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 Dev kanalının önceki sürümlerinde --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, Sanitizer API'yi deneysel bir özellik olarak da uygular. 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 denedikten sonra geri bildiriminiz olursa bizimle paylaşın. Sanitizer API GitHub sorunları hakkındaki düşüncelerinizi paylaşın ve spesifikasyon yazarlarıyla ve bu API ile ilgilenen kişilerle görüşün.

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

Demo

Sanitizer API'nin kullanımını görmek için Mike West tarafından hazırlanan Sanitizer API Playground'a göz atın:

Referanslar


Fotoğraf: Unsplash'taki Towfiqu barbhuiya