Aplikacje internetowe

Bełdziowe spojrzenie na aplikacje internetowe

Obsługa HTML

Jakiś czas temu w notce Niebezpieczeństwo HTML’a nakreśliłem problem wstrzyknięć kodu HTML. W komentarzach kilkukrotnie pojawiło się pytanie o sposób obrony przed tego typu zagrożeniem.

Najprostszą obroną jest usuwanie wszelkich tagów HTML z przesłanych danych. Możemy tego dokonać korzystając z funkcji strip_tags oraz htmlspecialchars. W przypadku drugiej funkcji warto zainteresować się jej opcjonalnymi parametrami tzn. sposobem postępowania z apostrofami i cudzysłowami oraz kodowaniem. Przykładowy kod chroniący przed HTML Injection dla strony kodowanej UTF-8 może wyglądać tak:

<?php
   $var = htmlspecialchars( strip_tags( $var ), ENT_QUOTES, 'UTF-8' );
?>

Metajęzyki

Często spotykanym sposobem obejścia problemów z przesyłanym kodem HTML jest skorzystanie z metajęzyków. Najpopularniejsze z nich to BBCode, Textile oraz Wikitekst. Ich zadaniem jest umożliwienie korzystania z dobrodziejstw HTML bez jego jawnego użycia, poprzez udostępnienie użytkownikom odpowiednich znaczników. Przykładowo w celu uzyskania efekty pogrubienia należy skorzystać z tagu [b] zamiast <b> czy <strong>. Po przesłaniu odpowiednio sformatowanego tekstu skrypt na serwerze odpowiednio parsuje go tworząc jego odpowiednik w HTML.

Czarna lista

Blacklist jest popularnym sposobem wykrywania ataków typu XSS. Opiera się on na zdefiniowaniu słów, które są charakterystyczne dla tego typu ataków. W przypadku XSS takim słowem może być script. W przypadku wykrycia jednego ze słów znajdującego się na czarnej liście w przesyłanym tekście podejmowana jest akcja mająca zaradzić niebezpieczeństwu np. przerwanie działania skryptu.

<?php
   if( strpos( $var, 'script' ) !== false )
      die( 'Wykryto XSS!' );
?>

Minusem takiego rozwiązania jest duża trudność w zdefiniowaniu wszelkich możliwych słów kluczowych charakteryzujących ataki XSS. Istnieje wiele sposobów przemycenia kodu JavaScript przez co metoda ta nie jest bardzo bezpieczna. Przekonali się o tym twórcy strony house.pl, którzy zdecydowani się na stworzenie własnego systemu filtracji.

Biała lista

Odwrotnością blacklist jest whitelist. Polega ona na zdefiniowaniu znaczników HTML, które mogą być użyte. Po raz kolejny przyda nam się nam funkcja strip_tags. Tym razem będziemy musieli skorzystać z jej opcjonalnego parametru określającego dozwolone tagi.

<?php
   $txt = strip_tags( $txt, '<h1><strong>' );
?>

Powyższy kod ma jedną wadę – nie kontroluje w żadne sposób atrybutów dozwolonych znaczników. Tak więc jeśli zmienna $txt przyjmie wartość

<h1 onmousemove="alert('XSS')">XSS</h1>

nie zablokuje ona wykonania XSS.

W przypadku gdy ilość udostępnianych znaczników nie jest duża możemy posłużyć się wyrażeniami regularnymi w celu przefiltrowania danych. W pierwszej kolejności zamieniamy wszystkie tagi na encje, a następnie odwracamy ten proces dla dozwolonych znaczników.

<?php
   $var = htmlspecialchars( $var, ENT_QUOTES, 'UTF-8' );
   $encje = array(
                     '@&lt;strong&gt;(.*?)&lt;/strong&gt;@si',
                     '@&lt;h1&gt;(.*?)&lt;/h1&gt;@si'
                 );

   $tagi = array(
                     '<strong>$1</strong>',
                     '<h1>$1</h1>'
                );

   echo preg_replace( $encje, $tagi, $var );
?>

HTML Purifier

HTML Purifier jest skryptem PHP mającym za zadanie usunięcie z kodu HTML wszelkich niebezpiecznych elementów. Dodatkowym atutem tego narzędzia jest poprawa kodu, tak aby był on zgodny ze specyfikacjami W3C.

W najprostszej formie użycie powyższego narzędzia sprowadza się do kilku linii kodu.

<?php
   set_include_path(get_include_path() . PATH_SEPARATOR . 'htmlPurifier' );
   require 'HTMLPurifier.php';
   $purifier = new HTMLPurifier( );

   $var = '<h1 onmousemove="alert(1)">dasdas</h1>
           <strong><em>test</strong></em>';
   echo $purifier -> purify( $var );
?>

wynik:

<h1>dasdas</h1><strong><em>test</em></strong>

Jak widać powyższy kod został oczyszczony z kodu JavaScript oraz zostało poprawione zagnieżdżenie tagów.

Warto zobaczyć:


Tagi: ,
Kategoria: Bezpieczeństwo, Filtracja i walidacja, Bezpieczeństwo, HTML Injection, Bezpieczeństwo, PHP


7 komentarzy

  1. ciekawy napisał(a):

    siema :) świetne artykuły piszesz :)
    mam pytanie, co sądzisz o przepuszczaniu wszystkich danych „wypluwanych” przez funkcję htmlentities(). W symfony jest coś takiego jak escaping_strategy http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Output%20Escaping
    czy jest sens robić to jeżeli do przed zapisaniem do bazy robię strip_tags, htmlentities(), a później zamieniam metajęzyk [Markdown].

  2. hi, jeśli poźniej będzie wyświetlał te dane w polach typu input / textarea to warto. strip_tags wytnie taki, ale pozostawi rzeczy w stylu => „> <= co będzie skutkowało wypisaniem ów danych poza tagami :) B.

  3. ciekawy napisał(a):

    hmm, źle chyba sprecyzowałem pytanie, jeszcze raz :P

    tworzę własny system blogowy w symfony, czy widzisz sens włączania tej opcji w frameworku ? http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Output%20Escaping
    Używam Markdown. Przy zapisie do bazy przepuszczam tekst przez PHP Input Filter która czyści wszystko co złe :P oprócz tego na co pozwolę czyli pogrubienie tekstu itp W tym momencie jak wyświetlam wpis na blogu to muszę zrobić
    echo $wpis->pobierzTresc(ESC_RAW)
    żeby treści nie przeleciało funkcją htmlentities(). Czy to ma sens? Włączanie tej funkcji dla całego outputu.
    W tym momencie można jeszcze użyć pluginu http://www.symfony-project.org/plugins/sfXssSafePlugin i wtedy
    echo $wpis->pobierzTresc(ESC_XSSSAFE)
    co przepuści html ale nie przepuści JS. Ale czy jest sens używać tego przy każdym wyświetlaniu wpisu? Czy zakładamy że skoro dane zapisane do bazy są bezpieczne to już nie ma potrzeby tyle kombinować i obciążać aplikację?

  4. hmm jeśli korzystasz z Markdown’a + dopuszczasz tylko wybrane znaczniki to nie ma potrzeby traktowania txt htmlentities

  5. ciekawy napisał(a):

    w takim razie nie za bardzo rozumie politykę w symfony
    no po choinke robić escape całego outputu, i to jeszcze jest domyślnym ustawieniem.
    Przecież lepiej propagować bezpieczny zapis danych. A tu widzę odwrotnie, zapisz co chcesz, ale i tak będzie bezpiecznie no bo jest output escaping – no i jeszcze zmniejszanie wydajności aplikacji
    Wiadomo, jak coś z GET’a jest wyświetlane no to samemu trzeba coś zrobić, ale nie cały output

  6. hmm wsio jest ok :-) generalnie jakbys zamist z Markdown’a korzystal z „czystego” html to funkcje te byłyby przydatne. po to korzystasz z Markdown’a zeby juz nie martwic sie o html :)

  7. Woo napisał(a):

Dodaj komentarz