Sanitizer API ile güvenli DOM işleme

Yeni Sanitizer API, rastgele dizelerin bir sayfaya güvenli bir şekilde eklenmesi için sağlam 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 gösterilmezse kötü amaçlı saldırganların istismar edebileceği siteler arası komut dosyası çalıştırma (XSS) fırsatları kolayca oluşturulabilir.

Bu riski azaltmak için yeni Sanitizer API önerisi, rastgele dizelerin bir 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="" 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

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. 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.

Sanitasyonun sonucunu dize olarak almak için setHTML() sonuçlarından .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ütmeyi 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: Sanitasyon aracının, 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" ]}) })
// <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çeneklerle temizleyicinin belirtilen özelliklere izin verip vermeyeceğini de kontrol edebilirsiniz:

  • allowAttributes
  • dropAttributes

allowAttributes ve dropAttributes mülkleri, özellik eşleme listeleri (anahtarları özellik adları, değerleri ise hedef öğelerin listeleri veya * joker karakteri olan nesneler) bekler.

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, temizleme işlevi sunan iyi bilinen 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="">`

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 harcamakla birlikte, ikinci ayrıştırmanın sonucunun ilkinden farklı olduğu durumlarda ilginç güvenlik açıklarına da neden olabilir.

HTML'nin ayrıştırılması için bağlam da gerekir. Örneğin, <td> <table> için geçerlidir ancak <div> için geçerli 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ını iyileştirir ve ç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, 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şturma 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 öneriyi prototiplemeye değer buluyor ve aktif olarak uyguluyor.

WebKit: Yanıtı WebKit posta listesinde bulabilirsiniz.

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 bölümündeki 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 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 hata veya beklenmedik bir davranışla karşılaşırsanız hatayı bildirmek için hata kaydı oluşturun. 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