Le mouvement DevOps est une tendance forte ces dernières années pour les organisations qui produisent du code source. Les synergies entre équipes de développement et d’exploitation permettent de déployer plus vite et plus souvent. Mais l’aspect sécurité est fréquemment mis sur le banc de touche et l’approche conventionnelle en mode audit n’arrive plus à suivre le rythme. Nous verrons dans cet article les avantages d’inclure la sécurité dans un pipeline de production DevOps.
1. DevOps : la sécurité cherche encore sa place
Les initiatives DevOps sont de plus en plus répandues que l’entreprise soit une startup ou une multinationale. Dans la lignée des méthodologies agiles qui s’appuyaient entre autres sur des outils d’intégration continue, DevOps pousse plus loin ces pratiques en adoptant le déploiement continu et en faisant collaborer étroitement les équipes de développement et d’exploitation.
Mais la sécurité ne fait que rarement partie de ces programmes, le management étant déjà bien occupé par ce changement d'organisation. Comme une initiative DevOps implique un contrôle sur la phase de développement, nous nous concentrerons dans cet article sur les entreprises qui produisent leur code source ou le font sous-traiter.
Historiquement, la sécurité est dans les mains des équipes d’exploitation, avec un fort accent sur les infrastructures réseau. Le principe fondateur est de tenter de conserver l’attaquant à l’extérieur du périmètre. Pour cela, toute une panoplie de solutions est couramment utilisée : firewall, reverse proxy, IDS, etc. Un autre aspect important est la réduction de la surface d’attaque avec un travail de configuration fine sur les systèmes d’exploitation et bases de données : désactivation des fonctions inutiles, mises à jour, droits d’accès, logs d’audit, chiffrement, etc.
En parallèle de cela, la sécurité applicative est trop peu souvent prise en compte dans le cycle de développement logiciel. Cela peut s’expliquer par l’organisation de l’entreprise, le responsable sécurité (CISO) ayant peu d’influence sur les équipes de développement et pas forcément d’expérience pratique dans ce domaine vu son parcours professionnel. Il doit donc considérer le logiciel comme une boîte noire et peut seulement émettre des recommandations de codage très génériques. Il va alors essentiellement collaborer avec les équipes d’exploitation et leur fournir un budget pour investir dans des produits sécurité reconnus afin de « boucher les trous » des applications.
Ces équipements nécessitent des étapes d’intégration supplémentaires, ce qui ralentit le déploiement. Mais cela cause aussi des problèmes en production, seul cet environnement ayant en général les règles les plus strictes configurées. Ces incidents sont souvent délicats à traiter par les équipes d’exploitation parce qu’ils nécessitent la connaissance détaillée de l’implémentation de l’application. On se retrouve alors dans une situation que cherche absolument à éviter une initiative DevOps. Si on ajoute à cela la pression du métier, retenant surtout l’aspect déploiement accéléré, on peut rapidement se retrouver avec le niveau de sécurité minimum acceptable : démontrer que l’organisation se soucie en quelque sorte de la sécurité le jour où les choses tournent mal.
2. L’approche Building Security In
Pour adresser avec plus de rigueur les problématiques de sécurité, l’industrie a commencé à se structurer. C’est notamment le but du projet BSIMM [1] qui recense les pratiques sécurité de grands groupes dans des secteurs d’activité variés comme l’industrie, le médical ou les banques. Elles sont organisées par catégorie et niveau de maturité, ce qui permet d’identifier efficacement une initiative facile à mettre en œuvre pour améliorer le niveau de sécurité.
Un travail comparable a été réalisé par l’OWASP [2] avec son Software Assurance Maturity Model [3] (la version française n’est pas encore disponible). Les activités recommandées sont découpées de la manière suivante :
* Gouvernance
** Stratégie et métriques
** Politiques et conformité
** Formation
* Construction
** Analyse de menaces
** Exigences sécurité
** Architecture sécurisée
* Vérification
** Revue d’architecture
** Revue de code
** Tests sécurité
* Déploiement
** Gestion des vulnérabilités
** Durcissement des environnements
** Procédures d’exploitation
Nous allons évoquer certaines de ces activités ayant une réelle plus-value si elles sont intégrées dans un pipeline DevOps.
Figure 1 : Les pratiques de sécurité recommandées par l'OWASP dans son modèle de maturité.
3. Activités sécurité à forte valeur ajoutée intégrables dans un pipeline DevOps
Le but de ce chapitre n’est pas d’être exhaustif, mais d’évoquer des pistes qui permettent d’améliorer significativement le niveau de sécurité sans y consacrer un temps ou un budget trop conséquent. Les recommandations s'appliquent que l'on produise du code pour le Web ou des clients lourds comme les applications mobiles. Mais l’OWASP [2] ayant dans son nom et ses racines un lien fort avec le monde du Web, l'article aura tendance à mettre plus en avant ce domaine.
3.1 Tests sécurité : tests d’intrusion automatisés
Procéder à des tests d’intrusion est la pratique la plus répandue et une section y est dédiée dans chaque numéro de ce magazine. Lorsque qu’il s’agit d’une application web ou mobile s’appuyant sur des services web, des outils automatisés existent et sont souvent utilisés par les professionnels pour dégrossir le travail. Certains de ces outils sont gratuits et accessibles à un adolescent sans grande connaissance du domaine.
Il est alors envisageable de les inclure à moindre coût dans l’environnement d’intégration continue. Cela permet d’avoir une première couverture contre les attaques classiques du Web, ce qui devrait décourager un adolescent et l’inciter à changer de cible. Les sociétés de test d’intrusion mandatées auront plus de temps à consacrer à des scénarios élaborés. Et surtout cela réduit la probabilité de se faire pirater à cause d’une faute élémentaire qui pourrait être catastrophique pour la réputation de l’entreprise.
Deux outils vont être présentés, les critères de sélection étant par ordre d’importance :
* la facilité d’intégration dans un environnement de déploiement continu ;
* le fait que les failles identifiées soient les plus répandues ;
* la facilité d’interprétation des résultats pour un développeur qui n’est pas expert en sécurité ;
* la gratuité.
Ils sont certainement déjà connus des lecteurs assidus ou considérés élémentaires ou pas assez efficaces par les professionnels du domaine.
3.1.1 OWASP ZAP
Le projet ZAP d’OWASP [4] est un outil graphique intégré de tests d’intrusion pour trouver des vulnérabilités dans les applications. Comme explicitement mentionné sur le site web, il a été conçu pour pouvoir être utilisé par des développeurs et testeurs fonctionnels, mais aussi par des personnes plus expérimentées. Il permet ainsi de faire des investigations manuellement. Il a notamment été élu meilleur outil sécurité 2015 par les lecteurs du site http://www.ToolsWatch.org.
L’utilisation la plus élémentaire est simplement de fournir une URL et de cliquer sur Attaquer. Puis d’attendre que le scanner teste une série de règles après avoir construit la cartographie des URLs du site. Il est particulièrement efficace sur la détection de failles XSS, en étant capable de tenir compte du comportement dynamique d’une page effectuant de nombreuses requêtes Ajax.
Figure 2 : L'outil ZAP dans son fonctionnement manuel nominal.
C’est un parfait candidat à l’intégration dans un pipeline DevOps puisqu’il dispose d’APIs permettant de piloter un scan. C’est ce qui est réalisé par le plugin ZA Proxy [5] pour Jenkins. On peut entre autres définir le type de scanner (XSS, CSRF, SQLi, etc.), les domaines sur lesquels rester (pour éviter de suivre des liens externes) et surtout exporter le rapport sous format XML. Mais aussi lui préciser ce qui a déjà été analysé au préalable (cf 3.1.3).
3.1.2 sqlmap
sqlmap [6] est un outil avancé en ligne de commandes permettant de détecter et exploiter des failles de type injection SQL. Il est notamment capable de détecter le type de base de données et d’en exporter l’intégralité de son contenu si elle est mal configurée et qu’il y a une requête exploitable. À ce sujet, il gère totalement les six différentes techniques d’injection connues à ce jour. Il est fréquemment utilisé par des groupes de pirates, l’option de passer par le réseau Tor étant attrayante pour les usages frauduleux.
L’utilisation la plus élémentaire consiste à lui fournir une URL d’un site web contenant un paramètre et il testera alors si ce dernier est injectable. Les résultats sont affichés directement dans la console en ligne de commandes.
Figure 3 : L'outil sqlmap en ligne de commandes.
sqlmap est un complément très utile à ZAP, ce dernier étant moins efficace sur la détection des injections SQL et générant un nombre conséquent de faux positifs pour cette catégorie. Comme sqlmap gère en entrée une liste d’URL (e.g. sitemap.xml), on peut lui fournir la cartographie qui a été découverte par ZAP. Cela peut facilement se configurer dans une tâche Jenkins par exemple. Il faut par contre parser les résultats qui ne sont pas structurés.
3.1.3 Défis dans la gestion de ces outils
Armés de ces deux outils automatisés, nous possédons une couverture raisonnable pour une application web contre les attaques SQLi et XSS, respectivement première et troisième du Top 10 OWASP [7] .
Le premier défi est celui des temps de scan, sqlmap et ZAP pouvant prendre de quelques minutes à plusieurs heures selon la complexité de l’application. Il est donc difficilement envisageable de faire une vérification à chaque fois que du code est envoyé dans la solution de gestion de version. Même si cela serait l’idéal en notifiant en quasi temps réel au développeur la vulnérabilité insérée et en pouvant la corriger de suite à moindres frais. Si l’on ne produit pas plusieurs releases par jour, un scan la nuit sur la branche de développement (au sens Git workflow) est un bon compromis et permet de ne pas écrouler l’infrastructure la journée quand les équipes travaillent. Et au minimum il faut que chaque version promue soit vérifiée.
Le second défi est certainement le plus important : il est question d’avoir la capacité de traiter toutes les informations remontées par les outils. Il ne s’agit plus de gérer quelques remarques particulières évoquées dans un seul audit concis accompagné de ses recommandations concrètes. Il va éventuellement falloir analyser des centaines de points tous les jours, dont une part significative est constituée de faux positifs. Il est alors essentiel :
- d’automatiser le traitement des résultats ;
- d’être capable de filtrer les points déjà traités et les faux positifs ;
- d’identifier uniquement une vulnérabilité pour la suivre dans le temps ;
- d’avoir un canal de correction efficace avec le développeur :
la stratégie « une vulnérabilité = une entrée dans la solution de bugtracking » va rapidement mener à des coûts de gestion considérables.
Aujourd’hui cela nécessite la mise en place d’outils ad hoc pour remplir ces critères. Mais si l’on parle d’une application web implémentée correctement vis-à-vis de l’OWASP Top 10 [7] ou qui a subi un audit externe, une approche moins industrielle est encore viable puisqu’il ne devrait pas y avoir trop de points identifiés (hors faux positifs). Si ce n’est pas le cas, il est certainement plus judicieux de commencer par corriger la sécurité de l’application ! Par exemple lors d’un sprint de consolidation technique si l’on suite une méthodologie agile.
Enfin, le dernier défi est de ne pas basculer dans un mode de correction suivant la loi du moindre effort. En effet, beaucoup de vulnérabilités ne sont plus exploitables en adaptant la configuration ou avec une rustine minimaliste. Mais il faut bien garder à l’esprit que ces vulnérabilités sont des erreurs d’implémentation comme tout bug, et qu’elles doivent donc être traitées avec la même cohérence. C’est-à-dire au minimum par l’écriture d’un test unitaire démontrant l’efficacité de la modification. Il est aussi recommandé de faire tourner ces outils dans un environnement volontairement laxiste, afin de rendre l’exploitation de ces erreurs la plus facile possible et ainsi d’améliorer le taux de détection.
3.2 Tests sécurité : tests unitaires de scénarios d’abus
Après avoir testé la sécurité de l’application depuis l’extérieur, processus assez fréquent, il est très important de la valider aussi depuis ses entrailles. C’est ce qui permet de s’assurer de sa robustesse sur le long terme, les vulnérabilités finissant par être identifiées tôt ou tard.
À ce titre, toute section sensible du code doit faire l’objet de tests unitaires exhaustifs. Cela inclut notamment :
- la logique d’authentification ;
- les mécanismes de contrôle d’accès ;
- les fonctionnalités de paiement ;
- les opérations cryptographiques [8].
Il ne suffit pas de tester que le cas nominal, mais aussi tous les cas d’erreur. Et de vérifier que les logs applicatifs ramènent le niveau de détail suffisant pour comprendre quelle est précisément la raison de l’échec, sans pour autant noyer le message utile dans des centaines de lignes très techniques (ce qui est souvent le problème des exceptions en Java).
Prenons l’exemple d’un service web qui utilise une authentification JWT [9]. À des fins pédagogiques, simplifions le contenu d’un tel token et supposons qu’il contienne :
- l’adresse mail d’une personne déjà authentifiée ;
- la durée de vie du token ;
- une signature cryptographique prouvant que ni l’adresse mail, ni la durée de vie n’ont été modifiées.
Bien souvent, un seul cas de test est effectué par les équipes de développement et de manière manuelle : passer un token valable et regarder que le service peut être accédé. Mais utiliser une librairie open source de validation du token n’est pas gage de qualité absolue. Ainsi il arrive que si la signature ne correspond pas, une erreur est effectivement retournée. Par contre, si la signature est rendue nulle dans le JSON, alors l’implémentation peut en conclure que la signature n’est pas mauvaise et accepter le token !
Il faudrait donc écrire les tests unitaires suivants pour en avoir le cœur net, et s’assurer que des régressions ne sont pas introduites plus tard dans la librairie (par exemple suite à une optimisation trop agressive) :
* vérifier qu’un token valide est bien accepté ;
* Vérifier que le token est refusé quand on :
** change un caractère de l’adresse mail ;
** change un chiffre dans la durée de vie ;
** change un caractère de la signature ;
** enlève la signature ;
** met une chaîne vide dans la signature.
3.3 Revue de code
Pratiquer une revue de code avec un regard sécurité permet d’identifier les vulnérabilités liées aux erreurs d’implémentation. Les audits manuels commencent à se multiplier, notamment pour des composants open source très utilisés comme OpenSSL. Mais c’est une activité chronophage et il n’est guère envisageable de relire méticuleusement plus de quelques centaines de lignes de code par jour.
Les outils d’analyse statique [10] permettent d’aborder la question de manière plus industrielle. Le principe consiste à rechercher dans le code source des constructions réputées dangereuses (par exemple des strcpy en C). Et pour les produits plus haut de gamme à simuler le graphe d’exécution pour détecter les données d’entrée qui sont utilisées sans filtrage et peuvent conduire à des injections. Ce dernier point est très difficile à mettre en œuvre pour des langages dynamiques (par exemple, PHP ou JavaScript) et les résultats seront en général décevants dans ce cas.
L’intégration est très simple comme il s’agit d’une problématique purement développement, il suffit de faire exécuter la commande de scan à l’environnement de déploiement continu.
Les défis sont alors les mêmes que pour les tests d’intrusion automatisés, à savoir des temps de traitement assez conséquents (notamment pour des langages comme C/C++ ou JavaScript difficiles à instrumenter). Et un nombre élevé de résultats et de faux positifs à traiter. Sans oublier que de nouvelles règles sont régulièrement introduites pour tenir compte de nouvelles attaques, ce qui fait ressortir de nouveaux problèmes sur du code ancien.
À ce jour, seules les solutions payantes permettent d’identifier assez efficacement les vulnérabilités majeures comme les injections. Les alternatives gratuites comme Sonar ou FindBugs (pour le monde Java) retournent surtout des points de basse importance qui sont plutôt des remarques sur la qualité. Elles peuvent alors avoir un effet contreproductif en laissant croire qu’au final il n’y a que des aspects mineurs à améliorer pour la sécurité.
HP Fortify est clairement au-dessus du lot dans sa capacité de détection des problèmes majeurs, mais la tarification pratiquée ne le met pas à la portée de toutes les bourses. Il est indéniable que cet outil a un autre avantage majeur : pouvoir écrire des règles sur mesure (dans un langage fonctionnel encore accessible) pour cibler la combinaison de technologies utilisées. C’est le point le plus important pour retourner aux développeurs seulement les vulnérabilités vraiment importantes.
À l’opposé, la solution dans le cloud de Veracode exécute son propre jeu de règles sans qu’on puisse intervenir dessus. Accepter ou non de transférer son code source sur des serveurs à l’étranger est une autre question.
3.4 Déploiement : gestion des vulnérabilités
Nous serons plus concis dans cette section. La gestion des vulnérabilités pour l’infrastructure ne sera pas directement abordée puisqu’il s’agit d’une problématique purement opérationnelle et des acteurs comme Nessus ou Qualys la rendent très visible.
Celle concernant la gestion des dépendances fournies avec le code (librairies, frameworks) est pour sa part souvent oubliée, même s’il s’agit du numéro 9 de l’OWASP Top 10 [7] . En général dans les équipes de développement, on utilise la version stable de la dépendance au moment d’écrire la fonctionnalité. Et après on n’y touche plus vraiment pour éviter d’avoir des régressions. C’est sans compter sur les vulnérabilités publiées pour ces composants, la plupart étant open source.
Pour remédier à cela, OWASP propose un plugin Dependency-Check [11] qui va vérifier dans la base des CVE si une librairie est vulnérable. Il est très efficace dans le monde Java, avec son intégration Maven ou Jenkins. Dans le monde JavaScript, le projet Snyk [12] fonctionne de manière similaire pour Node.js.
Si le code JavaScript à exécuter côté client est téléchargé depuis un CDN par le navigateur, la problématique de gestion des versions reste entière. Avec le risque supplémentaire de compromission. Pour ce dernier point, le W3C a récemment standardisé la fonctionnalité SubResource Integrity qui permet d’inclure un hash dans la page web pour valider que le JavaScript reçu est bien celui que l’on attendait.
3.5 Déploiement : Durcissement des environnements
Pour ce dernier point, nous évoquerons uniquement l’excellent document rédigé par l'ANSSI et intitulé Recommandations de configuration d’un système GNU/LINUX [13].
Intégrer ces recettes dans les images Docker ou les machines virtuelles permet de partager le même socle robuste pour les environnements de développement et de production. Et ainsi d’identifier rapidement des problèmes de déploiement ou de reproduire sans encombre les incidents constatés par les clients.
Conclusion
La sécurité est donc une problématique à la fois pour les équipes d’exploitation et de développement. À ce titre, elle a beaucoup à gagner à s’inscrire dans les initiatives DevOps actuellement en plein essor. La démocratisation des conteneurs permet d’amener dans les environnements de développement des configurations proches de la production. Des outils gratuits de tests d’intrusion automatisés sont disponibles et faciles d’accès, en faisant ainsi d’excellents candidats à intégrer dans le pipeline de production d’une application.
Ils ne permettent pas toutefois de se passer de tests unitaires poussés ou de revue de code, pour qui veut adresser la sécurité applicative sérieusement. Une fonctionnalité sécurité est une fonctionnalité avant tout et devrait à ce titre être validée avec les mêmes processus. Ces tests sont aussi vitaux pour être capable de gérer les vulnérabilités dans les dépendances sans être paralysé par la peur des régressions.
Il est utile de rappeler que si ces pratiques aident à l'identification d'une majorité de failles, en aucun cas elles ne peuvent prouver qu'il n'y a plus rien à trouver. Même les niveaux de maturité les plus élevés ne sont pas synonymes d’une sécurité absolue. Mais ils permettent de démontrer que l'entreprise adresse de manière consciencieuse la problématique.
Enfin, il est utile de noter que les outils d’intégration continus sont en général très peu sécurisés par défaut. Il s’agissait du thème de la présentation Continuous Intrusion [14] lors de la conférence Black Hat Europe 2015. Il est donc important de surveiller les options de déploiement, et notamment si des accès sont possibles depuis un réseau externe.
Références
[1] Building Security In Maturity Model : https://www.bsimm.com
[2] Open Web Application Security Project : https://www.owasp.org/index.php/Main_Page
[3] Software Assurance Maturity Model : http://www.opensamm.org/
[4] Zed Attack Proxy : https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project
[5] ZAProxy : https://wiki.jenkins-ci.org/display/JENKINS/ZAProxy+Plugin
[6] sqlmap : http://sqlmap.org/
[7] OWASP Top 10 2013 : https://www.owasp.org/index.php/Top_10_2013-Top_10
[8] T. Romand-Latapie, « Implémentations cryptographiques sécurisées », MISC n°83, janvier-février 2016.
[9] JSON Web Token : https://en.wikipedia.org/wiki/JSON_Web_Token
[10] Static Application Security Testing : http://www.gartner.com/it-glossary/static-application-security-testing-sast
[11] Owasp Dependency Check : https://www.owasp.org/index.php/OWASP_Dependency_Check
[12] Snyk : https://snyk.io/
[13] ANSSII, « Recommandations de sécurité relatives à un système GNU/Linux » : http://www.ssi.gouv.fr/guide/recommandations-de-securite-relatives-a-un-systeme-gnulinux/
[14] N. Mittal, « Continuous Intrusion : Why CI tools are an attacker's best friends » : https://www.blackhat.com/docs/eu-15/materials/eu-15-Mittal-Continuous-Intrusion-Why-CI-Tools-Are-An-Attackers-Best-Friend.pdf