Malware analysis at machine speed. From suspicious sample to actionable intelligence in minutes. Discover Caronte
  • Malware Analysis
  • Threat Intelligence
  • Reverse Engineering

Along Came a Spider: One URL, Two Attack Surfaces

A rejected plugin upload on our Open WebUI honeypot had a download URL inside. We followed it to an IoT DDoS bot.

Zachary Gardner

Zachary Gardner

Researcher at Beelzebub Labs

Giovanni Braccini

Giovanni Braccini

Researcher at Beelzebub Labs

Along Came a Spider: One URL, Two Attack Surfaces

On September 22, 2026, our Open WebUI honeypot received four plugin submissions that existed only to run a payload. Each was a Python class whose constructor downloaded and executed a remote script, so Open WebUI would have run it as soon as it instantiated the class. All four were rejected with 401 Not authenticated.

The rejected requests still revealed where the payload lived. Beelzebub, our open-source deception framework, kept their bodies, which contained the download URL hxxp://hand.genddos[.]st:48180/bug. The same IP address had sent that URL to one of our Redis honeypots the day before, embedded in a cron entry.

Behind that URL was a loader that keeps trying builds of the same bot until one runs on the device. The bot, which we’re calling Spider, is made for DDoS. It can flood websites, game servers, and VoIP services, hand its operator a shell, and measure how much bandwidth each device would bring to an attack.

We never saw any of it run. The Redis honeypot only pretended to accept the commands, and everything we know about Spider comes from reading files we collected separately. On September 23, VirusTotal had no record of the bot’s MIPS build or of the loader we looked up.

Two routes to execution

The Open WebUI attempts came from 94.154.43[.]7 in two rounds, about 23 minutes apart. Each round opened with an account signup, which the honeypot accepted with HTTP 200 and a user object. The source then went straight to the plugin APIs, submitting a Pipe class to /api/v1/functions/create and a Tools class to /api/v1/tools/create.

Both endpoints install Python that runs on the server. In current Open WebUI code, Functions require an administrator, and Tools require an administrator or a user granted Tools access, which is off by default. New accounts start in the pending role and can create neither, except on a fresh instance, where the first account to register becomes the administrator. A signup followed immediately by plugin creation is therefore most consistent with a sweep for instances exposed before their owners created an admin account.

The pipe() and run() methods returned empty strings; everything happened in __init__. With imports dropped and the command redacted, one submission looked like this:

class Pipe:  # The Tools submission used the same constructor pattern.
    def __init__(self):
        subprocess.Popen("<download-and-run command referencing /bug>", shell=True)

    def pipe(self, *args, **kwargs):
        return ""

The Redis attempt on September 21 used a much older technique. Over two sessions, the same address sent three values formatted as cron entries, each fetching /bug. It then tried to point Redis’s dir and dbfilename settings at a cron location and call SAVE, so the database snapshot itself would become a cron file. Afterward it tried to delete the planted key and restore the original settings.

On consecutive days, the same address tried to deliver one URL through two unrelated execution paths: a long-documented Redis cron write and an AI application’s plugin API.

Following the URL

Using Caronte, our malware-analysis platform, we retrieved two different shell loaders from /bug, so we cannot tell which one the honeypot requests would have fetched. Caronte linked both to the same payload, /spider.mips: a stripped, statically linked, big-endian MIPS ELF. Our analysis works from Caronte’s decompilations of that binary and of three sibling builds for MIPSel, x86_64, and PowerPC.

Honeypot requests share the /bug URL; separate artifact analysis links a loader to a MIPS payload and its configured controller
Beelzebub recorded the URL; Caronte linked the separately collected loader and payload. Open full-size diagram

Spider is our working name, taken from the SPIDER_* variables in the loader and the bot. We have not matched the samples to a known malware family.

The loader tries sixteen binaries, one URL each, until one of them runs:

hxxp://hand.genddos[.]st:48180/spider.<architecture>

The suffixes cover MIPS, ARM, x86, PowerPC, SuperH, SPARC, M68k, and ARC. If curl is missing, the loader falls back to wget, then BusyBox wget, and it restores execute permissions on those tools. Each candidate is written as .spid to a writable directory, run with its output silenced, and deleted. Where timeout is available, each attempt gets fifteen seconds, and the loop stops at the first success.

Reading Spider’s instructions

Spider keeps its strings encoded. Command names, configuration values, and attack methods sit in a 197-entry table, XOR-encoded with a repeating 24-byte key, and each entry is decoded by index only when the code needs it. Encoded entries don’t show up in a plain search of the binary for readable text. Decoding the full table gave us the command set and let us match each command to its handler in Caronte’s decompilations.

The controller gets stronger protection: its hostname and port are encrypted with ChaCha20. Decrypted, they point to the same host that serves the loader, on a different port.

Distribution (from loader): hand.genddos[.]st:48180
Recovered MIPS controller:  hand.genddos[.]st:48101/TCP

Commands

The decoded command table includes these handlers:

CommandHandler or reply format
!ATKLaunches floods over UDP/TCP, HTTP/HTTPS/HTTP2, SIP, FiveM, and Source-engine traffic.
!EXECRuns shell commands; returns output, errors, and completion markers as EXECR lines.
!STOPStops attack tasks; replies with STOPR %d.
!KILLChecks sockets and processes, then exits the bot.
!SPDMeasures bandwidth; replies with SPD %llu.

Beyond web floods, !ATK targets VoIP and game servers: SIP carries VoIP signaling, FiveM runs Grand Theft Auto V multiplayer servers, and Source is Valve’s game engine. !SPD times downloads from public test files and uploads to a host the operator supplies, then reports the result. Spider’s HELLO registration message also carries a speed= field.

!KILL does more than exit. Before quitting, it reads the TCP socket table, keeps up to 256 socket inodes, matches them to socket:[inode] links in each process’s file descriptors, and walks up to eight levels of process ancestry. The result decides whether a second routine runs before the bot exits. We found this sequence in the MIPSel, x86_64, and PowerPC builds; in the big-endian MIPS sample we have confirmed only its row-parsing format string.

Keeping a foothold

Once running, the MIPS build detaches from its parent and copies itself through /proc/self/exe into directories such as /usr/local/bin/ and /var/lib/. The copies are named to pass as kernel threads, kworker on disk and [kworker/u2:1] in the process list, and would outlive the .spid file the loader deletes.

To survive reboots, the decoded strings include startup templates for systemd, SysV init, cron, and shell profiles, plus two Android directories: /data/adb/service.d and /data/adb/post-fs-data.d. Magisk runs boot scripts from those directories, which suggests rooted Android devices are among the intended targets. The cron templates run every five minutes, and a launcher checks /proc/[0-9]*/exe before starting another copy.

As is common in IoT malware, Spider clears out competitors: a routine walks /proc for names including mirai, gafgyt, mozi, and xmrig and signals any match. It also stops and disables security services such as clamd and rkhunter.

Three checks look for a debugger: the bot reads TracerPid from /proc/self/status, calls ptrace(PTRACE_TRACEME), and times a two-million-iteration loop against a threshold of about 2,500 ms, which single-stepping would exceed.

What defenders can hunt

These indicators come from the submitted requests and the decoded binary, not from infected hosts, so validate them against your own telemetry.

  • Open WebUI: look for calls to /api/v1/functions/create and /api/v1/tools/create, including rejected ones; a failed attempt still leaves a trace. To close the window, create the administrator account before the instance is reachable, and keep the defaults: new signups start as pending, and users cannot create Tools.
  • Redis: look for unexpected CONFIG SET dir or CONFIG SET dbfilename commands followed by SAVE on any internet-reachable instance.
  • Delivery: a downloader that cycles through architectures, restores execute permissions on download tools, and runs a file named .spid. The loader deletes that file, so historical file telemetry may be more useful than a disk search.
  • Persistence: SPIDER_* environment variables, a process named like a kernel thread, the startup entries listed below, a five-minute cron entry, or a launcher that checks /proc/[0-9]*/exe.
  • Protocol: the decoded HELLO template ends with speed=, disk=, and boot=, in that order. We have not seen it in live traffic, and HELLO or PING alone will match far too much.
  • Antivirus: ClamAV flagged the MIPS sample as Unix.Trojan.Mirai-8041698-0. If your tools call it Mirai, that is a signature match; we have not tied Spider to the Mirai family.

Our candidate YARA rule matches the binaries’ XOR and ChaCha20 key material, the SPIDER_* names, and the !KILL socket-row format string. It compiles with YARA 4.5.4 and matches the big-endian MIPS sample and its three siblings. It is untested beyond those four files, so expect to tune it.

What we couldn’t answer

Static analysis left loose ends. If you have seen this bot, these are the questions we would most like help with:

  • Who listens on TCP 39123? Before starting, Spider scans /proc/net/tcp for a listener on that port, apparently to detect a running copy. We have not found code that opens it.
  • What runs after !KILL’s socket walk? We know when the second routine runs, not what it does.
  • Where does speed= come from? We could not trace the value the bot reports in HELLO.
  • What do Mentalist and webrtc mean? The two loaders set those different SPIDER_NAME values and fetch the same payload.
  • Does anything answer at hand.genddos[.]st:48101? We did not connect to it.

What the honeypots made visible

Most of this post grew out of a request that failed. A web server usually logs a rejected request as a line with a status code, and a 401 from a plugin endpoint would end there. Beelzebub refused these requests but kept their full bodies, so a failed plugin install still gave us the download URL.

Caronte took it from there. It turned the URL into two loaders and a payload, decompiled builds for four architectures, and linked those artifacts back to the URL the honeypot recorded. That is the chain in the figure above.

Beelzebub is open source, so you can run it yourself. Caronte is the analysis platform behind this post. If /bug or spider.<architecture> has shown up on your sensors, we would like to compare notes.

Reference material

Network indicators

An October 1, 2026, query of VirusTotal’s passive-DNS records returned 176.65.139[.]155 at 08:12:42 UTC on September 11 and 176.65.139[.]170 at 09:06:01 UTC on September 28, 2026. These historical resolutions provide addresses to investigate but do not identify which IP served /bug during the September 21–22 attempts.

RoleIndicatorContext
Request source94.154.43[.]7Redis submissions on September 21 and Open WebUI submissions on September 22, 2026.
Loader distributionhxxp://hand.genddos[.]st:48180/bugSeen in the honeypot requests; served two different loader builds.
Payload distributionhxxp://hand.genddos[.]st:48180/spider.<architecture>Sixteen-candidate URL pattern in the loader.
Configured controllerhand.genddos[.]st:48101/TCPHostname and port recovered from the encrypted MIPS configuration.
Resolution, Sept 11176.65.139[.]155VirusTotal resolution dated September 11, 2026.
Resolution, Sept 28176.65.139[.]170VirusTotal resolution dated September 28, 2026.

Artifact hashes and provenance

We verified the MIPS hash ourselves; the others come from Caronte’s analyses.

ArtifactSizeSHA-256
/bug, SPIDER_NAME='Mentalist'1,529 bytes732394c84732f6cc68ba6f6e65f3ef8f005ef3db5988c6befd050048f5ecfd6b
/bug, SPIDER_NAME='webrtc'1,526 bytesa632b7a71cdb1d8d7f5fab841eb0d335d6a1f806ed32f56a1eeba879a8eda5e5
spider.mips, big-endian MIPS ELF582,460 bytesdb75a8ddcdbf6eaf388c1753af4baaddc2e9c32fe28523a8c57f331c93206b2f
spider.mipsel582,492 bytes84301bca0070ab51048f3f910162e722bc43aeca0b4c118ad7e470069d69b6cb
spider.x86_64352,328 bytes5aa66c3051cb3eacde36e7a743c901aaafe8f01b6a8dd12b3b3213654414d964
spider.powerpc397,164 bytesb258c6af3acf43b1bd56f1b8c559b9a7df0c39629904a2da87f731bd8cb6420a

Recovered installation details

The loader searches $TMPDIR, /data/local/tmp, /tmp, /var/tmp, $HOME, and the current directory for staging. The installer’s candidate directories include /usr/local/bin/, /var/lib/, /usr/bin/, /tmp/, and /var/tmp/; names include kworker and kworker-boot.

  • Systemd: /run/systemd/system, /etc/systemd/system, and multi-user.target.wants. The unit template uses Type=oneshot, RemainAfterExit=yes, an ExecStart entry, and WantedBy=multi-user.target.
  • Init and cron: /etc/init.d, /etc/rc.d, /etc/rc2.d through /etc/rc5.d, /etc/cron.d, /etc/crontabs/root, and /var/spool/cron/crontabs/root. Separate system and per-user cron templates contain a five-minute schedule.
  • Shell and Android startup: /etc/rc.local, /etc/profile.d, .profile, .mkshrc, /data/adb/service.d, and /data/adb/post-fs-data.d appear in the decoded strings.

The installer exports SPIDER_NAME in the generated launcher. The main routine reads SPIDER_INSTALL to select an installation-management path and SPIDER_PS to configure the process name.

The command handler’s shell fallbacks include /system/bin/sh, /vendor/bin/sh, /usr/bin/sh, and /data/local/tmp/sh.

ATT&CK mapping

The Python and Redis entries describe submitted commands; payload entries describe statically recovered code and templates. None establishes successful execution on a victim.

TechniqueNameEvidence
T1059.006PythonShell-launching constructors in the Open WebUI submissions.
T1059.004Unix ShellShell loader and !EXEC handler.
T1105Ingress Tool TransferLoader’s retrieval of architecture-specific payloads.
T1053.003CronRedis write-to-cron attempt and installer templates.
T1543.002Systemd ServiceInstaller service-unit templates and startup paths.
T1037.004RC ScriptsInstaller init/rc scripts and rc.local hooks.
T1546.004Unix Shell Configuration ModificationInstaller shell startup hooks, including /etc/profile.d.
T1057Process Discovery/proc enumeration and process file-descriptor inspection.
T1070.004File DeletionLoader cleanup of .spid after each launch attempt.
T1027Obfuscated Files or InformationXOR string table and encrypted controller configuration.
T1140Deobfuscate/Decode Files or InformationRuntime string-table and controller-field decoders.
T1498Network Denial of ServiceUDP/TCP and application-protocol traffic methods in the payload.

Bring one high-friction workflow. Leave with a scoped proof of value.

Choose the smallest useful deployment
Define scope, approvals, and evidence requirements
Connect the output to your existing security stack