Nowy interfejs Sanitizer API ma na celu stworzenie niezawodnego procesora do bezpiecznego wstawiania dowolnych ciągów znaków na stronie.
Aplikacje cały czas korzystają z niezaufanych ciągów znaków, ale bezpieczne wyrenderowanie treści jako części dokumentu HTML może być wyzwaniem. Bez odpowiedniej staranności łatwo jest przypadkowo utworzyć skrypty między witrynami (XSS), które mogą zostać wykorzystane przez hakerów.
Aby zmniejszyć to ryzyko, nowa oferta pakietowa Sanitizer API ma na celu stworzenie niezawodnego procesora umożliwiającego bezpieczne wstawianie dowolnych ciągów znaków na stronie. W tym artykule przedstawiamy interfejs API i wyjaśniamy, jak z niego korzystać.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Zmiana znaczenia danych wejściowych użytkownika
Podczas wstawiania do modelu DOM danych wejściowych użytkownika, ciągów zapytań, zawartości plików cookie itd. trzeba odpowiednio zmieniać znaczenie ciągów znaków. Szczególną uwagę należy zwrócić na manipulację DOM za pomocą metody .innerHTML
, gdzie ciągi znaków bez zmiany znaczenia są typowym źródłem błędów XSS.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
Jeśli zmienisz znaczenie znaków specjalnych HTML w ciągu wejściowym powyżej lub rozszerzysz go za pomocą funkcji .textContent
, polecenie alert(0)
nie zostanie wykonane. Nie można jednak użyć tej metody, by zachować w kodzie HTML dekoracje tekstowe, ponieważ identyfikator <em>
dodany przez użytkownika jest również rozwinięty jako ciąg znaków.
Najlepiej nie uciekać, tylko dezynfekować.
Usuwam dane wejściowe użytkownika
Różnica między ucieczeniem a dezynfekcją
Zmiana znaczenia oznacza zastąpienie specjalnych znaków HTML encjami HTML.
Sanityzacja oznacza usuwanie szkodliwych semantycznie części (np. wykonania skryptu) z ciągów HTML.
Przykład
W poprzednim przykładzie reguła <img onerror>
powoduje wykonanie modułu obsługi błędów, ale jeśli moduł onerror
został usunięty, można go bezpiecznie rozwinąć w DOM, pozostawiając <em>
bez zmian.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
Aby prawidłowo przeprowadzić proces oczyszczania, trzeba przetworzyć ciąg znaków wejściowych na format HTML, pominąć tagi i atrybuty, które są uznawane za szkodliwe, i zachować te nieszkodliwe.
Proponowana specyfikacja interfejsu Sanitizer API ma zapewnić takie przetwarzanie jak standardowy interfejs API dla przeglądarek.
Interfejs API Sanitizer
Interfejs Sanitizer API jest używany w taki sposób:
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>
Domyślnym argumentem jest jednak { sanitizer: new Sanitizer() }
. Wygląda to tak jak poniżej.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Warto zauważyć, że definicja pola setHTML()
to Element
. Ponieważ jest to metoda Element
, kontekst do analizy nie wymaga wyjaśnienia (w tym przypadku <div>
). Analiza przeprowadza się raz wewnętrznie, a wynik jest bezpośrednio rozwijany do DOM.
Aby uzyskać wynik oczyszczania w postaci ciągu znaków, możesz użyć parametru .innerHTML
z wyników setHTML()
.
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
Dostosuj za pomocą konfiguracji
Interfejs Sanitizer API jest domyślnie skonfigurowany tak, aby usuwać ciągi znaków, które uruchamiałyby wykonanie skryptu. Możesz też dodać własne modyfikacje do procesu oczyszczania 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 dezynfekcji powinien traktować określony element.
allowElements
: nazwy elementów, które powinny zachować środki dezynfekcyjne.
blockElements
: nazwy elementów, które powinny zostać usunięte, ale zachowają dzieci.
dropElements
: nazwy elementów, które mają zostać usunięte, wraz z nazwami elementów podrzędnych.
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>
Za pomocą tych opcji możesz też określić, czy sanitizer będzie zezwalać na określone atrybuty lub je odrzucać:
allowAttributes
dropAttributes
Właściwości allowAttributes
i dropAttributes
wymagają list dopasowania atrybutów – obiektów, których klucze są nazwami atrybutów, a wartości to listy elementów docelowych lub symbolu wieloznacznego *
.
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
to opcja zezwalania na elementy niestandardowe lub ich odrzucania. 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 })
// <div><custom-elem>hello</custom-elem></div>
Interfejs API
Porównanie z DomPurify
DOMPurify to dobrze znana biblioteka z funkcją dezynfekcji. Główną różnicą między interfejsami Sanitizer API a DOMPurify jest to, że DOMPurify zwraca wynik oczyszczania w postaci ciągu znaków, który trzeba zapisać w elemencie DOM za pomocą funkcji .innerHTML
.
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="">`
Funkcja DOMPurify może działać jako wartość zastępczą, jeśli interfejs Sanitizer API nie jest zaimplementowany w przeglądarce.
Implementacja DOMPurify ma kilka wad. Jeśli zostanie zwrócony ciąg znaków, będzie on analizowany dwukrotnie: przez DOMPurify i .innerHTML
. Taka podwójna analiza marnuje czas przetwarzania, ale może też prowadzić do powstawania interesujących luk w zabezpieczeniach, które wynikają z sytuacji, gdy wynik drugiej analizy różni się od pierwszego.
Do analizy kodu HTML potrzebny jest też kontekst. Na przykład <td>
ma sens w <table>
, ale nie w <div>
. Ponieważ DOMPurify.sanitize()
przyjmuje tylko ciąg znaków jako argument, trzeba było odgadnąć kontekst analizy.
Interfejs Sanitizer API stanowi ulepszenie metody DOMPurify i ma na celu wyeliminowanie konieczności podwójnego analizowania i doprecyzowania kontekstu analizy.
Stan interfejsu API i obsługa przeglądarek
Interfejs Sanitizer API jest w trakcie procesu standaryzacji, a Chrome jest w trakcie jego wdrażania.
Krok | Stan |
---|---|
1. Utwórz wyjaśnienie | Zakończono |
2. Utwórz wersję roboczą specyfikacji | Zakończono |
3. Zbieraj opinie i ulepszaj projekt | Zakończono |
4. Testowanie origin Chrome | Zakończono |
5. Uruchom | Zamiar wysyłki M105 |
Mozilla: Uważa, że oferta warto utworzyć prototyp i aktywnie ją wdraża.
WebKit: zobacz odpowiedź na liście adresowej WebKit.
Jak włączyć interfejs Sanitizer API
Włączanie za pomocą about://flags
lub opcji interfejsu wiersza poleceń
Chrome
Chrome jest w trakcie wdrażania interfejsu Sanitizer API. W Chrome 93 i nowszych możesz wypróbować tę funkcję, włączając flagę about://flags/#enable-experimental-web-platform-features
. We wcześniejszych wersjach Chrome Canary i deweloperskich możesz ją włączyć za pomocą --enable-blink-features=SanitizerAPI
i od razu wypróbować. Zapoznaj się z instrukcjami uruchamiania Chrome z flagami.
Firefox
W przeglądarce Firefox jako funkcję eksperymentalną zastosowano także interfejs Sanitizer API. Aby go 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ę opinią, chętnie je poznamy. Podziel się swoimi przemyśleniami na temat problemów z GitHub API dotyczącym interfejsu Sanitizer i omów je z autorami specyfikacji oraz innymi osobami zainteresowanymi tym interfejsem API.
Jeśli znajdziesz błędy lub nieoczekiwane zachowanie implementacji Chrome, zgłoś błąd, aby go zgłosić. Wybierz komponenty Blink>SecurityFeature>SanitizerAPI
i udostępnij szczegóły, aby pomóc we wdrażaniu i śledzeniu problemu.
Prezentacja
Aby zobaczyć, jak działa Sanitizer API, zajrzyj do Sanitizer API Playground autorstwa Mike'a Westa:
Pliki referencyjne
- Specyfikacja interfejsu HTML Sanitizer API
- Repozytorium WICG/sanitizer-api
- Najczęstsze pytania dotyczące interfejsu Santizer API
- Dokumentacja interfejsu HTML Sanitizer API w MDN
Zdjęcie: Towfiqu barbhuiya, Unsplash.