Tűzfal készítése PHP segítségével
A leggondosabb tervezés mellett is maradhat sérülékeny kódrészlet a webalkalmazásokban. Ezt kihasználva rosszindulatú látogatók támadó kódokat helyezhetnek el a honlapokon, vagy egyszerűen teleszemetelik. Mindkettő komolyan csökkenti egy honlap és annak szolgáltatásainak értékét. Másrészt a sérülékenységeket kereső letöltések is terhelik a szervert. A rövid időn belül sok egymást követő próbálkozás akár elérhetetlenné is teheti a kiszemelt honlapot. Itt már nem elegendő a beérkező adatok hagyományos ellenőrzése, érdemes megfontolni az aktív védekezés lehetőségét is.
A cikkben egy egyszerű PHP alapú tűzfal osztályt mutatok be az alkalmazásának lehetőségeivel.A Most, hogy már van publikus függvénye, lássuk a használatát is:A fenti példában megadott Az elemzést a
Használatára példa:A határérték nullára csökkentésével a tűzfalunk semmilyen előfordulást nem tolerál. Így ezzel a megoldással a *script alapú kártevők jellemző kódjai ellen is felléphetünk.A fenti táblában az IP címet és az esemény dátumát rögzíti a program. A Az adatbázist a Vegyük észre, hogy immár két paramétere lett a A fenti példa egy Celeron 1,4 GHz processzoros gépen (laptop) átlagosan 1,8 ms alatt lefutott. Ez alapján az elkészített tűzfal osztály várhatóan nem okoz letöltésben is érezhető késedelmet. A szerverek nagyobb sebességű és korszerűbb processzorai még gyorsabban dolgozzák fel ezt a kis méretű kódot. Végezetül a kialakított osztály kódja – megjegyzések nélkül – az alábbi lett:
Fontos megemlíteni, hogy már léteznek elérhető árú tűzfal modulok a népszerű alkalmazásokhoz (pl. WordPress, Drupal). A fenti osztály kódját elsősorban gondolatébresztőnek szántam.
(A cikk ikonjához Ruzzilla New York City Firewall c. fotóját kölcsönöztük.)
■ Előnyök és hátrányok
Egy tűzfal alapvető funkciója, hogy meggátolja a kialakított házirend megszegését és megtagadja a szabályok ellen vétő felhasználók kiszolgálását. Alkalmazásának előnye, hogy csökken a rosszindulatú vagy spam bejegyzések elfogadása, valamint a sikeres támadások valószínűsége. A szerver terhelése is csökken azáltal, hogy a lapok feldolgozását már az elején megszakítjuk bizonyos esetekben. A tűzfal hátránya, hogy a normál felhasználók kiszolgálása is átesik az ellenőrzésen, ami viszont növeli a szerver terhelését. Egyes IP címek kitiltása pedig vétlen felhasználókat is érinthet.A cikkben egy egyszerű PHP alapú tűzfal osztályt mutatok be az alkalmazásának lehetőségeivel.
Beavatkozás
Az osztály minimális váza a következő listában olvasható. Természetesen elvárható, hogy a tűzfal opcionálisan egy hiba oldalra irányítsa a látogatót, vagy megtagadja a kiszolgálást.class SWFilter {
// az átirányítás címe
private $redirect_url;
// konstruktor, az alapértelmezett értékek beállítása
function __construct() {
$this->redirect_url = '';
}
// ez a belső függvény avatkozik közbe
function BlockUser($message) {
if (empty($this->redirect_url)) {
header('HTTP/1.0 403 Forbidden');
echo $message . "\n";
} else {
header('Location: '. $this->redirect_url);
}
exit;
}
// átirányítási cím beállítása
public function SetRedirectURL($new_value) {
$this->redirect_url = $new_value;
}
}
BlockUser()
függvény közvetlenül nem használható, az osztály ellenőrző rutinjai hívják meg. Futtatása megszakítja az oldal további feldolgozását. Alapértelmezetten hibaüzenettel itt megáll a program, de a SetRedirectURL()
segítségével beállítható egy gyűjtőlap címe.URL ellenőrzés
Az alábbiCheckURL()
függvény már publikus, és a paraméterként átadott tömb elemeit felhasználva ellenőrzi azok jelenlétét a címsorban. A $found==false
feltételnek köszönhetően a függvény az első megtalált elemnél leáll a kereséssel. Ezzel kiküszöbölhető a további elemek felesleges feldolgozása és időt takarítunk meg. // címsor ellenőrzése a megadott kifejezésekre
public function CheckURL($terms) {
$url = isset($_SERVER['PATH_INFO']) ? substr($_SERVER['PATH_INFO'],1) : '';
if (!isset($url) || strlen($url)<2) {
$url = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
}
$found = false;
for ($i=0; $i<count($terms) && $found==false; $i++) {
if (stripos($url,$terms[$i])!==false) $found=true;
}
if ($found==true) {
$this->BlockUser('Illegal expression found.');
}
}
<?php
include 'swfilter_class.php';
$myFireWall = new SWFilter();
$myFireWall->CheckURL(array('tp:/','tps:/'));
?>
array('tp:/','tps:/')
paraméter segítségével a címsorban előforduló http://
, https://
, ftp://
és ftps://
kezdetű linkeket tiltottuk le. Ezzel a módszerrel megakadályozható, hogy távoli fájlok és kódok megnyitását erőszakolják ki a programunkból. Természetesen bármely más közismert trükk mintája is megadható, akár a saját naplófájlok elemzése alapján is alkothatunk szabályt.GET és POST adatok ellenőrzése
A$_GET
és $_POST
globálisokon keresztül érkező adatok esete picit bonyolultabb. A mostanában divatos *script alapú támadások esetén például nem engedhető meg a támadó kód elfogadása, másrészt spam üzenetek esetében a kulcsszavak előfordulási gyakoriságát érdemes kiszámítani. Mindkét elvárásnak megfelelő kódot írhatunk, ha a határérték szabályozható. Az alapértelmezett érték beállítását ne felejtsük el hozzáadni a konstruktorhoz! // határérték SPAM elemzéshez, db/KByte
private $limit_spam;
// konstruktor, az alapértelmezett értékek beállítása
function __construct() {
$this->limit_spam = 2.0;
$this->redirect_url = '';
}
// határérték módosítása
public function SetLimitForSPAM($new_value) {
if (is_numeric($new_value)) $this->limit_spam = $new_value;
}
// adatok ellenőrzése GET-POST sorrendben (gyakoriság számítása)
public function CheckInputData($key, $term) {
$val = isset($_GET[ $key ]) ? $_GET[ $key ] : '';
$val = isset($_POST[ $key ]) ? $_POST[ $key ] : $val;
if (empty($val)) return;
$found = 0;
$offset = 0;
do {
$pos = stripos($val,$term,$offset);
if ($pos!==false) {
$found++;
$offset += $pos+1;
}
} while($pos!==false && $offset<strlen($val));
$spam_score = 1024*$found/strlen($val);
if ($spam_score > $this->limit_spam) {
$this->BlockUser('SPAM detected, score='. round($spam_score,2) );
}
}
CheckInputData()
publikus függvény végzi. A függvény két paramétere a változó neve és a keresett kulcsszó. A számított gyakoriság határértékét saját tapasztalat alapján 2.0-ra állítottam be. Ez nekem bevált a címeket tartalmazó spam üznetekkel szemben. A rövid és csak 1-2 címet tartalmazó spam üzenetek tipikusan 50 db/K feletti sűrűségűek. Jelenleg a függvény találat esetén kiírja üzenetben a sűrűséget, ami fejlesztés során hasznos lehet, de nem javasolt ilyen információ megosztása a spam írókkal. Használatára példa:
<?php
include 'swfilter_class.php';
$myFireWall = new SWFilter();
$myFireWall->CheckURL(array('tp:/','tps:/'));
$myFireWall->CheckInputData('formtext','http:');
?>
Büntetés: fekete lista
A büntető karantén az üzemeltető vérmérséklete szerint automatikusan is alkalmazható. Az automatizált támadások (script-kiddie) rövid időn belül nagy számú variációt próbálnak ki. Hasznos lehet egy feketelista készítése, amivel bizonyos címeket tartósan vagy átmenetileg kizárhatunk a szolgáltatásból. Ehhez egy adatbázis is szükséges, pl. SQLite segítségével:sqlite> CREATE TABLE badguy (addr TEXT UNIQUE KEY, day TEXT KEY);
sqlite> .quit
UNIQUE
attribútum biztosítja, hogy felesleges másodpéldányok nélkül töltsük fel a táblát. Az adatbázis műveleteket egyetlen függvény is elvégezheti: // adatbázis műveletek: ellenőrzés, rögzítés, karbantartás
function ManageDB($action) {
switch ($action) {
case 'check':
$sqlstr = 'SELECT * FROM badguy WHERE addr="' $_SERVER['REMOTE_ADDR'] .'";';
break;
case 'add':
$sqlstr = 'INSERT INTO badguy VALUES("'. $_SERVER['REMOTE_ADDR'] .'","' . date('Ymd') .'");';
break;
case 'refresh':
$sqlstr = 'DELETE FROM badguy WHERE day<'. date('Ymd', strtotime('-2 week')) .';';
break;
}
if (!$this->db) {
$this->db = new PDO('sqlite:spamdb.dat');
}
if ($this->db) {
$result = $this->db->query($sqlstr);
if ($action=='check') {
$row = $result->fetch(PDO::FETCH_ASSOC);
if ($row['addr']==$_SERVER['REMOTE_ADDR']) return true;
}
}
}
PDO
osztályon keresztül érjük el. A függvény paramétere szerint összeállított SQL utasítás A: ellenőrzi a látogató IP címét, B: rögzíti az új IP címet vagy C: törli a 2 hétnél régebbi bejegyzéseket. Ez utóbbi határidő ízlés szerint módosítható. Ha az automatizált támadásokat akarjuk csak kivédeni, akkor pár óra is elegendő. Ha azonban van pár zombi PC és onnan próbálkozik egy program, akkor érdemes több időt hagyni. Egy zombi gép felderítése és semlegesítése több napot is igénybe vehet. Végezetül a tábla karbantartás műveletét (refresh
) pedig időzített módon (pl. cron) illik lefuttatni. A fenti belső függvény két helyen biztosan szerepelhet, a cím rögzítését tehetjük a BlockUser()
függvénybe, míg az ellenőrzésre újat kell készíteni. // ez a belső függvény avatkozik közbe
function BlockUser($message,$addtodb) {
if ($addtodb) $this->ManageDB('add');
if (empty($this->redirect_url)) {
header('HTTP/1.0 403 Forbidden');
echo $message . "\n";
} else {
header('Location: '. $this->redirect_url);
}
exit;
}
// feketelista ellenőrzése
public function TestBlacklist() {
if ($this->ManageDB('check')) {
$this->BlockUser('Blacklisted.',false);
}
}
BlockUser()
függvénynek. A második eldönti, hogy rögzítsen-e IP címet. Amikor feketelistán szerepel a cím, felesleges megpróbálni az ismételt rögzítését. A többi esetben pedig egészítsük ki a függvényhívást egy true
második paraméterrel. Az adatbázissal kiegészített tűzfal rendszer felhasználására egy példa:<?php
include 'swfilter_class.php';
$myFireWall = new SWFilter();
$myFireWall->TestBlacklist();
$myFireWall->CheckURL(array('tp:/','tps:/'));
$myFireWall->CheckInputData('formtext','http');
?>
<?php
class SWFilter {
private $limit_spam;
private $redirect_url;
private $db;
function __construct() {
$this->limit_spam = 2.0;
$this->redirect_url = '';
}
function BlockUser($message, $addtodb) {
if ($addtodb) $this->ManageDB('add');
if (empty($this->redirect_url)) {
header('HTTP/1.0 403 Forbidden');
echo $message . "\n";
} else {
header('Location: '. $this->redirect_url);
}
exit;
}
// SQLite spamdb : 'CREATE TABLE badguy (addr TEXT UNIQUE KEY, day TEXT KEY);'
function ManageDB($action) {
switch ($action) {
case 'check':
$sqlstr = 'SELECT * FROM badguy WHERE addr="'. $_SERVER['REMOTE_ADDR'] .'";';
break;
case 'add':
$sqlstr = 'INSERT INTO badguy VALUES("'. $_SERVER['REMOTE_ADDR'] .'","' . date('Ymd') .'");';
break;
case 'refresh':
$sqlstr = 'DELETE FROM badguy WHERE day<'. date('Ymd', strtotime('-2 week')) .';';
break;
}
if (!$this->db) {
$this->db = new PDO('sqlite:spamdb.dat');
}
if ($this->db) {
$result = $this->db->query($sqlstr);
if ($action=='check') {
$row = $result->fetch(PDO::FETCH_ASSOC);
if ($row['addr']==$_SERVER['REMOTE_ADDR']) return true;
}
}
}
public function SetRedirectURL($new_value) {
$this->redirect_url = $new_value;
}
public function CheckURL($terms) {
$url = isset($_SERVER['PATH_INFO']) ? substr($_SERVER['PATH_INFO'],1) : '';
if (!isset($url) || strlen($url)<2) {
$url = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
}
$found = false;
for ($i=0; $i<count($terms) && $found==false; $i++) {
if (stripos($url,$terms[$i])!==false) $found=true;
}
if ($found==true) {
$this->BlockUser('Illegal expression found.',true);
}
}
public function SetLimitForSPAM($new_value) {
if (is_numeric($new_value)) $this->limit_spam = $new_value;
}
public function CheckInputData($key, $term) {
$val = isset($_GET[ $key ]) ? $_GET[ $key ] : '';
$val = isset($_POST[ $key ]) ? $_POST[ $key ] : $val;
if (empty($val)) return;
$found = 0;
$offset = 0;
do {
$pos = stripos($val,$term,$offset);
if ($pos!==false) {
$found++;
$offset += $pos+1;
}
} while($pos!==false && $offset<strlen($val));
$spam_score = 1024*$found/strlen($val);
if ($spam_score > $this->limit_spam) {
$this->BlockUser('SPAM detected, score='. round($spam_score,2) ,true);
}
}
public function TestBlacklist() {
if ($this->ManageDB('check')) {
$this->BlockUser('Blacklisted.',false);
}
}
}
?>
Összefoglalás
A bemutatott PHP osztály egyszerű tűzfal funkciókra képes, mint pl. címsor ellenőrzése adott kifejezésekre, kulcsszavak szűrése a beérkező adatokon, IP cím alapján történő kitiltás (spamdb) és türelmi idő után a tiltás automatizált feloldása. Olyan oldalakon javasolható a használata, ahol jelentős számú betörési kísérlet vagy spam szemetelés zajlik rövid időn belül, és ezek kiszűrése komolyabb erőforrásokat szabadítana fel. Szintén javasolható olyan oldalakon, ahol a tartalom minősége és megbízhatósága kiemelkedően fontos. A tűzfal szabályokat célszerű saját tapasztalatok és napló elemzések alapján összeállítani.Fontos megemlíteni, hogy már léteznek elérhető árú tűzfal modulok a népszerű alkalmazásokhoz (pl. WordPress, Drupal). A fenti osztály kódját elsősorban gondolatébresztőnek szántam.
(A cikk ikonjához Ruzzilla New York City Firewall c. fotóját kölcsönöztük.)
tetszett
Köszönöm
Nincs mit
Gratulálok
Tetszik ez a fajta megközelítés, azt hiszem még kiegészíteném web2-es dolgokkal. Spam-eknél feldolgoznám a spam-et, és fekete listára tenném a benne szereplő url-eket is. Az átcsúszott spameket meg fel lehetne deríttetni a felhasználókkal, pl 2 spam-nek minősítés után ellenőrizné a levelet a rendszer, hogy van e benne url, aztán spam-nek minősítené vagy tovább küldené az adminoknak.
IP címnél azt mondják, hogy nem szerencsés a ban, bár ezt döntse el mindenki saját maga, biztos, hogy van olyan eset, amikor szükséges.
SPAM
Kiváló
hasznos, de nem mindig életszerű
ezek a megoldások szerintem nem életszerűek, mert túl nagy terhelést róhatnak a szerverre, a helytelen inputokat máshol és más formában kell szerintem szűrni (validátorokkal, nem okvetlenül PHP szinten stb.), én legalábbis nagy terhelésű portáloknál nem alaklmaznék ehhez hasonló elgondolásra épülő megoldást.
Tudom, hogy ezek a kódok csak egy szemléltető példa gyanánt szolgálnak, de nekem maga a megközelítés sem tetszik túlzottan. A szűrő metodikák (POST, GET filterezése, URL cleaning stb.) természetesen nagyszerűek, de egy rendszerben nagyon jól meg kell választani azokat a pontokat ahol a szerver felé érkező input adatokat szűrjük stb.
Amúgy jó cikk, kiindulási alapnak megfelelő lehet és már az is pozitívum, hogy ha egy fejlesztő ügyel ezekre... :)
részben
Szerintem egy ilyen elő szűrőnek nem is kell input adatokat validálni, mert nem ez a feladata. Arra gondoltam, hogy a nyilvánvalóan kártékony kísérleteknek nem is kellene eljutni az űrlap adatok ellenőrzéséig.
Nyilván
Nyilvánvalóan az a logikus, ha nem végeztetünk felesleges munkát a szerverrel. A post/get validálási helyét meg sok esetben nem is olyan egyszerű meghatározni. Pl én már hallottam olyat, hogy script tageknél a kiírásnál raknak rá egy filtert, de csak akkor, ha a kimenet html stbstb...
kimenet szűrése
Yepp
Mondjuk én úgy gondolom, hogy a bejövő templatet (bbcode, etc..) a kimenő szűrők alakítsák át az oldal lekérésénél, mert így könnyen lehet módosítani a kimenő szűrőket, tehát a kinézetet. Pl ha a félkövér szövegnek piros színt akarok, akkor csak egy szűrőt kell átírnom, nem pedig az összes addigi commentet. Szóval sokkal dinamikusabb az oldal, ha bizonyos dolgokat a kimeneten szűrünk. (Cachelni meg nyilván kell ilyenkor.)
CSS
Hát
mod_security
hoppá
http://www.modsecurity.org/documentation/Ajax_Fingerprinting_and_Filtering_with_ModSecurity_2.0.pdf
Ha van tapasztalatod vele, nem írnál róla esetleg?
Háááát
???
El nem tudom képzelni, hogy ilyet mi célból kellhet???
A cikkben sokat markoltál,
Ha egy 3rd party portálmotort használsz, akkor sem kell megvárni a következő verziót. Azokra a résekre, amikre botokat írnak, már van pár soros, könnyen alkalmazható fix (patch) is. Vagy egyszerűen a regisztrációs formba beszárhatsz egy plusz mezőt "ide írd be hogy cseber", és máris lepattannak a botok az oldaladról.
A query string ellenőrzésre itt egy ötlet: a bementi filterrel nem tiltólistára teszed a próbálkozót, hanem csak összekavarod az URL-t. Pl:
http://
-bőlINGYOMBINGYOMhttp://
-t csinálsz (ezzel meg tudod védeni a remote include-tól a leglyukasabb motort is), majd egy output buffering függvényben visszaforgatod az URL-eket.