khanfarris

pwning my light bulbs
2026-08-10  ·  LIFX smart bulbs, Kali Linux, UDP 56700  ·  runbook PDF

I wanted to play around with some of my devices and see what I could mess with from my PC. I started with the two LIFX bulbs on my desk because they seemed like the least interesting yet easiest target I could start with.

Everything below happened on my own network, against hardware I own. TLDR: played with my light bulbs from my terminal, but the cool part to me was — I did not have to authenticate to anything (hell yeah). If they were someone elses light bulbs, it could make for some fun times.

Step 1 — Figuring out where I actually was

A preliminary note to remember when I spin up a new instance - before scanning anything I needed to confirm my Kali VM could even see the stuff on the network. A bridged VM sits on the real LAN; a NAT'd one lives in its own little bubble and will never find my light bulb.

ipconfig      # Windows: my IPv4 + gateway
ip a          # Kali: check the eth0 inet address

Windows came back 10.0.0.mywindowsip, Kali came back 10.0.0.mykaliip. Same 10.0.0.x subnet, so the bridge was working and Kali confirmed to be able to see every device on the network. My network was 10.0.0.0/24 with the gateway at 10.0.0.1.

Lesson: if Kali had landed on 10.0.2.x or 172.x, that's the NAT bubble — switch the VM adapter from NAT to Bridged and try again. Nothing else works until this does.

Step 2 — Initial Scan for devices

sudo nmap -sn 10.0.0.0/24

A ping sweep, no port scanning. The output lists every host with its MAC vendor, and that vendor tag is what identified the bulbs. Two entries I came across were tagged Lifi Labs Management. That's LIFX.

Lesson: MAC vendor is how you ID a device, not guesswork. You don't need to know the IP in advance — the manufacturer name hands it to you.

Step 3 — The scan that lied to me

sudo nmap -sV -sC -p- 10.0.0.7

All 65,535 TCP ports came back closed. I was about to ff 15 but then decided there's probably something going on here. If I'd stopped there I would have written "nothing exposed, looks fine" and moved on. That wouldn't be too great.

The bulb doesn't speak TCP at all. It speaks UDP. Always remember the fundamentals. So I did some 'research' and found out about port 56700.

sudo nmap -sU -sV -p 56700 10.0.0.7

Result: 56700/udp open|filtered. That open|filtered state means nmap sent a generic probe and got nothing back — completely normal for UDP. The bulb only answers packets that are actually shaped like the LIFX protocol. Ahhhhhhh.

Lesson: a fully closed TCP scan does not mean a device is secure. It can mean you scanned the wrong protocol. Always check UDP before calling a device empty.

The nmap flags I used (some not in writeup), and why

Step 4 — How I'd have found port 56700 on my own

Tbh I didn't pull this port out of thin (AI)r. But afterwards I did make sure to get familiar with ways to get there without being told, and I'll try to remember these methods for unfamiliar device / no help challenge scenario:

  1. nmap already knows it — it's listed in nmap-services as lifx.
  2. Vendor docs. Searching "LIFX LAN protocol" lands on https://lan.developer.lifx.com/docs/querying-the-device-for-data, which documents the whole thing.
  3. Open-source tools. Reading lifxlan or the Home Assistant integration on GitHub shows you exactly what port they talk to.
  4. Wireshark. Sniff the official phone app toggling a light and read the packets yourself.

Step 5 — Actually speaking the protocol

This is where it stopped being recon. I installed the community Python library:

pip install lifxlan --break-system-packages

The --break-system-packages flag is needed because Kali marks its system Python as externally-managed and pip refuses to touch it otherwise.

Discovery

python3 -c "from lifxlan import LifxLAN; lan = LifxLAN(); \
[print(d.get_label(), d.get_ip_addr(), d.get_power()) for d in lan.get_devices()]"

This broadcasts a LIFX discovery packet, and every bulb on the network replies with its name, IP, and current power state. No authentication, no pairing, no token. I got back Desk Lamp Right at 10.0.0.lightbulb1 and Desk Lamp Left at 10.0.0.lightbulb2.

That reply is the finding. My unauthenticated bulb just announced itself to me.

Fun time

python3 -c "from lifxlan import LifxLAN; import time; lan=LifxLAN(); \
d=[x for x in lan.get_devices() if x.get_label()=='Desk Lamp Left'][0]; \
d.set_power(True); time.sleep(2); d.set_power(False)"

The lamp came on, sat there for two seconds, and went off. Physical control of something in my room from a terminal, with zero credentials. SICK.

Color

python3 -c "from lifxlan import LifxLAN, RED, GREEN, BLUE; import time; \
d=[x for x in LifxLAN().get_devices() if x.get_label()=='Desk Lamp Left'][0]; \
[(d.set_color(c), time.sleep(1)) for c in (RED, GREEN, BLUE)]"

Red, green, blue, one second each. Same unauthenticated channel — it isn't just an on/off switch, it's full control of the device's state.

Finding & fix

Verdict: the old critical LIFX flaws — the ones where plaintext Wi-Fi credentials could be pulled off the hardware — required physical access and have been patched. What's still live is the unauthenticated LAN control protocol, and that one is working as designed. Anyone already on my Wi-Fi can find and control my bulbs.

Remediation: put IoT devices on a separate guest/IoT network or VLAN, so a compromised bulb is isolated from the PCs and phones that actually matter. The protocol isn't going to change; the network segmentation is the control I actually own.

The repeat checklist

Next time I run this, on this device or any other:

  1. ip a / ipconfig — confirm I'm on the same subnet as the target.
  2. sudo nmap -sn 10.0.0.0/24 — find the device by MAC vendor.
  3. sudo nmap -sU -sV -p 56700 <ip> — confirm the control port.
  4. lifxlan discover, then set_power / set_color to control.

It seems like I can swap the IP and the workflow is identical for any device. The only part that changes is the research step — figuring out which ports and protocol the thing actually speaks.

The full runbook

Every command above, in a single reference sheet:

🔒 protected document

This runbook is password protected. Enter the password to continue:

Wow, good job. I presume you used this method to find the password:

  1. Right-clicked the page and hit View Page Source (or pressed Ctrl + U).
  2. Skimmed the JavaScript near the bottom of the page.
  3. Found the password sitting right there in plaintext — itsnotpasswordprotected — because on a static site there's nowhere to hide it.

Or maybe you:

  1. Realized this is a static site whose source repo is public on GitHub — and just read the file list there.
  2. Or brute-forced the site root with gobuster/ffuf and a wordlist, watching for a 200 on a .pdf.
  3. Or simplest of all — noticed this very button spells out the filename, so you just tacked it onto the site root (khanfarris.com/LIFX_Pentest_Runbook.pdf) and opened it directly. No password required.

…which is the whole joke. Client-side "passwords" on a static site are theater. Anyway — here's your document: