Rogue Access Point Detector with Kismet
Detect evil twin attacks by monitoring Wi-Fi with Kismet and Python.
Introduction
30 Second Summary
Every time you connect to a Wi-Fi network, you trust that the network is what it claims to be. But what if someone set up a fake version of your home or office Wi-Fi, tricking your devices into connecting to an attacker-controlled network instead?
In this project, you will build a rogue access point detection system that monitors nearby Wi-Fi networks and alerts you when someone is impersonating your trusted network.
What You'll Build
A wireless intrusion detection system using Kismet and Python that scans your local airspace for evil twin attacks and unauthorized access points.
By the end of this project, you'll have:
- A running Kismet WIDS instance configured to monitor your local Wi-Fi environment and detect evil twin attacks.
- A Python detector script that queries Kismet's REST API, compares detected access points against a known-good allowlist, and flags any rogues.
- A kismet_site.conf configuration file with APSPOOF alert rules for your home or office network.
- Secret Mission: Extend the detector to run continuously and send macOS desktop notifications when a rogue AP appears.
Are there any prerequisites?
You should have basic familiarity with the command line and Python. This project runs entirely on macOS using the built-in Wi-Fi adapter.
Before We Start
Before We Start...
Install Kismet and Python Dependencies
Before you can detect rogue access points, you need the right tools installed on your Mac.
In this step, you will install Kismet (a wireless intrusion detection system) and the Python libraries needed to query its API.
In this step, get ready to:
- Install Homebrew and Kismet on your Mac.
- Verify Python 3 is available and install the requests library.
- Confirm Kismet installed correctly.
Install Homebrew and Kismet
Kismet is available on macOS through Homebrew, the macOS package manager.
- Open Terminal by pressing Cmd+Space to open Spotlight, typing Terminal, and pressing Enter.
- Install the Xcode Command Line Tools by running this command:
xcode-select --install
What does this command do?
This installs Apple's developer tools (compilers, headers, and build utilities) that Homebrew needs to compile software from source.
A popup window will appear asking you to confirm the install. Click Install and wait for it to finish. If you already have the tools installed, you will see a message saying so.
- Check whether Homebrew is already installed by running:
brew --version
✔️ I see a version number
Homebrew is already installed. Move on to the Kismet installation below.
ⓧ Command not found
- Install Homebrew by running this command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
What does this command do?
This downloads and runs the official Homebrew install script. The script will show you exactly what it plans to do and ask for confirmation before making changes.
It may ask for your Mac password. On Apple Silicon Macs, Homebrew installs to /opt/homebrew. On Intel Macs, it installs to /usr/local.
- After the install finishes, follow any instructions printed in the terminal to add Homebrew to your PATH.
- Confirm Homebrew works by running:
brew --version
You should see a version number printed in the terminal.
- Add the Kismet Homebrew tap by running this command:
brew tap kismetwireless/kismet
What is a Homebrew tap?
A tap is a third-party repository of Homebrew formulas. The kismetwireless/kismet tap contains the official Kismet package maintained by the Kismet developers.
- Install Kismet by running:
brew install kismet
What does this install?
This installs Kismet and all its dependencies. Kismet is a wireless network sniffer and WIDS (Wireless Intrusion Detection System) that can passively monitor nearby Wi-Fi traffic.
The install may take a few minutes as Homebrew downloads and compiles the package.
Seeing errors during install?
Make sure your Xcode Command Line Tools are up to date. Run xcode-select --install again to check for updates.
If Homebrew asks for your password, enter your Mac login password. The cursor will not move as you type.
Help me troubleshoot my Kismet Homebrew installation
Verify Python 3 and install requests
The rogue AP detector script is written in Python. It uses the requests library to talk to Kismet's REST API.
- Check your Python version by running:
python3 --version
✔️ I see Python 3.10 or higher
Your Python version is compatible. Move on to installing the requests library below.
ⓧ I see a version lower than 3.10
The requests library requires Python 3.10 or higher. macOS sometimes ships with an older version as the default.
- Install a current Python version using Homebrew by running:
brew install python3
- After the install completes, verify the new version by running:
python3 --version
You should now see Python 3.10 or higher.
Still seeing the old version?
Close and reopen your terminal so the new PATH takes effect. If macOS still resolves to the older system Python, Homebrew prints the exact path to the new binary during install. Use that full path or add Homebrew's Python to your shell config.
ⓧ Command not found
Python 3 is not installed on your system.
- Install Python 3 using Homebrew by running:
brew install python3
- After the install completes, verify by running:
python3 --version
You should see a version number like 3.14 or similar.
- Install the requests library by running:
pip3 install requests
What is requests?
The requests library is a popular Python package for making HTTP calls. Your detector script will use it to query Kismet's REST API and retrieve data about detected access points.
You should see a success message confirming the library was installed.
Seeing a permissions error?
If you see a permissions error, try adding the --user flag:
pip3 install --user requests
This installs the package into your home directory instead of the system Python path.
Help me fix a pip permissions error on macOS
Verify Kismet installed correctly
- Confirm Kismet is installed and accessible by running:
kismet --version
You should see version information printed in the terminal, confirming Kismet is ready to use.
Command not found?
If you see a command not found error, Homebrew's bin directory may not be in your PATH. Try closing and reopening your terminal, then running the command again.
If that does not help, run brew --prefix to find where Homebrew installed its files. Make sure that path plus /bin is in your shell's PATH variable.
Help me add Homebrew to my PATH on macOS
Your tools are installed and ready. Next up, you will configure Kismet to detect rogue access points impersonating your network.
Configure Kismet for Rogue AP Detection
Kismet is installed, but it does not yet know which access points are legitimate on your network. Without that information, it cannot distinguish between your real router and an attacker broadcasting the same network name.
In this step, you will create a configuration file that teaches Kismet which SSIDs and MAC addresses belong to you. Kismet uses this to raise alerts whenever it sees your network name coming from an unfamiliar device.
In this step, get ready to:
- Create a Kismet site configuration file with web UI credentials.
- Add an APSPOOF alert rule for your home network.
- Configure alert throttling to control how often Kismet fires alerts.
Create your Kismet site configuration file
Kismet keeps its default settings in read-only files that get overwritten on updates. The kismet_site.conf file is your personal override file where custom settings persist safely across upgrades.
- Create the Kismet configuration directory by running this command:
mkdir -p ~/.kismet
- Open a new file called kismet_site.conf inside that directory by running:
nano ~/.kismet/kismet_site.conf
- Paste the following configuration into the file:
# Kismet site configuration for rogue AP detection
# Place this file in ~/.kismet/ or /usr/local/etc/kismet/
# Web UI credentials
httpd_username=kismet
httpd_password=kismet
# APSPOOF alert: detect evil twin attacks on your home network
# Replace "MyHomeNetwork" with your actual SSID
# Replace the MAC address with your actual router's BSSID
apspoof=HomeNetwork:ssid="MyHomeNetwork",validmacs="AA:BB:CC:DD:EE:FF"
# Alert throttling for APSPOOF
alert=APSPOOF,10/min,1/sec
What does this file configure?
- httpd_username and httpd_password set the login credentials for Kismet's web interface. You will use these to access the dashboard in the next step.
- The apspoof line defines an alert rule that watches for a specific SSID coming from an unauthorized MAC address.
- The alert line controls how frequently Kismet fires the APSPOOF alert to prevent flooding your logs.
- Save the file by pressing Ctrl+O, then Enter to confirm the filename.
- Exit nano by pressing Ctrl+X.
- Confirm the file was created by running:
cat ~/.kismet/kismet_site.conf
You should see the full configuration you just pasted printed back to your terminal.
Seeing an empty output or 'No such file'?
- Make sure you pressed Ctrl+O (the letter O, not zero) to write the file before exiting nano.
- Double-check the path. The file should be at ~/.kismet/kismet_site.conf (note the dot before kismet, making it a hidden directory).
- If the directory does not exist, re-run mkdir -p ~/.kismet and try again.
Why isn't my kismet_site.conf file saving?
Customize the APSPOOF rule for your network
The APSPOOF alert is Kismet's built-in defense against evil twin attacks. It continuously checks every beacon frame it captures against your list of approved MAC addresses for a given SSID.
What is an evil twin attack?
An attacker sets up a rogue access point that broadcasts the same SSID as your legitimate network. Nearby devices may automatically connect to it, sending their traffic through the attacker's hardware.
APSPOOF catches this by checking the MAC address behind the SSID. Your real router has a fixed MAC address, so any other device broadcasting your SSID is suspicious.
The rule you pasted follows this format:
apspoof=RuleName:ssid="YourSSID",validmacs="MAC1,MAC2"
How do I find my router's BSSID?
Hold Option and click the Wi-Fi icon in your macOS menu bar. The BSSID field shows your router's MAC address.
You can also find it in the Kismet web UI after you launch Kismet in the next step. For now, the placeholder values work fine for completing the setup.
- When you are ready to monitor your real network, open ~/.kismet/kismet_site.conf and replace MyHomeNetwork with your actual Wi-Fi network name (SSID).
- Replace AA:BB:CC:DD:EE:FF with your router's actual BSSID (MAC address).
- If you have multiple access points for the same SSID, list all their MAC addresses separated by commas inside the quotes.
What about networks with multiple access points?
Mesh systems and enterprise networks often have several access points sharing one SSID. List every legitimate MAC address in the validmacs field, separated by commas.
Kismet also supports MAC masks for matching ranges. For example, validmacs="00:11:22:00:00:00/FF:FF:FF:00:00:00" matches all devices from the same manufacturer (OUI).
Review the alert throttling settings
Without throttling, a single rogue AP broadcasting continuously would generate hundreds of alerts per minute. The throttle line caps how often Kismet fires each alert type.
Your configuration uses this line:
alert=APSPOOF,10/min,1/sec
What do these numbers mean?
- 10/min means Kismet will raise a maximum of 10 APSPOOF alerts per minute, even if it sees the rogue AP on every single beacon frame.
- 1/sec means no more than 1 alert per second within that minute. This prevents a burst of back-to-back notifications.
These defaults are sensible for home monitoring. You can adjust them later if you find the alerts too noisy or too infrequent.
Why is Kismet most effective as a stationary IDS?
Kismet works best when it stays in one location and builds a consistent picture of the local airspace over time. A stationary setup sees the same access points repeatedly, making it easy to spot a new rogue the moment it appears.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file at ~/.kismet/kismet_site.conf.
ⓧ I'd like to double check the full code
Here is the complete kismet_site.conf file for comparison:
# Kismet site configuration for rogue AP detection
# Place this file in ~/.kismet/ or /usr/local/etc/kismet/
# Web UI credentials
httpd_username=kismet
httpd_password=kismet
# APSPOOF alert: detect evil twin attacks on your home network
# Replace "MyHomeNetwork" with your actual SSID
# Replace the MAC address with your actual router's BSSID
apspoof=HomeNetwork:ssid="MyHomeNetwork",validmacs="AA:BB:CC:DD:EE:FF"
# Alert throttling for APSPOOF
alert=APSPOOF,10/min,1/sec
Your Kismet configuration is ready. Next up, you will launch Kismet and see it start scanning the airwaves for access points around you.
Launch Kismet and Capture Wi-Fi Data
You have Kismet installed and configured with APSPOOF rules for your network. Now it is time to put it to work.
In this step, you will launch Kismet, point it at your Mac's built-in Wi-Fi adapter, and confirm it can see your local access points. This proves your wireless intrusion detection system is live and ready to catch rogue APs.
In this step, get ready to:
- Start Kismet with the macOS Airport Wi-Fi source.
- Verify Kismet's web UI is scanning and discovering access points.
- Identify your own network's BSSID in the device list.
Start Kismet with the macOS Airport Wi-Fi source
Kismet needs a capture source to listen to wireless traffic. On macOS, the built-in Airport adapter is exposed as the en0 network interface.
- Return to your Terminal window.
- Launch Kismet with the Airport Wi-Fi source by running this command:
kismet -c en0
What does this command do?
The -c flag tells Kismet which capture source to use. Passing en0 points Kismet at your Mac's built-in Airport Wi-Fi adapter.
Kismet automatically detects the adapter type (macOS CoreWLAN) and begins hopping across Wi-Fi channels to discover nearby access points.
What is a BSSID?
A BSSID is the MAC address of a wireless access point. Every router broadcasts this unique hardware address alongside its SSID (the human-readable network name).
Rogue AP detection works by comparing a BSSID against a list of known-good MAC addresses. If the SSID matches your network but the BSSID does not, something is impersonating your router.
Kismet will start printing status messages in the Terminal. You should see output indicating it is opening the en0 source and beginning to capture packets.
Why does Kismet disconnect my Wi-Fi?
To scan across all channels, Kismet puts your Wi-Fi adapter into monitor mode. This temporarily disconnects you from your current network.
Your internet connection will resume once you stop Kismet (Ctrl+C in the Terminal). If you need internet access while scanning, connect your Mac to Ethernet or a separate network adapter.
Seeing a permission error?
Kismet needs elevated permissions to put the Wi-Fi interface into monitor mode. If you see a permissions error, try running with sudo:
sudo kismet -c en0
Enter your macOS password when prompted. Help me fix my Kismet permissions issue.
Verify Kismet is scanning in the web UI
Kismet runs a built-in web dashboard that shows all discovered devices in real time. You will use this to confirm scanning is working.
- Open a web browser.
- Navigate to http://localhost:2501 in the address bar.
- When prompted for credentials, enter kismet for both the username and password (these are the values you set in kismet_site.conf).
- Look at the device list in the main dashboard view. You should see entries appearing as Kismet discovers nearby Wi-Fi access points.
What should I see?
The Kismet web UI shows a table of detected wireless devices. Each row represents an access point or client device, showing columns like name (SSID), MAC address, signal strength, and channel.
Give Kismet 30-60 seconds to hop across channels and populate the list. The device count at the top of the page should steadily increase.
Web UI not loading?
Make sure Kismet is still running in your Terminal (check for active log output). If the Terminal shows Kismet crashed or exited, re-run the launch command.
Confirm you are using http:// (not https://) in the URL. Kismet's web server does not use HTTPS by default.
Help me troubleshoot the Kismet web UI not loading at localhost:2501.
Identify your own network's BSSID in the device list
To confirm Kismet is capturing real data from your environment, you will find your own Wi-Fi network in the device list. The MAC address shown next to your SSID is the BSSID you will use in your Python detector script later.
- In the Kismet web UI device list, look for your home or office network name (SSID) in the name column.
- Click on the row for your network to expand its details.
- Find the MAC address field. This is your access point's BSSID.
- Write down or copy this MAC address. You will need it in the next step when you build your detector script's allowlist.
Use your real BSSID in the allowlist
The known_aps.json file you create in the next step should contain your actual router's MAC address, not the placeholder AA:BB:CC:DD:EE:FF from the example. Copying your real BSSID now saves time later.
Can't find your network in the list?
Kismet hops across channels one at a time. If your network has not appeared yet, wait another 30-60 seconds for Kismet to reach the channel your router uses.
If you still do not see it, confirm your Wi-Fi router is powered on and broadcasting. Hidden SSIDs will appear with a blank name but still show a MAC address.
Help me find my network in the Kismet device list.
Kismet is now live and watching your airspace. Next up, you will build a Python script that queries Kismet's REST API to automatically detect rogue access points impersonating your network.
Build the Rogue AP Detector Script
Kismet is now scanning your airspace and discovering access points in real time. The next challenge is turning that raw data into actionable intelligence.
In this step, you will build a Python script that connects to Kismet's REST API, pulls down every detected access point, and compares each one against a list of known-good networks you define. Any AP broadcasting your network's SSID from an unrecognized MAC address gets flagged as a rogue.
In this step, get ready to:
- Create a JSON allowlist that maps your trusted SSIDs to their legitimate MAC addresses.
- Write a Python detector script that queries Kismet's API and flags unauthorized access points.
- Run the detector and review a formatted report of all nearby APs.
Create the known-good AP allowlist
Before the script can flag a rogue, it needs to know which access points are legitimate. You will define this in a JSON file that maps each trusted SSID to its valid MAC address(es).
- Create a new file called known_aps.json in your project folder (the same folder where kismet_site.conf lives) and paste this content:
{
"trusted_networks": [
{
"ssid": "MyHomeNetwork",
"valid_macs": ["AA:BB:CC:DD:EE:FF"]
},
{
"ssid": "OfficeWiFi",
"valid_macs": ["11:22:33:44:55:66", "11:22:33:44:55:67"]
}
]
}
What does this file do?
Each entry in trusted_networks pairs an SSID with the MAC addresses of the legitimate routers that broadcast it.
If the detector sees your SSID coming from a MAC not in valid_macs, it flags that AP as a rogue. A network can have multiple valid MACs if you use mesh routers or range extenders.
- Replace MyHomeNetwork with your actual Wi-Fi network name (the same SSID you used in kismet_site.conf).
- Replace AA:BB:CC:DD:EE:FF with your router's actual BSSID (the MAC address you identified in the Kismet web UI in the previous step).
- Save known_aps.json.
- Confirm the file exists by running:
ls known_aps.json
You should see known_aps.json in the output.
File not found?
Make sure you are in the same directory where you created the file. Run pwd to check your current location, and ls to see what files exist there.
help me find my known_aps.json file
Write the rogue AP detector script
Now you will write the Python script that talks to Kismet. It connects to the REST API, pulls every detected access point, and compares them against your allowlist.
- Create a new file called rogue_detector.py in the same folder and add the imports, constants, and allowlist-loading function:
#!/usr/bin/env python3
"""Rogue Access Point Detector - Queries Kismet REST API to find unauthorized APs."""
import json
import sys
from pathlib import Path
import requests
# Kismet connection settings
KISMET_URL = "http://localhost:2501"
KISMET_USER = "kismet"
KISMET_PASSWORD = "kismet"
# Endpoint that returns only Wi-Fi access points
AP_ENDPOINT = "/devices/views/phydot11_accesspoints/devices.json"
def load_allowlist(filepath):
"""Load the known-good AP allowlist from a JSON file."""
path = Path(filepath)
if not path.exists():
print(f"[ERROR] Allowlist file not found: {filepath}")
sys.exit(1)
with open(path, "r") as f:
data = json.load(f)
# Build a dictionary mapping SSID -> list of valid MACs
allowlist = {}
for network in data.get("trusted_networks", []):
ssid = network["ssid"]
macs = [mac.upper() for mac in network["valid_macs"]]
allowlist[ssid] = macs
return allowlist
What does this code do?
- KISMET_URL points at the local Kismet web server on port 2501.
- AP_ENDPOINT is the Kismet REST API path that returns only Wi-Fi access point devices.
- load_allowlist() reads your known_aps.json file and builds a dictionary mapping each SSID to its valid MAC addresses (uppercased for consistent comparison).
You won't see a change yet. The next chunk adds the function that fetches data from Kismet.
- Add the API-fetching and analysis functions below load_allowlist:
def fetch_access_points():
"""Fetch all detected access points from Kismet's REST API."""
url = f"{KISMET_URL}{AP_ENDPOINT}"
# Request only the fields we need for detection
payload = {
"fields": [
"kismet.device.base.macaddr",
"kismet.device.base.name",
"kismet.device.base.signal/kismet.common.signal.last_signal",
"kismet.device.base.channel",
"kismet.device.base.last_time",
]
}
try:
response = requests.post(
url,
auth=(KISMET_USER, KISMET_PASSWORD),
json=payload,
timeout=10,
)
response.raise_for_status()
return response.json()
except requests.exceptions.ConnectionError:
print("[ERROR] Cannot connect to Kismet. Is it running on localhost:2501?")
sys.exit(1)
except requests.exceptions.HTTPError as e:
print(f"[ERROR] Kismet API returned an error: {e}")
sys.exit(1)
def analyze_access_points(access_points, allowlist):
"""Compare detected APs against the allowlist and flag rogues."""
results = []
for ap in access_points:
mac = ap.get("kismet.device.base.macaddr", "Unknown").upper()
ssid = ap.get("kismet.device.base.name", "")
signal = ap.get(
"kismet.device.base.signal/kismet.common.signal.last_signal", "N/A"
)
channel = ap.get("kismet.device.base.channel", "Unknown")
# Determine trust status
status = "UNKNOWN"
if ssid in allowlist:
if mac in allowlist[ssid]:
status = "TRUSTED"
else:
status = "ROGUE"
results.append(
{
"ssid": ssid if ssid else "(Hidden)",
"mac": mac,
"signal": signal,
"channel": channel,
"status": status,
}
)
return results
What does this code do?
- fetch_access_points() sends a POST request to Kismet's access point view endpoint with a fields array that limits the response to only the data we need (MAC, SSID name, signal strength, channel, last seen time).
- analyze_access_points() loops through the results and applies the detection logic: if an AP's SSID matches one in your allowlist but its MAC does not, it is flagged as ROGUE.
- APs whose SSID is not in the allowlist at all are marked UNKNOWN (they are neighbors' networks, not impersonating yours).
You won't see a change yet. The next chunk adds the reporting output and main entry point.
- Add the report printer, main function, and entry point below analyze_access_points:
def print_report(results):
"""Print a formatted report of detected access points."""
print("\n" + "=" * 70)
print(" ROGUE ACCESS POINT DETECTION REPORT")
print("=" * 70)
rogues = [r for r in results if r["status"] == "ROGUE"]
trusted = [r for r in results if r["status"] == "TRUSTED"]
unknown = [r for r in results if r["status"] == "UNKNOWN"]
if rogues:
print(f"\n \u26a0\ufe0f ROGUE APs DETECTED: {len(rogues)}")
print("-" * 70)
for ap in rogues:
print(f" [ROGUE] SSID: {ap['ssid']}")
print(f" MAC: {ap['mac']}")
print(f" Signal: {ap['signal']} dBm | Channel: {ap['channel']}")
print()
else:
print("\n \u2705 No rogue access points detected.")
print(f"\n Summary: {len(trusted)} trusted | {len(rogues)} rogue | {len(unknown)} unknown")
print("=" * 70 + "\n")
def main():
"""Main entry point for the rogue AP detector."""
print("[*] Rogue Access Point Detector")
print("[*] Loading allowlist...")
allowlist = load_allowlist("known_aps.json")
print(f"[*] Monitoring {len(allowlist)} trusted network(s)")
print("[*] Querying Kismet for detected access points...")
access_points = fetch_access_points()
print(f"[*] Found {len(access_points)} access point(s)")
# Analyze and report
results = analyze_access_points(access_points, allowlist)
print_report(results)
if __name__ == "__main__":
main()
What does this code do?
- print_report() formats a clear summary showing how many APs are trusted, rogue, or unknown. Rogue APs get detailed output including their MAC, signal strength, and channel.
- main() orchestrates the full workflow: load the allowlist, query Kismet, analyze, and print.
- The if __name__ == "__main__" block ensures main() runs only when you execute the script directly.
- Save rogue_detector.py.
- Confirm both project files exist by running:
ls
You should see known_aps.json and rogue_detector.py in the output.
Missing one of the files?
Double-check you saved both files in the same directory. If you are using a text editor, confirm the save location matches where your terminal is pointing (run pwd to check).
help me find my rogue_detector.py file
✔️ Awesome, I've got everything!
Great. Double check you have saved rogue_detector.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete rogue_detector.py file for comparison:
#!/usr/bin/env python3
"""Rogue Access Point Detector - Queries Kismet REST API to find unauthorized APs."""
import json
import sys
from pathlib import Path
import requests
KISMET_URL = "http://localhost:2501"
KISMET_USER = "kismet"
KISMET_PASSWORD = "kismet"
AP_ENDPOINT = "/devices/views/phydot11_accesspoints/devices.json"
def load_allowlist(filepath):
"""Load the known-good AP allowlist from a JSON file."""
path = Path(filepath)
if not path.exists():
print(f"[ERROR] Allowlist file not found: {filepath}")
sys.exit(1)
with open(path, "r") as f:
data = json.load(f)
allowlist = {}
for network in data.get("trusted_networks", []):
ssid = network["ssid"]
macs = [mac.upper() for mac in network["valid_macs"]]
allowlist[ssid] = macs
return allowlist
def fetch_access_points():
"""Fetch all detected access points from Kismet's REST API."""
url = f"{KISMET_URL}{AP_ENDPOINT}"
payload = {
"fields": [
"kismet.device.base.macaddr",
"kismet.device.base.name",
"kismet.device.base.signal/kismet.common.signal.last_signal",
"kismet.device.base.channel",
"kismet.device.base.last_time",
]
}
try:
response = requests.post(
url,
auth=(KISMET_USER, KISMET_PASSWORD),
json=payload,
timeout=10,
)
response.raise_for_status()
return response.json()
except requests.exceptions.ConnectionError:
print("[ERROR] Cannot connect to Kismet. Is it running on localhost:2501?")
sys.exit(1)
except requests.exceptions.HTTPError as e:
print(f"[ERROR] Kismet API returned an error: {e}")
sys.exit(1)
def analyze_access_points(access_points, allowlist):
"""Compare detected APs against the allowlist and flag rogues."""
results = []
for ap in access_points:
mac = ap.get("kismet.device.base.macaddr", "Unknown").upper()
ssid = ap.get("kismet.device.base.name", "")
signal = ap.get(
"kismet.device.base.signal/kismet.common.signal.last_signal", "N/A"
)
channel = ap.get("kismet.device.base.channel", "Unknown")
status = "UNKNOWN"
if ssid in allowlist:
if mac in allowlist[ssid]:
status = "TRUSTED"
else:
status = "ROGUE"
results.append(
{
"ssid": ssid if ssid else "(Hidden)",
"mac": mac,
"signal": signal,
"channel": channel,
"status": status,
}
)
return results
def print_report(results):
"""Print a formatted report of detected access points."""
print("\n" + "=" * 70)
print(" ROGUE ACCESS POINT DETECTION REPORT")
print("=" * 70)
rogues = [r for r in results if r["status"] == "ROGUE"]
trusted = [r for r in results if r["status"] == "TRUSTED"]
unknown = [r for r in results if r["status"] == "UNKNOWN"]
if rogues:
print(f"\n \u26a0\ufe0f ROGUE APs DETECTED: {len(rogues)}")
print("-" * 70)
for ap in rogues:
print(f" [ROGUE] SSID: {ap['ssid']}")
print(f" MAC: {ap['mac']}")
print(f" Signal: {ap['signal']} dBm | Channel: {ap['channel']}")
print()
else:
print("\n \u2705 No rogue access points detected.")
print(f"\n Summary: {len(trusted)} trusted | {len(rogues)} rogue | {len(unknown)} unknown")
print("=" * 70 + "\n")
def main():
"""Main entry point for the rogue AP detector."""
print("[*] Rogue Access Point Detector")
print("[*] Loading allowlist...")
allowlist = load_allowlist("known_aps.json")
print(f"[*] Monitoring {len(allowlist)} trusted network(s)")
print("[*] Querying Kismet for detected access points...")
access_points = fetch_access_points()
print(f"[*] Found {len(access_points)} access point(s)")
results = analyze_access_points(access_points, allowlist)
print_report(results)
if __name__ == "__main__":
main()
Run the detector and review the report
With Kismet still running in its Terminal window, you can now execute the detector and see which access points are in your airspace.
- Open a new Terminal tab or window (keep Kismet running in the original one).
- Run the detector script:
python3 rogue_detector.py
What should I see?
The script prints a status log followed by a detection report. You will see a summary line showing counts for trusted, rogue, and unknown access points.
If your home router's SSID and MAC match what you put in known_aps.json, it will appear as TRUSTED. Neighbors' networks will appear as UNKNOWN (they are not impersonating your SSID, so they are not rogues).
- Verify the output includes a ROGUE ACCESS POINT DETECTION REPORT banner and a summary line at the bottom.
Seeing a connection error?
If you see [ERROR] Cannot connect to Kismet, make sure Kismet is still running in your other Terminal window. Switch back to the Kismet terminal and confirm it is active.
If you see an authentication error, check that KISMET_USER and KISMET_PASSWORD in your script match the credentials you set in kismet_site.conf.
help me fix my Kismet connection error
Your detector is working. Next up, you will test the system by simulating an evil twin attack and confirming the detector catches it.
Test the Detection System
Your detection script is built and you've already confirmed it can pull data from Kismet. Now it's time to validate the entire system with a real simulated attack.
In this step, you'll create an actual evil twin access point using your phone and watch both Kismet's built-in APSPOOF alerting and your Python script catch it. This proves the detection logic works end-to-end.
In this step, get ready to:
- Verify Kismet's APSPOOF alert system in the web UI.
- Simulate an evil twin attack using a mobile phone hotspot.
- Run the detector script and confirm it flags the simulated rogue AP.
Verify APSPOOF alerts in the Kismet web UI
Kismet has a built-in alerting system that flags suspicious wireless activity in real time. The APSPOOF alert you configured in kismet_site.conf triggers whenever a beacon for your SSID comes from a MAC address not in your approved list.
- Switch back to the Kismet web UI in your browser at http://localhost:2501.
- Look at the upper-right area of the Kismet interface for the alerts icon (a small warning indicator).
- Click the alerts icon to expand the alerts panel.
If no APSPOOF alerts have triggered yet, that is expected. No one has broadcast your configured SSID from an unauthorized MAC address yet.
What are Kismet alert types?
Kismet categorizes alerts by type. The APSPOOF alert falls under the SPOOF category, meaning it detects attempts to impersonate an existing device. Alert severities range from 0 (INFO) to 20 (CRITICAL).
Your alert=APSPOOF,10/min,1/sec throttle setting means Kismet will raise at most 10 APSPOOF alerts per minute and at most 1 per second. This prevents alert flooding if someone is actively attacking your network.
Simulate an evil twin attack with your phone
To test the detection system, you need an access point that impersonates your network. The safest way to do this is by renaming your phone's mobile hotspot to match your home SSID exactly.
- On your mobile phone, open your hotspot or personal hotspot settings.
- Change the hotspot name to match the SSID you configured in known_aps.json (for example, MyHomeNetwork).
- Enable the hotspot so it starts broadcasting.
Why does this work as a test?
Your phone's hotspot has a different MAC address (BSSID) than your actual router. When Kismet picks up the beacon from your phone broadcasting the same SSID as your trusted network, it sees an SSID match with a MAC mismatch. This is exactly what a real evil twin attack looks like.
An attacker would do the same thing with a more powerful antenna, hoping nearby devices auto-connect to the fake network instead of the real one.
- Wait 30-60 seconds for Kismet to detect the new access point on its next channel hop.
- Return to the Kismet web UI and click the alerts icon in the upper-right area.
- You should see a new APSPOOF alert indicating that your configured SSID was seen from an unknown MAC address.
Not seeing an APSPOOF alert?
Make sure your phone's hotspot name matches your configured SSID exactly, including capitalization. SSID matching is case-sensitive.
Kismet may take a minute to scan the channel your phone is broadcasting on. Wait a bit longer and check again.
Confirm your phone's hotspot is actually enabled and broadcasting (check that another device can see it in its Wi-Fi list).
help me troubleshoot why Kismet is not detecting my phone's hotspot as an APSPOOF alert
Run the detector script and confirm the rogue flag
Now that your simulated evil twin is broadcasting, your Python script should flag it as a rogue. The script compares every detected AP's MAC address against the allowlist for that SSID.
- Switch back to your terminal.
- Run the detector script again by running this command:
python3 rogue_detector.py
You should see the detection report with your phone's hotspot flagged as [ROGUE] in the output. It will show the SSID matching your trusted network name, but with a MAC address that is not in your allowlist.
How does the detection logic work?
The detector uses a simple but effective rule: SSID match + MAC mismatch = rogue. If a detected access point broadcasts an SSID that is in your known_aps.json allowlist, but its MAC address does not match any of the valid_macs for that SSID, it gets flagged as ROGUE.
Access points whose SSIDs are not in your allowlist at all are marked UNKNOWN. These are neighbouring networks that are not impersonating yours.
What about MAC spoofing?
This detection approach will not catch an attacker who also spoofs the MAC address of your legitimate router. The Kismet documentation explicitly notes this limitation. In practice, MAC spoofing requires the attacker to know your router's exact BSSID, which adds difficulty.
For deeper defence, enterprise networks use 802.1X authentication and certificate-based validation. This project covers the most common attack vector where the attacker clones only the SSID.
- After confirming the detection, turn off your phone's hotspot.
- Optionally rename your phone's hotspot back to its original name.
Script not showing a ROGUE entry?
If the report only shows UNKNOWN entries and no ROGUE, Kismet may not have detected your phone's hotspot yet. Wait another 30 seconds and run the script again.
Check that your phone's hotspot SSID matches the SSID in known_aps.json exactly. A single character difference means the script treats it as UNKNOWN rather than ROGUE.
help me figure out why my rogue_detector.py script is not flagging my phone hotspot as ROGUE
Secret mission
Continuous Monitoring with Desktop Notifications
Your detector currently runs once and exits. A real wireless intrusion detection system monitors continuously. In this secret mission, you will extend your script to poll Kismet every 30 seconds in a loop, fire native macOS desktop notifications when a rogue AP appears, and handle graceful shutdown with Ctrl+C.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally using open-source tools, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Kismet running process (on localhost:2501).
- Kismet package (installed via Homebrew).
- kismetwireless/kismet Homebrew tap.
- requests Python library.
- Project files: rogue_detector.py, known_aps.json.
- kismet_site.conf configuration file (in ~/.kismet/).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with rogue AP detection.
- Kismet continues scanning on localhost:2501. You can return to the web UI or run your detector script anytime.
- Your kismet_site.conf APSPOOF alerts remain active for future monitoring sessions.
- Kismet uses your Wi-Fi adapter while running, which prevents normal internet use on that adapter. Stop Kismet with Ctrl+C when you want your connection back.
Pause - I'll come back to this later
Stop the running Kismet process to free up your Wi-Fi adapter and system resources. Your configuration and scripts stay in place for next time.
- Switch to the terminal where Kismet is running and press Ctrl+C to stop it.
- Your kismet_site.conf, known_aps.json, and rogue_detector.py all remain on disk, ready for your next session.
- To resume later, start Kismet again with the same source and run your detector script.
Delete - I don't want to use this again
Remove all project resources and uninstall Kismet completely.
- Stop Kismet by pressing Ctrl+C in the terminal where it is running.
Uninstall Kismet and remove the Homebrew tap by running:
brew uninstall kismet
brew untap kismetwireless/kismet
Uninstall the requests library by running:
pip3 uninstall requests
Delete your project files by running:
rm rogue_detector.py known_aps.json
Delete the Kismet site configuration by running:
rm ~/.kismet/kismet_site.conf
What about Kismet log files?
Kismet creates .kismet log files in the directory where you launched it. Check that directory and delete any log files you no longer need.
Nice Work!
Nice Work!
You just built a wireless intrusion detection system that monitors your local airspace for rogue access points and evil twin attacks.
You've learned how to:
- Installed and configured Kismet as a wireless intrusion detection system on macOS, scanning nearby Wi-Fi access points via the built-in Airport adapter.
- Configured APSPOOF alerts to detect evil twin attacks by mapping trusted SSIDs to their legitimate MAC addresses.
- Built a Python script that queries Kismet's REST API to detect rogue access points using allowlist comparison and MAC address validation.
- Extended the detector with continuous monitoring and macOS desktop notifications that fire when a rogue AP is detected.
Ready to quiz yourself?