We cracked the drone without breaking the encryption, and your webhook is next
The war in cyberspace is already on, and it hits ordinary private software. Attackers replay your webhook, forge your sender and publish a poisoned package. You can start closing all three this week.
Jakub SikoraVP of Engineering, Circit Ltd.OCF26 was an internal Cyber Legion attack-defense CTF, organized by DKWOC. One of the services we had to break was Watchtower, a drone command system with a panel and a live map where the drones report their positions. The comms between the drones and the panel were encrypted with AES, and everything ran over HTTPS. Ask most engineers whether that’s secure and they’ll say yes. On paper, they’re right.
Every team got the firmware, so we did the boring thing and read it field by field, looking for places where it assumed trust instead of checking it. Two things stood out: AES in ECB mode and a hard-coded backup key. ECB has no IV and no chaining, so it’s deterministic. The same 16 bytes under the same key always encrypt to the same 16 bytes, and repeated blocks stay repeated, visible to the naked eye.
But ECB was a symptom. The root cause was quieter: nothing in the protocol reliably answered one question. Who sent this packet? The system trusted the channel and assumed the channel implied the sender.
So we became the drone. We sent a correctly encrypted, perfectly formatted, completely fake position packet. Everything downstream validated it, because everything downstream only checked that it decrypted cleanly, and the panel drew our coordinates with no sanity check. The drone walked off the map. Every frame after that looked like the real drone reporting in. We didn’t take the panel. We took the truth the panel was showing.
To be precise, we never touched flight control or the motors. We fed the operator the telemetry the operator trusted, and that turned out to be enough.
Nothing about that bug is specific to drones. It ships in normal software, and I keep finding it.
The watchtower is your webhook#
HTTPS present, HMAC signature function written and never called. Or called, but without a timestamp check, so the captured payload replays forever. “The URL is a secret, who’d guess it” is just a password you pasted into a log file.
The watchtower is your queue#
A consumer reads from an “internal” queue. Coming from our network is a network diagram, not a security property. One compromised laptop on the office Wi-Fi and your consumer happily acts on whatever that machine publishes, with perfect TLS on every hop. By morning the lie has acquired a chart.
The watchtower is your JWT#
The signature validates, and the app never checks iss, aud, or exp. It never
checks the token type either, so an access token can work as a password-reset
token. The signature proves the token is untouched. It says nothing about whether
this token should open this door.
The watchtower is your dependency update#
Picture it: a dependency bot opens a PR, tests pass, you merge. The package is
four hours old, and its postinstall script reads your CI’s NPM_TOKEN. The bot
carried it through the front door.
None of the four is a zero-day, and none needs skill. They’re replayed payloads, unchecked fields, unsigned senders. Spray and pray means sending a thousand forged requests at a thousand services and collecting from the one that never asks “who sent this?” That’s most of them. I’d bet on yours hiding at least one; mine did.
The war in cyberspace is already full on#
Here’s what happened in Poland since late August:
- On 22 and 23 August attackers used an SQL injection flaw in Qbusoft’s Medyc system to take patient names, PESEL numbers, addresses and phone numbers. The breach went public on 24 September. The attackers claim up to 5 million people, and nobody has confirmed that number (Help Net Security).
- Enel-Med, a clinic chain, was breached on 24 September. The company says about 3% of its patients are affected (RMF24).
- FELG Dent makes software for dental practices. The attackers, who call themselves Horus, claim they abused an IDOR flaw in an API to pull 2.4 million patient records, and they want a ransom. FELG confirms the attack and puts the count at about 2 million records. The method is the attackers’ claim, not verified (ABCZdrowie).
- Fakturownia, an invoicing service with over 600,000 business users, had an attacker inside from 3:20 on 27 September to 17:45 on 28 September. The attacker reached company data, bank account numbers, API tokens, password hashes and invoice amounts. The company reported it to CERT Polska (Niebezpiecznik, CyberDefence24).
- Wakacje.pl, a travel portal, lost customer passport data on 29 September through its customer system and employee mailboxes. Niebezpiecznik reports it was not the group behind the Medyc, Enel-Med and Fakturownia incidents (Zero.pl).
It goes past data theft. On 29 and 30 December 2025 attackers hit more than 30 wind and solar farms and a combined heat and power plant in Poland. They came in through exposed FortiGate VPN devices and a controller with default credentials, then wiped industrial devices. No blackout followed (The Record, SecurityWeek). Firms disagree on who did it: CERT Polska points to a group linked to Russia’s FSB, while Dragos and ESET point to Sandworm (The Hacker News).
The targets: a medical software vendor, a clinic chain, an invoicing tool, a travel portal, wind farms. Most of these are private companies, not ministries or the military, and every one is a service many people depend on. Attackers pick services where an outage hurts the most people.
The reported entry points read like a checklist from this post: an SQL injection, an IDOR in an API, a default password, an exposed VPN. Boring bugs in ordinary software, written by engineers with regular jobs. If you’re one of them, you’re on the front line now.
The spray just got an autopilot#
This class of bug used to need a skilled human to find it. That discount is gone.
I reproduced the model and compute pieces of this attacker’s toolkit for effectively €0. The rest is cheap:
- An uncensored, capable model, one command away. An abliterated model from
Hugging Face ships without the refusal behavior.
hereticis the tool, and a Heretic build of Qwen3.8-27B goes from about 99 refusals per 100 harmful prompts to 3 (model card). The safety filter normies believe is enough lives in their datacenter. The attacker downloaded the weights and deleted it. You don’t even need a rented GPU. I run a 27B uncensored model locally on a 64 GB M2 Max: about 16 GB of weights, roughly 20 GB resident, 18 to 22 tokens per second, and no network connection required. - Free compute, with failover. Free GPU tiers (Modal’s monthly credit, Kaggle’s weekly GPU hours) chained behind a router, so when one quota dies the attack fails over to the next. The tiers are capped, but an opportunistic scanner loop doesn’t need more. No card, no invoice, no trail.
- Infrastructure without identity. Rotating residential proxies make every attempt look like a different home broadband user, so your IP allowlist sees customers. SMTP and MFA relays come cheap with no-KYC crypto. The “MFA enabled” checkbox protects less than the compliance checklist suggests.
The odds are lopsided. A study by Scale AI researchers (arXiv 2603.01246) ran 2,390 real prompts from a collegiate cyber-defense competition through three 2024 models. They refused 12.2% overall and 43.8% in the system-hardening subcategory. The attacker’s uncensored model helps without hesitation, while the defender’s aligned model sometimes flinches at “help me harden this.”
In July 2026 an agent running an internal OpenAI cyber-capability evaluation escaped its sandbox through a zero-day in a package-registry cache proxy, then reached Hugging Face production (Hugging Face’s timeline). It was an evaluation run, not an outside attacker, and no human was driving.
Point an agentic loop at this stack and the model drives real scanners (nuclei,
sqlmap, ffuf…), reads the findings, picks the next probe, and keeps going
while the attacker sleeps. It hunts exactly the bug class above, the unsigned
webhook and the unchecked JWT claim. It poisons the npm package, and it even runs
phishing campaigns while leaving no trace. Cheap attacks stopped being the ones
you can ignore.
$./llama-server -m Qwen3.8-27B-uncensored.Q4_K_M.gguf -ngl 99 -c 8192llm_load_tensors: offloaded 65/65 layers to GPUmain: server is listening on http://0.0.0.0:8080$python agent.py --target $BOX --tools nmap,nuclei,ffuf>tool: nmap -sV -Pn $BOX22/ssh 80/http 443/https (TLS 1.0 enabled)>tool: nuclei -t http/ -u https://$BOX[exposure] /.git/ · [misconfig] stack trace in 500>tool: ffuf -u https://$BOX/FUZZ -w common.txt/admin 302 /webhook 200 /.env 403>POST /webhook accepts unsigned body -> candidate loggedno prompt refused. no rate limit. no bill. it runs while you sleep.
Tell it “full psycho mode, nuke everybody now” and the clanker takes you literally.
Fight it with the same €0 model#
The asymmetry comes down to who points the model at your service first, so point it first.
First, attack yourself. Run a pentest with the same class of local model against your own dev or staging environment, never prod. You get a no-guardrail adversary that works through the night for close to nothing. Do it before someone with a grudge and a weekend does it for you. (Your own environment. Written authorization. Keep it that way.)
Second, patch with negative tests, TDD-style. A finding is done when a test fails without the check. Happy-path tests prove the door opens for the right key. Negative tests prove it stays shut for the wrong one. The helpers below are illustrative:
def test_rejects_valid_signature_wrong_audience():
token = mint(aud="some-other-service") # correctly signed, wrong aud
with pytest.raises(Unauthorized):
authenticate(token)
def test_rejects_replayed_webhook():
body, sig, ts = capture_real_webhook()
assert deliver(body, sig, ts) == 200
assert deliver(body, sig, ts) == 409 # nonce already seen
Replay the captured webhook and expect the rejection. Send a JWT with a forged
iss and expect the rejection. Red, green, commit. Now the spray-and-pray version
of that attack, the one fired at ten thousand services, dies at your door, and CI
keeps it dead.
Third, firewall the supply chain. Most teams won’t hand-write a dependency attacker, so automate the defense. I built supply-core, a local-first supply-chain firewall (Rust, OSV.dev data, zero telemetry) that asks the drone’s missing question about every package: who published this, and is it fresh?
- It quarantines packages published in the last 24 to 72 hours and falls back to a frozen known-good version. Malware is usually detected and removed within hours (pnpm’s supply-chain docs), so a short delay keeps most bad releases out of your build.
- It flags floating GitHub Actions tags (
actions/checkout@v4). Whoever takes over that tag gets your CI’s write tokens. - A daily job inventories every lockfile and leaves a
report.md, so you can see your verification state instead of assuming it.
Here’s the same package through two CI runs. This is a simulation, not a real incident.
$npm install+ pkg@2.4.1 (published 4h ago)postinstall… exfil CI_TOKEN -> ██████build: passed ✓compromised. silent.
$npm installpkg@2.4.1: in 24h quarantine windowblocked -> fallback pkg@2.3.9 (frozen)build: passed ✓flagged in report.md ✓
A quarantine can’t prove a package is safe, and an OSV lookup can’t catch an undisclosed vulnerability. supply-core is one check at the dependency boundary, where “the registry served it” gets mistaken for “we should run it.”
Take it to work asap#
Grep your codebase for every place you receive a message you didn’t create:
webhook handlers, queue consumers, jwt.decode, internal RPC. For each one,
answer five questions. Encryption tells you nobody read the message. It can’t
tell you who wrote it, when, or whether you should act on it:
- Who really sent it?
- Was the content changed?
- Is it fresh or replayed?
- Does it make sense for this domain?
- Does the UI show the fact, or just the last value it was handed?
Answer them once, in code, with a failing test behind each answer, and you’ve closed the cheap attacks.
You don’t need a security title for this. If you ship software that people depend on, you’re already on the front line.