Nette Documentation Preview

syntax
Latte jest synonimem bezpieczeństwa
***********************************

<div class=perex>

Latte to jedyny system szablonów PHP z efektywną ochroną przed krytyczną luką Cross-site Scripting (XSS). Dzieje się tak dzięki tzw. ucieczce kontekstowej (context-sensitive escaping). Porozmawiajmy,

- czym jest luka XSS i dlaczego jest tak niebezpieczna
- co sprawia, że Latte tak skutecznie broni się przed XSS
- jak Twig, Blade, itp. mogą łatwo stworzyć dziurę bezpieczeństwa w szablonach

</div>


Cross-site Scripting (XSS) .[#toc-cross-site-scripting-xss]
===========================================================

Cross-site Scripting (w skrócie XSS) to jedna z najczęstszych luk w stronach internetowych, a przy tym bardzo niebezpieczna. Pozwala on atakującemu na umieszczenie złośliwego skryptu (zwanego malware) w obcej witrynie, który wykonuje się w przeglądarce niczego nie podejrzewającego użytkownika.

Co może zrobić taki skrypt? Może na przykład wysłać arbitralną treść ze skompromitowanej strony do atakującego, w tym wrażliwe dane wyświetlane po zalogowaniu. Może modyfikować stronę lub wykonywać inne żądania w imieniu użytkownika.
Przykładowo, gdyby był to webmail, mógłby odczytać wrażliwe wiadomości, zmodyfikować wyświetlaną treść lub zmienić konfigurację, np. włączyć przekazywanie kopii wszystkich wiadomości na adres atakującego, aby uzyskać dostęp do przyszłych e-maili.

To również dlatego XSS znajduje się na szczycie listy najbardziej niebezpiecznych podatności. W przypadku znalezienia luki na stronie internetowej, należy ją jak najszybciej usunąć, aby zapobiec jej wykorzystaniu.


Jak powstaje ta podatność? .[#toc-how-does-the-vulnerability-arise]
-------------------------------------------------------------------

Luka występuje w miejscu, w którym generowana jest strona internetowa i wyprowadzane są zmienne. Wyobraź sobie, że tworzysz stronę wyszukiwania, a na początku będzie akapit z wyszukiwanym hasłem w postaci:

```php
echo '<p>Výsledky vyhledávání pro <em>' . $search . '</em></p>';
```

Atakujący może wpisać dowolny ciąg znaków, w tym kod HTML, do pola wyszukiwania, a tym samym do zmiennej `$search` jako `<script>alert("Hacked!")</script>`. Ponieważ wyjście nie jest traktowane w żaden sposób, staje się częścią wyświetlanej strony:

```html
<p>Výsledky vyhledávání pro <em><script>alert("Hacked!")</script></em></p>
```

Zamiast wypisać ciąg wyszukiwania, przeglądarka wykonuje JavaScript. I w ten sposób napastnik przejmuje stronę.

Można argumentować, że umieszczenie kodu w zmiennej rzeczywiście wykona JavaScript, ale tylko w przeglądarce atakującego. Jak to się dzieje, że trafia do ofiary? Z tej perspektywy możemy wyróżnić kilka rodzajów XSS. W naszym przykładzie wyszukiwania mówimy o *refleksyjnym XSS*.
Tutaj musimy jeszcze naprowadzić ofiarę na kliknięcie w link, który w parametrze będzie zawierał złośliwy kod:

```
https://example.com/?search=<script>alert("Hacked!")</script>
```

Naprowadzenie użytkownika na link wymaga nieco inżynierii społecznej, ale nie jest to trudne. Użytkownicy klikają w linki, czy to w mailach, czy w mediach społecznościowych, bez większego zastanowienia. A to, że w adresie jest coś podejrzanego można zamaskować za pomocą skracacza URL, użytkownik widzi wtedy tylko `bit.ly/xxx`.

Istnieje jednak druga, znacznie groźniejsza forma ataku znana jako *stored XSS* lub *persistent XSS*, w której atakującemu udaje się przechowywać złośliwy kod na serwerze, tak aby był on automatycznie wstawiany na określone strony.

Przykładem tego są strony, na których użytkownicy zamieszczają komentarze. Napastnik wysyła post zawierający kod i jest on przechowywany na serwerze. Jeśli strona nie jest wystarczająco bezpieczna, będzie ona wtedy uruchamiana w przeglądarce każdego odwiedzającego.

Wydaje się, że istotą ataku jest wprowadzenie do strony ciągu znaków `<script>`. W rzeczywistości "istnieje wiele sposobów na osadzenie JavaScript":https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html.
Weźmy przykład osadzania za pomocą atrybutu HTML. Załóżmy galerię zdjęć, w której do zdjęć można wstawić podpis, który jest drukowany w atrybucie `alt`:

```php
echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">';
```

Atakujący musi tylko wstawić sprytnie skonstruowany ciąg `" onload="alert('Hacked!')` jako napis, a jeśli wyjście nie zostanie obsłużone, wynikowy kod będzie wyglądał tak:

```html
<img src="photo0145.webp" alt="" onload="alert('Hacked!')">
```

Spofingowany atrybut `onload` staje się teraz częścią strony. Przeglądarka wykonuje zawarty w nim kod, gdy tylko obrazek zostanie pobrany. Zhakowany!


Jak bronić się przed XSS? .[#toc-how-to-defend-against-xss]
-----------------------------------------------------------

Wszelkie próby wykrycia ataku przy użyciu czarnej listy, np. ciągu blokad `<script>` itp. są niewystarczające. Podstawą skutecznej obrony jest **konsekwentna sanityzacja wszystkich danych wyrzucanych wewnątrz strony**.

Przede wszystkim polega na zastąpieniu wszystkich znaków o specjalnym znaczeniu innymi odpowiadającymi im sekwencjami, co w slangu oznacza **escaping** (pierwszy znak sekwencji nazywany jest znakiem escape, stąd nazwa).
Na przykład w tekście HTML znakiem o znaczeniu specjalnym jest `<`, který když nemá být interpretován jako začátek tagu, musíme jej nahradit vizuálně odpovídající sekvencí, tzv. HTML entitou `&lt;`.
A przeglądarka drukuje mniejszość.

**Bardzo ważne jest rozróżnienie kontekstu, w jakim dane są wyprowadzane**. Ponieważ różne konteksty sanitują ciągi inaczej. W różnych kontekstach różne znaki mają specjalne znaczenie.
Na przykład ucieczka różni się w tekście HTML, w atrybutach HTML, wewnątrz niektórych elementów specjalnych itp. Za chwilę omówimy to szczegółowo.

Najlepiej jest wykonać escaping bezpośrednio podczas wypisywania ciągu na stronie, zapewniając, że jest on rzeczywiście wykonywany i wykonywany tylko raz. Najlepiej, jeśli obróbka jest obsługiwana **automatycznie** bezpośrednio przez system szablonów.
Ponieważ jeśli leczenie nie jest wykonywane automatycznie, programista może o nim zapomnieć. A jedno przeoczenie oznacza, że strona jest narażona na niebezpieczeństwo.

Jednak XSS nie wpływa tylko na wyprowadzanie danych w szablonach, ale także na inne części aplikacji, które muszą prawidłowo obsługiwać niezaufane dane. Na przykład JavaScript w twojej aplikacji nie może używać `innerHTML`, ale tylko `innerText` lub `textContent` w połączeniu z nimi.
Należy zachować szczególną ostrożność w przypadku funkcji oceniających ciągi znaków, takich jak JavaScript, który jest `eval()`, ale także `setTimeout()`, lub używając funkcji `setAttribute()` z atrybutami zdarzeń, takimi jak `onload`, itp. Ale to wykracza poza zakres objęty szablonami.

**idealna obrona 3-punktowa:**

1) Rozpoznać kontekst, w którym dane są wyprowadzane
2) oczyszcza dane zgodnie z zasadami tego kontekstu (tj. "context sensitive")
3) robi to automatycznie


Ucieczka kontekstowa .[#toc-context-aware-escaping]
===================================================

Co dokładnie oznacza słowo kontekst? Jest to miejsce w dokumencie z własnymi zasadami obsługi danych wyjściowych. Zależy on od rodzaju dokumentu (HTML, XML, CSS, JavaScript, zwykły tekst, ...) i może się różnić w określonych częściach dokumentu.
Na przykład w dokumencie HTML istnieje wiele takich miejsc (kontekstów), w których obowiązują bardzo różne zasady. Możesz być zaskoczony, jak wiele ich jest. Oto pierwsze cztery:

```html
<p>#text</p>
<img src="#atribut">
<textarea>#rawtext</textarea>
<!-- #komentář -->
```

Początkowym i podstawowym kontekstem strony HTML jest tekst HTML. Jakie są tu zasady? Szczególne znaczenie mają znaki `<` a `&`, które reprezentują początek znacznika lub encji, dlatego musimy uciec, zastępując je encją HTML (`<` za `&lt;` `&` za `&amp` ).

Drugim najczęstszym kontekstem jest wartość atrybutu HTML. Od tekstu różni się tym, że specjalne znaczenie ma tu cudzysłów `"` nebo `'`, który ogranicza atrybut. To powinno być napisane z podmiotem, aby nie było postrzegane jako koniec atrybutu.
I odwrotnie, znak `&lt;` może być bezpiecznie użyty w atrybucie, ponieważ nie ma tu specjalnego znaczenia; nie może być postrzegany jako początek znacznika lub komentarza.
Ale uwaga, w HTML można pisać wartości atrybutów bez cudzysłowów, w takim przypadku cały szereg znaków ma specjalne znaczenie, więc jest to kolejny oddzielny kontekst.

Może Cię to zaskoczy, ale wewnątrz elementów obowiązują specjalne zasady `<textarea>` a `<title>`, gdzie zastosowano znak `<` nemusí (ale může) escapovat, pokud za ním nenásleduje `/` Ale to raczej perełka.

Ciekawa rzecz znajduje się wewnątrz komentarzy HTML. Tutaj encje HTML nie są używane do ucieczki. Nie ma nawet specyfikacji, która określa, jak uciekać w komentarzach.
Trzeba tylko przestrzegać nieco "ciekawych zasad":https://html.spec.whatwg.org/multipage/syntax.html#comments i unikać w nich pewnych kombinacji znaków.

Konteksty mogą być również warstwowe, co ma miejsce, gdy osadzamy JavaScript lub CSS w HTML-u. Można to zrobić na dwa różne sposoby, element i atrybut:

```html
<script>#js-element</script>
<img onclick="#js-atribut">

<style>#css-element</style>
<p style="#css-atribut"></p>
```

Dwie ścieżki i dwa różne sposoby ucieczki od danych. Wewnątrz elementu `<script>` a `<style>` tak jak w przypadku komentarzy HTML, ucieczka przy użyciu encji HTML nie jest wykonywana. Jedyną zasadą, której należy przestrzegać podczas ucieczki od danych wewnątrz tych elementów jest to, że tekst nie może zawierać sekwencji `</script` lub `</style`.

Natomiast atrybuty `style` i `on***` są escape'owane przy użyciu encji HTML.

I, oczywiście, zasady ucieczki tych języków mają zastosowanie wewnątrz osadzonego JavaScript lub CSS. Tak więc ciąg w atrybucie takim jak `onload` jest najpierw escaped zgodnie z regułami JS, a następnie zgodnie z regułami atrybutów HTML.

Ugh... Jak widać, HTML jest bardzo złożonym dokumentem z warstwami kontekstów, a nie wiedząc dokładnie, gdzie wyprowadzam dane (tj. W jakim kontekście), nie ma mowy o tym, jak zrobić to dobrze.


Chcesz przykład? .[#toc-do-you-want-an-example]
-----------------------------------------------

Niech będzie to ciąg `Rock'n'Roll`.

Jeżeli wyprowadzasz go w postaci tekstu HTML, to w tym przypadku nie ma potrzeby dokonywania żadnych podstawień, ponieważ łańcuch nie zawiera żadnego znaku o specjalnym znaczeniu. Sytuacja wygląda inaczej, jeśli napiszemy go wewnątrz atrybutu HTML ujętego w pojedyncze cudzysłowy. W tym przypadku musisz uciec od cytatów do encji HTML:

```html
<div title='Rock&apos;n&apos;Roll'></div>
```

To było łatwe. Znacznie ciekawsza sytuacja występuje w przypadku kontekstów warstwowych, na przykład jeśli ciąg jest częścią JavaScript.

Najpierw więc wypisujemy go do samego JavaScriptu. Czyli zawijamy go w cudzysłów, jednocześnie uciekając od zawartych w nim cudzysłowów za pomocą znaku `\`:

```js
'Rock\'n\'Roll'
```

Możemy dodać wywołanie funkcji, aby kod coś zrobił:

```js
alert('Rock\'n\'Roll');
```

Jeśli wstawimy ten kod do dokumentu HTML używając `<script>`, nie jest potrzebna dalsza edycja, ponieważ zabroniona sekwencja `</script` nie występuje:

```html
<script> alert('Rock\'n\'Roll'); </script>
```

Jeśli jednak chcemy wstawić go do atrybutu HTML, nadal musimy uciec od cudzysłowów do encji HTML:

```html
<div onclick='alert(&apos;Rock\&apos;n\&apos;Roll&apos;)'></div>
```

Zagnieżdżony kontekst nie musi być JS ani CSS. Parametry w adresach URL są escape'owane przez konwersję znaków specjalnych na sekwencje zaczynające się od `%`. Przykład:

```
https://example.org/?a=Jazz&b=Rock%27n%27Roll
```

A kiedy wyprowadzamy ten ciąg w atrybucie, nadal stosujemy escaping zgodnie z tym kontekstem i zastępujemy `&` za `&amp`:

```html
<a href="https://example.org/?a=Jazz&amp;b=Rock%27n%27Roll">
```

Jeśli przeczytałeś to do tej pory, gratuluję, to było wyczerpujące. Teraz masz dobre pojęcie o tym, czym są konteksty i ucieczka. I nie musisz się martwić, że będzie to skomplikowane. Latte robi to za Ciebie automatycznie.


Latte a systemy naiwne .[#toc-latte-vs-naive-systems]
=====================================================

Pokazaliśmy, jak prawidłowo stosować escaping w dokumencie HTML i jak kluczowa jest znajomość kontekstu, czyli miejsca, w którym wyprowadzamy dane. Innymi słowy, jak działa ucieczka kontekstowa.
Chociaż jest to warunek wstępny dla funkcjonalnej obrony przed XSS, **Latte jest jedynym systemem szablonów dla PHP, który to robi.**

Jak to możliwe, skoro wszystkie dzisiejsze systemy twierdzą, że mają automatyczną ucieczkę?
Automatyczne uciekanie bez znajomości kontekstu to bzdura, która **tworzy fałszywe poczucie bezpieczeństwa**.

Systemy szablonujące takie jak Twig, Laravel Blade i inne nie widzą w szablonie żadnej struktury HTML. Dlatego nie widzą też kontekstów. W porównaniu z Latte są ślepi i naiwni. Obsługują tylko niestandardowe tagi, wszystko inne jest dla nich nieistotnym strumieniem znaków:

<div class="juxtapose juxtapose--dark-handle" data-startingposition="80" data-animation="juxtapose-wiper">

```twig .{file:Twig šablona, jak ji vidí samotný Twig}
░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░
░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░
```

```twig .{file:Twig šablona, jak ji vidí designer}
- v textu: <span>{{ text }}</span>
- v tagu: <span {{ text }} ></span>
- v atributu: <span title='{{ text }}'></span>
- v atributu bez uvozovek: <span title={{ text }}></span>
- v atributu obsahujícím URL: <a href="{{ text }}"></a>
- v atributu obsahujícím JavaScript: <img onload="{{ text }}">
- v atributu obsahujícím CSS: <span style="{{ text }}"></span>
- v JavaScriptu: <script>var = {{ text }}</script>
- v CSS: <style>body { content: {{ text }}; }</style>
- v komentáři: <!-- {{ text }} -->
```

</div>

Systemy naiwne po prostu mechanicznie konwertują znaki `< > & ' "` na encje HTML, co jest poprawną metodą ucieczki w większości przypadków użycia, ale daleko od zawsze. Nie mogą więc wykryć ani zapobiec różnym lukom w zabezpieczeniach, co pokażemy poniżej.

Latte widzi szablon tak samo jak ty. Rozumie HTML, XML, rozpoznaje tagi, atrybuty itp. A dzięki temu rozróżnia konteksty i odpowiednio traktuje dane. Oferuje więc naprawdę skuteczną ochronę przed krytyczną podatnością Cross-site Scripting.


Demonstracja na żywo .[#toc-live-demonstration]
===============================================

Po lewej stronie widać szablon w Latte, po prawej wygenerowany kod HTML. Zmienna `$text` jest wyświetlana kilkakrotnie, za każdym razem w nieco innym kontekście. I w ten sposób uciekł trochę inaczej. Możesz samodzielnie edytować kod szablonu, np. zmienić zawartość zmiennej itp. Spróbuj:

<div class="grid fiddle wiki-expanded">
<div class="contains-pre">

``` .{file:template.latte; min-height: 14em}[fiddle-source]
{* ZKUS UPRAVIT TUTO ŠABLONU *}
{var $text = "Rock'n'Roll"}
- <span>{$text}</span>
- <span title='{$text}'></span>
- <span title={$text}></span>
- <img onload="{$text}">
- <script>var = {$text}</script>
- <!-- {$text} -->
```

</div>

<div class="contains-pre">

``` .{file:view-source:...; min-height: 14em}[fiddle-output]
- <span>Rock'n'Roll</span>
- <span title='Rock&apos;n&apos;Roll'></span>
- <span title="Rock&apos;n&apos;Roll"></span>
- <img onload="&quot;Rock&apos;n&apos;Roll&quot;">
- <script>var = "Rock'n'Roll"</script>
- <!-- Rock'n'Roll -->
```

</div>
</div>

Czyż nie jest to wspaniałe! Latte robi kontekstowe ucieczki automatycznie, więc programista:

- nie musi myśleć ani wiedzieć, jak uciec, gdzie
- nie można się pomylić
- nie można zapomnieć o ucieczce

To nawet nie są wszystkie konteksty, które Latte rozróżnia przy wyprowadzaniu i dla których dostosowuje obróbkę danych. Teraz przejdziemy przez ciekawsze przypadki.


Jak włamać się do systemów naiwnych .[#toc-how-to-hack-naive-systems]
=====================================================================

Na kilku praktycznych przykładach pokażemy jak ważne jest rozróżnianie kontekstu i dlaczego naiwne systemy templatek nie zapewniają wystarczającej ochrony przed XSS, w przeciwieństwie do Latte.
W przykładach użyjemy Twiga jako przedstawiciela systemu naiwnego, ale to samo dotyczy innych systemów.


Podatność atrybutów .[#toc-attribute-vulnerability]
---------------------------------------------------

Spróbujemy wstrzyknąć złośliwy kod do strony za pomocą atrybutu HTML, jak pokazaliśmy [powyżej |#How-Does-the-Vulnerability-Arise]. Miejmy na uwadze, że szablon w Twigu renderuje obraz:

```twig .{file:Twig}
<img src={{ imageFile }} alt={{ imageAlt }}>
```

Zauważ, że wokół wartości atrybutów nie ma cudzysłowów. Koder mógł o nich zapomnieć, co po prostu się zdarza. Na przykład w React kod jest napisany tak, bez cytatów, a koder, który zmienia języki, może łatwo zapomnieć o cytatach.

Atakujący wstawiłby sprytnie skonstruowany ciąg znaków `foo onload=alert('Hacked!')` jako podpis obrazka. Wiemy już, że Twig nie potrafi określić, czy zmienna jest wyprowadzana w strumieniu tekstu HTML, wewnątrz atrybutu, wewnątrz komentarza HTML itd. I po prostu mechanicznie konwertuje znaki `< > & ' "` na jednostki HTML.
Tak więc wynikowy kod będzie wyglądał tak:

```html
<img src=photo0145.webp alt=foo onload=alert(&#039;Hacked!&#039;)>
```

**Powstała dziura w zabezpieczeniach!**

Fałszywy atrybut `onload` stał się częścią strony i przeglądarka uruchamia go natychmiast po pobraniu obrazu.

Teraz zobaczmy, jak Latte radzi sobie z tym samym szablonem:

```latte .{file:Latte}
<img src={$imageFile} alt={$imageAlt}>
```

Latte widzi szablon tak samo jak ty. W przeciwieństwie do Twig, rozumie HTML i wie, że zmienna jest drukowana jako wartość atrybutu, która nie jest w cudzysłowie. Dlatego też dodaje je. Gdy atakujący wstawi tę samą etykietę, wynikowy kod będzie wyglądał tak:

```html
<img src="photo0145.webp" alt="foo onload=alert(&apos;Hacked!&apos;)">
```

**Latte skutecznie zapobiegła XSS.**


Wypisywanie zmiennej w JavaScript .[#toc-printing-a-variable-in-javascript]
---------------------------------------------------------------------------

Dzięki ucieczce kontekstowej, możliwe jest używanie zmiennych PHP natywnie wewnątrz JavaScript.

```latte
<p onclick="alert({$movie})">{$movie}</p>

<script>var movie = {$movie};</script>
```

Jeśli zmienna `$movie` zawiera ciąg `'Amarcord & 8 1/2'`, to zostanie wygenerowane następujące wyjście. Zauważ, że wewnątrz HTML używane jest inne escaping niż wewnątrz JavaScript, a nawet inne w atrybucie `onclick`:

```latte
<p onclick="alert(&quot;Amarcord &amp; 8 1\/2&quot;)">Amarcord &amp; 8 1/2</p>

<script>var movie = "Amarcord & 8 1\/2";</script>
```


Kontrola połączeń .[#toc-link-checking]
---------------------------------------

Latte automatycznie sprawdza, czy zmienna użyta w atrybutach `src` lub `href` zawiera adres URL strony internetowej (tj. protokół HTTP) i zapobiega wypisywaniu linków, które mogą stanowić zagrożenie dla bezpieczeństwa.

```latte
{var $link = 'javascript:attack()'}

<a href={$link}>click here</a>
```

Wydruki:

```latte
<a href="">klikni</a>
```

Sprawdzanie można wyłączyć za pomocą filtra [nocheck |filters#nocheck].


Limity na latte .[#toc-limits-of-latte]
=======================================

Latte nie jest kompletną ochroną XSS dla całej aplikacji. Nie chcielibyśmy, abyś przestał myśleć o bezpieczeństwie podczas korzystania z Latte.
Celem Latte jest zapewnienie, że atakujący nie może zmienić struktury strony, ani sfałszować elementów lub atrybutów HTML. Ale nie sprawdza poprawności merytorycznej danych wyjściowych. Albo poprawność zachowania JavaScript.
To wykracza poza zakres systemu szablonowania. Weryfikacja poprawności danych, zwłaszcza wprowadzanych przez użytkownika, a więc niezaufanych, jest ważnym zadaniem dla programisty.

Latte jest synonimem bezpieczeństwa

Latte to jedyny system szablonów PHP z efektywną ochroną przed krytyczną luką Cross-site Scripting (XSS). Dzieje się tak dzięki tzw. ucieczce kontekstowej (context-sensitive escaping). Porozmawiajmy,

  • czym jest luka XSS i dlaczego jest tak niebezpieczna
  • co sprawia, że Latte tak skutecznie broni się przed XSS
  • jak Twig, Blade, itp. mogą łatwo stworzyć dziurę bezpieczeństwa w szablonach

Cross-site Scripting (XSS)

Cross-site Scripting (w skrócie XSS) to jedna z najczęstszych luk w stronach internetowych, a przy tym bardzo niebezpieczna. Pozwala on atakującemu na umieszczenie złośliwego skryptu (zwanego malware) w obcej witrynie, który wykonuje się w przeglądarce niczego nie podejrzewającego użytkownika.

Co może zrobić taki skrypt? Może na przykład wysłać arbitralną treść ze skompromitowanej strony do atakującego, w tym wrażliwe dane wyświetlane po zalogowaniu. Może modyfikować stronę lub wykonywać inne żądania w imieniu użytkownika. Przykładowo, gdyby był to webmail, mógłby odczytać wrażliwe wiadomości, zmodyfikować wyświetlaną treść lub zmienić konfigurację, np. włączyć przekazywanie kopii wszystkich wiadomości na adres atakującego, aby uzyskać dostęp do przyszłych e-maili.

To również dlatego XSS znajduje się na szczycie listy najbardziej niebezpiecznych podatności. W przypadku znalezienia luki na stronie internetowej, należy ją jak najszybciej usunąć, aby zapobiec jej wykorzystaniu.

Jak powstaje ta podatność?

Luka występuje w miejscu, w którym generowana jest strona internetowa i wyprowadzane są zmienne. Wyobraź sobie, że tworzysz stronę wyszukiwania, a na początku będzie akapit z wyszukiwanym hasłem w postaci:

echo '<p>Výsledky vyhledávání pro <em>' . $search . '</em></p>';

Atakujący może wpisać dowolny ciąg znaków, w tym kod HTML, do pola wyszukiwania, a tym samym do zmiennej $search jako <script>alert("Hacked!")</script>. Ponieważ wyjście nie jest traktowane w żaden sposób, staje się częścią wyświetlanej strony:

<p>Výsledky vyhledávání pro <em><script>alert("Hacked!")</script></em></p>

Zamiast wypisać ciąg wyszukiwania, przeglądarka wykonuje JavaScript. I w ten sposób napastnik przejmuje stronę.

Można argumentować, że umieszczenie kodu w zmiennej rzeczywiście wykona JavaScript, ale tylko w przeglądarce atakującego. Jak to się dzieje, że trafia do ofiary? Z tej perspektywy możemy wyróżnić kilka rodzajów XSS. W naszym przykładzie wyszukiwania mówimy o refleksyjnym XSS. Tutaj musimy jeszcze naprowadzić ofiarę na kliknięcie w link, który w parametrze będzie zawierał złośliwy kod:

https://example.com/?search=<script>alert("Hacked!")</script>

Naprowadzenie użytkownika na link wymaga nieco inżynierii społecznej, ale nie jest to trudne. Użytkownicy klikają w linki, czy to w mailach, czy w mediach społecznościowych, bez większego zastanowienia. A to, że w adresie jest coś podejrzanego można zamaskować za pomocą skracacza URL, użytkownik widzi wtedy tylko bit.ly/xxx.

Istnieje jednak druga, znacznie groźniejsza forma ataku znana jako stored XSS lub persistent XSS, w której atakującemu udaje się przechowywać złośliwy kod na serwerze, tak aby był on automatycznie wstawiany na określone strony.

Przykładem tego są strony, na których użytkownicy zamieszczają komentarze. Napastnik wysyła post zawierający kod i jest on przechowywany na serwerze. Jeśli strona nie jest wystarczająco bezpieczna, będzie ona wtedy uruchamiana w przeglądarce każdego odwiedzającego.

Wydaje się, że istotą ataku jest wprowadzenie do strony ciągu znaków <script>. W rzeczywistości istnieje wiele sposobów na osadzenie JavaScript. Weźmy przykład osadzania za pomocą atrybutu HTML. Załóżmy galerię zdjęć, w której do zdjęć można wstawić podpis, który jest drukowany w atrybucie alt:

echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">';

Atakujący musi tylko wstawić sprytnie skonstruowany ciąg " onload="alert('Hacked!') jako napis, a jeśli wyjście nie zostanie obsłużone, wynikowy kod będzie wyglądał tak:

<img src="photo0145.webp" alt="" onload="alert('Hacked!')">

Spofingowany atrybut onload staje się teraz częścią strony. Przeglądarka wykonuje zawarty w nim kod, gdy tylko obrazek zostanie pobrany. Zhakowany!

Jak bronić się przed XSS?

Wszelkie próby wykrycia ataku przy użyciu czarnej listy, np. ciągu blokad <script> itp. są niewystarczające. Podstawą skutecznej obrony jest konsekwentna sanityzacja wszystkich danych wyrzucanych wewnątrz strony.

Przede wszystkim polega na zastąpieniu wszystkich znaków o specjalnym znaczeniu innymi odpowiadającymi im sekwencjami, co w slangu oznacza escaping (pierwszy znak sekwencji nazywany jest znakiem escape, stąd nazwa). Na przykład w tekście HTML znakiem o znaczeniu specjalnym jest <, který když nemá být interpretován jako začátek tagu, musíme jej nahradit vizuálně odpovídající sekvencí, tzv. HTML entitou &lt;. A przeglądarka drukuje mniejszość.

Bardzo ważne jest rozróżnienie kontekstu, w jakim dane są wyprowadzane. Ponieważ różne konteksty sanitują ciągi inaczej. W różnych kontekstach różne znaki mają specjalne znaczenie. Na przykład ucieczka różni się w tekście HTML, w atrybutach HTML, wewnątrz niektórych elementów specjalnych itp. Za chwilę omówimy to szczegółowo.

Najlepiej jest wykonać escaping bezpośrednio podczas wypisywania ciągu na stronie, zapewniając, że jest on rzeczywiście wykonywany i wykonywany tylko raz. Najlepiej, jeśli obróbka jest obsługiwana automatycznie bezpośrednio przez system szablonów. Ponieważ jeśli leczenie nie jest wykonywane automatycznie, programista może o nim zapomnieć. A jedno przeoczenie oznacza, że strona jest narażona na niebezpieczeństwo.

Jednak XSS nie wpływa tylko na wyprowadzanie danych w szablonach, ale także na inne części aplikacji, które muszą prawidłowo obsługiwać niezaufane dane. Na przykład JavaScript w twojej aplikacji nie może używać innerHTML, ale tylko innerText lub textContent w połączeniu z nimi. Należy zachować szczególną ostrożność w przypadku funkcji oceniających ciągi znaków, takich jak JavaScript, który jest eval(), ale także setTimeout(), lub używając funkcji setAttribute() z atrybutami zdarzeń, takimi jak onload, itp. Ale to wykracza poza zakres objęty szablonami.

idealna obrona 3-punktowa:

  1. Rozpoznać kontekst, w którym dane są wyprowadzane
  2. oczyszcza dane zgodnie z zasadami tego kontekstu (tj. „context sensitive“)
  3. robi to automatycznie

Ucieczka kontekstowa

Co dokładnie oznacza słowo kontekst? Jest to miejsce w dokumencie z własnymi zasadami obsługi danych wyjściowych. Zależy on od rodzaju dokumentu (HTML, XML, CSS, JavaScript, zwykły tekst, …) i może się różnić w określonych częściach dokumentu. Na przykład w dokumencie HTML istnieje wiele takich miejsc (kontekstów), w których obowiązują bardzo różne zasady. Możesz być zaskoczony, jak wiele ich jest. Oto pierwsze cztery:

<p>#text</p>
<img src="#atribut">
<textarea>#rawtext</textarea>
<!-- #komentář -->

Początkowym i podstawowym kontekstem strony HTML jest tekst HTML. Jakie są tu zasady? Szczególne znaczenie mają znaki < a &, które reprezentują początek znacznika lub encji, dlatego musimy uciec, zastępując je encją HTML (< za &lt; & za &amp ).

Drugim najczęstszym kontekstem jest wartość atrybutu HTML. Od tekstu różni się tym, że specjalne znaczenie ma tu cudzysłów " nebo ', który ogranicza atrybut. To powinno być napisane z podmiotem, aby nie było postrzegane jako koniec atrybutu. I odwrotnie, znak &lt; może być bezpiecznie użyty w atrybucie, ponieważ nie ma tu specjalnego znaczenia; nie może być postrzegany jako początek znacznika lub komentarza. Ale uwaga, w HTML można pisać wartości atrybutów bez cudzysłowów, w takim przypadku cały szereg znaków ma specjalne znaczenie, więc jest to kolejny oddzielny kontekst.

Może Cię to zaskoczy, ale wewnątrz elementów obowiązują specjalne zasady <textarea> a <title>, gdzie zastosowano znak < nemusí (ale může) escapovat, pokud za ním nenásleduje / Ale to raczej perełka.

Ciekawa rzecz znajduje się wewnątrz komentarzy HTML. Tutaj encje HTML nie są używane do ucieczki. Nie ma nawet specyfikacji, która określa, jak uciekać w komentarzach. Trzeba tylko przestrzegać nieco ciekawych zasad i unikać w nich pewnych kombinacji znaków.

Konteksty mogą być również warstwowe, co ma miejsce, gdy osadzamy JavaScript lub CSS w HTML-u. Można to zrobić na dwa różne sposoby, element i atrybut:

<script>#js-element</script>
<img onclick="#js-atribut">

<style>#css-element</style>
<p style="#css-atribut"></p>

Dwie ścieżki i dwa różne sposoby ucieczki od danych. Wewnątrz elementu <script> a <style> tak jak w przypadku komentarzy HTML, ucieczka przy użyciu encji HTML nie jest wykonywana. Jedyną zasadą, której należy przestrzegać podczas ucieczki od danych wewnątrz tych elementów jest to, że tekst nie może zawierać sekwencji </script lub </style.

Natomiast atrybuty style i on*** są escape'owane przy użyciu encji HTML.

I, oczywiście, zasady ucieczki tych języków mają zastosowanie wewnątrz osadzonego JavaScript lub CSS. Tak więc ciąg w atrybucie takim jak onload jest najpierw escaped zgodnie z regułami JS, a następnie zgodnie z regułami atrybutów HTML.

Ugh… Jak widać, HTML jest bardzo złożonym dokumentem z warstwami kontekstów, a nie wiedząc dokładnie, gdzie wyprowadzam dane (tj. W jakim kontekście), nie ma mowy o tym, jak zrobić to dobrze.

Chcesz przykład?

Niech będzie to ciąg Rock'n'Roll.

Jeżeli wyprowadzasz go w postaci tekstu HTML, to w tym przypadku nie ma potrzeby dokonywania żadnych podstawień, ponieważ łańcuch nie zawiera żadnego znaku o specjalnym znaczeniu. Sytuacja wygląda inaczej, jeśli napiszemy go wewnątrz atrybutu HTML ujętego w pojedyncze cudzysłowy. W tym przypadku musisz uciec od cytatów do encji HTML:

<div title='Rock&apos;n&apos;Roll'></div>

To było łatwe. Znacznie ciekawsza sytuacja występuje w przypadku kontekstów warstwowych, na przykład jeśli ciąg jest częścią JavaScript.

Najpierw więc wypisujemy go do samego JavaScriptu. Czyli zawijamy go w cudzysłów, jednocześnie uciekając od zawartych w nim cudzysłowów za pomocą znaku \:

'Rock\'n\'Roll'

Możemy dodać wywołanie funkcji, aby kod coś zrobił:

alert('Rock\'n\'Roll');

Jeśli wstawimy ten kod do dokumentu HTML używając <script>, nie jest potrzebna dalsza edycja, ponieważ zabroniona sekwencja </script nie występuje:

<script> alert('Rock\'n\'Roll'); </script>

Jeśli jednak chcemy wstawić go do atrybutu HTML, nadal musimy uciec od cudzysłowów do encji HTML:

<div onclick='alert(&apos;Rock\&apos;n\&apos;Roll&apos;)'></div>

Zagnieżdżony kontekst nie musi być JS ani CSS. Parametry w adresach URL są escape'owane przez konwersję znaków specjalnych na sekwencje zaczynające się od %. Przykład:

https://example.org/?a=Jazz&b=Rock%27n%27Roll

A kiedy wyprowadzamy ten ciąg w atrybucie, nadal stosujemy escaping zgodnie z tym kontekstem i zastępujemy & za &amp:

<a href="https://example.org/?a=Jazz&amp;b=Rock%27n%27Roll">

Jeśli przeczytałeś to do tej pory, gratuluję, to było wyczerpujące. Teraz masz dobre pojęcie o tym, czym są konteksty i ucieczka. I nie musisz się martwić, że będzie to skomplikowane. Latte robi to za Ciebie automatycznie.

Latte a systemy naiwne

Pokazaliśmy, jak prawidłowo stosować escaping w dokumencie HTML i jak kluczowa jest znajomość kontekstu, czyli miejsca, w którym wyprowadzamy dane. Innymi słowy, jak działa ucieczka kontekstowa. Chociaż jest to warunek wstępny dla funkcjonalnej obrony przed XSS, Latte jest jedynym systemem szablonów dla PHP, który to robi.

Jak to możliwe, skoro wszystkie dzisiejsze systemy twierdzą, że mają automatyczną ucieczkę? Automatyczne uciekanie bez znajomości kontekstu to bzdura, która tworzy fałszywe poczucie bezpieczeństwa.

Systemy szablonujące takie jak Twig, Laravel Blade i inne nie widzą w szablonie żadnej struktury HTML. Dlatego nie widzą też kontekstów. W porównaniu z Latte są ślepi i naiwni. Obsługują tylko niestandardowe tagi, wszystko inne jest dla nich nieistotnym strumieniem znaków:

░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░
░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░
- v textu: <span>{{ text }}</span>
- v tagu: <span {{ text }} ></span>
- v atributu: <span title='{{ text }}'></span>
- v atributu bez uvozovek: <span title={{ text }}></span>
- v atributu obsahujícím URL: <a href="{{ text }}"></a>
- v atributu obsahujícím JavaScript: <img onload="{{ text }}">
- v atributu obsahujícím CSS: <span style="{{ text }}"></span>
- v JavaScriptu: <script>var = {{ text }}</script>
- v CSS: <style>body { content: {{ text }}; }</style>
- v komentáři: <!-- {{ text }} -->

Systemy naiwne po prostu mechanicznie konwertują znaki < > & ' " na encje HTML, co jest poprawną metodą ucieczki w większości przypadków użycia, ale daleko od zawsze. Nie mogą więc wykryć ani zapobiec różnym lukom w zabezpieczeniach, co pokażemy poniżej.

Latte widzi szablon tak samo jak ty. Rozumie HTML, XML, rozpoznaje tagi, atrybuty itp. A dzięki temu rozróżnia konteksty i odpowiednio traktuje dane. Oferuje więc naprawdę skuteczną ochronę przed krytyczną podatnością Cross-site Scripting.

Demonstracja na żywo

Po lewej stronie widać szablon w Latte, po prawej wygenerowany kod HTML. Zmienna $text jest wyświetlana kilkakrotnie, za każdym razem w nieco innym kontekście. I w ten sposób uciekł trochę inaczej. Możesz samodzielnie edytować kod szablonu, np. zmienić zawartość zmiennej itp. Spróbuj:

{* ZKUS UPRAVIT TUTO ŠABLONU *}
{var $text = "Rock'n'Roll"}
- <span>{$text}</span>
- <span title='{$text}'></span>
- <span title={$text}></span>
- <img onload="{$text}">
- <script>var = {$text}</script>
- <!-- {$text} -->
- <span>Rock'n'Roll</span>
- <span title='Rock&apos;n&apos;Roll'></span>
- <span title="Rock&apos;n&apos;Roll"></span>
- <img onload="&quot;Rock&apos;n&apos;Roll&quot;">
- <script>var = "Rock'n'Roll"</script>
- <!-- Rock'n'Roll -->

Czyż nie jest to wspaniałe! Latte robi kontekstowe ucieczki automatycznie, więc programista:

  • nie musi myśleć ani wiedzieć, jak uciec, gdzie
  • nie można się pomylić
  • nie można zapomnieć o ucieczce

To nawet nie są wszystkie konteksty, które Latte rozróżnia przy wyprowadzaniu i dla których dostosowuje obróbkę danych. Teraz przejdziemy przez ciekawsze przypadki.

Jak włamać się do systemów naiwnych

Na kilku praktycznych przykładach pokażemy jak ważne jest rozróżnianie kontekstu i dlaczego naiwne systemy templatek nie zapewniają wystarczającej ochrony przed XSS, w przeciwieństwie do Latte. W przykładach użyjemy Twiga jako przedstawiciela systemu naiwnego, ale to samo dotyczy innych systemów.

Podatność atrybutów

Spróbujemy wstrzyknąć złośliwy kod do strony za pomocą atrybutu HTML, jak pokazaliśmy powyżej. Miejmy na uwadze, że szablon w Twigu renderuje obraz:

<img src={{ imageFile }} alt={{ imageAlt }}>

Zauważ, że wokół wartości atrybutów nie ma cudzysłowów. Koder mógł o nich zapomnieć, co po prostu się zdarza. Na przykład w React kod jest napisany tak, bez cytatów, a koder, który zmienia języki, może łatwo zapomnieć o cytatach.

Atakujący wstawiłby sprytnie skonstruowany ciąg znaków foo onload=alert('Hacked!') jako podpis obrazka. Wiemy już, że Twig nie potrafi określić, czy zmienna jest wyprowadzana w strumieniu tekstu HTML, wewnątrz atrybutu, wewnątrz komentarza HTML itd. I po prostu mechanicznie konwertuje znaki < > & ' " na jednostki HTML. Tak więc wynikowy kod będzie wyglądał tak:

<img src=photo0145.webp alt=foo onload=alert(&#039;Hacked!&#039;)>

Powstała dziura w zabezpieczeniach!

Fałszywy atrybut onload stał się częścią strony i przeglądarka uruchamia go natychmiast po pobraniu obrazu.

Teraz zobaczmy, jak Latte radzi sobie z tym samym szablonem:

<img src={$imageFile} alt={$imageAlt}>

Latte widzi szablon tak samo jak ty. W przeciwieństwie do Twig, rozumie HTML i wie, że zmienna jest drukowana jako wartość atrybutu, która nie jest w cudzysłowie. Dlatego też dodaje je. Gdy atakujący wstawi tę samą etykietę, wynikowy kod będzie wyglądał tak:

<img src="photo0145.webp" alt="foo onload=alert(&apos;Hacked!&apos;)">

Latte skutecznie zapobiegła XSS.

Wypisywanie zmiennej w JavaScript

Dzięki ucieczce kontekstowej, możliwe jest używanie zmiennych PHP natywnie wewnątrz JavaScript.

<p onclick="alert({$movie})">{$movie}</p>

<script>var movie = {$movie};</script>

Jeśli zmienna $movie zawiera ciąg 'Amarcord & 8 1/2', to zostanie wygenerowane następujące wyjście. Zauważ, że wewnątrz HTML używane jest inne escaping niż wewnątrz JavaScript, a nawet inne w atrybucie onclick:

<p onclick="alert(&quot;Amarcord &amp; 8 1\/2&quot;)">Amarcord &amp; 8 1/2</p>

<script>var movie = "Amarcord & 8 1\/2";</script>

Latte automatycznie sprawdza, czy zmienna użyta w atrybutach src lub href zawiera adres URL strony internetowej (tj. protokół HTTP) i zapobiega wypisywaniu linków, które mogą stanowić zagrożenie dla bezpieczeństwa.

{var $link = 'javascript:attack()'}

<a href={$link}>click here</a>

Wydruki:

<a href="">klikni</a>

Sprawdzanie można wyłączyć za pomocą filtra nocheck.

Limity na latte

Latte nie jest kompletną ochroną XSS dla całej aplikacji. Nie chcielibyśmy, abyś przestał myśleć o bezpieczeństwie podczas korzystania z Latte. Celem Latte jest zapewnienie, że atakujący nie może zmienić struktury strony, ani sfałszować elementów lub atrybutów HTML. Ale nie sprawdza poprawności merytorycznej danych wyjściowych. Albo poprawność zachowania JavaScript. To wykracza poza zakres systemu szablonowania. Weryfikacja poprawności danych, zwłaszcza wprowadzanych przez użytkownika, a więc niezaufanych, jest ważnym zadaniem dla programisty.