Nowy interfejs Sanitizer API ma na celu stworzenie solidnego procesora, który umożliwia bezpieczne wstawianie dowolnych ciągów znaków na stronie.
Aplikacje przez cały czas obsługują niezaufane ciągi, ale bezpieczne renderowanie ich jako części dokumentu HTML może być trudne. Bez odpowiedniej staranności łatwo jest przypadkowo utworzyć możliwości wykorzystania przez cyberprzestępców ataków typu cross-site scripting (XSS).
Aby ograniczyć to ryzyko, nowa oferta interfejsu Sanitizer API ma na celu zbudowanie niezawodnego procesora, który umożliwia bezpieczne wstawianie dowolnych ciągów znaków na stronie. W tym artykule opisujemy 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 danych wejściowych użytkownika, ciągów zapytań, zawartości plików cookie itd. do obiektu DOM ciągi znaków muszą mieć odpowiednie znaczenie zmiany znaczenia. Szczególną uwagę należy zwrócić na manipulację DOM za pomocą .innerHTML
, gdzie ciągi znaków bez zmiany znaczenia są typowym źródłem XSS.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
Jeśli w powyższym ciągu wejściowym zmienisz znaczenie znaków specjalnych HTML lub rozwiniesz go za pomocą funkcji .textContent
, polecenie alert(0)
nie zostanie wykonane. Ponieważ jednak atrybut <em>
dodany przez użytkownika jest też rozwijany jako ciąg znaków, nie można użyć tej metody, by zachować dekorację tekstu w kodzie HTML.
Najlepszą rzeczą, jaką należy zrobić, jest oczyszczenie, a nie ucieczkę.
Dane wejściowe użytkownika dotyczące dezynfekcji
Różnica między ucieczką a oczyszczaniem
Zmiana znaczenia oznacza zastępowanie znaków specjalnych HTML encjami HTML.
Sanityzacja polega na usuwaniu z ciągów HTML elementów szkodliwych dla semanty (np. wykonywania skryptów).
Przykład
W poprzednim przykładzie funkcja <img onerror>
powoduje wykonanie modułu obsługi błędów, ale jeśli moduł onerror
został usunięty, można bezpiecznie rozwinąć go 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 zadbać o prawidłową higienę, należy przeanalizować ciąg wejściowy jako kod HTML, pominąć tagi i atrybuty, które są uznawane za szkodliwe, i pozostawić te, które są nieszkodliwe.
Proponowana specyfikacja interfejsu Sanitizer API ma zapewnić takie przetwarzanie w standardowym interfejsie API dla przeglądarek.
Interfejs API Sanitizer
Interfejs Sanitizer API jest używany w następujący 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>
Jednak domyślnym argumentem jest { sanitizer: new Sanitizer() }
. Może to więc wyglądać tak jak poniżej.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Warto zauważyć, że właściwość setHTML()
jest definiowana w elemencie Element
. Ponieważ jest to metoda Element
, kontekst do przeanalizowania nie jest jasny (w tym przypadku <div>
), analiza jest przeprowadzana raz wewnętrznie, a wynik jest bezpośrednio rozszerzany do modelu DOM.
Aby uzyskać wynik oczyszczania w postaci ciągu znaków, możesz użyć funkcji .innerHTML
z wyników wyszukiwania z usługi 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 w taki sposób, aby usuwać ciągi tekstowe, które aktywują 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 oczyszczania powinien traktować określony element.
allowElements
: nazwy elementów, które środek do dezynfekcji powinien zachować.
blockElements
: nazwy elementów, które sanitizer musi usunąć przy zachowaniu dzieci.
dropElements
: nazwy elementów, które środek dezynfekcyjny powinien usunąć wraz z dziećmi.
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 czy je odrzucać:
allowAttributes
dropAttributes
Właściwości allowAttributes
i dropAttributes
oczekują list dopasowania atrybutów, czyli obiektów, których klucze to nazwy atrybutów, a wartości to listy elementów docelowych lub symbol wieloznaczny *
.
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 oferująca funkcję oczyszczania. Główna różnica między interfejsem Sanitizer API a DOMPurify polega na tym, że DOMPurify zwraca wynik procesu oczyszczania w postaci ciągu 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="">`
Funkcja DOMPurify może zostać użyta jako kreacja zastępcza, jeśli w przeglądarce nie zaimplementowano interfejsu Sanitizer API.
Implementacja DOMPurify ma kilka wad. Jeśli ciąg zostanie zwrócony, to ciąg wejściowy jest analizowany dwukrotnie przez DOMPurify i .innerHTML
. Taka podwójna analiza skraca czas potrzebny na przetwarzanie danych, ale może też prowadzić do powstawania interesujących luk w zabezpieczeniach spowodowanych przypadkami, gdy wynik drugiej analizy różni się od pierwszego.
Analizowanie kodu HTML wymaga też podania kontekstu. Na przykład <td>
ma sens w języku <table>
, ale nie w <div>
. Funkcja DOMPurify.sanitize()
przyjmuje tylko ciąg znaków jako argument, więc kontekst analizy musi być odgadnięty.
Interfejs Sanitizer API stanowi ulepszenie w stosunku do metody DOMPurify, ponieważ został zaprojektowany tak, aby wyeliminować konieczność podwójnego analizowania i uściślić kontekst analizy.
Stan interfejsu API i obsługa przeglądarek
Interfejs Sanitizer API jest obecnie omawiany w procesie standaryzacji, a obecnie jest on wdrażany w Chrome.
Step | Stan |
---|---|
1. Utwórz wyjaśnienie | Zakończono |
2. Utwórz wersję roboczą specyfikacji | Zakończono |
3. Zbieranie opinii i ulepszanie projektu | Zakończono |
4. Testowanie origin Chrome | Zakończono |
5. Wprowadzenie na rynek | Zamiar wysyłki w wersji M105 |
Mozilla: uważa tę propozycję za warto utworzyć prototyp i aktywnie ją wdraża.
WebKit: odpowiedź znajdziesz na liście adresowej WebKit.
Jak włączyć interfejs Sanitizer API
Włączanie za pomocą opcji about://flags
lub interfejsu wiersza poleceń
Chrome
Jesteśmy w trakcie wdrażania interfejsu Sanitizer API w Chrome. W Chrome 93 i nowszych wersjach możesz wypróbować tę funkcję, włączając flagę about://flags/#enable-experimental-web-platform-features
. We wcześniejszych wersjach Chrome Canary i deweloperskiej możesz ją włączyć w aplikacji --enable-blink-features=SanitizerAPI
i wypróbować ją już teraz. Zapoznaj się z instrukcjami uruchamiania Chrome z flagami.
Firefox
W przeglądarce Firefox funkcja Sanitizer API jest również funkcją eksperymentalną. Aby je włączyć, ustaw flagę dom.security.sanitizer.enabled
na true
w interfejsie about:config
.
Wykrywanie funkcji
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Prześlij opinię
Jeśli wypróbujesz ten interfejs API i chcesz przekazać nam swoją opinię, chętnie poznamy Twoją opinię. Podziel się swoimi przemyśleniami na temat problemów z Sanitizer API na GitHubie i omów je z autorami specyfikacji oraz osobami zainteresowanymi tym interfejsem API.
Jeśli zauważysz błędy lub nieoczekiwane działanie w implementacji Chrome, zgłoś błąd. Wybierz komponenty Blink>SecurityFeature>SanitizerAPI
i udostępnij szczegóły, aby ułatwić implementatorom śledzenie problemu.
Pokaz
Aby zobaczyć, jak działa interfejs Sanitizer API, zajrzyj do narzędzia Sanitizer API Playground opracowanego przez Mike'a Westa:
Źródła
- Specyfikacja interfejsu HTML Sanitizer API
- Repozytorium WICG/sanitizer-api
- Najczęstsze pytania dotyczące interfejsu Sanitizer API
- Dokumentacja interfejsu HTML Sanitizer API w MDN
Autor zdjęcia: Towfiqu barbhuiya, Unsplash.