J’ai repris le ZIP hucial_antibot fichier par fichier et j’ai vérifié la logique serveur, les entrées utilisateur, les signatures, le tri, le PoW, la base SQL, l’ACP et le JavaScript.
Résultat de l’audit
Pas de SQL injection évidente, pas de XSS évident, pas de RCE, pas de backdoor et pas de fuite de secret.
En revanche, j’ai trouvé 3 points que je corrigerais avant de considérer l’extension comme réellement durcie.
Gravité - Problème - Fichier
Haute - Challenge marqué solved réutilisable pendant sa durée de vie - captcha/antibot.php
Moyenne - Garbage collector pouvant provoquer une grosse suppression SQL en une seule requête - captcha/antibot.php
Moyenne - Création de challenges et comptage des abus insuffisamment bornés sous forte charge - captcha/antibot.php
Faible - Migration 1.1.x ne durcit pas le schéma/indexation - migrations/*.php
1/ Le challenge solved peut être rejoué
Les lignes concernés 240 - 244
Code : Tout sélectionner
if (!empty($this->challenge['solved']))
{
$this->solved = true;
return false;
}
Le problème est que lorsqu'un challenge est validé :
Code : Tout sélectionner
SET solved = 1,
solved_at = ...
mais il n'est pas supprimé ni invalidé cryptographiquement après utilisation. Le challenge reste donc valide jusqu'à son expiration, actuellement, soit 20 minutes :
Code : Tout sélectionner
'expires_at' => $now + 1200,C'est problématique car un utilisateur ou un bot qui a réussi le CAPTCHA peut potentiellement conserver et les réutiliser pendant la durée de vie du challenge.
Code : Tout sélectionner
hucial_ab_id
hucial_ab_sigC'est particulièrement intéressant pour les usages :
login ;
posting ;
report ;
autres actions utilisant le même type de CAPTCHA.
Le comportement est cohérent avec une partie du fonctionnement historique de phpBB : le CAPTCHA de phpBB conserve lui aussi un état solved. Mais dans ton extension, tu as explicitement présenté le challenge comme un mécanisme à usage unique /replay-controlled. Le code actuel ne garantit pas réellement cette propriété.
Il faut distinguer :
CAPTCHA résolu pour permettre l'affichage du formulaire suivant
autorisation CAPTCHA consommée lors de l'action protégée
La solution propre est d'ajouter un état d'utilisation et de refuser un challenge déjà consommé.
2/ Garbage collector dangereux sous forte charge
Lignes 65 - 68
Code : Tout sélectionner
if (mt_rand(1, 100) === 1)
{
$this->garbage_collect(0);
}puis 374–381
Code : Tout sélectionner
$sql = 'DELETE FROM ' . $this->table . '
WHERE expires_at < ' . (int) $now . $where_type;
$this->db->sql_query($sql);Le problème
Le principe du nettoyage aléatoire est acceptable. Mais la suppression est sans limite
Code : Tout sélectionner
DELETE ... WHERE expires_at < NOWSi ton forum subit une attaque importante et accumule plusieurs centaines de milliers de challenges expirés, un heureux tirage à 1 % peut déclencher une énorme suppression SQL.
Cela peut provoquer :
verrouillage ;
forte charge CPU/IO ;
temps d'exécution important ;
ralentissement du forum ;
timeout selon le SGBD.
La solution serait de faire des suppressions par lots, 500 à 1 000 lignes maximum par passage. Ou, selon le SGBD supporté, utiliser directement une suppression limitée lorsque c'est correctement abstrait par phpBB.
Code : Tout sélectionner
$sql = 'SELECT challenge_id
FROM ' . $this->table . '
WHERE expires_at < ' . (int) $now;
$result = $this->db->sql_query_limit($sql, 500);
$ids = array();
while ($row = $this->db->sql_fetchrow($result))
{
$ids[] = $row['challenge_id'];
}
$this->db->sql_freeresult($result);
if ($ids)
{
$sql = 'DELETE FROM ' . $this->table . '
WHERE ' . $this->db->sql_in_set('challenge_id', $ids);
$this->db->sql_query($sql);
}3/ La table peut grossir fortement pendant une attaque
Le point intéressant est ici :
Code : Tout sélectionner
$challenge = array(
'challenge_id' => bin2hex(random_bytes(16)),
...
);puis
Code : Tout sélectionner
INSERT INTO hucial_antibot_challengesLe challenge dure 20 minutes, donc un attaquant peut multiplier les sessions et provoquer :
session 1 → challenge
session 2 → challenge
session 3 → challenge
...
session N → challenge
Le challenge est bien lié à la session [incode]'session_id' => (string) $this->user->session_id,[/incode] ce qui est correct du point de vue sécurité. Mais cela signifie aussi qu'un bot qui fabrique énormément de sessions peut faire grossir la table.
Le nettoyage à 1 % n'est pas une protection contre cette situation ; c'est seulement du garbage collection.
4/ Les index sont bons mais peuvent être améliorés
Actuellement
Code : Tout sélectionner
'session_type' => array('INDEX', array('session_id', 'confirm_type')),
'expires' => array('INDEX', 'expires_at'),
'ip_created' => array('INDEX', array('ip_hash', 'created_at')),Mais les requêtes importantes sont
Code : Tout sélectionner
WHERE session_id = ?
AND confirm_type = ?
AND expires_at >= ?
AND solved = 0
ORDER BY created_at DESCet
Code : Tout sélectionner
WHERE ip_hash = ?
AND created_at >= ?Le deuxième est très bien couvert. Le premier pourrait bénéficier d'un index composite supplémentaire :
Code : Tout sélectionner
'session_lookup' => array(
'INDEX',
array('session_id', 'confirm_type', 'solved', 'expires_at', 'created_at')
),Il faut éviter de multiplier inutilement les index sur une table qui reçoit beaucoup d'INSERT.