Seven days of reconnaissance, then eight hours to deploy an XMRig Monero miner across three payload variants. The last one hid a remote shell behind a health check. Eight days later a second wave killed and replaced them. Targets: exposed ComfyUI, Ollama, Jupyter, MLflow, Airflow, Gradio and Portainer. The source was a UK home connection, itself compromised.
Publication date : Sept 26 2026
From 23 August to 9 September 2026, one source address sent 651 requests to internet-facing AI services. All times are UTC. Pool figures are as of 11 September 2026, when the campaign was still mining.
/health endpoint straight to eval, which turns it into an interactive command channel.On the wire, it looks like an ordinary monitoring agent. Every few seconds it calls /health, identifies itself as HealthCheck/2.1.0 (linux), and reports a number. Few paths look less interesting, and few user agents invite a second look.

The number is the hashrate, taken directly from the miner’s log.
But the response is not used as a health check. It is treated as a command, passed to eval, and the result is sent back to the server.
At that point, this is no longer just cryptomining. It becomes an interactive shell running with the same privileges as the compromised service account. The miner simply keeps running between commands.
The polling interval is also randomized between three and ten seconds instead of using a fixed interval. This makes simple periodic beacon detection less reliable, so timing alone is unlikely to identify the channel.
We confirmed the channel is live and that both C2 hosts answered on the beacon path and accepted the campaign key. However, during our observation, we did not see the server send a command through it.

The first contact was on 23 August. The first payload did not arrive until 30 August. During those seven days, no payload was delivered. But the operator was not idle. It repeatedly tried to get Jupyter to start a kernel or open a terminal, which would have given it code execution directly. Across the campaign, requests to /api/kernels and /api/terminals made up 289 of the 651 requests.
Where direct execution was not possible, it went after credentials instead. On 28 and 29 August it requested SSH keys, AWS credentials and /etc/shadow directly and through path traversal in MLflow, ComfyUI and Gradio, and sprayed default passwords at Portainer. It also asked exposed models to run nvidia-smi and read /proc/cpuinfo through Jupyter, checking whether the host was worth mining on.
Credential theft continued after the first payload. On 30 August it ran an Airflow credential spray, then queried /api/v1/connections?limit=1 to retrieve stored connection credentials. From 31 August it used Ollama’s /api/pull endpoint with path traversal strings targeting /root/.aws/credentials, /root/.ssh/id_rsa, and /etc/passwd, and kept doing so for nine days (Appendix D).

/tmp/xmrig, and runs it once. It connects to the mining pool over port 3333 with no encryption. There is no attempt to hide the process and no way to control it after launch.nice -n 19, with one thread and a 25% CPU hint, and writes logs to /tmp/.xmr.log. It still uses port 3333, still makes no attempt to hide itself, and still has no control channel.The goal stayed the same. The way the miner was deployed did not.
exec -a changes argv[0], so tools such as ps show the process as kworker/0:1H, the name used by a real Linux kernel worker thread. But this only changes the displayed name. It does not give the process kernel access. The process still has a file-backed executable, a working directory, and a network socket — things a real kernel thread does not have.

We retrieved both delivered archives and analyzed them offline. We queried VirusTotal using only their hashes. Both files are byte-for-byte identical to published upstream XMRig releases (Appendix F).
One of the six execution paths tells us more than the request count alone. At 03:37:43, the operator sent Variant 3 to Ollama’s model-creation endpoint inside a Modelfile:

CMD is a Dockerfile instruction. It is not a valid Ollama Modelfile instruction. A Modelfile supports seven instructions: FROM, PARAMETER, TEMPLATE, SYSTEM, ADAPTER, LICENSE, and MESSAGE. Because CMD is unsupported, Ollama rejects the request and nothing is created.
That failed request is one of the most useful pieces of evidence in the capture. The operator appears to have seen a file format that looked similar to a container definition and tried to use it without understanding how Ollama worked.
That also tells us something about the target selection. The campaign was not narrowly looking for ComfyUI or Ollama installations. It was probing HTTP endpoints that might accept input and turn it into code execution.
The six paths gave us the count but this failed Ollama request gave us the evidence of the approach behind it.
The campaign returned on 8 September at 07:09. This time, where the payload came from is important.
The second-generation payload downloads the miner from am114.duckdns[.]org:7331.
That hostname resolved to the same IP address that sent all 651 requests in our capture: a Sky UK residential connection in Oxford. The host exposed an unauthenticated Ollama server and RDP and appeared to be running Windows 11 (Appendix E).
By this point, the campaign was using that residential host both to stage payloads and to receive beacons. The server still returns a cmd field, but the second-generation client discards the response, so it can no longer run commands. Based on the evidence available to us, it does not appear to be infrastructure owned by the operator.
The same payload was sent again over port 80 later that day.

It also kills /tmp/.d/r and /tmp/.d/b when it starts. Those paths only appear in the second-generation script. That suggests a working copy had already run on other hosts.
The captured copies could not have created those files because this command is invalid:

sh rejects &; as a syntax error before the command runs.

The first generation beaconed to beacon.ltdglobal[.]co[.]uk.
The domain was registered through Cloudflare on 21 March 2026, five months before we first saw it used. A rule looking only for newly registered domains would therefore have missed it.
Its origin server is hidden behind Cloudflare. That means a takedown request has one obvious recipient, and it is not the hosting provider. This is also the only piece of infrastructure in the campaign that appears to belong directly to the operator.
The second generation stopped using it. Instead, it used free dynamic DNS pointing to the residential host described earlier. Blocking that hostname is reasonable.
b3fc2a91d8e047f6a3b1cc5d appears unchanged in both generations. It survived eight days, a domain change, and a protocol change.
That makes it a campaign-level identifier rather than something tied to one deployment.
Rig IDs follow this format:
<2 letters>-<word>-<word>-<4 hex>
We saw four:
The middle two reached the same system six hours apart through different endpoints. They differ only in the first two characters.
That suggests the prefix identifies the product surface; cu for ComfyUI and ol for Ollama, while the rest identifies the host.
The same identifier is then reused in three places: as the miner's --rig-id, as the pool password, and as the beacon URL path. That means one string links a compromised host's mining activity to its C2 session. That is unusually detailed per-victim tracking for commodity cryptomining.
None of these four identifiers appeared in the pool's worker list, which is expected: the payloads were captured, not executed.
A worker seen in an earlier capture of the pool's worker list follows the same naming scheme. That is what supports this interpretation.
Look at each part in isolation and very little stands out. The miner is a stock XMRig release downloaded from GitHub's own CDN. It is byte-for-byte identical to upstream.
The same hash has been submitted to VirusTotal under 70 different filenames, so neither the hash nor the local filename identifies this campaign.
Mining traffic goes to a public pool over TLS on port 443. The process calls itself kworker/0:1H, which is also the name of a legitimate Linux kernel worker thread. The C2 domain was registered five months before it was used.
The beacon looks like a /health request from something calling itself a monitoring agent. At any one layer, reputation checks have little to work with. The combination is what matters.
A download is piped into tar under /tmp, followed by chmod +x and execution. A process reports an argv[0] that does not match its executable.
A host that is not a monitoring agent starts making outbound /health requests carrying command-related parameters.
The useful detection question is therefore not just, "Is this artifact known to be bad?"
It is, "Does this combination of behaviour make sense?"
The wallet appears directly in the payload, so the mining activity can be checked independently. The figures below came from SupportXMR's public API. The XMR price was captured from a public price endpoint in the same pass at 00:52:55Z on 11 September 2026.
The campaign was still mining at that point. Lifetime hashes alone cannot tell us when the campaign started. Dividing them by the current hashrate would assume that rate had remained constant the entire time.
The domain registration gives us a better upper bound.
beacon.ltdglobal[.]co[.]uk was registered on 21 March 2026, so the campaign could be at most 174 days old at the time of capture. That would imply an average rate of about 950 H/s, compared with the 2,127 H/s we measured.
Either way, the total work is far too high to fit inside the seventeen days between our first sighting and the capture date. The campaign was already running before we first saw it.
Earnings. The wallet had 0.011580 XMR outstanding, worth about $5.91 at $510.44/XMR.
Check it yourself. SupportXMR exposes three public endpoints that require no API key. Use the wallet from Appendix A:

These values change continuously, so each figure is only valid for the timestamp at which it was captured.
Workers. Three identifiers were listed as recently active at 00:52:55Z:

Protect internet-facing execution endpoints.
The campaign did not need an exploit. It used exposed, unauthenticated services that accepted commands. Identify any internet-facing service that can execute user-controlled input and put authentication or network controls in front of it.
Alert on the reconnaissance.
The operator spent seven days probing before sending a payload. Requests to endpoints such as /api/kernels, empty token parameters, or attempts to read files like /proc/cpuinfo are strong signals when they come from the internet.
Restrict outbound traffic from AI hosts.
Inference servers usually have little reason to connect to mining pools, dynamic DNS hosts, or unknown external domains. Egress controls would have disrupted the payload download, mining traffic, and command channel.
Treat a miner as more than a mining incident.
One variant also provided remote command execution. If you find a miner, investigate what the service account could access and assume the incident may extend beyond cryptomining.
Rotate credentials on exposed hosts.
Where the operator could not execute code, it tried to read secrets. If an AI service was exposed to the internet, rotate any credentials stored on or accessible from that host.
Most of the investigation was passive. The activity described here is traffic observed to exposed AI services. We used Shodan and VirusTotal only for existing records, and queried VirusTotal by hash without uploading any samples. We also used public sources including RDAP, AbuseIPDB, DNS, and the SupportXMR API.
The four rig IDs below are the per-host identifiers; the campaign key is the pivot that survives every rotation.


The delivery platform is a probable victim and its address is masked throughout this report. Block the hostname am114.duckdns[.]org rather than the address behind it: that address is a home connection and it will change. Pool traffic runs to pool.supportxmr[.]com:3333 in the clear for generation 1 variants 1 and 2, and to :443 over TLS for variant 3 and generation 2.


651 requests, 23 August to 9 September 2026, across eight exposed systems, each receiving between 33 and 214 of them, grouped by API endpoint and intended purpose.

30 August:

31 August:

Everything in this table comes from the captured request bodies.

Staging. Idempotent: it will not re-download if kw already exists.

Execution. The throttling is deliberate — one thread, a 25% CPU hint, RandomX light mode, huge pages off — and trades revenue for staying below resource-utilization alerts.

The beacon loop is shown at the top of this report. Two things in it are worth stating outright. eval "$CMD" runs on whatever the server returns, and nothing in the loop constrains what that can be, so a host that ran this payload should be treated as having had hands-on access rather than merely a miner. And the jitter is in one expression, sleep $((RANDOM % 8 + 3)), which is what produces the three-to-ten-second interval.

The second generation is louder on most axes: no argv[0] masquerade, a 90% CPU ceiling in place of one throttled thread, plaintext HTTP. The exception is the beacon, which slows to a fixed thirty seconds. Whether that reflects a different operator, a rushed redeploy, or a judgment that stealth was not paying is not something we can determine from delivery alone.
Opening an execution channel. The campaign's largest activity by far. The operator repeatedly asked the Jupyter server to start a kernel or return a terminal: 142 of the /api/kernels requests are POSTs carrying a kernel spec ({"name": "python3"}, also python, ir, julia-1.0), and 32 of the /api/terminals requests are POSTs with an empty body. Three /api/kernels?token= requests test whether the server accepts an empty token. It also asked Jupyter's contents API for /proc/cpuinfo, which returns the core count: the number you check before deciding whether a host is worth mining on.

Credential theft. Where the operator could only read, it took secrets instead.

We assess with moderate confidence that the host is a compromised intermediary rather than operator-owned infrastructure. Shodan's cached scan from 2026-08-24, one day after this activity began, records:

Four things support the victim assessment:
How the host itself was compromised is not established. Because it is likely a victim, it is treated here as a platform rather than an identity, and its address is masked throughout.
The binary dropped by every variant is XMRig, a legitimate open-source Monero CPU miner and the most commonly abused miner in cryptojacking, which is why most scanners flag it as an unwanted program. Both archives we retrieved are byte-identical to published upstream releases: the bundled SHA256SUMS matches the shipped binary, and config.json is unmodified stock, still carrying "user": "YOUR_WALLET_ADDRESS" and the default donate pool. The wallet is supplied on the command line at runtime. The two generations dropped different versions.

The filename is no better than the hash. VirusTotal flags 38 of 75 engines on the 6.26.0 build and 42 of 75 on 6.22.2, both as miner.xmrig/bitcoinmi, and it records the 6.26.0 binary under 70 different filenames across 132 submissions, including cpanel-updater, kernel_miner_bin, dns-filter, .sysupd, polkitd and wp-worker.exe. Several impersonate real Linux daemons or administrative tooling. Renaming stock XMRig to suit the target is evidently routine, which is what proves the hash identifies the tool rather than the incident. Only the runtime arguments and the network behavior discriminate: the wallet, the rig ID, the campaign key and the argv[0] mismatch.

The domain sits behind Cloudflare and its origin is not exposed, so a takedown request has exactly one recipient, and that recipient is not a hosting provider. This is the one asset in the campaign that appears genuinely to belong to the operator.
Protocol behavior below is from one request per endpoint, made from a disposable, isolated VM using the rig IDs and key recovered from the captured payloads. No command was requested, and no sample was uploaded anywhere. The same isolated VM retrieved the second-generation archives, which are served only from the victim host.

One example of the health endpoint used by the payload to reach the C2 in this generation:
hxxps://beacon.ltdglobal[.]co[.]uk/health/<rig-id>?hr=<n>&k=b3fc2a91d8e047f6a3b1cc5d
In the URL, b3fc2a91d8e047f6a3b1cc5d is the campaign key and rig-id is the per-host identifier the miner also reports to the pool.
The domain used as C2 by the second generation resolves to 151.231.x.x: the same address the payloads were delivered from, and the same address running the exposed Ollama server (Appendix E). It is served on ports 7331 and 80, and its tasking response was captured the same way: {"cmd":null,"ok":true}, where cmd: null is an idle state and ok: true confirms the key was accepted. The second-generation client never reads this response, so the field has no effect on hosts running it.
By the second generation the campaign is staging its payloads and receiving its beacons on a machine it does not appear to own.
hxxp://am114.duckdns[.]org:7331/health/<rig-id>?k=b3fc2a91d8e047f6a3b1cc5d&hr=<n>
The key and the rig-ID scheme are unchanged from the first generation. The practical consequence is that the campaign's infrastructure cost has fallen to a single domain registration, and that response options narrow: blocking am114.duckdns[.]org blocks a victim's home connection, and a takedown request has no hosting provider to send it to.