Mise en œuvre de la limitation de débit et du blocage IP dans Willow CMS
Cet article de blog détaille la mise en œuvre de la limitation de débit et du blocage IP dans un CMS Willow, en se concentrant sur les composants clés du code et leur raison d'être. Ces informations sont destinées aux développeurs qui cherchent à mettre en œuvre des mesures de sécurité similaires dans leurs projets. Depuis que Willow a été mis en ligne pour ce site, il a été intéressant de consulter les journaux et de voir toutes les tentatives de Singapour pour trouver des exploits !
Limitation de débit
La limitation de débit est une technique de protection des ressources d'une application en limitant le nombre de requêtes qu'un client peut effectuer dans un laps de temps donné. Cette implémentation utilise le modèle middleware et le mécanisme de mise en cache de CakePHP pour plus d'efficacité.
RateLimitMiddleware
Le cœur de la logique de limitation de débit réside dans src/Middleware/RateLimitMiddleware.php . Lisez le code complet sur GitHub. Voici une répartition des éléments clés :
class RateLimitMiddleware implements MiddlewareInterface
{
// Use default configuration or that provided to the constructor
public function __construct(array $config = [])
{
$this->limit = $config['limit'] ?? 3;
$this->period = $config['period'] ?? 60;
// Specific some default routes that should be rate limited
$this->rateLimitedRoutes = $config['routes'] ?? [
'/users/login',
'/users/register',
'/articles/add-comment/',
];
}
// Process a server request and return a response.
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// Use CakePHP method to get the client IP (more on this later)
$ip = $request->clientIp();
$route = $request->getUri()->getPath();
if ($this->isRouteLimited($route)) {
$key = "rate_limit_{$ip}_normal";
$rateData = $this->updateRateLimit($key, $this->period);
if ($rateData['count'] > $this->limit) {
// ...
throw new TooManyRequestsException(
// ...
);
}
}
return $handler->handle($request);
}
// ... more methods below, see the full file on GitHub
}
Aspects clés de ce middleware :
- Itinéraires ciblés : Le
isRouteLimited()
La méthode vérifie si l'itinéraire actuel est soumis à une limitation de débit, permettant un contrôle granulaire. - Mise en cache pour plus d'efficacité :
updateRateLimit()
La méthode lit et incrémente le nombre de requêtes à partir du cache. Cela évite les requêtes de base de données à chaque requête, ce qui améliore les performances. Si le délai est écoulé, le compteur est réinitialisé. - Gestion des exceptions : lorsque la limite est dépassée, un
TooManyRequestsException
est levée, interrompant l'exécution et envoyant une erreur 404 au client, y compris leRetry-After
en-tête. - Journalisation : les violations sont enregistrées à des fins d’analyse et de surveillance.
- Configuration : Le nombre de requêtes autorisées et les périodes de temps sont récupérés à partir des valeurs de paramètres vous permettant de configurer cela depuis la zone d'administration.
- Prise en charge des caractères génériques : la gamme de routes à débit limité prend en charge les caractères génériques, vous pouvez donc inclure
pages/*
ouarticles/*
. Voir la méthode isRouteLimited() .
Blocage IP
Le blocage IP ajoute une couche de sécurité supplémentaire en empêchant les requêtes provenant d'adresses IP malveillantes connues. L'implémentation combine la mise en cache avec la recherche dans la base de données, gérant ainsi efficacement la liste des adresses IP bloquées.
IpBlockerMiddleware
Ce middleware src/Middleware/IpBlockerMiddleware.php gère le blocage et le suivi des activités suspectes. Lisez le code complet sur GitHub. Voici une liste des éléments clés :
class IpBlockerMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// ...
$clientIp = $request->clientIp();
if ($this->ipSecurity->isIpBlocked($clientIp)) {
// Return 403 Forbidden
}
if ($this->ipSecurity->isSuspiciousRequest($route, $query)) {
// Log suspicious activity
$this->ipSecurity->trackSuspiciousActivity($clientIp, $route, $query);
// Return 403 Forbidden
}
return $handler->handle($request);
}
}
Voici comment cela fonctionne :
- Blocage IP : Si l'IP requérante est dans la liste bloquée (vérifiée via
isIpBlocked()
), une réponse 403 Forbidden est immédiatement renvoyée. - Détection de requête suspecte : la
isSuspiciousRequest()
La méthode vérifie une liste de modèles d'URL suspects. Si une correspondance est trouvée, l'activité est enregistrée et suivie.
IpSecurityService
Le fichier src/Service/IpSecurityService.php gère la logique principale du blocage des adresses IP et de la détection des activités suspectes. Il utilise la mise en cache pour améliorer les performances et fournit un emplacement centralisé pour les fonctions liées aux adresses IP. Lisez le code complet sur GitHub. Voici une liste des éléments clés :
class IpSecurityService
{
private array $suspiciousPatterns = [/* list of regular expressions */ ];
public function isIpBlocked(string $ip): bool
{
// Caching logic ...
// Database lookup if not in cache
$blockedIp = $blockedIpsTable->find()
// ... expiry checks ...
->first();
// ... caching logic
return $blockedStatus;
}
public function blockIp(string $ip, string $reason, ?DateTime $expiresAt = null): bool
{
// ... database save to blocked_ips table and logging the event
}
public function isSuspiciousRequest(string $route, string $query): bool
{
// Regex matching against suspicious patterns
// ... logging ...
}
public function trackSuspiciousActivity(string $ip, string $route, string $query): void
{
// Increase suspicious activity counter using cache
// ... progressive blocking logic (if IP has offended in last 24 hours block for 48 hours) ...
}
}
Principales améliorations et fonctionnalités de ce service :
- Mise en cache : les deux
isIpBlocked()
ettrackSuspiciousActivity()
utiliser la mise en cache pour réduire la charge de la base de données. - Blocage progressif : Le
trackSuspiciousActivity()
La méthode met en œuvre un blocage progressif. Les récidivistes bénéficient de durées de blocage plus longues. - Liste complète des modèles : une liste détaillée des modèles suspects dans
$suspiciousPatterns
aide à détecter une variété de requêtes malveillantes. Je pense qu'à l'avenir, je les chargerai/mettrai en cache à partir de la base de données pour faciliter l'édition/l'ajout de nouvelles règles.
Prise en charge de l'équilibrage de charge
Le code prend en charge Willow CMS exécuté derrière un équilibreur de charge en faisant confiance aux en-têtes de proxy. Ceci est activé en définissant'Security.trustProxy'
àtrue
dans la page Admin->Paramètres de Willow CMS. Cela garantit que l'adresse IP du client et les autres informations de la demande sont correctement récupérées à partir des en-têtes transférés et nous utilisons le code du framework CakePHP intégré pour obtenir de manière fiable l'adresse IP du client.
if (SettingsManager::read('Security.trustProxy', false)) {
$request = $request->withAttribute('trustProxy', true);
}
Tests unitaires PHP
Il s'agit d'un code assez important, il doit donc être accompagné de quelques tests unitaires. Vous pouvez les lire dans RateLimitMiddlewareTest et IpBlockerMiddlewareTest . J'utilise des fournisseurs de données dans PHPUnit pour garder les tests propres et facilement actualisables.
IpBlockerMiddlewareTest
Ce cas de test se concentre sur laIpBlockerMiddleware
, couvrant les cas d'adresses IP bloquées, les statuts mis en cache, les blocs expirés, les requêtes suspectes et la traversée de chemin.
- Configuration de l'appareil : Le
$fixtures = ['app.BlockedIps']
la déclaration utilise un accessoire pour leBlockedIps
table, fournissant un ensemble de données cohérent pour tester les interactions de la base de données. - Railleur:
RequestHandlerInterface
est moqué pour isoler le comportement du middleware et simuler le traitement des requêtes. - Cas de test :
- testProcessWithBlockedIp : confirme que les adresses IP bloquées reçoivent une réponse 403.
- testProcessWithNonBlockedIp : vérifie que les adresses IP non bloquées passent.
- testProcessWithCachedBlockedStatus : valide que les statuts des blocs mis en cache sont respectés.
- testProcessWithExpiredBlockedIp : vérifie que les blocs expirés ne provoquent pas d'erreur 403.
- testProcessWithMissingRemoteAddr : confirme le comportement lorsque
REMOTE_ADDR
est absent. - testProcessWithSuspiciousRequest , testProcessWithEncodedSuspiciousRequest : vérifie la détection de modèles d'URL suspects, y compris les modèles codés.
- testMultipleSuspiciousRequestsLeadToBlock : simule plusieurs requêtes suspectes et vérifie que l'IP est finalement bloquée et enregistrée dans la base de données.
- Fournisseurs de données : pathTraversalProvider et sqlInjectionProvider offrent un moyen simple de tester différents vecteurs d'attaque sans duplication de code. Ces fournisseurs alimentent les méthodes de test paramétrées en données, augmentant ainsi la couverture des tests.
RateLimitMiddlewareTest
Cette suite de tests cibleRateLimitMiddleware
, en se concentrant sur la logique de limitation de débit, les routes génériques et la gestion différente des routes.
- Installation : Le
setUp
La méthode efface le cache « rate_limit » avant chaque test, garantissant ainsi un comptage précis du taux de transfert. Elle définit également des paramètres de limitation de débit (limit
etperiod
) pour les tests. - Cas de test :
- testRateLimitGeneralRoute : teste un itinéraire qui n'est pas limité en débit.
- testRateLimitSensitiveRoute : teste une route dont le débit est limité, en affirmant une
TooManyRequestsException
après avoir dépassé la limite. - testDifferentRoutesDoNotAffectRateLimit : valide que les différentes routes ont des limites de débit indépendantes, évitant ainsi les interférences involontaires.
- testRateLimitWildcardRoute : démontre la limitation de débit avec des routes génériques en utilisant PagesController comme exemple.
Ces cas de test illustrent les bonnes pratiques pour tester les intergiciels. Ils proposent des scénarios ciblés, exploitent les simulations lorsque cela est approprié et utilisent des fournisseurs de données pour tester efficacement plusieurs variantes.