Nowy interfejs Sanitizer API ma na celu stworzenie niezawodnego procesora dowolnych ciągów znaków, które można bezpiecznie wstawiać na stronę.
Aplikacje często mają do czynienia z niezaufanymi ciągami znaków, ale bezpieczne renderowanie tych treści w ramach dokumentu HTML może być trudne. Jeśli nie zachowasz ostrożności, łatwo możesz przypadkowo stworzyć możliwości cross-site scripting (XSS), które mogą wykorzystać złośliwi hakerzy.
Aby zmniejszyć to ryzyko, nowa propozycja interfejsu Sanitizer API ma na celu stworzenie niezawodnego procesora dowolnych ciągów znaków, które można bezpiecznie wstawiać na stronę. Z tego artykułu dowiesz się, czym jest ten interfejs API i jak go używać.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerro>r=alert(0)`, new Sanitizer())
Unikanie danych wejściowych użytkownika
Podczas wstawiania do DOM danych wprowadzonych przez użytkownika, ciągów zapytania, zawartości plików cookie itp. ciągi znaków muszą być odpowiednio zmienione. Szczególną uwagę należy zwrócić na manipulowanie DOM za pomocą .innerHTML, gdzie niekodowane ciągi znaków są typowym źródłem ataków XSS.
const user_input = `<em>hello world</em><img src="" onerro>r=alert(0)`
$div.innerHTML = user_input
Jeśli w ciągu wejściowym powyżej zastosujesz znaki specjalne HTML lub rozwiniesz go za pomocą .textContent, funkcja alert(0) nie zostanie wykonana. Jednak ponieważ znak <em> dodany przez użytkownika jest również rozwijany jako ciąg znaków, tej metody nie można użyć, aby zachować dekorację tekstu w HTML.
Najlepszym rozwiązaniem w tym przypadku nie jest ucieczka, ale oczyszczenie.
Usuwanie informacji z danych wejściowych użytkownika
Różnica między unikaniem znaków specjalnych a oczyszczaniem danych
Ucieczka polega na zastąpieniu specjalnych znaków HTML encjami HTML.
Oczyszczanie polega na usuwaniu z ciągów HTML części, które mogą być szkodliwe semantycznie (np. wykonywanie skryptów).
Przykład
W poprzednim przykładzie <img onerror> powoduje wykonanie procedury obsługi błędów, ale jeśli procedura obsługi onerror zostanie usunięta, można bezpiecznie rozwinąć element w DOM, pozostawiając element <em> bez zmian.
// 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=""`
Aby prawidłowo oczyścić ciąg znaków, należy przeanalizować go jako kod HTML, pominąć tagi i atrybuty, które są uważane za szkodliwe, i zachować te, które są bezpieczne.
Proponowana specyfikacja interfejsu Sanitizer API ma na celu udostępnienie takiego przetwarzania jako standardowego interfejsu API dla przeglądarek.
Sanitizer API
Interfejs Sanitizer API jest używany w ten sposób:
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
Argumentem domyślnym jest jednak { sanitizer: new Sanitizer() }. Może to być np. tak jak poniżej.
$div.setHTML(user_input) // <div><em>hello world</em><img src=&q><uot;>"/div
Warto zauważyć, że setHTML() jest zdefiniowane w Element. Jako metoda Element kontekst do analizy jest oczywisty (w tym przypadku <div>), analiza jest wykonywana wewnętrznie tylko raz, a wynik jest bezpośrednio rozwijany w DOM.
Aby uzyskać wynik czyszczenia w postaci ciągu tekstowego, możesz użyć .innerHTML z wyników setHTML().
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.inner<HT>ML // emhel<lo ><world/emim>g src=""
Dostosowywanie za pomocą konfiguracji
Interfejs Sanitizer API jest domyślnie skonfigurowany tak, aby usuwać ciągi znaków, które mogłyby uruchomić skrypt. Możesz jednak dodać własne dostosowania do procesu czyszczenia za pomocą obiektu konfiguracji.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Poniższe opcje określają, jak wynik czyszczenia ma traktować określony element.
allowElements: nazwy elementów, które narzędzie do czyszczenia powinno zachować.
blockElements: nazwy elementów, które narzędzie do czyszczenia powinno usunąć, zachowując ich elementy podrzędne.
dropElements: nazwy elementów, które narzędzie do czyszczenia powinno usunąć wraz z elementami podrzędnymi.
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
Możesz też określić, czy narzędzie do czyszczenia ma zezwalać na określone atrybuty, czy je odrzucać, korzystając z tych opcji:
allowAttributesdropAttributes
Właściwości allowAttributes i dropAttributes oczekują list dopasowania atrybutów – obiektów, których klucze są nazwami atrybutów, a wartości są listami elementów docelowych lub symbolem wieloznacznym *.
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 to opcja zezwalająca na elementy niestandardowe lub zabraniająca ich stosowania. Jeśli są dozwolone, nadal obowiązują inne konfiguracje elementów i atrybutów.
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
Powierzchnia interfejsu API
Porównanie z DomPurify
DOMPurify to znana biblioteka, która oferuje funkcję oczyszczania. Główna różnica między Sanitizer API a DOMPurify polega na tym, że DOMPurify zwraca wynik oczyszczania jako ciąg znaków, który musisz zapisać w elemencie DOM za pomocą .innerHTML.
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 może służyć jako rozwiązanie rezerwowe, gdy interfejs Sanitizer API nie jest zaimplementowany w przeglądarce.
Implementacja DOMPurify ma kilka wad. Jeśli zwracany jest ciąg znaków, ciąg wejściowy jest analizowany dwukrotnie przez DOMPurify i .innerHTML. Podwójne parsowanie marnuje czas przetwarzania, ale może też prowadzić do ciekawych luk w zabezpieczeniach, gdy wynik drugiego parsowania różni się od pierwszego.
HTML wymaga też kontekstu do przetworzenia. Na przykład reguła <td> ma zastosowanie do witryny <table>, ale do witryny <div> już nie. Funkcja DOMPurify.sanitize() przyjmuje tylko ciąg znaków jako argument, więc kontekst analizowania musiał zostać odgadnięty.
Sanitizer API jest ulepszoną wersją DOMPurify. Został zaprojektowany tak, aby wyeliminować konieczność podwójnego parsowania i wyjaśnić kontekst parsowania.
Stan interfejsu API i obsługa przeglądarek
Interfejs Sanitizer API jest obecnie omawiany w ramach procesu standaryzacji, a Chrome jest w trakcie jego wdrażania.
| Krok | Stan |
|---|---|
| 1. Tworzenie wyjaśnienia | Zakończono |
| 2. Tworzenie wersji roboczej specyfikacji | Zakończono |
| 3. Zbieranie opinii i ulepszanie projektu | Zakończono |
| 4. Testowanie origin w Chrome | Zakończono |
| 5. Uruchom | Zamiar wysyłki w przypadku M105 |
Mozilla: uważa, że ten pomysł warto przetestować, i aktywnie go wdraża.
WebKit: odpowiedź znajdziesz na liście mailingowej WebKit.
Jak włączyć interfejs Sanitizer API
Browser Support
Włączanie za pomocą opcji about://flags lub interfejsu wiersza poleceń
Chrome
Chrome wdraża interfejs Sanitizer API. W Chrome w wersji 93 lub nowszej możesz wypróbować to zachowanie, włączając flagę about://flags/#enable-experimental-web-platform-features. W starszych wersjach Chrome Canary i na kanale deweloperskim możesz włączyć tę funkcję za pomocą ikony --enable-blink-features=SanitizerAPI i od razu ją wypróbować. Zapoznaj się z instrukcjami uruchamiania Chrome z flagami.
Firefox
Firefox również implementuje interfejs Sanitizer API jako funkcję eksperymentalną. Aby ją włączyć, ustaw flagę dom.security.sanitizer.enabled na true w about:config.
Wykrywanie cech
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Prześlij opinię
Jeśli wypróbujesz ten interfejs API i chcesz podzielić się z nami opinią, chętnie ją poznamy. Podziel się swoimi przemyśleniami na temat problemów z interfejsem Sanitizer API w GitHubie i porozmawiaj z autorami specyfikacji oraz osobami zainteresowanymi tym interfejsem API.
Jeśli w implementacji Chrome znajdziesz błędy lub nieoczekiwane zachowania, zgłoś je. Wybierz komponenty Blink>SecurityFeature>SanitizerAPI i udostępnij szczegóły, aby pomóc osobom wdrażającym śledzić problem.
Prezentacja
Aby zobaczyć działanie interfejsu Sanitizer API, odwiedź Sanitizer API Playground (w języku angielskim) stworzony przez Mike’a Westa:
Odniesienia
- Specyfikacja interfejsu HTML Sanitizer API
- Repozytorium WICG/sanitizer-api
- Najczęstsze pytania dotyczące interfejsu Sanitizer API
- Dokumentacja interfejsu HTML Sanitizer API w MDN
Zdjęcie: Towfiqu barbhuiya, Unsplash