Zigbee
Why this matters
Zigbee is the oldest of the modern low-power mesh protocols and the one with the largest installed base of insecure devices. It is in bulbs, locks, blinds, smoke detectors, industrial sensors and half the products sold under a hub vendor’s own brand.
Its security architecture makes one decision that determines everything else: the entire mesh shares a single network key. Compromise any device — the cheapest bulb, the one nobody thought about — and you hold the key that decrypts and forges traffic for the whole network. Every Zigbee finding you will write is, in the end, about how someone obtained that key.
The radio layer
Zigbee does not define a radio. It sits on IEEE 802.15.4, which it shares with Thread, and which supplies the PHY and MAC.
| 2.4 GHz (channels 11–26) | 915 MHz (channels 1–10) | 868 MHz (channel 0) | |
|---|---|---|---|
| Modulation | O-QPSK with DSSS | BPSK / O-QPSK | BPSK / O-QPSK |
| Rate | 250 kb/s | 40–250 kb/s | 20–100 kb/s |
| Channel width | 2 MHz, 5 MHz spacing | 2 MHz | — |
| Usage | Effectively all Zigbee | Rare | Rare |
In practice Zigbee means 2.4 GHz, channels 11 through 26, and a handful of those dominate: channels 11, 15, 20 and 25 are the conventional choices because they fall in the gaps between WiFi channels 1, 6 and 11. When you go hunting, start there.
Range per hop is modest — tens of metres indoors — and the mesh is what gives it building coverage. Routers (mains-powered devices, typically bulbs and plugs) forward for battery-powered end devices that spend their lives asleep.
⚠️ Coexistence is a real operational effect. 802.15.4’s 2 MHz channels sit inside WiFi’s 20 MHz ones. A busy WiFi network on channel 6 will degrade Zigbee channels 15–19 measurably. If a mesh behaves strangely during a lab, check the WiFi before you blame your capture.
Capturing it
Unlike Bluetooth, 802.15.4 does not hop. Pick the channel and you see everything on it — which makes Zigbee one of the easiest radios in this course to observe, and a good first mesh to study.
| Hardware | Notes |
|---|---|
| TI CC2531 USB dongle | Cheapest option, widely available, KillerBee support |
| TI CC1352/CC2652 LaunchPad | Current-generation, multiprotocol |
| Atmel RZUSBstick | The classic KillerBee target; long discontinued |
| ApiMote | Purpose-built for 802.15.4 security work |
| nRF52840 dongle | With 802.15.4 sniffer firmware; also does Thread |
Wireshark dissects 802.15.4 and Zigbee natively, and will decrypt the payloads if you give it the network key — which is the point of everything below.
The stack above the radio
| Layer | Provides | Key used |
|---|---|---|
| ZCL / ZDO | Clusters: on/off, level, lock, OTA | — |
| APS | End-to-end addressing, binding | Link key (pairwise) |
| NWK | Mesh routing, broadcast | Network key (shared) |
| 802.15.4 MAC/PHY | Framing, CSMA-CA, addressing | (optionally MAC key) |
Three device roles: exactly one coordinator (which is also the Trust Center), any number of routers, and end devices that sleep and do not forward.
The Trust Center decides who joins and distributes keys. In a home system it is the hub; compromise it and there is nothing left to attack.
The security model, and its single point of failure
All Zigbee cryptography is AES-128 in CCM* — the primitive is not the problem. The problem is key distribution.
The network key is shared by every node. It protects NWK-layer frames, so any device holding it can decrypt all mesh traffic and inject frames that every other node accepts. There is no per-device isolation at this layer and no forward secrecy: recover the key and historical captures decrypt too.
Link keys are pairwise, between a device and the Trust Center, protecting APS-layer messages. Their main job is protecting the network key in transit, during joining.
And that is where it has always gone wrong.
The default link key
For a device to join, the Trust Center sends it the network key, encrypted with a link key. But the joining device does not have a pairwise link key yet — it has just arrived. Something has to bootstrap it.
Pre-3.0 Zigbee bootstrapped with a global, publicly documented default link
key, the ASCII string ZigBeeAlliance09. It was specified to make testing
and interoperability easy, and vendors shipped it enabled.
The consequence is direct: capture a join, decrypt the key transport with a key everyone knows, and you have the network key. No cryptanalysis, no implementation bug. The design told you the answer.
⚠️ You do not have to wait for a join. Deauthenticating a device forces a rejoin, and a rejoin re-transports the key. This is the Zigbee analogue of deauth-to-capture-a-handshake on WiFi — the same idea with a different acronym.
What Zigbee 3.0 changed
Zigbee 3.0 addressed this in two ways:
- Install codes. A 128-bit random value generated at manufacture, printed on the device or its packaging (usually as a QR code) and hashed with AES-MMO to produce a device-unique preconfigured link key. The installer enters it into the hub out of band. There is no global secret to leak.
- Mandatory key update. A device joining a centralised Zigbee 3.0 network must request a randomly generated Trust Center link key immediately after joining, which is then used for subsequent APS traffic.
Both are real improvements. Neither helps if the network still accepts legacy joins — and networks do, because the hub vendor wants your ten-year-old sensors to keep working. A Zigbee 3.0 hub in backward-compatible mode has Zigbee 1.x’s key transport problem, and backward-compatible mode is the shipping default.
That is the assessment question to ask, and it is not answered by the version number on the box.
Touchlink
Zigbee Light Link’s Touchlink commissioning lets you pair a bulb by being physically near it. Proximity is enforced by transmit power alone, and it is authenticated by a global master key shared across all ZLL products — which leaked years ago.
The practical result: from a distance considerably greater than “touching”, using an amplifier, an attacker can factory-reset bulbs, steal them onto their own network, or command them. Ronen et al.’s IoT Goes Nuclear combined Touchlink range abuse with a flaw in Philips Hue’s OTA image verification to demonstrate a worm propagating bulb-to-bulb across a city — the canonical demonstration that a “harmless” device class is a routable attack surface.
Proximity is not authentication. It is a recurring theme: Bluetooth needed Channel Sounding for the same reason.
Replay and frame counters
NWK and APS frames carry monotonic frame counters, and receivers reject counters they have already seen. The protection is real and it has the same weakness as everywhere else in this course: the counter must survive a reboot, so it lives in flash, and flash wears out.
Implementers write it lazily, or skip forward in blocks, or reset it. A node that resets its counter to zero must either be rejected forever or be allowed to re-announce itself — and vendors overwhelmingly choose availability. See LoRa for the identical failure in a completely different protocol.
Worked example: recovering the network key from a join
zbdsniff exists precisely because the default-link-key problem is so
mechanical. Identify the interface, find the network, capture, then decrypt.
$ sudo zbid
Dev Product String Serial Number
1:6 CC2531 USB Dongle __01F5C4E2
Survey for networks. zbstumbler transmits beacon requests, so it is active —
use it in lab, not in the wild:
$ sudo zbstumbler -i 1:6
zbstumbler: Transmitting and receiving on interface '1:6'
New Network: PANID 0x8A1C Source 0x0000
Ext PANID: 00:12:4b:00:1c:a3:f1:22
Stack Profile: ZigBee Enterprise
Stack Version: ZigBee 2006/2007
Channel: 15
Lock to channel 15 and capture. Then force the target to rejoin — power-cycling the device in lab is the honest way to do it:
$ sudo zbdump -i 1:6 -f 15 -w join.pcap
zbdump: listening on '1:6', channel 15, page 0 (2425 MHz), link-type DLT_IEEE802_15_4, capture size 127 bytes
^C
42 packets captured
Now decrypt the key transport frame. zbdsniff takes the capture with -f
and the link key to try with -k, and the link key is the mandatory
argument — which is the whole lesson stated as a command-line flag. Here it
is ZigBeeAlliance09 as hex:
$ zbdsniff -f join.pcap -k 5a6967426565416c6c69616e63653039
Processing join.pcap
[+] Extended Source: 00:12:4b:00:1c:a3:f1:22 mapped to 0x0000
[+] Decrypted:
APS Command: APS_CMD_TRANSPORT_KEY
Key Type: KEY_TYPE_STANDARD_NWK
Value: 41:6c:32:31:30:30:4c:61:62:4e:65:74:4b:65:79:21
[+] Processed 1 capture files.
That value is ASCII: Al2100LabNetKey!.
⚠️ Older write-ups show
zbdsniff <file>printingNETWORK KEY FOUND:. That is KillerBee 2.x. Currentzbdsniffwas reimplemented on Scapy, takes-f/-dfor input, requires-k, and prints the format above; invoked the old way it exits with “A packet capture file or directory must be specified”.
Hand that key to Wireshark and the whole capture becomes readable —
Preferences → Protocols → ZigBee → Pre-configured Keys. From the command
line you cannot pass it with -o uat:…: the ZigBee NWK table and the Zigbee
Direct table are both registered under the display name Pre-configured
Keys, so the lookup resolves to the wrong one. Write the table file instead —
~/.config/wireshark/zigbee_pc_keys, one record per line:
"41:6c:32:31:30:30:4c:61:62:4e:65:74:4b:65:79:21","Normal","Lab"
The key field also accepts a quoted 16-character string, so
"Al2100LabNetKey!" works and is easier to check by eye. Then:
$ tshark -r join.pcap \
-Y zbee_zcl -T fields -e zbee_zcl.cmd.id -e zbee_zcl_general.onoff.cmd.srv_rx.id
If you want to force the security level as well, the preference is
zbee_nwk.seclevel and it takes the level’s name, not its number:
-o "zbee_nwk.seclevel: AES-128 Encryption, 32-bit Integrity Protection".
The point of the exercise is not the tooling. It is that the network key was protected by a key printed in a public specification, and everything else followed from that one decision.
Key takeaways
- Zigbee is 802.15.4 plus a mesh network layer; it does not define its own radio, and it shares 2.4 GHz with WiFi, BLE and Thread.
- It does not hop. Pick a channel — 11, 15, 20 and 25 are the common ones — and you see everything.
- One network key is shared by the entire mesh. Any compromised node yields full read and write access to all traffic, with no forward secrecy.
- Link keys exist mainly to protect the network key during joining, which is the moment worth capturing.
- Pre-3.0 networks bootstrap with the published default link key
ZigBeeAlliance09, so a captured join hands over the network key outright. - Zigbee 3.0’s install codes and mandatory key update genuinely fix this — and backward compatibility, which is on by default, reinstates the flaw.
- Touchlink treats proximity as authentication and uses a leaked global master key; with an amplifier, “nearby” becomes very far away.
- Frame counters provide replay protection only as long as they persist across reboots, and vendors choose availability over that.
References
- IEEE 802.15.4 — https://standards.ieee.org/ieee/802.15.4/7029/
- Connectivity Standards Alliance, Zigbee specifications — https://csa-iot.org/all-solutions/zigbee/
- Silicon Labs, Zigbee Security documentation — https://docs.silabs.com/zigbee/latest/zigbee-security/
- Silicon Labs AN1089, Using Installation Codes with Zigbee Devices — https://www.silabs.com/documents/public/application-notes/an1089-using-installation-codes-with-zigbee-devices.pdf
- Texas Instruments, The Key to Security: Zigbee 3.0’s Security Features — https://www.ti.com/lit/ta/sszt545/sszt545.pdf
- Silicon Labs UG103.05, Fundamentals: Security — https://www.silabs.com/documents/public/user-guides/ug103-05-fundamentals-security.pdf
- Ronen, Shamir, Weingarten & O’Flynn, IoT Goes Nuclear: Creating a ZigBee Chain Reaction (IEEE S&P 2017) — https://eprint.iacr.org/2016/1047.pdf
- Wright, KillerBee: Practical ZigBee Exploitation Framework — https://www.willhackforsushi.com/presentations/toorcon11-wright.pdf
- KillerBee toolkit — https://github.com/riverloopsec/killerbee
- Kaspersky Securelist, Zigbee protocol security assessment — https://securelist.com/zigbee-protocol-security-assessment/118373/
- Wireshark ZigBee dissector reference — https://www.wireshark.org/docs/dfref/z/zbee_nwk.html
Related course pages: Radio Protocols · Thread and Matter · Z-Wave · WiFi · Attacks · Tools of the Trade
🛠️ Maintenance note: KillerBee’s hardware support is the fragile part — the RZUSBstick it was built around has been discontinued for years and CC2531 dongles are increasingly scarce, so re-verify the lab’s capture hardware and the
zbidoutput before each offering. Thezbdsnifftranscript assumes a network still using the default link key; if the lab hub is upgraded to a Zigbee 3.0 stack with install codes, that example needs rebuilding around a deliberately legacy-configured network. KillerBee itself is lightly maintained: at the time of writingzbstumbleron upstreammasterstill contains Python 2zbdumpandzbdsniffare fine — run the whole chain before lab, and be ready to fall back to a CC2531 runningzigbee2mqtt’s sniffer firmware or an nRF52840 with Nordic’s 802.15.4 sniffer feeding Wireshark directly. Wireshark’s ZigBee key UAT is reachable only by writing~/.config/wireshark/zigbee_pc_keys— its display name collides with Zigbee Direct’s table, so-o uat:cannot address it. Re-check that against the installed version.