Shared-key 802.11 auth accepts cleartext challenge response: full authentication bypass without the WEP key
Summary
hostap_input() decrypts an AUTH frame only when the Protected bit is set and then CLEARS the bit, so hostap_auth_shared() processes the seq-3 challenge response with no knowledge of whether it arrived encrypted. It memcmp()s the challenge IE against ni->ni_challenge - the exact challenge the AP itself transmitted IN CLEARTEXT in its seq-2 frame (frame 2 is legitimately cleartext per 802.11-2016 12.3.3, frame 3 MUST be protected). An RF-adjacent attacker sends seq1, hears seq2, echoes the challenge back in a cleartext seq3 and ieee80211_node_authorize() fires: authenticated, then associated, with ZERO knowledge of the WEP key. VERIFIED on stock guest via the fw4 software-radio KLD: AUTHORIZED=1 + associd 0xc001 under wepmode on; corrupt-challenge control correctly refused; under wepmode mixed a cleartext TODS data frame is delivered to the AP stack (Ipkts 1->2). Fix validated (reject unprotected algo=SHARED seq=RESPONSE before the bit is stripped, >=30B length guard): bypass gone, is_rx_bad_auth++, open-auth flow unaffected. Remote unauthenticated RF-adjacent attacker defeats the only authentication of a shared-key WEP AP. Protocol bypass, not memory corruption - no uid0 route.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3323 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fw4.c | β | 16.5 KB | view raw | |
| Makefile | β | 144 B | β download | |
| build.sh | β | 87 B | view raw | |
| run.sh | β | 111 B | view raw | |
| build.log | β | 5.6 KB | view raw | |
| run.log | β | 6.0 KB | view raw | |
| run.2.log | β | 1.6 KB | view raw | |
| fix.log | β | 1.7 KB | view raw | |
| fixcheck.sh | β | 1.6 KB | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| env.txt | β | 198 B | view raw | |
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 4.4 KB | β raw | |
| verdict.json | β | 3.8 KB | view raw |
DF-3323 β shared-key auth: cleartext challenge response accepted (auth bypass)
File: sys/netproto/802_11/wlan/ieee80211_hostap.c
Severity: Medium Β· CWE-287 Β· confidence: certain Β· REPRODUCED on the stock guest kernel
What
hostap_input() only decrypts an AUTH frame when the Protected bit is set,
and clears the bit before dispatch (:851-880). hostap_auth_shared()
(:1122-1150) processes a seq-3 shared-key response with no knowledge of
whether the frame arrived encrypted, and simply memcmp()s the challenge IE
against ni->ni_challenge β the exact challenge the AP itself transmitted
in cleartext in its seq-2 response (ieee80211_output.c: add_auth()
marks frames for encryption only when arg == IEEE80211_AUTH_SHARED_RESPONSE,
a seq the AP never sends; frame 2 is legitimately cleartext per
802.11-2016 12.3.3). Frame 3 must be protected. The kernel accepts it
unprotected β ieee80211_node_authorize() (:1149).
An RF-adjacent attacker completes shared-key authentication with zero
knowledge of the WEP key: send seq1, hear seq2, echo it back in a
cleartext seq3. Then associate normally (attack=4).
How to reproduce
sh findings/poc/DF-3323/build.sh # compiles the fw4 fake-radio KLD in the guest sh findings/poc/DF-3323/run.sh # baseline on stock kernel
Success criterion (see run.log / run.2.log):
fw4.attack=2: ... AUTHORIZED=1 ... <<<< BYPASSED fw4.state[after-assoc]: ... AUTHORIZED=1 associd 0xc001
Control (fw4.attack=3, corrupted challenge): AP answers seq4 status=15
(STATUS_CHALLENGE) and the node stays AUTHORIZED=0 β the memcmp discipline
works; only the encryption gate is missing.
Impact amplifier (wepmode mixed, i.e. DROPUNENC off β also the state of
the natural authmode shared wepkey β¦ config without an explicit
wepmode on): ONE injected cleartext TODS data frame is delivered to the
AP stack β netstat -I wlan0 Ipkts 1β2 (run.2.log).
Fix validation
fix.diff adds an else if in hostap_input()'s MGT path: an AUTH frame
that arrives without the Protected bit and parses as
algo=SHARED/seq=RESPONSE is discarded (is_rx_bad_auth++). Validated on a
rebuilt kernel (make -j6 nativekernel, kernel #1 21:14 / re-checked on
the doubly-patched #1 21:29): fw4.attack=2 no longer authorizes
(AUTHORIZED=0, no seq-4 TX), while fw4.attack=1 still elicits the seq-2
challenge (legit cleartext frame 1/2 path unbroken). See fix.log.
DF-3323 β VERDICT
Status: reproduced (baseline, stock INVARIANTS kernel #0 Thu Jul 2 06:02:54
UTC 2026) Β· impact: remote RF-adjacent authentication bypass (no key material
required) β authorized + associated station; cleartext data delivery when
DROPUNENC is off Β· fix: validated (kernel rebuilt with fix.diff).
Root cause (path:line)
sys/netproto/802_11/wlan/ieee80211_hostap.c:851-880βhostap_input()MGT path: decryption happens iffwh->i_fc[1] & IEEE80211_FC1_PROTECTED; afterieee80211_crypto_decap()the bit is cleared (:879), so downstream code cannot tell an encrypted frame from a cleartext one.sys/netproto/802_11/wlan/ieee80211_hostap.c:1122-1150βhostap_auth_shared()case IEEE80211_AUTH_SHARED_RESPONSE: onlymemcmp(ni->ni_challenge, &challenge[2], challenge[1])(:1138); on matchieee80211_node_authorize(ni)(:1149). No Protected-bit requirement anywhere on the seq-3 path.sys/netproto/802_11/wlan/ieee80211_output.c:2380-2417βadd_auth(): the AP's seq-2 CHALLENGE frame carries the 128-byte challenge IE and is not marked for encryption (onlyarg == IEEE80211_AUTH_SHARED_RESPONSEsetsIEEE80211_BPF_CRYPTO, and the AP never sends seq 3). Frame 2 is legitimately clearttext (802.11-2016 12.3.3), frame 3 must be protected β the kernel never checks.
Attack chain (executed on the guest via the fw4 software radio)
sysctl kern.fw4_attack=1β inject AUTH algo=SHARED seq=1 (30 bytes, legitimately cleartext). AP (wlanmode ap,authmode shared wepkey β¦ wepmode on) creates the node, allocates + randomizes the challenge, transmits seq-2 with the 128-byte challenge IE protected=0; the harness'sraw_xmitsniffer captures it byte-for-byte ("off the air").- Control
fw4_attack=3β respond seq-3 with one corrupted challenge byte: AP answers seq-4 status=15 (IEEE80211_STATUS_CHALLENGE), node stays AUTHORIZED=0. (Proves the exchange is otherwise enforced.) - Attack
fw4_attack=2β respond seq-3 with the captured challenge, Protected bit CLEAR: AP answers seq-4 status=0 and the node reportsAUTHORIZED=1 <<<< BYPASSED. fw4_attack=4β ASSOC_REQ:associd 0xc001,list staFLAGA(authorized+assoc) β underwepmode on, the strictest WEP config.- Part B (
wepmode mixed, DROPUNENC off β also the default state ofauthmode shared wepkey β¦without explicitwepmode on): steps 1-4 repeat, then ONE cleartext TODS data frame is injected (fw4_attack=5):netstat -I wlan0Ipkts 1β2, Ibytes 20β42 β the frame traversedhostap_input's privacy path (F_PRIVACY set, Protected bit clear βkey = NULLat :711), was decapped, and delivered toif_input.
No WEP key was ever known or used by the attacker.
Why not higher severity
No memory corruption. Requires an RF-adjacent attacker and a WEP
shared-key-configured AP (legacy, non-default config). Under the
recommended wepmode on (DROPUNENC set) cleartext data is still dropped
(non-PAE), but 802.11 authentication, association, AID/power-save state and
the EAPOL (PAE) pass-through are gained without the key.
Fix validation (kernel #1, both fixes applied)
fix.diff (in hostap_input, before the Protected-bit is stripped):
discard subtype==AUTH frames that arrive unprotected and parse as
algo == IEEE80211_AUTH_ALG_SHARED && seq == IEEE80211_AUTH_SHARED_RESPONSE
(802.11-2016 12.3.3 requires frame 3 to be protected). Requires
m_pkthdr.len >= 30 && m_len >= 30 so it never reads a fragmented header
(if shorter, hostap_recv_mgmt's own VERIFY_LENGTH rejects the frame
anyway).
- baseline: attack=2 β AUTHORIZED=1 + AP TX seq4 status=0
- patched : attack=2 β frame dropped (
is_rx_bad_auth++), NO AP TX, node AUTHORIZED=0; attack=1 still elicits the seq-2 challenge (legit cleartext frames 1/2 unaffected). - regression: open-authmode vap, open auth (attack=6) β authorized β assoc succeeds (see DF-3326 fixcheck in findings/poc/DF-3326/fix.log).
fix_baseline_reproduced=1, fix_patched_reproduced=0 (the bug behavior
is GONE on the patched kernel) β fix_status=fixed.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedmake -j6 nativekernel rc=0 with fix.diff (+DF-3326 fix); baseline bypass (AUTHORIZED=1) is gone: cleartext seq3 dropped with is_rx_bad_auth++, no seq-4 TX, node stays AUTHORIZED=0; regression: seq-1 request still elicits the cleartext seq-2 challenge, open-auth flow associates normally.
findings/poc/DF-3323/fix.log + findings/poc/DF-3326/fix.log (shared kernel #1 21:29)
Confirmed kernel references
Detail
Exploit chain
1) inject AUTH shared seq1 (30B) -> AP creates node, sends seq2 challenge cleartext; 2) capture challenge off the air in raw_xmit; 3) inject AUTH shared seq3 carrying the captured challenge with Protected bit CLEAR -> AP replies seq4 status 0, port authorized; 4) inject ASSOC_REQ -> associd 0xc001; 5) (wepmode mixed / no wepmode) inject cleartext TODS data frames -> delivered to AP stack
Evidence (decisive lines)
run.log/run.2.log: 'fw4[air]: AP TX AUTH seq=2 status=0 challenge_ie_len=128 protected=0', 'fw4.state[current]: ... AUTHORIZED=1 ... <<<< BYPASSED', 'associd 0xc001', netstat Ipkts 1->2; fix.log: patched kernel drops the cleartext seq3 (AUTHORIZED=0, no seq4 TX) while attack=1 still elicits the challenge
PoC changes
seed none (new harness): fw4.c = fw3.c pattern (DF-3282/3312/3314) + raw_xmit sniffer capturing the AP's outgoing AUTH/challenge frames + sysctl-driven injectors (attack=1 request, 2 captured-challenge response, 3 corrupt-challenge control, 4 assoc, 5 cleartext data, 6 open-auth regression). DF ifconfig spells hostap mode 'wlanmode ap' not 'wlanmode hostap'.
Verified recommended fix
In hostap_input()'s MGT path, discard AUTH frames that arrive unprotected and parse as algo=SHARED seq=RESPONSE (802.11-2016 12.3.3); see fix.diff
Verdict
hostap_input() decrypts AUTH frames only when the Protected bit is set and then clears it (:851-880); hostap_auth_shared() processes the seq-3 challenge response with no encryption requirement and memcmp()s it against the challenge the AP itself sent in cleartext (:1138). Echoing the overheard challenge in a cleartext seq-3 frame authenticates the attacker without the WEP key (ieee80211_node_authorize :1149). Reproduced on the stock INVARIANTS kernel: AUTHORIZED=1 + associd 0xc001 under wepmode on; corrupt-challenge control correctly refused (status 15); one cleartext TODS data frame delivered under wepmode mixed (netstat Ipkts 1->2).
No comments yet.