Blog
>
Poetry as a dead drop

Poetry as a dead drop

Operation Poetic Injustice. Your LiteLLM gateway is somebody else's C2 server

Ratnesh Pandey, Japneet Singh, Varun Anand
Publication date: September 8, 2026
Executive Summary
  • Adversaries are targeting LiteLLM exploiting vulnerabilities
  • They used pseudo poetry hosted in Github repos to dead-drop C2 addresses 
  • Commit history across 3 linked accounts recorded 48 config rotations and 37 distinct C2 addresses
  • No verified record of the loader or implant being detected by VirusTotal at the time of analysis 
  • Payload delivery used CVE-2026-42271
  • Of the ~1300 LiteLLM gateways with readable versions, 370 were affected by a KEV listed flow, and 3 C2 nodes were themselves vulnerable LiteLLM hosts 

Four ordinary-looking lines of pseudo-poetry on GitHub are enough to tell this botnet where to connect next. The malware decodes those lines using word lists embedded in the binary, turning them into the IP address of its command-and-control server. The operation has been active for nine months and has recently started targeting internet-exposed LiteLLM gateways, using a publicly documented vulnerability and a default credential taken straight from the vendor’s own documentation. We are calling this operation as "Poetic Injustice".

Introduction

We discovered these four unremarkable lines of Poetry sitting in a file called NewAPi.readme, buried eleven directories deep in a forked copy of an open-source home-server project on GitHub:

Figure 1. The dead-drop file NewAPi.readme as served from GitHub

Decoded against four sixteen-entry word lists compiled into a stripped ELF binary, those four lines produce four bytes, and those four bytes produce four numbers, that is the IP address (217[.]76[.]63[.]67) of a C2 for a Linux implant on port 5002 and an internet-exposed LiteLLM gateway on port 4000 with no authentication, which was itself vulnerable to the exact remote code execution flaw that we observed.

Since some time, we have been tracking an operator running what at first looks like a fairly conventional botnet. What makes it interesting is how the infrastructure evolved. The operator moved command-and-control onto cloud-hosted systems, including several machines exposing AI gateways, and appears to have done so using a publicly known LiteLLM vulnerability that is already listed in CISA’s KEV catalog. There is no sign they needed any novel AI-specific technique to do it.

At the time of writing, none of the 63 antivirus engines that scanned it on VirusTotal detected the first level payload. Its command and control server’s address is delivered through pseudo-poetry hosted on GitHub, making simple content-based searches ineffective. Looking at GitHub history we were able to get 48 configuration rotations and 37 distinct C2 addresses.

In this article, we start with that GitHub channel, then trace the implant behind it, look at how the malware is delivered, and finally compare those findings with our scan of the exposed LiteLLM population to understand how much of this activity is visible in the wild today.

Part One: The channel
Three forks of Github leads to different C2s

The implant does not contain a command-and-control address. Instead, the malware carries three GitHub URLs. Each points to a fork of a legitimate open-source project, with the configuration buried deep in a plausible-looking path and stored under a .readme extension:

Table 1. The three dead-drop files. Each is a .readme buried inside a forked GitHub project, holding the encoded server address.

The three GitHub locations were forks of  Umbrel (https://github.com/getumbrel/umbrel), deepin-wine-ubuntu (https://github.com/zitsen/deepin-wine-ubuntu) and discord.js (https://github.com/discordjs). Each file held four short lines of pseudo-poetry.

The repeated phrases across those files pointed to a fixed grammar rather than genuinely generated text. We found the decoding logic in plaintext: four ordered lists, each containing exactly 16 entries (Figure 2), worked through in Figure 3.

Figure 2. The four sixteen-entry word lists recovered from the binary, the codebook that maps each poem line to a single byte.

‍

Figure 3. Decoding the four lines to 217[.]76[.]63[.]67: each line yields one byte from a word index (high nibble) and an ending index (low nibble)

That address has port 5002 open, the exact default compiled into main, and it is the fallback mirror that served the loader in the exploit attempt described in Part Three. We built the decoder from the binary alone without knowing what answer to expect, so the match is a confirmation rather than a fitted result. The same decoder recovers a clean address from all the other drops.

Figure 4. How sntnl_v1 resolves its command-and-control server
‍Forked repositories are excluded from GitHub code search

The use of forks here is not just camouflage. It is part of how the channel stays difficult to find. GitHub excludes forked repositories from code search, so even knowing an exact phrase from one of these files is not enough to surface it through normal search. We found the files only because they were listed in the implant's own configuration blob.

As a covert channel, the setup is effective for several reasons. Traffic goes over HTTPS to raw.githubusercontent[.]com, a destination that is rarely blocked by egress controls and is unlikely to attract attention on reputation alone. And because each poem is regenerated from the word lists, no two rotations look alike, so a detection written against one sample fails on the next.

Internet as a memory

Using a version control system as a dead drop came with an unintended consequence for the operator: every previous configuration remained available in the repository history.

We cannot use this channel to estimate victim count because GitHub does not expose download statistics for raw files. What we can reconstruct is the operator’s infrastructure over time. By walking the commit history and decoding each version of the configuration, we identified 48 rotations between 9 December 2025 and 27 August 2026, covering 37 distinct command-and-control addresses. Forty-seven of the 48 entries decode successfully.

The operator also changed the encoding scheme during that period. The first 40 configurations used an earlier, simpler format that remained in use until June 2026.

One operator used multiple Github accounts 
Figure 5. The three GitHub accounts behind the dead drops, created within minutes of one another

The three accounts were created within 13 minutes of one another. Their GitHub user IDs are nearly sequential, all three use the same anonymous email provider for commit attribution, and none has followers or any obvious public connection to the others.

Five command-and-control addresses appear in repositories belonging to more than one of the accounts. It is difficult to explain three otherwise unrelated GitHub users independently publishing the same C2 infrastructure, which strongly links the accounts to the same operator.

There is one other useful detail in the commit history. All 48 changes were made through GitHub's web interface rather than pushed from a local Git client. The commits are therefore signed by GitHub's own key, leaving no local Git configuration or signing metadata behind. The trade-off is operational: someone appears to be logging into these accounts through a browser each time the C2 configuration is rotated.

The fallback relays are off-the-shelf

The five fallback proxies were not custom infrastructure either, and their banner identifies below:

HTTP/1.1 404 Not Found
Access-Control-Allow-Origin: *
Via: allOrigins v2.7.0

allOrigins is an open-source CORS proxy with a simple API built around /get?url=. That makes the individual relay IPs relatively disposable. Blocking the five addresses embedded in this sample would stop those specific endpoints, but another allOrigins instance could provide the same functionality.

The software fingerprint is more useful for understanding the wider infrastructure. Internet-wide scan data shows only 11 exposed allOrigins instances, with six listening on port 1458, an unusual port for this service. Four of those six are already present in the operator's relay list, while the fifth known relay uses a different port.

That leaves two additional allOrigins systems exposed on the same unusual port that do not appear anywhere in the sample we recovered. We cannot tie them to the operator from this evidence alone, but they are worth tracking because they may be associated with other builds or infrastructure we have not yet seen.

Part Two: The sntnl_v1 implant

In this case, the loader hosts serve a file at private/manager: a UPX-packed, statically linked x86-64 ELF built on Alpine Linux with musl. Unpacked, it is 896,512 bytes. 

Figure 6. Detection on VirusTotal: zero of 62 engines on the packed loader, zero of 63 on the unpacked binary

‍

Both hashes were already present on VirusTotal when we checked them, so these results represent actual multi-engine scans rather than the empty response returned for an unknown sample. At the time of analysis, none of the engines detected either file. The only classification we found was a YARA match caused by code shared with Alpine’s standard mbedTLS package.

The implant stores its configuration behind a single-byte XOR with 0x5A. Decoding it reveals the identifier sntnl_v1, version 1.5.1, the three GitHub dead-drop URLs, a 16-byte session secret, and the commands supported by the implant. Those commands include remote shell execution, upload and download, self-update, process supervision, anti-debugging, tamper detection, and an anti-forensics command. 

It reaches its controller over TCP 5002 with no TLS: four random bytes each way in the clear, after which both directions switch to RC4 keyed from those nonces and a secret compiled into the binary.

The implant also creates /dev/shm/.i2c_36589256 and does not remove it, leaving a persistent host indicator. 

Persistence is spread across several mechanisms. The sample installs both system and user-level systemd units, creates cron entries at boot and at five-minute intervals, marks artifacts immutable with chattr +i, masquerades under daemon-like process names, and injects into running Python interpreters. 

Part Three: How it arrives
One exploit, one default credential

On 19 July 2026, our sensors recorded two requests attempting to exploit a published remote code execution vulnerability in LiteLLM. The requests arrived 70 minutes apart. 

timestamp   2026-07-19T06:58:22Z
source_ip   20[.]51[.]211[.]0 (AS8075 Microsoft)
request     POST /mcp-rest/test/connection
user_agent  python-requests/2.32.5
ja4h        po11nn070000_c6aaf02af040_000000000000_000000000000
client_fp   h4POST_046adf0d
Host        vik-rb:8081
headers     Authorization: Bearer sk-1234

The second request arrived around similar time from a different host, also on Microsoft Azure. It used the same user agent and client fingerprint, with only one field in the payload changed. 

/mcp-rest/test/connection is one of two LiteLLM endpoints used to preview MCP servers. In affected versions, the endpoint accepts a complete stdio MCP server definition and launches the supplied command as a subprocess on the LiteLLM proxy host. 

This is CVE-2026-42271, which was disclosed on 20 April 2026 with a CVSS score of 8.7, fixed in LiteLLM 1.83.7 on 8 May, and added to CISA's Known Exploited Vulnerabilities catalog on 8 June following confirmed exploitation in the wild. Affected versions are from 1.74.2 through 1.83.6.

Exploitation requires a valid LiteLLM proxy API key. In these requests, the operator used sk-1234. That value is not random: it appears in LiteLLM's own quickstart documentation as the example master key:

Payload Analysis

The exploit attempts with python3 as the command and passed a detached shell command through its arguments. 

Figure 7. The delivered payload.

The shell attempted download across two mirrors for private/manager, followed by marking the payload executable and executing it. The payload also attempts persistence via cron.

The payload fails with a Bash syntax error, so this attempt would not have executed. The infrastructure it referenced was operational: the loader endpoint was live at the time, and served as a functional implant.

The same tool ran account takeover

The two LiteLLM exploit attempts share the client fingerprint h4POST_046adf0d. We found the same fingerprint in 24 additional events during the same six-hour period.

Those events were not additional LiteLLM exploitation attempts. They were part of an account-takeover sequence targeting Open WebUI, repeated three times with the same behavior.

The sequence first attempted to register the account svcupd8@svc.local. Only when registration failed did it begin trying default credentials. That ordering matters because on an unclaimed Open WebUI deployment, the first account created can become the administrator.

Figure 8. One toolkit, two products, one six-hour window

The same tooling was therefore targeting two different AI infrastructure products within roughly 6.5 hours: 

  • LiteLLM through an RCE vulnerability, and
  • Open WebUI through account registration and default credentials. 

That is broader than a script written around a single CVE and suggests a toolkit designed to identify and compromise exposed AI services through whichever path is available. 

Eighteen operators found the default key. One turned it into code execution.

The capture above is only one event. The more useful question is what else we see on the internet using the same credential.

Between 15 July and 6 September 2026, we observed 208 events from 18 distinct source addresses presenting sk-1234. What distinguishes them is what happened after the credential was accepted.

Table 2. Endpoints reached with the default sk-1234 key

‍

Only the two addresses described earlier ever reached /mcp-rest/test/connection with this key, one request each. The path /private/manager also appears only in those two events. None of the other sk-1234 sources referenced it.

That difference is important. The exposed LiteLLM population is publicly discoverable, and sk-1234 appears in the vendor documentation. Eighteen separate sources found both. But only one activity cluster used the credential as part of a path toward code execution.

The administrative requests are also worth calling out. We observed accesses to /key/generate and /spend/logs (discussed later in Part 5). In several cases, those requests arrived within minutes of a valid key being accepted.

One source spent five weeks presenting Authorization: Bearer 1 to Ollama endpoints. That does not establish exploitation, but it does say something about the level of authentication attackers expect to encounter on internet-exposed AI infrastructure.

Part Four: The exposed population
Measuring it rather than sampling it

Shodan returned 1,936 LiteLLM candidates on port 4000. We enumerated the population and then independently validated each candidate.

The figures below are based on our own confirmation rather than simply relying on the Shodan tag.

Table 3. Exposed LiteLLM gateways on port 4000, from our own scan

The 370 count reflects three KEV-listed CVEs affecting this version range; CVE-2026-42271, the flaw this campaign used, accounts for 214 of them (see Part Five and the References). Of the 370 affected systems, 46 were fully open.

Three of the C2 nodes are themselves vulnerable LiteLLM hosts

‍

Three command-and-control addresses from the campaign were themselves running vulnerable LiteLLM deployments. 

All three were running versions affected by the same RCE while they were serving as campaign infrastructure. Two were accessible without authentication.

‍

Figure 9. Exposed AI gateways serving as both target and campaign infrastructure
‍A measured compromise rate

Hosts running vulnerable LiteLLM versions contacted our sensors about fourteen times more often than patched hosts. Nine of the twelve systems that contacted us were running version 1.82.6, even though that version represented only 16.8% of the affected population.

We understand that this alone is not strong enough evidence to call a system compromised, so we validated this using an independent signal: Abuse-report history.

We compared every exposed LiteLLM 1.82.6 host against a control group of similarly aged LiteLLM deployments running versions not affected by any of the KEV-listed flaws.

Both groups run the same product, are internet exposed, and are from roughly the same deployment era. Yet more than a third of the 1.82.6 population has an abuse score of at least 50, while none of the control systems does.

Why 1.82.6; the finding that should worry you most

At first glance, the prevalence of LiteLLM 1.82.6 has an obvious explanation: LiteLLM's own SDK quickstart continued to instruct users to install that exact version with:uv add 'litellm==1.82.6'

Version 1.82.6 was also the last release before the LiteLLM PyPI supply-chain compromise on 24 March 2026. During that incident, an account takeover was used to publish backdoored versions 1.82.7 and 1.82.8. The malicious packages harvested data including credentials, secrets, and environment variables.

The sensible response was to pin to the last known-good release, and many projects did exactly that, merging emergency changes that said, in effect, stay on 1.82.6 until the supply-chain issue is resolved.

Within weeks, three CVEs were published (including CVE-2026-42271 which is being discussed in this article) were disclosed, and 1.82.6 fell inside the affected range for those.

The same version that protected those deployments from a credential-stealing package in March later left them exposed to SQL injection, remote command execution, and authentication bypass.

The broader lesson goes beyond LiteLLM. Emergency version pins are usually created as temporary controls, but they are implemented as permanent configuration. Unless there is something that forces a later review, a pin that reduces risk today can quietly become the thing creating risk months later.

How much of this is happening in the field

Independent research shows the same surface under broad exploitation: Sysdig reported SQL-injection exploitation within 36 hours of disclosure, Microsoft documented a gateway compromise in August, and Wiz reported both the auth-bypass and command-injection paths across its honeypot telemetry. This is no longer a vulnerability class confined to proof-of-concept code.

Part Five: Impact and Mitigation
What a compromised LiteLLM Gateway exposes

The earlier requests to /key/generate and /spend/logs show that attackers are already probing the administrative and credential-management surface, so it is worth looking at what a compromised LiteLLM gateway can expose.

The /key/generate endpoint can mint usable virtual keys from the master key. Spend logs, audit data, and cached prompts may also contain sensitive operational information and are not necessarily encrypted at rest.

That matters in this campaign because sntnl_v1 includes process-injection functionality. On a LiteLLM host, the gateway process itself is a high-value target: it holds configuration and credentials in memory and handles prompts and responses in plaintext as part of normal operation.

Mitigation and Protection Guidance 

Patch LiteLLM to at least 1.84.0, rather than stopping at 1.83.7.
Version 1.83.7 addresses CVE-2026-42271, but 1.84.0 moves deployments beyond the affected range of all three KEV-listed issues discussed here.

Review emergency version pins.
A hard pin should have an owner and a review date. The largest high-risk group in our scan was not created by obviously careless deployments; many teams had deliberately pinned to 1.82.6 because it was the safest response to a supply-chain compromise at the time.

Replace vendor defaults before exposure.
Credentials copied from quickstarts should never survive into an internet-facing deployment. The repeated use of sk-1234 shows that attackers are testing exactly this assumption.

Audit the broader tool-execution surface.
The vulnerable MCP routes are one example of a more general problem: agent infrastructure contains many places where configuration eventually becomes code execution. Connector definitions, tool manifests, workflow runners, and similar features should all be treated as execution boundaries and reviewed accordingly.

Claim every control plane during provisioning.
An unclaimed Open WebUI instance can hand administrative access to the first registered account. User databases should be audited for accounts that were not created through the expected provisioning process, including svcupd8@svc.local.

Hunt on behavior, not only file hashes.
The sample was undetected by the VirusTotal engines we observed, so host behavior is likely to be more useful than signature-only detection. Relevant signals include unexpected user-level systemd units, cron jobs launching binaries from temporary directories, immutable attributes on unusual files, persistent /dev/shm/.i2c_* objects, and .readme fetches from raw.githubusercontent[.]com on systems that have no reason to consume developer content from GitHub.

This campaign did not require a novel AI attack.

The operator took an existing botnet workflow, added a publicly documented vulnerability in an AI gateway, reused a credential printed in vendor documentation, and operated against a population of systems that had accumulated exposure faster than many teams could patch them.

The immediate security problem around agent infrastructure is not only new attack techniques. It is also familiar infrastructure security problems appearing inside a stack that is being deployed very quickly leading to problems like default credentials, exposed management interfaces, stale versions, emergency pins that never get revisited, and execution surfaces with too much trust.

Limits of this research

The payloads this loader installs, because we never obtained tasking, so everything said about intent derives from capability.

Whether the analysed binary is byte-identical to what the July infrastructure served. Six weeks separate the exploit attempt from our retrieval and we fetched the fallback mirror; the binary parses --id and --port, exactly the flags the dropper passes, from a mirror the dropper names; a strong circumstantial link, not proof.

One signal in our own capture was partly an artifact. Both source addresses requested GET /v1/models a second before the exploit and got a 200, which reads as targeted fingerprinting; our sensor facade answered that path on every port at the time, a defect since fixed.

The loader was analysed statically and never executed, third-party services were queried by hash and address only, and no operator-controlled host was contacted.

Indicators of compromise

Defanged for publication. Loader hosts are likely dead by publication; hashes, host artifacts and behaviour are the durable indicators. The full set, the thirty-seven decoded C2 addresses, the rotation history, the poem decoder and detection rules are available to defenders on request.

‍

Files 
Config channel
Delivery (LiteLLM RCE)
Open WebUI takeover (same operator, same window)
Host artifacts (durable)
Network behaviour (durable)
References
  • NVD: CVE-2026-42271 (CVSS 8.7, 1.74.2–1.83.6, fixed 1.83.7) · CVE-2026-42208 (CVSS 9.3 v4, 1.81.16 to before 1.83.7) · CVE-2026-48710 (Starlette “BadHost”)
  • CISA KEV catalog: 42208 added 8 May 2026 · 42271 added 8 June 2026 · 59822 added 2 September 2026
  • Horizon3.ai on chaining 42271 with 48710, June 2026; Sysdig on 42208 exploitation 36 hours after disclosure, April 2026
  • LiteLLM security update, March 2026, on the PyPI compromise of 1.82.7 and 1.82.8
  • Microsoft Security Blog, When AI infrastructure becomes the target, 26 August 2026
  • Wiz, Attacks on AI Infrastructure: 90-Day Honeypot Telemetry, 27 August 2026
  • LiteLLM documentation: proxy configuration, virtual keys, SDK quickstart, security encryption FAQ

‍