---
title: 'Deauthentication Attacks'
source: 'https://academia.sh/en/courses/network-security/deauthentication-attacks'
course: 'Network Security'
language: en
updated: '2026-09-26T03:02:54+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Separates wireless frame types by class and integrity proof, and counts protected against unprotected'
    - 'Lists the mechanisms that cause a connection to drop and measures how many protection closes'
    - 'Measures the indicator the defense sees: the catch-rate and false-alarm balance of threshold selection in the disconnect counter'
    - 'Shows the effect on catch rate of protection creating a counter whose baseline value is zero'
---

# Deauthentication Attacks

Of twelve frame types, only one carries integrity proof at baseline; protected management frames bring this count to four and tie three of eight management frames to proof, leaving five unprotected because they are pre-connection. Three of the eight mechanisms that disconnect a connection close; the record the defense sees moves from the disconnect counter to the rejected-frame counter, and the catch rate rises from three sixths to six sixths.

The previous lesson counted the unproven signals a client looks at when choosing a network
and measured how many profiles mutual authentication closes. The whole measurement rested on
one assumption: that the client establishes its connection on its own. A client that is
already connected never sees a rogue candidate.

A client **staying** connected, however, is a separate verification question. Data frames are
encrypted and carry integrity proof; the frames that establish, sustain, and terminate the
connection are a separate class. **Deauthentication** is the name for this distinction: a
management frame that terminates the connection is accepted without any proof of the claim it
carries. This lesson does not measure how that technique is carried out, but which frame is
protected, how many paths protection closes, and what the defense sees in which counter.

- **WS18.** Frame types fall into three classes: data, control, management. The
  classification and whether each type carries integrity proof is a model decision.
- **WS19.** A pre-connection frame is one exchanged between the parties before they share a
  common key. Because there is no shared key, integrity proof cannot be produced.
- **WS20.** A path is a mechanism that causes a client's connection to drop. A path closing
  means the claim that mechanism carries becomes verifiable.
- **WS21.** The log data is fictional and produced by a hand-written generator; the seed is
  visible and the run is reproducible. The expected normal disconnects per hour is a model
  value.
- **WS22.** The threshold is a multiple of the average and standard deviation drawn from past
  days for the same access point and the same hour. The event day is excluded from the
  baseline calculation.
- **WS23.** The attack steps, tooling, and frame generation are outside this lesson; what is
  measured is the scope of protection and what the defense's record shows.

## Which Frame Is Protected

A wireless connection does not carry only data. Three classes of frame travel over the same
medium, and their encryption coverage is not the same.

```js
// frame.mjs — wireless frame types and protected management frames (model).
// Attack steps are not written; what is measured is which frame carries integrity proof.
// [frame type, class, is it pre-connection, proof at baseline, proof with protection on]
export const FRAME = [
  ['data frame', 'data', false, true, true],
  ['acknowledgment', 'control', false, false, false],
  ['request to send / clear to send', 'control', false, false, false],
  ['power-save poll', 'control', false, false, false],
  ['beacon frame', 'management', true, false, false],
  ['probe request', 'management', true, false, false],
  ['probe response', 'management', true, false, false],
  ['authentication frame', 'management', true, false, false],
  ['association request / response', 'management', true, false, false],
  ['disassociation', 'management', false, false, true],
  ['deauthentication', 'management', false, false, true],
  ['action frame (channel, measurement)', 'management', false, false, true],
];
// Mechanisms that cause a connection to drop (model). The 'closing defense' field says
// which defense closes the path; 'none' = no defense.
export const PATH = [
  ['forged disassociation frame', 'management frame', 'protected management frames'],
  ['forged deauthentication frame', 'management frame', 'protected management frames'],
  ['forged action frame (channel change)', 'management frame', 'protected management frames'],
  ['impersonating a pre-connection probe response', 'management frame', 'none'],
  ['making the medium appear busy with a control frame', 'control frame', 'none'],
  ['jamming at the physical layer', 'physical', 'none'],
  ['the authentication server being unreachable', 'infrastructure', 'local authentication cache'],
  ['a client that does not support protection connecting', 'client', 'protection is made mandatory'],
];
```

```js
// run1.mjs — which frame is protected, how many paths close with protection on
import { FRAME, PATH } from './frame.mjs';

const E = [38, 13, 18, 12, 14];
const row = (h) => h.map((d, i) => String(d)[i ? 'padStart' : 'padEnd'](E[i])).join('');
const mark = (b) => b ? 'present' : 'absent';
console.log(row(['frame type', 'class', 'pre-connection', 'baseline', 'protection on']));
for (const [name, s, o, a, b] of FRAME) console.log(row([name, s, o ? 'yes' : 'no', mark(a), mark(b)]));
const count = (i) => FRAME.filter((c) => c[i]).length;
console.log(`\nframe type ${FRAME.length}; carrying integrity proof: baseline ${count(3)}, ` +
  `protection on ${count(4)}`);
const management = FRAME.filter((c) => c[1] === 'management');
console.log(`management frame ${management.length}; protected ${management.filter((c) => c[4]).length}, ` +
  `unprotected ${management.filter((c) => !c[4]).length} (all pre-connection)`);

const A = [50, 22, 32];
const row2 = (h) => h.map((d, i) => String(d)[i ? 'padStart' : 'padEnd'](A[i])).join('');
console.log('\n' + row2(['mechanism causing the disconnection', 'class', 'closing defense']));
for (const [name, s, k] of PATH) console.log(row2([name, s, k]));
const protectionCloses = PATH.filter((y) => y[2] === 'protected management frames').length;
console.log(`\npath ${PATH.length}; protected management frames close ${protectionCloses}, ` +
  `${PATH.length - protectionCloses} stay open`);
console.log(`of those left open, ${PATH.filter((y) => y[2] === 'none').length} are closed by no ` +
  `verification at all (not a claim problem)`);
```

```
frame type                                    class    pre-connection    baseline protection on
data frame                                     data                no     present       present
acknowledgment                              control                no      absent        absent
request to send / clear to send             control                no      absent        absent
power-save poll                             control                no      absent        absent
beacon frame                             management               yes      absent        absent
probe request                            management               yes      absent        absent
probe response                           management               yes      absent        absent
authentication frame                     management               yes      absent        absent
association request / response           management               yes      absent        absent
disassociation                           management                no      absent       present
deauthentication                         management                no      absent       present
action frame (channel, measurement)      management                no      absent       present

frame type 12; carrying integrity proof: baseline 1, protection on 4
management frame 8; protected 3, unprotected 5 (all pre-connection)

mechanism causing the disconnection                                class                 closing defense
forged disassociation frame                             management frame     protected management frames
forged deauthentication frame                           management frame     protected management frames
forged action frame (channel change)                    management frame     protected management frames
impersonating a pre-connection probe response           management frame                            none
making the medium appear busy with a control frame         control frame                            none
jamming at the physical layer                                   physical                            none
the authentication server being unreachable               infrastructure      local authentication cache
a client that does not support protection connecting                client    protection is made mandatory

path 8; protected management frames close 3, 5 stay open
of those left open, 3 are closed by no verification at all (not a claim problem)
```

At baseline, **one** of the twelve frame types carries integrity proof. Encryption's coverage
is the data frame; the remaining eleven types travel in the open. This row is the entire basis
for deauthentication — the frame that terminates the connection carries no proof at all of the
"this connection is ending" claim it makes, and the client accepts it.

When protection is turned on, the count rises from one to four. The three types gained are
management frames sent **after** the connection is established: disassociation,
deauthentication, and the action frame. What the three have in common is that a shared key
exists between the parties at that point; that is the condition for proof to be produced.

The five management frames that stay unprotected also share what is read in the same column:
all of them are **pre-connection**. The beacon frame, the probe request and response,
authentication, and the association request are exchanged while there is no shared key yet.
The previous lesson's twin-access-point path lives exactly in these five rows; protected
management frames do not close that path, and cannot.

Control frames are a third set. Acknowledgment and request-to-send frames never carry proof
under any circumstance, because the timing of medium sharing depends on these frames being
small and fast. There is also no content in them worth protecting; the only claim they carry
is "I will use the medium for this long" or "received." Adding proof would enlarge these
frames and slow down the medium sharing itself — their staying outside protection's scope is
not a gap, it is a measured trade-off.

The second table translates this into a path count. Of the eight mechanisms, **three** close,
and five stay open. Three of the five that stay open are closed by no verification at all, and
the reason matters: jamming at the physical layer is not a claim problem. There is no claim
accepted without proof there; the medium is unusable, and verification does not bring
the medium back. This course's measure reaches its limit here.

The remaining two paths fall to other items of defense. The authentication server being
unreachable is met with a local authentication cache; the problem of a client that does not
support protection is closed by making protection **mandatory**, and its cost is the devices
left out in the previous lessons' inventory table. When protection is left optional, a client
can choose to connect without it, and for that client the three paths reopen.

## The Counter the Defense Sees

When protection is not on, the disassociation frame is treated as valid, and the only trace it
leaves is clients reconnecting. The run below extracts, from thirty days of fictional log
data, what threshold selection actually buys.

```js
// run2.mjs — what the defense sees in the record: the deviation in the disconnect counter and threshold selection.
// The generator is hand-written, the seed is visible; the run is reproducible.
const SEED = 20240917;
let s = SEED;
const rand = () => (s = (s * 1103515245 + 12345) % 2147483648) / 2147483648;
const poisson = (l) => { let k = 0, p = 1, L = Math.exp(-l); do { k++; p *= rand(); } while (p > L); return k - 1; };

const DAY = 30, HOUR = 24, AP = 6, EVENT_DAY = 17, EVENT_AP = 3;
const EVENT_HOURS = [7, 10, 13, 16, 19, 22];
// Expected normal disconnects per hour (model): higher during work hours, low at night.
const BASELINE = Array.from({ length: HOUR }, (_, h) => (h >= 8 && h < 18) ? 6 : 1.2);
const disconnects = [], rejected = [];
for (let g = 0; g < DAY; g++) {
  disconnects.push([]); rejected.push([]);
  for (let a = 0; a < AP; a++) {
    disconnects[g].push([]); rejected[g].push([]);
    for (let h = 0; h < HOUR; h++) {
      const event = g === EVENT_DAY && a === EVENT_AP && EVENT_HOURS.includes(h);
      disconnects[g][a].push(poisson(BASELINE[h]) + (event ? poisson(7) : 0));
      // While protection is on, the same frames are rejected at the integrity check and counted separately.
      rejected[g][a].push(event ? poisson(7) : 0);
    }
  }
}
// The baseline is derived per hour and access point from the days outside the event day.
const historyDays = [...Array(DAY).keys()].filter((g) => g !== EVENT_DAY);
const avg = (v) => v.reduce((a, b) => a + b, 0) / v.length;
const stdev = (v) => Math.sqrt(avg(v.map((x) => (x - avg(v)) ** 2)));
const E = [10, 14, 16, 18, 16];
const row = (h) => h.map((d, i) => String(d)[i ? 'padStart' : 'padEnd'](E[i])).join('');
console.log(`day ${DAY}, access point ${AP}, hour ${DAY * AP * HOUR}, event hour ${EVENT_HOURS.length}`);
console.log(row(['threshold', 'caught', 'missed', 'false alarm', 'false/day']));
for (const k of [2, 3, 4, 5, 6]) {
  let caught = 0, falseAlarm = 0;
  for (let g = 0; g < DAY; g++) for (let a = 0; a < AP; a++) for (let h = 0; h < HOUR; h++) {
    const hist = historyDays.map((x) => disconnects[x][a][h]);
    const threshold = avg(hist) + k * stdev(hist);
    if (disconnects[g][a][h] <= threshold) continue;
    if (g === EVENT_DAY && a === EVENT_AP && EVENT_HOURS.includes(h)) caught++; else falseAlarm++;
  }
  console.log(row([k, `${caught}/${EVENT_HOURS.length}`, EVENT_HOURS.length - caught, falseAlarm,
    (falseAlarm / DAY).toFixed(2)]));
}
// While protection is on: the same frames do not disconnect the client, they accumulate in the rejected-frame counter.
const totalRejected = rejected.flat(2).reduce((a, b) => a + b, 0);
const rejectedHours = rejected.flat(2).filter((x) => x > 0).length;
console.log(`\nprotection on: rejected management frame ${totalRejected}, deviating hour ${rejectedHours}`);
console.log(`because this counter's baseline is 0, the threshold is 1: caught ${rejectedHours}/${EVENT_HOURS.length}, false alarm 0`);
const eventDisconnects = EVENT_HOURS.reduce((t, h) => t + disconnects[EVENT_DAY][EVENT_AP][h], 0);
const normalDisconnects = EVENT_HOURS.reduce((t, h) => t + avg(historyDays.map((x) => disconnects[x][EVENT_AP][h])), 0);
console.log(`disconnects during event hours ${eventDisconnects}, the same hours' normal ` +
  `${normalDisconnects.toFixed(1)}, factor ${(eventDisconnects / normalDisconnects).toFixed(1)}`);
```

```
day 30, access point 6, hour 4320, event hour 6
threshold         caught          missed       false alarm       false/day
2                    4/6               2               167            5.57
3                    4/6               2                20            0.67
4                    3/6               3                 2            0.07
5                    1/6               5                 0            0.00
6                    0/6               6                 0            0.00

protection on: rejected management frame 42, deviating hour 6
because this counter's baseline is 0, the threshold is 1: caught 6/6, false alarm 0
disconnects during event hours 58, the same hours' normal 23.8, factor 2.4
```

The disconnect counter is a weak indicator, and the table shows why. During event hours, the
counter rises to **two point four times** the normal; that is a deviation hard to distinguish
within a record of four thousand three hundred twenty hours. With threshold two, four of the
six event hours are caught, but thirty days produce one hundred sixty-seven false alarms: five
and a half alarms a day, none of them real. Raising the threshold to four brings false alarms
down to two, while the catch rate drops to three. At threshold six, false alarms are zero, and
so is the catch rate.

None of the five rows is a good option. The counter's baseline is not zero, it is noise that
varies with the hour of day, and the event itself is spread inside that noise. For a counter to
produce a usable indicator, a large deviation is not enough; **its baseline has to be small**.

The last three lines give protection's second gain, and it is a gain the path table does not
show. While protection is on, the same frames do not disconnect the client; they fail the
integrity check and are rejected. The rejected management frame is a separate counter, and
this counter's normal value is **zero**. Forty-two frames accumulate over six hours, the
threshold is taken as one, and all six of the six event hours are caught with zero false
alarms.

The three paths protection closes are therefore not the whole measurement. Protection does not
only make the frame verifiable; it also gives the defense **a counter whose baseline is zero**.
The byproduct of every claim that becomes verifiable is that the case where verification fails
becomes countable.

## Summary

- Of the twelve frame types, only **one** carries integrity proof at baseline: the data frame.
  Protected management frames bring this count to four.
- Three of the eight management frames are protected, five are not, and all five unprotected
  ones are **pre-connection** frames: proof cannot be produced without a shared key. The
  twin-access-point path lives in these five rows, and protection does not close it.
- Of the eight mechanisms causing a connection to drop, three close, and five stay open. Three
  of the ones that stay open cannot be closed by any verification; jamming at the physical
  layer is not a claim problem.
- In the disconnect counter, the event is 2.4 times the normal. At threshold 2, 4/6 is caught
  but at a cost of 5.57 false alarms a day; at threshold 4, the catch rate is 3/6; at
  threshold 6, it is 0/6. No threshold is a good option.
- While protection is on, the same frames fall into the rejected-management-frame counter;
  because that counter's baseline is zero, threshold 1 gives a 6/6 catch rate with 0 false
  alarms. Protection's second gain is a counter with a zero baseline.

## Next Step

This lesson separated out three paths that protection cannot close, and all three had one
thing in common: none of them is about verifying a claim. Jamming at the physical layer,
making the medium appear busy, and impersonating pre-connection frames can all be done
**anywhere the signal reaches**. That is why wireless defense's last question is not a
protocol question but a geography question: how far the signal travels. The next lesson
actually calculates a coverage map — how many square meters of area the signal spills outside
the building's boundary, and which items an unauthorized-device scan finds in that area, and
which it does not. The same lesson also gathers the course's twenty lessons into a single
table.
