Nowy interfejs Sanitizer API ma na celu stworzenie niezawodnego procesora, który umożliwia bezpieczne wstawianie dowolnych ciągów znaków na stronie.
Aplikacje stale mają do czynienia z niezaufanymi ciągami znaków, ale bezpieczne renderowanie tych treści w ramach dokumentu HTML może być trudne. Bez odpowiedniej staranności łatwo jest przypadkowo utworzyć skrypty między witrynami (XSS), które mogą zostać wykorzystane przez hakerów.
Aby zminimalizować to ryzyko, nowa propozycja Sanitizer API ma na celu stworzenie niezawodnego procesora, który umożliwia bezpieczne wstawianie dowolnych ciągów znaków na stronie. Z tego artykułu dowiesz się, czym jest interfejs API i jak z niego korzystać.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Umieszczanie znaku ucieczki w danych wejściowych użytkownika
Wstawiając dane użytkownika, zapytania, zawartość plików cookie itp. do DOM, musisz odpowiednio oznaczać znaczenia tych ciągów. Szczególną uwagę należy zwrócić na manipulowanie DOM za pomocą .innerHTML
, gdzie nieotagowane ciągi znaków są typowym źródłem 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. Jednak ponieważ <em>
dodane przez użytkownika jest również rozszerzane jako ciąg znaków, tej metody nie można użyć do zachowania dekoracji tekstu w kodzie HTML.
Najlepiej nie uciekać się do ucieczki, ale do sanityzowania.
Usuwam dane wejściowe użytkownika
Różnica między ucieczeniem a dezynfekcją
Uciekające znaki to znaki specjalne HTML zastępowane przez entyte HTML.
Sanitizing oznacza usuwanie z ciągów HTML elementów, które mogą być szkodliwe semantycznie (np. uruchamianie skryptu).
Przykład
W poprzednim przykładzie element <img onerror>
powoduje wykonanie modułu obsługi błędów, ale jeśli moduł obsługi onerror
zostanie usunięty, można go bezpiecznie rozwinąć w DOM, nie zmieniając przy tym elementu <em>
.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
Aby prawidłowo odizolować dane, należy przeanalizować podany ciąg znaków jako kod HTML, pomijając tagi i atrybuty uważane za szkodliwe, a zachowując te, które są nieszkodliwe.
Proponowana specyfikacja interfejsu Sanitizer API ma zapewnić takie przetwarzanie jak standardowy interfejs API dla przeglądarek.
Sanitizer API
Interfejs Sanitizer API jest używany w ten 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 setHTML()
jest zdefiniowana w Element
. Ponieważ jest to metoda Element
, kontekst do przeanalizowania jest oczywisty (w tym przypadku jest to <div>
), analiza jest wykonywana raz wewnętrznie, a jej wynik jest bezpośrednio rozszerzany w DOM.
Aby uzyskać wynik odkażania jako ciąg znaków, możesz użyć funkcji .innerHTML
z wyników funkcji setHTML()
.
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
Dostosowywanie za pomocą konfiguracji
Interfejs Sanitizer API jest domyślnie skonfigurowany tak, aby usuwać ciągi znaków, które mogłyby spowodować wykonanie skryptu. Możesz jednak dodać własne ustawienia do procesu sterylizacji za pomocą obiektu konfiguracji.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Opcje podane poniżej określają, jak wynik skanowania ma traktować określony element.
allowElements
: nazwy elementów, które powinny zachować środki dezynfekcyjne.
blockElements
: nazwy elementów, które dezynfektor powinien usunąć, zachowując ich elementy podrzędne.
dropElements
: nazwy elementów, które ma usunąć oczyszczacz, wraz z ich 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" ]}) })
// <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>
Możesz też określić, czy oczyszczanie ma zezwalać na określone atrybuty lub je blokować, korzystając z tych opcji:
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 zezwalająca na elementy niestandardowe lub ją blokująca. 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 znana biblioteka, która oferuje funkcję sterylizacji. Główna różnica między interfejsem Sanitizer API a DOMPurify polega na tym, że DOMPurify zwraca wynik skanowania jako ciąg znaków, który musisz 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="">`
DOMPurify może służyć jako rozwiązanie zastępcze, gdy interfejs Sanitizer API nie jest zaimplementowany w przeglądarce.
Implementacja DOMPurify ma kilka wad. Jeśli zwrócony zostanie ciąg znaków, ciąg wejściowy jest 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 reguła <td>
ma sens w przypadku <table>
, ale nie <div>
. Funkcja DOMPurify.sanitize()
przyjmuje jako argument tylko ciąg znaków, więc kontekst analizy musiał być zgadywany.
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. Tworzenie wyjaśnienia | Zakończono |
2. Tworzenie wersji roboczej specyfikacji | Zakończono |
3. Zbieraj opinie i ulepszaj projekt | Zakończono |
4. Testowanie origin w Chrome | Zakończono |
5. Uruchom | Intencja wysyłki w M105 |
Mozilla: uważa, że to rozwiązanie warto prototypować, i aktywnie je wdraża.
WebKit: odpowiedź znajdziesz na liście mailingowej WebKit.
Jak włączyć interfejs Sanitizer API
Włączanie za pomocą opcji about://flags
lub interfejsu wiersza poleceń
Chrome
W Chrome trwa implementacja 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
. W wcześniejszych wersjach Chrome Canary i na kanale deweloperskim możesz go włączyć za pomocą --enable-blink-features=SanitizerAPI
i od razu go 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 sekcji about:config
.
Wykrywanie cech
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Prześlij opinię
Jeśli wypróbujesz ten interfejs API i masz opinię na jego temat, chętnie ją poznamy. Podziel się swoimi opiniami na temat problemów z interfejsem Sanitizer API na GitHubie i porozmawiaj z autorami specyfikacji oraz osobami zainteresowanymi tym interfejsem API.
Jeśli znajdziesz błędy lub nieoczekiwane działanie w implementacji Chrome, zgłoś je. Wybierz komponenty Blink>SecurityFeature>SanitizerAPI
i udostępnij szczegóły, aby ułatwić śledzenie problemu.
Prezentacja
Aby zobaczyć interfejs Sanitizer API w działaniu, skorzystaj z Sandbox Sanitizer API przygotowanego przez Mike’a Westa:
Pliki referencyjne
- Specyfikacja interfejsu API oczyszczania kodu HTML
- Repozytorium WICG/sanitizer-api
- Najczęstsze pytania dotyczące interfejsu Sanitizer API
- Dokumentacja referencyjna interfejsu HTML Sanitizer API na stronie MDN
Zdjęcie autorstwa Towfiqu Barbhuiya z Unsplash.