Il va y avoir du ban !

Je disais il y de cela deux semaines, que j’avais profité du retour d’Unicoda chez OVH pour faire un tour du côté des logs du serveur. En continuant de creuser et d’analyser, l’intérêt de mettre en place un outil automatique, ou un processus d’analyse périodique, se fait clairement sentir.

En effet, en étudiant les résultats de l’agrégation des logs sur une dizaine de jours, je constate avec étonnement que l’url la plus demandée est xmlrpc.php. Pas de problème, la chose est connue, un ou plusieurs bots doivent encore être en train de s’amuser à essayer d’infiltrer la plateforme. En revanche, ce qui m’intrigue au plus haut point, c’est que le nombre d’appels soit si élevé, étant donné que je déploie systématiquement une configuration très agressive contre ceux qui spam les appels à cette page, à savoir ban IP d’une journée via fail2ban.

En allant regarder les logs de fail2ban, je constate que celui-ci se plaint de ne pas arriver à déterminer la date correcte d’un événement. Aie ! Les filtres fonctionnent correctement, mais la configuration n’est plus suffisante pour que le timestamp Apache du log soit bien compris par fail2ban. Après plusieurs essais, je décide d’en profiter pour mettre à jour fail2ban vers une version plus récente que celle fournie de base dans Debian 10, en suivant la procédure décrite sur cette page. Cela ne suffisant pas à résoudre le problème, je passe donc à la modification de mes filtres.

Pour protéger la page xmlrpc.php, j’obtiens finalement la configuration suivante:

[INCLUDES]
before = apache-common.conf

[Definition]
failregex = ^<HOST> -.* "(GET|POST|HEAD) /xmlrpc.php HTTP.*"
ignoreregex =
datepattern = ^[^[]*[({DATE})
              {^LN-BEG}

En ce qui concerne la page de login, la mise à jour de la configuration nous donne le filtre ci-dessous.

[INCLUDES]
before = apache-common.conf

[Definition]
failregex = ^<HOST> -.* "(GET|POST|HEAD) /wp-login.php HTTP.*"
ignoreregex =
datepattern = ^[^\[]*\[({DATE})
              {^LN-BEG}

Enfin, j’ai remarqué dans les logs qu’un bot testait des urls au pif, afin d’essayer de récupérer des éventuels fichiers de sauvegarde ou de configuration, à l’aide de requêtes HEAD qui terminent donc toutes en erreurs 404. J’en profite donc pour ajouter un nouveau filtre afin de traiter ce cas précis.

[INCLUDES]
before = apache-common.conf

[Definition]

failregex = ^<HOST> -.* "HEAD /.* HTTP.*" 404
ignoreregex =
datepattern = ^[^\[]*\[({DATE})
              {^LN-BEG}

Après ces quelques modifications, mon fail2ban fonctionne à nouveau correctement et il est vraiment satisfaisant de voir les bots se faire bannir automatiquement en temps réel. Cela devrait également permettre de soulager un peu le serveur, en évitant qu’il traite du trafic non désiré.

Depuis combien de temps fail2ban ne fonctionnait plus correctement ? Aucune idée… Et c’est là que le manque de monitoring se fait doucement sentir. Rien qu’avec une agrégation des logs, j’aurais été en mesure de détecter l’augmentation du nombre d’appel tentant de trouver une faille. A une époque, je recevais un mail quotidien de fail2ban résumant le nombre d’IPs bloquées et d’autres métriques similaires, malheureusement, lorsque j’avais tenté de réactiver l’envoi, le mail se faisait systématiquement attraper par les filtres de l’hébergeur, car contenant ce qui était identifié comme des liens vers du contenu malfaisant (spam, piratages, etc).

L’année 2021 s’oriente donc vers la question du monitoring, longtemps laissée de côté, faute de temps, ou pour des questions de priorité. Affaire à suivre. Les mécanismes de blocage automatique des attaquants sont donc à nouveau en place sur Unicoda et en totalité depuis le week-end dernier. Une bonne chose de faite, et qui ne devrait en aucun cas impacter les lecteurs !

Ban IP 11/2020 – Les gagnants

Depuis quelques jours, la quantité de spam du côté du formulaire de commentaire a explosé en quantité. On passe d’une centaine par semaine à une centaine par jour. Tout finit naturellement dans la file des indésirables, mais cela reste ennuyeux.
Voici donc les heureux gagnants d’un ban IP sans limitation de durée:

5.188.84.15
5.188.84.25
5.188.84.35
5.188.84.45
5.188.84.65
5.188.84.95
5.188.84.115
5.188.84.147 
5.188.84.220
5.188.84.221
5.188.84.233

Le spam n’est pas suffisamment régulier pour définir des règles strictes de filtrage dans fail2ban. Une petite recherche sur le net semble indiquer que les IPs sont bien identifiées comme responsable de spam et font donc partie de listes de blocage. Il pourrait donc être intéressant de s’interroger sur la possibilité de connecter un blocage par iptables, ou autre, sur une liste dynamique des IPs des spammeurs connus. Quels seraient alors les impacts sur les performances, vu la taille de certaines de ces listes ?

Migration vers Ansible Vault

Je l’avais évoqué plusieurs fois dans quelques articles, j’utilise ansible pour gérer l’installation automatique des services que j’auto-héberge. J’étais jusqu’à présent resté dans une gestion simple des mots de passe liés aux services (comprendre « exposé en clair dans les fichiers »). Vraiment pas terrible du point de vue sécurité, mais permettant de me concentrer sur la partie (ré-)installation automatique, là où réside le vrai gain de temps. Disposant de quelques jours de congés, je me suis donc attelé à ce sujet longtemps repoussé qu’est la migration des secrets vers le vault ansible.

Un vault ansible, en quelques mots, est un fichier chiffré contenant des pairs clé/valeur permettant de référencer un secret dans un script ansible.

Configuration

Côté implémentation, j’ai choisi de stocker mon vault dans le dossier group_vars/all/ et j’ai commencé en créant deux fichiers all.yml et vault_all.yml. Dans vault_all.yml, je stocke l’ensemble de mes secrets associés à un identifiant, par exemple, le mot de passe secretpassword associé à la clé vault_test_user_password donnera le fichier suivant :

---
vault_test_user_password: secretpassword

Ensuite, dans all.yml, je référence la variable du vault et l’expose dans une nouvelle variable. Cette étape n’est pas forcément nécessaire, il est tout à fait possible d’utiliser directement l’identifiant présent dans le vault. L’intérêt de cette méthode quelque peu répétitive au moment de la mise en place, réside dans le fait que l’on dispose d’un inventaire des variables stockées dans notre fichier vault, sans avoir besoin de déchiffrer le fichier vault pour parcourir son contenu. Voici donc, en reprenant l’exemple ci-dessus, le contenu de notre fichier de référence :

---
test_user_password: "{{ vault_test_user_password }}"

Maintenant que je dispose de mon dictionnaire de secrets, je peux passer à l’opération de chiffrement de ce dernier. Par défaut, ansible demandera le mot de passe à utiliser à chaque opération de chiffrement/déchiffrement du fichier, ce qui se révèle vite rébarbatif. Heureusement, il est possible de créer un fichier .vault_pass pour y stocker le mot de passe du vault. Évidemment, on prendra soin de l’ajouter à notre .gitignore, afin de ne pas commit cette information dans notre dépôt git. Dernière étape, référencer l’emplacement du mot de passe dans la configuration ansible, soit dans ansible.cfg, par ajout de la ligne :

[defaults]
vault_password_file = .vault_pass

Maintenant que tout est prêt, il convient de chiffrer le fichier vault. Pour effectuer cette opération, on utilisera la commande :

ansible-vault encrypt group_vars/all/vault_all.yml

Le fichier vault étant désormais chiffré, l’édition s’effectuera désormais avec :

ansible-vault edit group_vars/all/vault_all.yml

Parmi les autres opérations de la commande ansible-vault, on notera en particulier decrypt, view et rekey.

Utilisation

Maintenant que tout est configuré, je peux donc remplacer les références directes au mot de passe présent dans mes playbooks, par les nouvelles variables créées. Toujours avec l’exemple précédent, on passera de l’extrait de playbook suivant :

password: secretpassword

au nouveau contenu « sécurisé »:

password: "{{ test_user_password }}"

Il ne reste plus qu’à déclencher l’exécution de l’un des playbooks afin de vérifier que tout a été configuré correctement. Je pourrais m’arrêter là; les secrets de mes playbooks sont maintenant chiffrés dans un fichier dédié, tout va pour le mieux, mais un dernier point de faiblesse subsiste, le mot de passe du vault stocké en clair dans notre fichier .vault_pass. Évidemment, si un attaquant devait mettre la main sur ce fichier, il y a fort à parier que ce serait le cadet de mes soucis, mais voyons tout de même si je peux améliorer les choses.

Sécurisation du mot de passe Vault

En parcourant la documentation ansible liée au concept de vault, j’ai découvert que le paramètre de configuration vault_password_file est susceptible de prendre un fichier de script exécutable en entrée. Seule condition, le script doit renvoyer le mot de passe à utiliser. Étant donné que j’utilise déjà password-store en ligne de commande pour la gestion de mes mots de passe, j’y ai vu une occasion parfaite d’augmenter encore la sécurité en stockant le mot de passe du vault dans une entrée de mon password-store. Quelques recherches plus tard, j’obtiens donc le script suivant; script chargé de retourner le contenu de l’entrée ansible-vault.

#!/bin/bash

###################################
## Get password from password store
# Inspired by https://github.com/paulRbr/ansible-makefile/blob/master/pass.sh
###################################

if (command -v pass >/dev/null 2>&1)
then
    existingVault=$(pass "ansible-vault" || true)

    if [ -n "${existingVault}" ]
    then
        >&2 echo "Using passphrase found at 'ansible-vault' in your password store."
        echo "${existingVault}"
    else
        >&2 echo "No passphrase found at 'ansible-vault' in your password store."
        exit 0
    fi
fi

Ma configuration ansible devient donc (Ne pas oublier de rendre le fichier de script exécutable):

[defaults]
vault_password_file = ./get-vault-pass.sh

Autres améliorations

Il est possible d’avoir plusieurs vaults dans un projet ansible. Dans l’exemple précédent, toutes les entrées du vault sont partagées avec l’ensemble des playbooks, du fait d’appartenir au groupe all. Pour simplifier l’organisation, j’ai dans mon cas déplacé tous les secrets liés à unicoda et à son playbook de déploiement dans le dossier group_vars/unicoda/ et son vault dédié. De la même façon, on peut alors envisager d’améliorer le script pour utiliser un mot de passe différent pour chaque vault, chacun des mots de passe étant alors stocké de façon chiffrée dans password-store.

Par ailleurs, j’ai découvert pendant la mise en place de vault, qu’il existe un plugin ansible maintenu par la communauté et qui permet d’aller chercher directement le contenu d’un mot de passe dans password-store. Je n’ai pas poussé plus avant l’expérimentation, mais l’utilisation de ce plugin pourrait être une alternative valable à l’utilisation de vault.

Conclusion

Grâce à cette dernière amélioration, mes secrets ansible sont désormais stockés en lieu sûr et ne sont plus accessible au premier venu. C’est un élément de sécurité que j’encourage à mettre en place immédiatement, dès que le premier secret apparaît dans le dépôt de code.

Sources

Des Headers. En veux-tu ? En voilà !

J’ai commencé à me pencher sur HAProxy et à faire quelques tests, ce qui m’a conduit à m’intéresser aux entêtes de sécurité qu’un serveur Web peut renvoyer. Dans la même veine que SSL Labs, j’ai découvert securityheaders.io qui permet de faire rapidement le point sur les entêtes des réponses http d’un serveur pour une url donnée.

De mon côté, je me suis donc assuré de la présence des entêtes suivantes:

  • Referrer-Policy
  • Strict-Transport-Security
  • X-Content-Type-Options
  • X-Frame-Options
  • X-XSS-Protection

Configurées de la manière suivante :

http-response set-header Strict-Transport-Security "max-age=63072000;"
http-response set-header X-Content-Type-Options nosniff
http-response set-header Referrer-Policy no-referrer-when-downgrade
http-response set-header X-Frame-Options SAMEORIGIN
http-response set-header X-XSS-Protection 1;mode=block

Par ailleurs, j’en profite pour m’assurer que les informations concernant le serveur répondant à la requête ne soient pas renvoyées :

http-response del-header Server

Du coup, j’obtiens un petit A, étant donné que je n’ai pour l’instant pas intégré l’entête « Content-Security-Policy ».

Source: Securing haproxy and nginx via HTTP Headers.

[SFR] Faille sur le WPS de la box

Comme vous le savez certainement, une faille de sécurité a été découverte sur le WPS du WiFi des box de SFR et d’Orange. A priori, Orange aurait déployé un correctif, SFR étudierait la question.

J’ai eu l’occasion de tester l’attaque sur la box SFR de mes parents (avec leur autorisation). Son efficacité est impressionnante: 4 secondes montre en main pour la seule opération de récupération de la clé WPA. J’en ai donc profité pour désactiver le WPS dans l’interface d’admin et vérifier la neutralisation de l’attaque.

Tous les détails sont sur le forum crack-wifi.