new cv 2024
                   
|


Ecocondeption Web : Stratégie de Firewall php efficace pour sites mutualisés, petit sites

Face à la recrudescence des tentatives d'attaques ddos / scans de failles / tentatives d'injections sur de nombreux sites clients je viens à m'interogger :

- Pourquoi les hébergeurs ne proposent pas de solutions anti-ddos, anti scan de failles natives à tous les hébergements ?

Il s'agit d'une chose très embarassante à gérer à laquelle j'ai été confronté très rapidement en début de carrière ..
Le client qui vous appelle et vous dit : je ne parviens plus à me connecter à mon site ..

Une petite visite en ssh sur la console montre un loadavg proche des 220, tous les cpus utilisés, plusieurs milliers de requêtes par secondes, avec une utilisation de la ram qui aura tendance à déclencher des OOM sur le processus mysql ..


Il convient alors d'engager un garde à la sécurité afin de réguler le traffic sur son site ( et d'empêcher des intrus de repérer des failles pour rentrer dans son batiment .. )

Idéalement ce garde doit dissuader le maximum de tentatives de répérages ... Ainsi si votre client ajoute des plugins wordpress non sécurisés à ce dernier, alors vous augmenterez drastiquement le temps nécessaire à n'importe quel bot avant de trouver quelconque faille, vu que ce dernier ne pourra que ré-essayer l'heure suivante, selon la configuration .. :)



- Un fail2ban combiné avec un redis me semble être le pré-requis minimum afin de sastisfaire ces besoins ( et permettrait donc de réaliser des économies d'énergies en neutralisant ce traffic )

- J'ai donc à cet effet réalisé une lib que j'adapte ainsi à tous mes projets afin de réduire leur consommation intrasèque, permettant donc de diminuer le nombre de requêtes par 10 => permettant ainsi de n'avoir qu'un traffic légitime => ce qui me permet de baisser les ressources nécessaires pour faire tourner chacun de ces projets ..

- Nb pour les personnes n'ayant pas redis vous pouvez effectuer en php un simple
touch('../ban/'.$ip',time()+$tempsBannissement);
// si ce dernier existe vous finaliser la requête, si la date ( dans le futur ) du fichier est dépassé, vous l'effacez, tout simplement ( je procédais ainsi à mes débuts lorsque les sites des clients demeuraient sur des hébergements mutualisés ... 1) prérequis : avoir une instance de redis contactable par votre applicatif, et en option, fail2ban (apt-get install fail2ban) afin de bloquer les ip avant de donner les requêtes à php 2) Toujours mettre son application dans un dossier site.fr/public qui n'expose aucun fichier sensible, exemple : - monsupersite.fr/.ssh - monsupersite.fr/.git - monsupersite.fr/conf.php - monsupersite.fr/vendor ne doivent jamais être accessible 3) Faire en sorte que toutes les erreurs 404 passent bien par un frontController : Pour apache: ErrorDocument 404 /index.php?e=404&b=1 Pour nginx: error_page 404 = /index.php?e=404; 4) composer install alptech/wip

6) firewall.php

<?php // firewall.php = à intégrer dans votre index, frontcontroller, ou auto_append rajouter ces lignes avant de démarrer tout votre applicatif
// Si vous disposez d'autre points d'entrée ( autre fichiers php, vous pouvez soit inclure ce dernier, soit ajouter une directive auto_prepend_file afin de l'executer à chaque requête

use Alptech\Wip\Fun as a;
require_once'vendor/alptech/wip/fun.php';
// remplisser cette configuration
a::conf(['redisHost'=>'127.0.0.1','redisPass'=>'pass','redisPort'=>6379]);




if (a::rexists('blocked:' . a::$ip)) {//   ip déjà bloquée, on renvoie une 404 bête
   header('HTTP/1.0 404 Not Found', true, 404);
   if (function_exists('fastcgi_finish_request')) {fastcgi_finish_request();}
   //file_put_contents('ban.log',"\n".a::$ip,8);
}

$ip = a::$ip;$a = $_SERVER;$ua = $a['HTTP_USER_AGENT'] ?? '';$x = explode('?', $a['REQUEST_URI']);$u = trim(array_shift($x)); $u4 = substr($u, -4);

DebutDuFirewall:  BlocageParUserAgent:

if ($ua && (stripos($ua, 'python-requests') !== false or stripos($ua, 'go-http-client') !== false or stripos($ua, 'Python-urllib') !== false or stripos($ua, 'QualysGuard') !== false or stripos($ua, 'ALittle Client') !== false)) a::fastBlock($ip, 'hour', 'ua');

BlocageParMethodesCustom:

if (isset($a['REQUEST_METHOD']) && !in_array($a['REQUEST_METHOD'], ['HEAD', 'OPTIONS', 'GET', 'POST'])) {/* Kick off : PUT|DELETE|PATCH  ,  whitelist : HEAD && OPTIONS for xhr preflight*/
    a::fastBlock($ip, 'hour', '#' . __LINE__);
 }

if (strpos($a['REQUEST_URI'], 'php://')) {
    a::fastBlock($ip, 'year', __LINE__);
}
if (stripos($a['REQUEST_URI'], '/vendor/') !== FALSE) {
    a::fastBlock($ip, 'day', 'vendor#' . __LINE__);
}
if (stripos($a['REQUEST_URI'], '/.git/') !== FALSE) {
    a::fastBlock($ip, 'day', 'git#' . __LINE__);
}
if (strpos($u, '.svn/') !== FALSE) {
    a::fastBlock($ip, 'hour', __LINE__);
}

$isNotFoundPage = ( $_SERVER['REDIRECT_STATUS'] ?? $_SERVER['status']) == 404 || $_GET['e'] == 404 );

if($isNotFoundPage){   // Les scanneurs de failles, notamment envoient des payloads sur des tas d'url de failles connues ...
   if (isset($a['REQUEST_METHOD']) && !in_array($a['REQUEST_METHOD'], ['HEAD', 'GET'])) {
      a::fastBlock($ip, 'day', __LINE__);
   }
// une erreur 404 sur un fichier avec une exension d'executable ou de fichiers de configuration est hautement suspicieuse ...
   if (in_array($u4, ['.env', '.cgi', '.asp', '.dat', '.cfm', '.jsa', '.php', '.exe])) {
       a::fastBlock($ip, 'hour', __LINE__);
   }

// on ne bloque pas toutes les ips sur des 404, certains bots vérifient cela afin de s'assurer que vous n'ayez pas recounrs à du blackhat
}

// Motifs d'injections sql, commande, byzarreries, upload de fichiers éxecutables ...
if(($reason4ban = a::firewall($a['REQUEST_URI'])) && $reason4ban){
    a::fastBlock($ip, 'day', $reason4ban);
}


if(false && 'bonus, vous avez cloudflare en reverse proxy devant votre site, alors vous pouvez directement bannir cette ip au travers de leur api'){
   $url = 'https://api.cloudflare.com/client/v4/accounts/xyz/firewall/access_rules/rules';
   $headers = ['Authorization: Bearer xyz',];
  $opt = [CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false];// faster no ssl check :)
   $payload = ['configuration' => ['target' => 'ip', 'value' => $ip], 'mode' => 'challenge', 'notes' => $x];
   $a = a::cuj($url, $payload, $opt, $headers);//assumed as true :)
}

7) Optionnel : Fail2ban

Ajoutez ces lignes à votre /etc/fail2ban/jail.local {
[sshd]
logpath=/var/log/auth.log
enabled=true

[sshd-ddos]
logpath=/var/log/auth.log
enabled=true

[min1]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 60

[min5]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 300

[hour]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 3600
maxretry = 2

[day]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 86400

[week]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 604800

[week2]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 1209600

[month]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 2678400

[month3]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 8035200

[year]
enabled = true
action = iptables-allports[name=HTTP,protocol=all]
bantime = 31536000
}
#on ajoute toutes ces jails avec definitions minimales :)
echo '[Definition]'>/etc/fail2ban/filter.d/week2.conf;
echo '[Definition]'>/etc/fail2ban/filter.d/month3.conf;
echo '[Definition]' >  /etc/fail2ban/filter.d/min1.conf
echo '[Definition]' >  /etc/fail2ban/filter.d/min5.conf
echo '[Definition]' >  /etc/fail2ban/filter.d/hour.conf
echo '[Definition]' >  /etc/fail2ban/filter.d/day.conf
echo '[Definition]' >  /etc/fail2ban/filter.d/week.conf
echo '[Definition]' >  /etc/fail2ban/filter.d/month.conf
echo '[Definition]' >  /etc/fail2ban/filter.d/year.conf

Fail2ban.php ( php Fail2ban.php & )

<?php // Fail2ban.php, lancé sur le container / pod / prennant en charge votre traffic http consomme simplement la liste redis "toblock" afin d'inscrire les ip dans iptables, ce qui soulagera votre php-fpm

$c='toblock';
$r=new \Redis();$r->connect('127.0.0.1');// remplir les informations de connection à votre redis
while(true){
  $m=$r->blpop($c,86000);
  if($m){
    [$chan,$p]=$m;
    $p2=json_decode($p,true);
    shell_exec('fail2ban-client set '.$p2['jail'].' banip '.$p2['ip'].' 2>&1');
  }
}