Fix Evil Twin attack: make it actually work, and harden the portal#527
Merged
Conversation
The Evil Twin subsystem had several issues — some that prevented the attack
from working at all, several dead/unwired components, and security/robustness
gaps. Found via audit + a live debug run against an authorized AP.
Core correctness (attack couldn't work before):
- Rogue AP is now OPEN. hostapd was unconditionally configured WPA2 with a
secret passphrase, so victims could never associate to reach the portal.
generate_config() now emits an open AP (auth_algs=1, no wpa lines) unless a
passphrase is explicitly supplied.
- dnsmasq now starts: added except-interface=lo so it no longer tries to bind
127.0.0.1:53 and collide with the host resolver ("Address already in use").
dnsmasq startup errors are now read from stderr (were read from stdout, so
failures were blank/undiagnosable).
- Dual-interface mode now actually engages: _get_interface_assignment()
imported InterfaceAssignment from a non-existent module (ModuleNotFoundError),
silently falling back to single-interface every run. run() also ran _setup()
AND _run_dual_interface(), double-starting hostapd/dnsmasq/portal; run() now
picks the mode first and only sets up once.
- Added the missing Airmon helpers the dual path calls (put_interface_down,
set/get_interface_mode, set/get_interface_channel) — they didn't exist, so
the dual path raised AttributeError. The deauth NIC is now put in monitor
mode AND locked to the target channel, and restored to managed on cleanup.
Credential handling / honesty:
- Captured credentials are now validated against the real AP (CredentialValidator,
honoring eviltwin_validate_credentials); a confirmed-wrong password is rejected
and the attack continues, and unverifiable captures are clearly flagged rather
than reported as a confirmed key.
- CredentialHandler is now wired into the portal POST path, adding server-side
input validation and per-client rate limiting (was dead code).
Portal / templates:
- Configurable templates are now applied (PortalServer was always built without
a renderer, so only the hardcoded generic page was served); --eviltwin-template
now takes effect. Template variable substitution is HTML-escaped (the untrusted
SSID was injected raw).
- Captive portal now uses ThreadingHTTPServer + per-request socket timeout so one
stalled client can't block every other victim.
- POST Content-Length is bounds-checked (negative/non-numeric rejected; oversized
413) — a negative value previously caused rfile.read(-1) to drain until EOF.
- Credential/log callbacks are wrapped in staticmethod when bound to the handler
class, so plain-function callbacks aren't turned into bound methods (extra arg).
- __del__ no longer does network/thread teardown (fragile at GC); added context
manager support.
hostapd SSID injection:
- SSID is written safely: printable-ASCII SSIDs use ssid=, anything with control
chars / non-ASCII uses ssid2=<hex>, so an SSID with a newline can't inject
hostapd directives. Clamped to the 32-byte 802.11 limit; channel is int-coerced.
Config:
- Fixed eviltwin_*/evil_twin_* naming drift so configured options actually apply
(deauth interval, portal template) and added a real eviltwin_timeout
(+ --eviltwin-timeout); the attack previously never timed out.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR repairs and hardens the Evil Twin attack flow so the rogue AP + captive portal path works end-to-end (association, DNS/DHCP, portal submission), while also addressing several robustness and injection issues (hostapd SSID config injection, portal HTML/template injection, and safer request handling).
Changes:
- Make hostapd emit an open AP by default (unless a passphrase is explicitly provided) and harden SSID handling to prevent hostapd config injection.
- Improve dnsmasq startup reliability and error visibility (avoid loopback binding conflict; read stderr/combined output on failure).
- Wire portal templating + add portal-side hardening (ThreadingHTTPServer, socket timeout, POST bounds checks) and add Evil Twin timeout config plumbing.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| wifite/tools/hostapd.py | Default to open AP; safe SSID directive formatting; channel int coercion. |
| wifite/tools/dnsmasq.py | Prevent loopback binding; surface startup errors via stderr/out. |
| wifite/tools/airmon.py | Add missing interface up/down/mode/channel helper methods used by dual-interface logic. |
| wifite/config/parsers/eviltwin.py | Parse and apply --eviltwin-timeout. |
| wifite/config/defaults.py | Add eviltwin_timeout default. |
| wifite/config/init.py | Add eviltwin_timeout config attribute. |
| wifite/attack/portal/templates.py | HTML-escape template variable substitution values. |
| wifite/attack/portal/server.py | Threaded server + timeouts; POST Content-Length bounds checks; wire credential handler; staticmethod-wrap callbacks; safer lifecycle semantics. |
| wifite/attack/portal/credential_handler.py | Add check_and_record() for synchronous admission control (validate + rate limit + record). |
| wifite/attack/eviltwin.py | Fix config key drift; enable template renderer; dual-interface flow correction; add credential verification path; cleanup restores interface mode. |
| wifite/args.py | Add --eviltwin-timeout CLI option. |
| tests/test_eviltwin_unit.py | Add/adjust unit tests for open AP default + SSID injection hardening. |
| tests/test_eviltwin_e2e.py | Update config attribute names to eviltwin_*. |
| tests/test_adaptive_deauth_integration.py | Update config attribute name for deauth interval. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1284
to
+1290
| if self.client_monitor: | ||
| self.client_monitor.record_credential_attempt(client_ip, success=False) | ||
| self.credential_attempts.append({ | ||
| 'client_ip': client_ip, | ||
| 'mac': client_ip, | ||
| 'ssid': ssid, | ||
| 'password': password, |
Comment on lines
1299
to
1313
| if self.client_monitor: | ||
| self.client_monitor.record_credential_attempt(client_ip, success=True) | ||
|
|
||
| # Store the result — the monitoring loop checks self.crack_result | ||
| self.crack_result = self.create_result(password) | ||
| # Annotate verification status for downstream reporting. | ||
| self.crack_result.verified = (verified is True) | ||
| self.credential_attempts.append({ | ||
| 'client_ip': client_ip, | ||
| 'mac': client_ip, | ||
| 'ssid': ssid, | ||
| 'password': password, | ||
| 'success': True, | ||
| 'timestamp': time.time(), | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Evil Twin subsystem had several issues — some that prevented the attack from working at all, several dead/unwired components, and security/robustness gaps. Found via audit + a live debug run against an authorized AP.
Core correctness (attack couldn't work before):
Credential handling / honesty:
Portal / templates:
hostapd SSID injection:
Config: