Question

Récemment, un de mon serveur obtient un ciblage par un certain nombre d'attaques DOS (milliers de demandes / min) par une IP chinoise (ces IP ne sont pas toujours les mêmes).

Donc, au début de mon cadre, j'ai fait une petite fonction à bloquer après une IP s'il a fait trop de demandes.

function firewall() {
  $whitelist = array('someips');

  $ip = $_SERVER['REMOTE_ADDR'];

  if (in_array($ip,$whitelist))
    return null;

  if (search($ip,$pathToFileIpBanned))
    die('Your ip did too many requests')

  appendToFile($ip,$pathTofileIpLogger); //< When the file reaches 13000 bytes truncate it

  if (search($ip,$pathTofileIpLogger) > $maxRequestsAllowed)
     appendToFile($ip,$pathToFileIpBanned);   
}
  • Fondamentalement, le script vérifie si l'IP actuel se trouve dans un fichier `` ipblocké '' s'il est trouvé qu'il meurt.
  • S'il n'est pas trouvé, il ajoute l'IP actuel à un enregistreur de fichiers «iPlogger».
  • Après cela, cela compte les événements de l'IP dans le fichier iPlogger s'ils sont> $ max, il bloque cette IP en ajoutant l'IP au fichier ipblocked

ATM fonctionne. Il a interdit certains ips chinois / tw

Le goulot d'étranglement de ce script est la fonction de recherche qui doit compter les occurrence dans un fichier d'une chaîne (l'IP). Pour ces raisons, je garde bas le fichier (le fichier iPlogger est tronqué dès qu'il atteint 600-700 IPS enregistré)

Bien sûr, pour ajouter des IP dans le fichier sans avoir à se soucier de l'état de course, je le fais comme ceci:

file_put_contents($file,$ip."\n",FILE_APPEND | LOCK_EX);

Le seul problème avec lequel je rencontre est le pople derrière Nat. Ils ont tous la même IP mais leurs demandes ne doivent pas être bloquées

Était-ce utile?

La solution

Un code de fichier / sérialisation très basique, vous pouvez utiliser comme exemple:

<?php
$ip = $_SERVER['REMOTE_ADDR'];

$ips = @unserialize(file_get_contents('%path/to/your/ipLoggerFile%'));
if (!is_array($ips)) {
  $ips = array();
}

if (!isset($ips[$ip])) {
  $ips[$ip] = 0;
}

$ips[$ip] += 1;
file_put_contents('%path/to/your/ipLoggerFile%', serialize($ips));

if ($ips[$ip] > $maxRequestsAllowed) {
  // return false or something
}

Bien sûr, vous devrez l'intégrer d'une manière ou d'une autre dans votre firewall fonction.

Autres conseils

Bien que cela arrête les demandes avant de faire quelque chose de plus lourd, comme DB Reads et autres, vous voudrez peut-être envisager de réduire cela un niveau vers le serveur Web ou encore plus d'un pare-feu logiciel / matériel.

Les niveaux inférieurs traiteront avec beaucoup plus gracieusement et avec beaucoup moins de frais généraux. N'oubliez pas qu'en élevant PHP, ils consomment toujours l'un de vos travailleurs pendant un certain temps.

Voici mes quelques notes, j'espère que vous les trouverez utiles.

À mon avis, le pare-feu de fonction fait trop et n'est pas très spécifique dans son nom. Il gère à la fois la sauvegarde de la propriété intellectuelle / les visites, mettant fin au script ou ne faisant rien. Je m'attendais à ce que le mur soit défini lors de l'appel de cette fonction.

J'irais pour une approche plus orientée objet, où le pare-feu, n'est pas nommé pare-feu, mais quelque chose comme la liste noire.

$oBlackList = new BlackList();

Cet objet serait responsable de la liste noire elle-même, mais rien de plus. Il serait en mesure de dire si une adresse IP se trouve sur une liste noire, implémentez ainsi une fonction comme:

$oBlackList = new BlackList();
if ($oBlackList->isListed($sIpAddress)) {
    // Do something, burn the intruder!
}

De cette façon, vous pouvez être créatif dans la façon dont vous souhaitez gérer et vous n'êtes pas limité au corps de votre fonction. Vous pouvez étendre l'objet avec une fonction pour ajouter une adresse à la liste. $oBlackList->addToList($sIpAddress); peut-être.

De cette façon, la manipulation de la quantité de visites, ou de son stockage ne se limite pas à votre corps de pare-feu. Vous pouvez implémenter le stockage de la base de données, le stockage de fichiers (comme vous utilisez maintenant) et changer à tout moment sans invalider votre liste noire.

Quoi qu'il en soit, juste la divagation!

Vous devez créer pour chaque fichier IP bloqué. Par là que vous pouvez bloquer le visiteur à travers .htaccess comme suit:

# redirect if ip has been banned
ErrorDocument 403 /
RewriteCond %{REQUEST_URI} !^/index\.php$
RewriteCond /usr/www/firewall/%{REMOTE_ADDR} -f
RewriteRule . - [F,L]

Comme vous pouvez le voir, il ne permet qu'à l'accès au index.php. Par là que vous pouvez faire un simple file_exists() Dans la première ligne avant que les demandes de DB lourdes ne soient faites et vous pouvez lancer un CAPTCHA de déverrouillage IP pour éviter le blocage permanent des faux positifs. Vous avez une meilleure expérience utilisateur par rapport à un simple pare-feu matériel qui ne renvoie aucune information ou n'a pas de mécanisme de déverrouillage. Bien sûr, vous pouvez lancer un fichier texte HTML simple (avec un fichier PHP comme formulaire cible) pour éviter que le PHP l'analyseur fonctionne également.

En ce qui concerne DOS, je ne pense pas que vous ne devriez compter que sur les adresses IP car cela entraînerait de nombreux faux positifs. Ou vous avez un 2ème niveau pour les IP de la liste blanche. Par exemple, si une IP a été débloquée plusieurs fois. Quelques idées pour bloquer les demandes indésirables:

  1. Est-ce un humain ou un robot? (HTTP_USER_AGENT)
  2. Si le cheniller, est-ce que cela respecte robots.txt?
  3. S'il est humain, accéde-t-il à des liens qui ne sont pas visités par les humains (comme des liens qui sont rendus invisibles via CSS ou quittent la gamme ou les formes visibles ...)
  4. Si Crawler, qu'en est-il d'une liste blanche?
  5. Si humain, ouvre-t-il des liens comme un humain? (Exemple: dans le pied de page de Stackoverflow, vous trouverez tour help blog chat data legal privacy policy work here advertising info mobile contact us feedback. Aucun humain n'ouvrira 5 ou plus, je pense, mais un mauvais robot pourrait = bloquer son IP.

Si vous voulez vraiment compter sur IP / Min, je suggère de ne pas utiliser LOCK_EX et un seul fichier car il en résultera un goulot d'étranglement (tant que la verrouillage existe longtemps que toutes les autres demandes doivent attendre). Vous avez besoin d'un fichier de secours tant qu'il existe un verrou. Exemple:

$i = 0;
$ip_dir = 'ipcheck/';
if (!file_exists($ip_dir) || !is_writeable($ip_dir)) {
    exit('ip chache not writeable!');
}
$ip_file = $ip_dir . $_SERVER['REMOTE_ADDR'];
while (!($fp = @fopen($ip_file . '_' . $i, 'a')) && !flock($fp, LOCK_EX|LOCK_NB, $wouldblock) && $wouldblock) {
    $i++;
}
// by now we have an exclusive and race condition safe lock
fwrite($fp, time() . PHP_EOL);
fclose($fp);

Cela en résultera un fichier appelé 12.34.56.78_0 Et s'il frappe un goulot d'étranglement, il créera un nouveau fichier appelé 12.34.56.78_1. Enfin, vous n'avez besoin que de fusionner ces fichiers (respectez les verrous!) Et vérifiez à de nombreuses demandes dans une période donnée.

Mais maintenant, vous êtes confronté au prochain problème. Vous devez commencer un chèque pour chaque demande. Pas vraiment une bonne idée. Une solution simple serait d'utiliser mt_rand(0, 10) == 0 Avant de commencer un chèque. Une autre solution consiste à vérifier le filesize() Nous n'avons donc pas besoin d'ouvrir le fichier. Cela est possible car la taille des fichiers augmente par chaque demande. Ou vous vérifiez le filemtime(). Si le dernier changement de fichier est effectué dans la même seconde ou il y a une seconde. Ps Les deux fonctions sont égales rapidement.

Et par là, je viens à ma dernière suggestion. Utiliser seulement touch() et filemtime():

$ip_dir = 'ipcheck/';
$ip_file = $ip_dir . $_SERVER['REMOTE_ADDR'];
// check if last request is one second ago
if (filemtime($ip_file) + 1 >= time()) {
    mkdir($ip_dir . $_SERVER['REMOTE_ADDR'] . '/');
    touch(microtime(true));
}
touch($ip_file);

Vous avez maintenant un dossier pour chaque IP qui pourrait être une attaque DOS contenant le microtime de sa demande et si vous pensez qu'il contient à plusieurs de ces demandes, vous pouvez bloquer l'IP en utilisant touch('firewall/' . $_SERVER['REMOTE_ADDR']). Bien sûr, vous devriez nettoyer périodique le tout.

Mon expériences (allemand) L'utilisation d'un tel pare-feu est très bonne.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top