Author: canutethegreat

  • Patronus dev log 5: security holes, netlab, and the 0.2.0 release

    Patronus dev log 5: security holes, netlab, and the 0.2.0 release

    Part 5 in the Patronus dev log series.

    Backdated, written in November 2026 from git and CI records. Part 4 ended with the installer test paused.

    October 16: two things at once

    First, the installer test moved off the Mac. It now runs on a KVM runner with QEMU directly: install with the ISO attached, then a cold boot of the disk with the media removed. A live system in about 25 seconds, install plus reboot about 6 minutes, and the installed system comes up on rpool with zpool ONLINE. The first green run is recorded in the commit. Getting there found five real installer bugs, which the commit lists: a bad parted mkpart fs-type for ZFS, a free-space calculation that started partition 2 at the disk end, a root pool that kept its temporary mountpoint forever, pool features GRUB couldn’t read, and chroot bind mounts that stacked and never unmounted, so the pool export failed and the next boot refused to import it. From then to November 6 that workflow has 25 passing runs and 3 cancelled ones.

    Second, the security commits of the same day. From the commit messages:

    patronus-web created 'admin'/'admin' and 'operator'/'operator' accounts on
    every install, so anyone reaching :8443 could take over the firewall.
    SECURITY (CRITICAL): only /api/auth/me and /api/auth/logout checked the
    session. Every other page and the whole management API -- add/delete
    firewall and NAT rules, WireGuard peers, interface up/down, backups,
    service start/stop -- answered anonymous requests

    So for a year after the “Complete Implementation” commit the web UI had no login that mattered and shipped with default passwords. Both are fixed: the admin password is per install (a random 24 character one written to a 0600 file on first start, and the service refuses to start with a known or short one), and a session is required for every page and API call, with regression tests.

    netlab

    There’s a harness called netlab: network namespaces, a router and clients and an ISP, real nftables and real daemons, scenarios as shell scripts, and power-cut and upgrade tests. A README claims ledger maps every claim to a test that proves it, and a “honesty guard” test scans the web code for old fixture values.

    CI-only failures: sh -c with bash functions (dash can’t see them), a DNS resolver in the CI image binding port 53 before a scenario’s own, grep -q under pipefail killing nft with SIGPIPE, kernel logging that doesn’t reach a namespace, and a runner with br_netfilter loaded that made bridged traffic hit the firewall. Some scenarios couldn’t pass at all and were parked in a pending/ directory instead of leaving main red: UPnP port mapping (miniupnpd answered 501 on the nftables backend and I never solved it), DHCPv6 prefix delegation for a while, and VRF.

    From Forgejo, October 16 to November 6: 416 runs, 209 success, 108 failure, 82 cancelled. About 294 commits.

    Features, and what they were proven on

    Multi-WAN with failover, QoS (CAKE, fq_codel), DHCPv6 prefix delegation, OpenVPN, IPsec road warriors, VRFs, zero-touch provisioning, factory reset, LDAP login, GitOps, and per-device segmentation with DNS policy and time limits. The 0.2.0 CHANGELOG lists its own limits: unmanaged switches, IPv6 binding “next”, and simulated radios only. Most of this was proven in namespaces and one lab VM, not on a production network.

    The ISO and 0.2.0

    On October 27 Patronus got a live installer ISO using the same builder as Horcrux, then a text installer with no default password and key-only SSH, aarch64 images and a console menu. The v0.2.0 release on the 30th (“device segmentation (opt-in), per-device Wi-Fi, DNS rebinding, per-device DNS policy, time limits, insight history, uplink health”) needed five migrations. The first release.yml didn’t produce a release, so another commit made it do so, and fat LTO of the web crate was OOM-killed in a 6 GB runner slot so aarch64 uses thin LTO. The stable ISO gate also failed on stale red statuses from a superseded tag.

    Design decisions

    Headless first. After Horcrux’s rule “it should work on a headless server too, it shouldn’t be a desktop-only OS”, I made the same call for Patronus on November 3. A box is installed and recovered from the console, and operated over SSH and the web UI. There is no desktop edition and none is planned, and CI fails the build if a desktop or personal package lands in the image inputs. There’s also deliberately no private image variant: per-box data comes in via an answer file on the install USB and via GitOps.

    Encryption and boot: a firewall must never stop at boot waiting for a console passphrase. So the installer encrypts only if an unattended unlock works (a TPM2-sealed key, self-tested), and otherwise installs unencrypted and says so.

    ZFS by default with an XFS fallback below 8 GB (the disk page shows the reason). I want it for recovery, bit rot protection and resilience, and ext4 is never the default: Gentoo recommends XFS, and ZFS is just superior tech. There is no on-disk swap on a firewall.

    Opt-in segmentation: the release made it off by default, and an upgrade test asserts an upgraded box has isolation off.

    State on November 6

    The UI now requires a login, the installer passes a KVM test, and v0.2.0 is out with its limits written down.

    Other parts: Part 1, Part 2, Part 3, Part 4.

  • Flipper Zero, part 5: the Wi-Fi board, rogue access points and why 802.11w matters

    Flipper Zero, part 5: the Wi-Fi board, rogue access points and why 802.11w matters

    Part 5 of 5 in the Flipper Zero series. Part 1: what it actually is and what it can’t do. Part 2: why so many office badges are weaker than they look. Part 3: Sub-GHz remotes, rolling codes and how gates should be built. Part 4: USB keystroke injection and how to lock USB down.

    What the Wi-Fi board is

    The last part of this series is about the small green board that clips onto the back of the Flipper. Mine is the official Wi-Fi dev board, an Espressif ESP32-S2 module on a carrier, and I run the community Marauder firmware on it. The rules are the same as in the earlier parts: my own hardware, my own network, defender’s goals. Sending forged frames at networks you don’t control is illegal in most places. In 2014 the FCC fined Marriott $600,000 for using deauthentication frames to stop guests from using their own hotspots, so “they’re only management frames” has already been tried as a defense.

    Below is a screenshot from the Flipper mobile app’s remote screen, showing the Marauder GUI’s Wi-Fi submenu on my device.

    Screenshot of the Flipper mobile app remote screen showing the Marauder WiFi submenu: WiFi Spam, Attacks, Monitor&Sniff, Network Tools

    The menu has four entries: WiFi Spam, Attacks, Monitor&Sniff and Network Tools. I’m not going to walk through the offensive ones. What matters to a defender is what each of them puts on the air, because that is what your monitoring has to recognise. The next photo is the info screen on the same board, which shows the firmware name and the hardware it thinks it is running on.

    Photo of the Flipper Zero screen showing the Marauder info page with the firmware name and hardware Flipper Zero Dev Board

    One limit worth knowing: the ESP32-S2 has a 2.4 GHz radio only, 802.11 b/g/n, and no Bluetooth. If you scan with it, everything on 5 GHz and 6 GHz is invisible. A clean scan from this board says nothing about your 5 GHz network.

    What a passive scan gives away

    Beacons are broadcast in the clear by design. Anyone in range gets your SSID, the access point’s BSSID, the channel, the signal strength and the security suite from the RSN information element, with no association and no traffic of their own. That element also carries two bits called MFPC and MFPR, management frame protection capable and required. So an outsider can see whether you require protected management frames before sending anything.

    I use the scan list as a cheap audit of my own airspace: what am I advertising, on which channels, with which security. I haven’t put a screenshot of a scan result in this post. On my unit it lists the MAC addresses and names of whatever networks are nearby, and those belong to my neighbours.

    Deauthentication and what 802.11w changes

    Original 802.11 management frames were unauthenticated. A deauthentication frame only had to carry the access point’s address as its source for the client to believe it and drop the connection. Send them in a loop and the client never stays up. That is the whole attack, and it needs no password and no association.

    802.11w, ratified in 2009 and now part of the base standard, protects those frames. After the four-way handshake, unicast management frames are encrypted and integrity checked with the pairwise key, and broadcast ones are protected with an integrity group key. A forged deauth fails the check and the client throws it away. There is also an SA Query exchange for the case where an unprotected disassociation arrives for a session the client believes is still alive.

    The diagram below shows the same forged frame against a client with and without protection. It is a diagram I drew, not a capture of any tool.

    Diagram: a forged deauthentication frame drops a client without protected management frames and is ignored when PMF is required

    Protected management frames are optional in WPA2 and mandatory in WPA3. The Wi-Fi Alliance started certifying WPA3 in 2018, and certification requires PMF. The catch is transition mode, where one SSID accepts both WPA2-PSK and WPA3-SAE clients. PMF is only “capable” there, so a WPA2-only client on that SSID can still be kicked off.

    The diagram also lists what 802.11w leaves alone. Beacons, probe requests and responses and the frames exchanged before association remain unprotected, and nothing in software stops someone from jamming the channel. PMF raises the cost of one specific cheap attack. It doesn’t make the radio link trustworthy.

    On a hostapd-based access point, requiring it for a WPA3-only network looks like this:

    wpa=2
    wpa_key_mgmt=SAE
    rsn_pairwise=CCMP
    ieee80211w=2
    sae_require_mfp=1
    sae_pwe=2

    sae_pwe=2 selects hash-to-element only. It closes the timing side channel behind the SAE flaws that Mathy Vanhoef and Eyal Ronen published as Dragonblood in 2019 (CVE-2019-9494 and CVE-2019-9495). It also locks out clients that only speak the older method, so test your device mix first. For a mixed network use wpa_key_mgmt=WPA-PSK SAE with ieee80211w=1 and accept the downgrade exposure.

    Evil twins and captive portals

    Marauder can also stand up a fake access point with a login page. That is the evil twin: same SSID, different BSSID, often a stronger signal, and a portal asking for a password. On an open network the client can’t tell the difference at all. With WPA3-SAE the twin has to know the password to complete the handshake, because SAE authenticates both sides. With WPA2-PSK the same is true in principle, but everyone who has the shared key can build a convincing twin, and that includes former employees and guests.

    The enterprise case is where I see the real damage. A client set to PEAP-MSCHAPv2 that doesn’t validate the RADIUS server certificate will happily send its challenge response to a twin, and MSCHAPv2 responses can be cracked offline. The fix is on the client side. Push a Wi-Fi profile through MDM that pins the CA and the expected server name, and prefer EAP-TLS where you can issue device certificates.

    What to watch for

    Detection comes down to a few facts you can state precisely:

    • Your SSID appears from a BSSID that is not in your access point inventory.
    • Your SSID appears with a different security suite than your own access points advertise.
    • A burst of deauthentication or disassociation frames, or a sudden crowd of unfamiliar SSIDs on one channel.

    You need a monitor-mode adapter, or the wireless controller’s own rogue detection, to see any of this. With a Linux adapter in monitor mode, this shows deauthentication frames (management subtype 12) with their reason codes:

    tshark -i wlan0mon -Y 'wlan.fc.type_subtype == 0x000c' \
      -T fields -e frame.time -e wlan.sa -e wlan.da -e wlan.fixed.reason_code

    A handful of deauths a day from real roaming events is normal. Hundreds per second aimed at one client is not. Alert on the rate per source, and when PMF is required the frames are ignored by clients anyway, so the alert tells you someone is trying rather than that something broke.

    Two older bugs are worth a mention because PMF does not cover them. Kr00k, CVE-2019-15126, let some Broadcom and Cypress Wi-Fi chips encrypt data with an all-zero key after a disassociation. It was fixed in client firmware, which means it is fixed by patching laptops and phones. Treat Wi-Fi chip firmware like any other software you have to update.

    If I had to pick one change from this part, it would be requiring PMF on every SSID whose clients all support it, then adding the unknown-BSSID alert to the same place where you read your other detections.

  • Flipper Zero, part 4: USB keystroke injection and how to lock USB down

    Flipper Zero, part 4: USB keystroke injection and how to lock USB down

    Part 4 of 5 in the Flipper Zero series. Part 1: what it actually is and what it can’t do. Part 2: why so many office badges are weaker than they look. Part 3: Sub-GHz remotes, rolling codes and how gates should be built.

    A keyboard is the most trusted thing you can plug into a computer. There is no driver prompt and no consent dialog, and the operating system has no way to ask whether whatever is typing is a person. That is all keystroke injection is. Anything that enumerates as a USB keyboard can type at machine speed into whichever window has focus.

    Same rules as the earlier parts: my own device, a machine I own, a defender’s goal. Typing into a computer you are not authorized to test is unauthorized access in most places, whatever the script says.

    What the Flipper does here

    The Bad USB app turns the Flipper into a USB keyboard and plays a script from the SD card. The script format is DuckyScript style, named after the Hak5 USB Rubber Ducky, which made the idea popular. Below is a photo of my own Flipper’s main menu with Bad USB highlighted.

    Flipper Zero main menu photo with the Bad USB entry highlighted

    A harmless script looks like this:

    REM types one line into the focused window and nothing else
    DELAY 1000
    STRING hello from a keyboard that isn't one
    ENTER

    Each STRING becomes a series of HID key presses, translated through a keyboard layout. Keys are sent as positions rather than characters, so a script written for a US layout types the wrong characters on a machine set to another layout unless you pick the matching one.

    The Flipper is not special. A Rubber Ducky, a Raspberry Pi Pico, a Digispark-style board or a phone in gadget mode does the same job. In 2014 Karsten Nohl and Jakob Lell showed at Black Hat that the firmware in common USB controller chips could be rewritten so an ordinary thumb drive presented itself as a keyboard, which they called BadUSB. In January 2022 the FBI warned that the FIN7 group was mailing parcels to US businesses containing USB devices that acted as keyboards. So this happens outside conference talks too.

    Why the host goes along with it

    A USB device describes itself with descriptors. A keyboard claims interface class 0x03 (HID), subclass 1 (boot interface), protocol 1 (keyboard), and the host loads its built-in HID driver. Nothing authenticates the device. The vendor ID, product ID and serial number are all self-reported, so anyone who has seen the IDs of your approved keyboard can claim the same ones.

    I think that is the weak spot in most USB policies. An allow list keyed on VID:PID stops the casual attacker and the unmodified gadget, and it does very little against someone who prepared for your site. I rank controls that do not depend on device identity higher.

    What the attack needs

    It needs an unlocked session. Keystrokes arriving at a lock screen go to the password field, not to a shell. It then needs a way to run something, usually the Run dialog or a terminal, followed by a command. The payload runs as the logged-in user.

    That gives three places to cut: refuse the device, make sure the session is locked, or make a freshly opened shell unable to do anything useful.

    The flowchart below shows those cuts in order. It is a diagram I drew, not a capture of any tool.

    Flowchart: USB policy, session lock and application control each stop a keystroke injection before the payload runs

    Refusing the device on Linux

    The kernel authorizes new USB devices by default. You can flip that for a bus so nothing is trusted until something approves it:

    echo 0 | sudo tee /sys/bus/usb/devices/usb1/authorized_default

    USBGuard is the practical way to manage this. usbguard generate-policy writes a rule set from whatever is plugged in right now, so run it only when your known-good devices are attached. After that you can tighten it by hand. Two rules from my own template:

    # one specific keyboard, and only as a keyboard
    allow id 046d:c31c with-interface 03:01:01
    # anything claiming to be mass storage and HID at the same time
    reject with-interface all-of { 08:*:* 03:*:* }

    The first ID is an example (a Logitech K120), so substitute your own from usbguard list-devices. The second rule matters more than it looks. Composite devices that present a drive and a keyboard are the classic disguise.

    Windows and macOS

    Windows has Device Installation Restrictions under Computer Configuration, Administrative Templates, System, Device Installation in Group Policy. You can block by device setup class or allow only listed hardware IDs. It has the same identity weakness as above, and a blanket block of keyboards will also block your own the next time someone replaces one, so test on a pilot group first.

    On Apple silicon laptops, macOS 13 and later can ask before a new accessory connects (Settings, Privacy & Security, Allow accessories to connect). It is a prompt for the user, which makes it a decent control for people who will read it and a poor one for people who click through everything.

    Making the typing useless

    If the device gets through, the next control is what a shell can do. On Windows, application control with WDAC or AppLocker, and PowerShell in Constrained Language Mode, turn most pasted one-liners into error messages. A screen lock after five minutes or less closes the window for the unattended-desk case. I will admit this helps least on developer laptops, because developers legitimately need shells, and those are usually the machines worth attacking.

    Detection

    Windows logs new external devices as event 6416 when the Audit PNP Activity policy is on, with the hardware IDs and device class. On Linux the kernel log line is New USB device found with idVendor, idProduct and the manufacturer and product strings, and a keyboard whose product string reads like a development board stands out. A new HID keyboard on a server or a kiosk is a signal by itself, since nobody types on those.

    Typing speed is the obvious second signal, and I would be careful with it. Barcode scanners are HID keyboards and send hundreds of characters in a fraction of a second, as do macro keyboards and some password-manager hardware. A rule that fires on speed alone will page you for the warehouse. Pair it with a process event: a shell started from the Run dialog within seconds of a 6416 event for a new keyboard is a much better thing to alert on.

    What I would do first

    On any machine that holds something worth stealing, I would start with the USB allow list, because it removes the attack before anything else matters. The lock timeout and application control come next, since they hold when the allow list is bypassed. Event 6416 logging costs almost nothing and gives you the evidence afterwards. Reception desks and kiosks get physical port blockers, which are cheap and work.

    Part 5 moves from the USB port to the radio side, with the Wi-Fi dev board and what rogue access points look like from a defender’s chair.

    Other parts of the Flipper Zero series: part 1, part 2, part 3, part 4, part 5.