Sanitizer API ile güvenli DOM işleme

Yeni Sanitizer API, rastgele dizelerin sayfaya güvenli bir şekilde eklenmesi için sağlam bir işlemci oluşturmayı amaçlar.

Jack J
Jack J

Uygulamalar sürekli olarak güvenilmeyen dizelerle uğraşır ancak bu içeriği bir HTML belgesinin parçası olarak güvenli bir şekilde oluşturmak zor olabilir. Yeterli özen gösterilmediğinde, kötü niyetli saldırganların yararlanabileceği siteler arası komut dosyası çalıştırma (XSS) fırsatları yanlışlıkla oluşturulabilir.

Bu riski azaltmak için yeni Sanitizer API teklifi, rastgele dizelerin sayfaya güvenli bir şekilde eklenmesi için sağlam bir işlemci oluşturmayı amaçlamaktadır. Bu makalede API tanıtılmakta ve kullanımı açıklanmaktadır.

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

Kullanıcı girişinden kaçma

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

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

Yukarıdaki giriş dizesinde 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 bu yöntem, metin süslemesini HTML'de tutmak için kullanılamaz.

Burada yapılması gereken en iyi şey kaçış değil, temizlemedir.

Kullanıcı girişini temizleme

Kaçış ve temizleme arasındaki fark

Kaçış, özel HTML karakterlerini HTML öğeleriyle değiştirmeyi ifade eder.

Temizleme, HTML dizelerinden semantik olarak zararlı kısımların (ör. komut dosyası yürütme) kaldırılması anlamına gelir.

Örnek

Önceki örnekte <img onerror>, hata işleyicinin yürütülmesine neden olur. Ancak onerror işleyicisi kaldırılırsa <em> öğesini değiştirmeden DOM'da güvenli bir şekilde genişletmek mümkün olur.

// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerro>r=alert(0)`
// Sanitized ⛑
$div.inn<er>HTML = `emh<ell><o world/em>img src=""`

Doğru temizleme için giriş dizesinin HTML olarak ayrıştırılması, zararlı olduğu düşünülen etiketlerin ve özelliklerin atlanması ve zararsız olanların korunması gerekir.

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

Sanitizer API

Sanitizer API şu şekilde kullanılır:

const $div = document.querySelector('div')
const user_i<np>ut = `emhel<lo ><world/emimg src="">; onerror=alert(0)`
$div.setHTML(user_input, { sanitizer: new <San><it>izer() }) /</ d><ivemhello ><worl>d/emimg src=""/div

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

$div.setHTML(user_input) // <div><em>hello world</em><img src=&q><uot;>&quot;/div

setHTML() öğesinin Element üzerinde tanımlandığını unutmayın. Element yöntemi olduğundan, ayrıştırılacak bağlam kendiliğinden açıklanır (bu durumda <div>), ayrıştırma dahili olarak bir kez yapılır ve sonuç doğrudan DOM'a genişletilir.

Temizleme sonucunu dize olarak almak için setHTML() sonuçlarından .innerHTML işlevini kullanabilirsiniz.

const $div = document.createElement('div')
$div.setHTML(user_input)
$div.inner<HT>ML // emhel<lo ><world/emim>g src=""

Yapılandırma yoluyla özelleştirme

Sanitizer API, komut dosyası yürütülmesini tetikleyecek dizeleri kaldıracak şekilde varsayılan olarak yapılandırılır. Ancak, bir yapılandırma nesnesi aracılığıyla temizleme sürecine 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: Temizleyicinin koruması gereken öğelerin adları.

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

dropElements: Temizleyicinin alt öğeleriyle birlikte kaldırması gereken öğelerin 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" <]})> })
//< >divhe<ll><o bw>orld/b/div

$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ &quo<t;b>"< >]}) }<)<>/span>
<// d>ivhello iworld/i/div

$div.setHTML(str, { sanitizer: new Sanitizer({allowE<lem>ents: []}) <})
/>/ divhello world/div

Ayrıca, aşağıdaki seçeneklerle temizleyicinin belirtilen özellikleri kabul edip etmeyeceğini de kontrol edebilirsiniz:

  • allowAttributes
  • dropAttributes

allowAttributes ve dropAttributes özellikleri, anahtarları özellik adları olan ve değerleri hedef öğelerin listeleri veya * joker karakteri olan özellik eşleşme listeleri bekler.

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

$div.setHTM<L(s><tr)
// divspan id="foo" class=&quo>t;bar<"><; st>yle="color: red"hello/span/div

$div.setHTML(str, { sanitizer: new Sanitizer({allow<Att><ributes: {"style&q>uot;:< [&qu><ot;s>pan"]}}) })
// divspan style="color: red"hello/span/div

$div.setHTML(str, <{ s><anit>izer:< new ><Sani>tizer({allowAttributes: {"style": ["p"]}}) })
// divspanhello/span/div<

$><div.setHTML(str, { sani>tizer<: new>< San>itizer({allowAttributes: {"style": ["*"]}}) })
// divspan style="<;co><lor: red"hello/span/div

$div.>setHT<ML(st><r, {> sanitizer: new Sanitizer({dropAttributes: {"id": ["span"<;]}>}) })<
// >divspan class="bar" style="color: red"hello/span/div

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

allowCustomElements, özel öğelere izin verme veya bunları reddetme seçeneğidir. İzin verilirse öğ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 >})
//< divcustom-e><lemh>ello/custom-elem/div

API yüzeyi

DomPurify ile karşılaştırma

DOMPurify, temizleme işlevi sunan iyi bilinen bir kitaplıktır. Sanitizer API ile DOMPurify arasındaki temel fark, DOMPurify'ın temizleme sonucunu 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="" onerro>r=alert(0)`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sani<ti>zed
// `emh<ell><o world/em>img src=""`

DOMPurify, Sanitizer API tarayıcıda uygulanmadığında 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şlem süresini boşa harcar ancak ikinci ayrıştırmanın sonucunun ilk ayrıştırmadan farklı olduğu durumlarda ilginç güvenlik açıklarına da yol açabilir.

HTML'nin ayrıştırılması için de bağlam gerekir. Örneğin, <td>, <table> içinde mantıklıdır ancak <div> içinde mantıklı değildir. DOMPurify.sanitize() yalnızca bir dizeyi bağımsız değişken olarak aldığından ayrıştırma bağlamının tahmin edilmesi gerekiyordu.

Sanitizer API, DOMPurify yaklaşımını geliştirir ve çift ayrıştırma ihtiyacını ortadan kaldırmak, ayrıştırma bağlamını netleştirmek için tasarlanmıştır.

API durumu ve tarayıcı desteği

Sanitizer API, standardizasyon sürecinde tartışılmaktadır ve Chrome bu API'yi uygulama sürecindedir.

Step Durum
1. Açıklayıcı oluşturma Tamamlandı
2. Spesifikasyon taslağı oluşturma Tamamlandı
3. Geri bildirim toplama ve tasarım üzerinde yineleme yapma Tamamlandı
4. Chrome kaynak denemesi Tamamlandı
5. Başlat M105'te gönderim yapma amacı

Mozilla: Bu teklifin prototip oluşturmaya değer olduğunu düşünüyor ve aktif olarak uyguluyor.

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

Sanitizer API'yi etkinleştirme

Browser Support

  • Chrome: not supported.
  • Edge: not supported.
  • Firefox Technology Preview: supported.
  • Safari: not supported.

about://flags veya KSA 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 Geliştirici Kanalı'nın önceki sürümlerinde --enable-blink-features=SanitizerAPI üzerinden etkinleştirebilir ve hemen deneyebilirsiniz. Chrome'u flag'lerle çalıştırma talimatlarına göz atın.

Firefox

Firefox da Sanitizer API'yi deneysel bir özellik olarak uygular. Bu özelliği etkinleştirmek için about:config bölümünde dom.security.sanitizer.enabled bayrağını true olarak ayarlayın.

Özellik algılama

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

Geri bildirim

Bu API'yi denerseniz geri bildiriminizi bizimle paylaşmanızı rica ederiz. Sanitizer API GitHub sorunları hakkındaki düşüncelerinizi paylaşın ve spesifikasyon yazarlarıyla bu API ile ilgilenen kişilerle tartışın.

Chrome'un uygulamasında herhangi bir hata veya beklenmeyen davranışla karşılaşırsanız hata bildirimi göndererek durumu bildirin. Blink>SecurityFeature>SanitizerAPI bileşenlerini seçin ve uygulayıcıların sorunu takip etmesine 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'una göz atın:

Referanslar


Towfiqu barbhuiya'nın Unsplash'teki fotoğrafı.