Sans être un paranoïaque de la sécurité, je fais plutôt partie des gens qui entretiennent un hygiène informatique sérieuse. Et c'est bienvenu car c'est quand même mon métier.
En plus de 20 ans de pratique quotidienne je n'ai jamais découvert de compromission sur mes machines. Ça ne veut pas dire que je n'ai jamais fait de bourde : un mot de passe tapé « par réflexe » mais dans la mauvaise fenêtre, une petite fuite d'info dans des moteurs de recherches, etc.
Ceci étant, comme on le verra, ça n'empêche pas d'accumuler certaines mauvaises pratiques, en partie parce que la façon « moderne » de faire l'impose pour être efficace au quotidien.
Mais la compromission de sa station de travail, routinière pour les usagers de Windows, a un caractère exceptionnel pour un utilisateur de GNU/Linux de longue date comme moi. Et comme on va le voir, ça ressemble aussi à une mise à nu.
Note: Je partage mon expérience y compris dans le but d'améliorer ma réaction. N'hésitez pas à me contacter si je donne l'impression de passer à coté d'un gros truc.
Si ya un doute, ya pas de doute
Au détour d'un travail sur un projet de l'April je vois un subliminal mais inquiétant:
[1]+ Fini /home/francois/April/drupal2spip_lal/venv/bin/python3 /home/francois/.uds/_err.log > /dev/null 2>&1 (wd : ~)
(maintenant, wd : ~/April/spamotron)
J'ouvre ce fichier et très vite le doute s'installe :
import base64,lzma;exec(lzma.decompress(base64.b85decode("{Wp48S^xk9=GL@E0stWa761SMbT8$j;4$nE7F_@lh
[...]
Je remplace le exec
par un print
et j'obtiens un source python assez bien
écrit (surtout après le passage de
black), qui ressemble à un service de
télécommande.
Un ps fauxww
me montre que le script s'exécute encore dans un shell. Par
curiosité je regarde la liste des fichiers ouverts. Celle-ci se limite quasiment
au minimum vital pour un script python. Mais une connexion est ouverte :
$ lsof -p 3755
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
[...]
python3 3755 francois 4u IPv4 7991113 0t0 TCP renard:38026->199.247.5.158.vultr.com:3500 (ESTABLISHED)
Bon, ok. Il apparait via le ~/.bashrc
. Je le nettoie et vérifie qu'il ne
revient pas. J'en profite pour vérifier mes crontab
et files at
.
Il ne revient pas.
Pas de panique
« sur sa couverture on peut lire en larges lettres amicales la mention : PAS DE PANIQUE »
— Douglas Adams, The Hitchhiker's Guide to the Galaxy
Ok. Pas de panique. Pas d'urgence, on respire un coup et un prend un café.
Première chose : rapidement cerner l'origine dans le temps de la chose. En fouillant les backups je déduis le 3 août comme première valeur.
Deuxième chose : en parcourant le code je comprends que c'est un botnet. Le code ne comprend rien d'autre qu'une télécommande, mais le maitre du botnet a pu avoir accès à toute ma machine.
Troisième chose : convaincu que le bot a eu potentiellement accès à tous mes fichiers perso, je commence sans tarder à demander la révocation de mes accès :
- à mes collègues de travail ;
- à mes collègues associatifs.
Je fais retirer mes accès à pas moins de quatre parcs de machines complètement
disjoints, chacun contenant des dizaines de serveurs, de kvm
et de conteneurs.
Mes machines perso attendront, elles ne sont plus à ça près.
Comprendre
Le chemin de l'exécutable python désigne clairement un venv pour un projet que j'ai mené pour l'April en juillet/août. Je recherche sans succès les traces de l'origine du trojan.
J'examine le code à la recherche d'éléments spécifiques à chercher dans Google. Rien d'évident. Je recherche « pip .uds _err.log ». Je tombe sur une page dans un dialecte asiatique datée du 6 aout : https://guanjia.qq.com/news/n1/2583.html
Je la fait traduire par Google. Malgré le résultat très approximatif de la traduction, ça raconte assez bien la chose. Je remarque les mêmes serveurs impliqués, je retrouve certains des extraits de code montrés.
Leur explication ? Une opération de phishing impliquant PyPI au 31 juillet et
ciblant le paquet request
.
Request. Là mon cerveau fait tilt.
Mais putain quel con !
La semaine dernière j'ai finalisé drupal2spip_lal
et testé sa mise en prod sur
une Debian nue. J'ai été amené à corriger des
dépendances,
notamment requests
. Comme un con pendant le dev j'avais fait une fôte
(écrivant
request
)
et c'était passé inaperçu. Je ne l'avais remarqué qu'au déploiement à cause
d'une erreur de pip
.
En fait ce qui s'était passé est qu'un développeur attentionné et conscient que
parfois les gens se trompent a publié un paquet nommé request
dont le principe
est le suivant :
import sys,requests
#Some people might mistake this module for the popular python requests module
#So let's add an alias of that
sys.modules[__name__] = sys.modules['requests']
C'est gentil, inoffensif mais peu productif : je fais partie de ceux qui préfèrent avoir des erreurs et les corriger très tôt plutôt que des bricolages d'adaptation.
Par contre ce qui n'est pas du tout inoffensif a été le script d'installation.
Je n'ai pas la copie sous la main mais visiblement il s'est chargé a minima de
modifier mon ~/.bashrc
et d'installer la première charge dans
~/.uds/_err.log
.
Tout ça pour une bête typo suivie d'un make update
dans un contexte de
pratiques discutables.
Résultat
Ma station de travail -- c'est-à-dire la machine avec laquelle je passe plus de temps qu'avec n'importe qui d'autre sur terre -- est compromise. J'ai hébergé un trojan à mon insu pendant 22 jours. C'est un cataclysme assez élevé sur l'échelle des drames.
Il n'y a pas à mégoter sur l'étendue de la situation : j'ai offert un shell python à un inconnu qui en avait envie. Il a eu accès à potentiellement tout sur ma machine. Il va falloir réinstaller et révoquer tout ce qui est possible.
Heureusement l'essentiel de mes secrets sont protégés :
- fichiers importants chiffrés GPG ;
- clés GPG et SSH toutes protégées par une passphrase ;
- trousseau de mot de passe Firefox protégé par une passphrase.
Mais tout n'est pas rose :
- des scans administratifs en clair ;
- les mots de passe courriels en presque clairs : mon MUA sait gérer une passphrase dans les versions modernes, mais j'avais raté l'info donc je ne l'ai mis en œuvre que ce jour ;
- j'utilise des agents SSH et GPG qui donnent un potentiel accès total sans passer par mes passphrases. Idem pour l'accès root à ma machine. Je suppose même possible d'exfiltrer la totale en examinant la carte mémoire des agents.
Bon, voilà pour la théorie. En pratique je ne suis même pas sûr qu'il y ait eu fuite de données. Les seuls codes d'exfiltration que j'ai lu sont conçus uniquement pour Windows et n'ont aucun effet sur ma machine (cf la charge « C2 » présentée par guanjia.qq.com). Il est possible et probable que la machine n'ai servi à rien d'autre que relais d'exécution à un botnet.
Le rôle de pip
J'ai parlé de mauvaises pratiques ou de pratiques discutables. J'en vois
essentiellement une : le recours excessif à pip
à la place du gestionnaire de
paquet de l'OS. Pendant longtemps j'ai été réfractaire et râleur à ce sujet (et
là je fais une dédicace à mon ami Christian qui lui y est toujours
réfractaire...).
Mais depuis 6 ans que je fais du développement web et du « devops », pip
est
devenu incontournable. Même des gens plutôt précautionneux m'ont poussé à en
faire. De plus, c'est quasiment impossible de faire du django
(mon
environnement de prédilection) sans pip
, tout comme c'est quasiment impossible
de faire du front sans npm
(auquel je fais beaucoup beaucoup beaucoup moins
confiance que à pypi
/pip
).
Notons que dans ce cas précis ça n'a aucun intérêt précis d'installer
requests
via pip
: ce paquet est tellement populaire que disponible
partout dans une version convenable. C'est par un pédantisme mal placé que je
l'ai introduit (avec typo) dans mes requirements
pour être exhaustif. En
l'occurence i) je n'en avais pas besoin en local car requests
était déjà
présent sur ma machine et ii) ce projet ne sera jamais installé que par moi et
sur une seule machine. Mais en bout de chaîne ce paquet s'est installé par la
suite par un effet de bord attendu.
Le rôle de pypi
Les mainteneurs de pypi ont visiblement détecté le problème et supprimé le paquet. C'est pour ça que j'ai vu l'erreur au déploiement. Ceci étant :
- Je n'ai trouvé nulle part de trace de ça. Il serait intéressant que les mainteneurs de pypi aient un registre qui liste ce genre de menace.
- D'ailleurs ça m'étonne tellement que je suis sûr que ça existe mais je le n'ai pas trouvé. Google non plus.
- Je n'ai pas été averti du problème alors qui a été connu des mainteneurs. On pourrait imaginer que les mainteneurs, à la place de supprimer le paquet, forgent un paquet « virtuel » en version ultime, qui lors d'un upgrade informe la personne de la situation avec de gros messages en gras rouge.
Je vais dorénavant utiliser plus assidûment des outils comme safety. Mais ce n'est pas évident que ça m'aurait protégé d'une charge à l'installation comme celle qu'il y a eu là.
D'ailleurs il n'y a pas eu de CVE et ce problème est ignoré de safety
.
Conclusion
Comme disait Frédéric, « ce qui ne me tue pas me rend plus fort ». Mais comme répond echarp, « ou te laisse blessé ! ».
Bien sûr on n'est jamais sûr de rien car j'ignore la nature de la charge potentiellement téléchargée et exécutée pendant ces 22 jours.
Une fois passé la bourde, j'ai de bonnes raisons de penser qu'il n'y a pas à paniquer. Mes secrets importants sont protégés par un secret qui n'est que dans mon esprit. Dans la durée ça ne vaut rien mais dans l'instant ça protège.
Les backups c'est le bien. Ça m'a permis de remonter le temps et de comprendre
l'origine et la nature des soucis. Car bien évidemment l'état de mon dépôt git
ne conserve pas les détails historiques (je ré-écrit souvent l'historique). Idem
pour le venv
qui est régulièrement changé (ça sert à ça).
Notons que sans Google je ne trouve ni analyse, ni traduction de l'analyse. Je n'aime pas les GAFAMs et passe beaucoup de temps à lutter contre eux, mais force est de constater que Google rend des services encore difficiles à remplacer.
J'en vois une autre de mauvaise pratique : le recourt permanent aux agents. Je n'utilise pas le transfert d'agent car en cas de compromission c'est un cataclysme. Voir par exemple cette analyse pour un cas concret. En revanche j'utilise l'agent SSH et l'agent GPG fourni par Debian avec la configuration fournie par Gnome. Ils sont raisonnablement utiles (peut on vraiment faire de l'orchestration sans agent SSH ?) et raisonnablement limités, mais puisque je les utilise quotidiennement, ils ont donc des fenêtres d'ouverture pendant lesquelles je ne peux que supposer qu'ils exposent mes accès.
Mieux limiter mon recours à pip et aux agents sera un réflexion à mener. A minima un agent PGP ne me sert pas à grand-chose et devrait disparaître.
Notons également que j'ai tendance à ne pas restreindre ni tracer les accès réseaux vers la sortie, mais en l'occurrence ça aurait pu m'aider à y voir clair post mortem. Ceci étant, c'est une telle reponsabilité à maintenir correctement que je pense ne pas commencer. Idem pour les systèmes types IDS (OSSEC, Wazuh, Samhain) : les restrictions et les alertes me semblent exploitables sur des systèmes stables de production, mais sur une station de travail dédiée au développement mais une autre paire de manche à mettre en œuvre efficacement.
À défaut de mieux je vais m'employer à mieux ségréger mes développements (coucou docker).
Notons que le chiffrement de mon disque dur n'est dans ce cas d'aucun secours.