---
title: 'Drive-By Downloads'
source: 'https://academia.sh/en/courses/attack-techniques/drive-by-downloads'
course: 'Attack Techniques and the Threat Landscape'
language: en
updated: '2026-09-26T03:02:49+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Models browser-based infection as a six-condition AND chain and measures how many endpoints hold all conditions together'
    - 'Counts the remaining endpoint count taking into account which condition each defense breaks and its coverage'
    - 'Establishes the boundary of early breaking and shows the condition at which the item is written to disk, and its cost'
---

# Drive-By Downloads

Counting browser-based infection as a precondition chain: across six hundred fictional endpoints, all six of six conditions hold at once in only twenty-two of them; the item has already been written to disk at seventy-three endpoints that hold through the fourth condition; the single most effective defense on its own brings the remainder down to one; the defense that breaks two conditions at once ends up the weakest because its coverage is narrow; and two defenses reach, with a legitimate-work cost of nine, the same result that seven defenses reach with a cost of fifty-five.

In all seven classes so far, the chain always started with a request from the attacker: a field
was filled in, a link was sent, an attempt was made. In this class, the first step is thrown by
the **victim**, and what they do is ordinary — they open a page.

Drive-by download is not the name of a single step; it is the name of several independent
conditions holding at the same time. That is why this lesson counts the chain as an **AND
sequence**: if all six of the six conditions do not hold, nothing happens. The measurement runs
over a fictional fleet of endpoints; no infection step, no page, and no component name gets
written.

## Six Conditions

1. **Page reached.** The user opens a page that carries content under the attacker's control.
   Precondition: there is a path to that page.
2. **Third-party content.** The page can load content from outside its own origin.
   Precondition: there is no source restriction.
3. **Unpatched component.** There is an unfixed item in the loading component. Precondition: the
   browser or plugin is not up to date.
4. **Unapproved write.** An item can be written to disk without user approval. Precondition: the
   download completes on its own.
5. **Executability.** The written item can be run. Precondition: there is no app allowlist.
6. **Protection miss.** Endpoint protection does not stop the item. Precondition: there is no
   signature or behavior match.

An AND chain favors the defense: **dropping a single condition is enough**. The difficulty is not
in choosing the condition; it is in how much of the fleet the defense reaches. This lesson
measures those two numbers — the broken condition and the coverage — together.

The conditions do not all belong to the same side either. The first and second condition belong
to the network and the page's own configuration, the third and fifth to the endpoint's software
state, the fourth to browser behavior, the sixth to protection software. This distribution shows
why, inside an organization, this class cannot be handed to a single team's responsibility: the
six conditions are spread across four separate management domains, and none of them can see the
others' numbers.

**TA30:** the chain is an AND sequence; `rate` is the share of endpoints in the fictional fleet
where that condition holds. **TA31:** each defense breaks one or two conditions; `coverage` is
the share of endpoints it is deployed to. **TA32:** after the fourth condition, the item is
considered written to disk; the boundary of early breaking is the third condition.

```js
// db/fleet.mjs — browser-based infection's precondition chain (model). No real page, no real
// component, and no infection step at all; each endpoint is only a bit vector carrying the
// state of the conditions and the defenses.
export const ENDPOINT_COUNT = 600;

export const CONDITION = [
  { name: "page-reached",         rate: 0.60 },
  { name: "third-party-content",  rate: 0.75 },
  { name: "unpatched-component",  rate: 0.40 },
  { name: "unapproved-write",     rate: 0.60 },
  { name: "executability",        rate: 0.80 },
  { name: "protection-miss",      rate: 0.35 },
];

export const DEFENSE = [
  { name: "name-resolution-filter", breaks: [0], coverage: 0.85, legit: 5 },
  { name: "content-policy",        breaks: [1], coverage: 0.60, legit: 7 },
  { name: "automatic-update",      breaks: [2], coverage: 0.75, legit: 3 },
  { name: "download-approval",     breaks: [3], coverage: 0.80, legit: 9 },
  { name: "app-allowlist",         breaks: [4], coverage: 0.45, legit: 14 },
  { name: "endpoint-protection",   breaks: [5], coverage: 0.90, legit: 6 },
  { name: "browser-isolation",     breaks: [2, 4], coverage: 0.30, legit: 11 },
];

function generator(seed) {
  let s = seed >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296;
}

// The fleet is generated once; the seed is visible, gives the same result run to run.
export function fleet(seed = 20260803) {
  const r = generator(seed);
  const f = [];
  for (let i = 0; i < ENDPOINT_COUNT; i++) {
    const k = CONDITION.map((x) => r() < x.rate);
    const s = DEFENSE.map((x) => r() < x.coverage);
    f.push({ k, s });
  }
  return f;
}

export const write = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));
```

```js
// db/measure.mjs — how many endpoints hold all six conditions at once, and where each defense
// brings that number down to. Randomness comes from the generator, the seed is visible.
import { ENDPOINT_COUNT, CONDITION, DEFENSE, fleet, write } from "./fleet.mjs";

const F = fleet();
// At an endpoint, the nth condition holds if none of the deployed defenses break it.
const holds = (u, i, set) => u.k[i] &&
  !DEFENSE.some((s, j) => set.includes(s.name) && u.s[j] && s.breaks.includes(i));
const fullChain = (u, set) => CONDITION.every((_, i) => holds(u, i, set));
const nConditions = (u, set, n) => CONDITION.slice(0, n).every((_, i) => holds(u, i, set));
const count = (set, f) => F.filter((u) => f(u, set)).length;

console.log(`${ENDPOINT_COUNT} fictional endpoints, ${CONDITION.length} conditions, ${DEFENSE.length} defenses\n`);

const A = [-22, 13, 10, 14];
console.log("1. endpoints holding per condition");
write(A, "condition", "target rate", "holding", "up to here");
for (const [i, k] of CONDITION.entries())
  write(A, k.name, k.rate.toFixed(2), F.filter((u) => u.k[i]).length,
    count([], (u) => nConditions(u, [], i + 1)));
const baseline = count([], fullChain);
console.log(`all six of the six conditions hold at ${baseline} endpoints ` +
  `(${((baseline / ENDPOINT_COUNT) * 100).toFixed(1)}%); the item is already written to disk ` +
  `at ${count([], (u) => nConditions(u, [], 4))} endpoints that hold through the fourth condition`);

const B = [-24, 12, 12, 14, 10];
console.log("\n2. when a single defense is deployed");
write(B, "defense", "breaks", "coverage", "remaining", "legit");
for (const s of DEFENSE)
  write(B, s.name, s.breaks.map((i) => i + 1).join(","), s.coverage.toFixed(2),
    count([s.name], fullChain), s.legit);
console.log(`remaining with no defense at all: ${baseline}`);

const C = [-24, 12, 12, 18, 14];
console.log("\n3. cumulative, starting from the biggest reducer");
write(C, "added defense", "remaining", "reduction", "cumulative legit", "remaining %");
let set = [], legit = 0;
while (set.length < DEFENSE.length) {
  const candidate = DEFENSE.filter((s) => !set.includes(s.name))
    .map((s) => ({ s, remaining: count([...set, s.name], fullChain) }))
    .sort((a, b) => a.remaining - b.remaining || a.s.legit - b.s.legit)[0];
  const before = count(set, fullChain);
  set.push(candidate.s.name); legit += candidate.s.legit;
  write(C, candidate.s.name, candidate.remaining, before - candidate.remaining, legit,
    `${((candidate.remaining / baseline) * 100).toFixed(1)}%`);
}

const OUTCOME = ["no trace", "no trace", "no trace", "item written to disk",
  "item ran", "protection missed it"];
const D = [-11, 20, 27, -24];
console.log("\n4. the cost of late breaking (undefended fleet)");
write(D, "condition", "holding up to here", "dropped at this condition", "  stage outcome");
for (let n = 1; n <= CONDITION.length; n++) {
  const hold = count([], (u) => nConditions(u, [], n));
  write(D, `${n}.`, hold, count([], (u) => nConditions(u, [], n - 1)) - hold, `  ${OUTCOME[n - 1]}`);
}
console.log(`after the fourth condition the item has been written to disk: the boundary of ` +
  `early breaking is the third condition (${count([], (u) => nConditions(u, [], 3))} endpoints)`);
console.log(`remaining in full deployment: ${count(DEFENSE.map((s) => s.name), fullChain)}, ` +
  `holding through the fourth condition: ` +
  `${count(DEFENSE.map((s) => s.name), (u, q) => nConditions(u, q, 4))}`);
```

```
600 fictional endpoints, 6 conditions, 7 defenses

1. endpoints holding per condition
condition               target rate   holding    up to here
page-reached                   0.60       362           362
third-party-content            0.75       440           262
unpatched-component            0.40       241           118
unapproved-write               0.60       362            73
executability                  0.80       482            63
protection-miss                0.35       221            22
all six of the six conditions hold at 22 endpoints (3.7%); the item is already written to disk at 73 endpoints that hold through the fourth condition

2. when a single defense is deployed
defense                       breaks    coverage     remaining     legit
name-resolution-filter             1        0.85             5         5
content-policy                     2        0.60            10         7
automatic-update                   3        0.75             5         3
download-approval                  4        0.80             6         9
app-allowlist                      5        0.45            10        14
endpoint-protection                6        0.90             1         6
browser-isolation                3,5        0.30            16        11
remaining with no defense at all: 22

3. cumulative, starting from the biggest reducer
added defense              remaining   reduction  cumulative legit   remaining %
endpoint-protection                1          21                 6          4.5%
automatic-update                   0           1                 9          0.0%
name-resolution-filter             0           0                14          0.0%
content-policy                     0           0                21          0.0%
download-approval                  0           0                30          0.0%
browser-isolation                  0           0                41          0.0%
app-allowlist                      0           0                55          0.0%

4. the cost of late breaking (undefended fleet)
condition    holding up to here  dropped at this condition  stage outcome
1.                          362                        238  no trace
2.                          262                        100  no trace
3.                          118                        144  no trace
4.                           73                         45  item written to disk
5.                           63                         10  item ran
6.                           22                         41  protection missed it
after the fourth condition the item has been written to disk: the boundary of early breaking is the third condition (118 endpoints)
remaining in full deployment: 0, holding through the fourth condition: 0
```

## The Intersection of Six Conditions

The first output counts the conditions one by one and cumulatively. Looked at one by one, no
condition is rare: the narrowest condition holds at 221 of 600 endpoints, the widest at 482. The
cumulative column, though, drops fast: 362, 262, 118, 73, 63, and finally **22**.

The ratio between the cumulative column's first row and its last row is sixteenfold. Every step
of this drop is a multiplication, and none of the factors is close to zero; what makes the
result small is the **number** of factors. Halving a condition's rate halves the result too, but
removing a condition entirely zeroes the result out — in an AND chain, the difference between
these two moves is not quantitative, it is qualitative.

At **3.7%** of the fleet, all six of the six conditions hold at the same time. This number
explains why the class looks both widespread and rare at once. Every condition on its own is
ordinary; what is out of the ordinary is the intersection. In a report, the sentence "40% of
endpoints have an out-of-date component" can be true and still say nothing about the size of
this class.

The biggest drop after the first condition is at the third: from 262 to 118. The unpatched-
component condition is the item that narrows the intersection the most in the middle of the
chain; the reason is that a relatively low rate is being applied to what is still a large
remainder. **In an AND chain, the narrowest condition determines the result** — and from a
defense standpoint, this means narrowing the narrowest condition further is the most efficient
move.

## Broken Condition and Coverage

The second output deploys each defense on its own. The results range from 1 to 16, and the
ranking has nothing to do with the order of the broken condition; **it is about coverage.**

Endpoint protection brings the remainder from 22 down to **1**: its coverage is 0.90, and the
condition it breaks is already rare. The name-resolution filter brings it to 5, automatic update to 5,
download approval to 6. App allowlist stays at 10, because its coverage is 0.45.

The last row is this output's most notable result. Browser isolation **breaks two conditions at
once** — none of the other six defenses do this — and despite that, it only brings the remainder
down to 16; it is the weakest of the seven. The reason is that its coverage is 0.30. **A defense
that breaks two conditions and reaches a third of the fleet is weaker than a defense that breaks
one condition and reaches nine-tenths of it.** A layer's strength is measured not by the number
of conditions it breaks, but by the product of the broken condition and its coverage.

## Cumulative Addition

The third output adds defenses in each round starting from the biggest reducer. In the first two
steps, the remaining endpoint count goes from 22 to 1, then to **0**, and the cumulative
legitimate-work cost is **9**. The remaining five defenses drop no further endpoints at all and raise the cost
to 55.

This does not mean the other five defenses are unnecessary, and this distinction matters. The
measurement is a **snapshot**: at this fleet, with these coverage values, with these condition
rates. When the third condition's rate rises from 0.40 to 0.70 — when a new unpatched item
appears — the ranking changes, and the zero-remainder defenses start producing numbers. In an
AND chain, spare layers' function is not to bring down today's number, but to **keep the number
from spiking when a condition's rate changes**.

Still, one thing is clear: two items bring this fleet to zero with a legitimate-work cost of nine units.
The seven items' total cost is 55. If the defense budget is limited, the ranking is read off
this table, not off a layer list.

The order itself is not a coincidence either. The greedy selection takes endpoint protection in
the first round, because the condition it breaks is the fleet's narrowest, and its coverage is
among the highest. It takes automatic update in the second round; the fact that the one
remaining endpoint falls outside protection's coverage requires dropping a second condition at
that endpoint. From the third round on, there is nothing left to choose, and the ranking is made
by legitimate-work cost alone.

## The Boundary of Early Breaking

The fourth output gives the cost stage by stage. Through the first three conditions, **no trace
is left at all**: the user has opened a page, the page has loaded content, an item has been
found in the component. At the fourth condition, an item is written to disk — at **73 endpoints**
in the undefended fleet. At the fifth condition the item runs (63 endpoints), at the sixth
protection does not see it (22 endpoints).

The boundary of early breaking is in this table: if the chain stops by the third condition,
nothing is left behind. Stopping after the fourth condition means an item is present at the
endpoint and an investigation is required. **The 51 endpoints between 73 and 22 are exactly this
in-between zone**: the chain has not completed, but an item has been written to disk, and a work
item is born that needs to be found, classified, and cleaned up.

This distinction often gets lost in defense reports. The sentence "no infection occurred" looks
at the number 22; the real workload is in the number 73. A defense that breaks the first three
conditions never produces this workload at all; a defense that breaks the last two produces it,
and while doing so, looks "successful." This course's third column — the cost of late breaking —
exists exactly to write down this difference.

In full deployment, both numbers are zero: neither a disk write nor a completed chain remains.
This deployment's cost is 55 legitimate-work units, and it is also possible to reach the same result with
two items that cost 9 — the difference will show up once the condition rates change.

## Summary

- Drive-by download is an AND chain: all six of the six conditions must hold. The individual
  conditions are not rare (between 221 and 482 endpoints), the intersection is: 22 of 600
  endpoints, or 3.7%.
- The cumulative count drops 362, 262, 118, 73, 63, 22; the biggest drop is at the narrowest
  condition. In an AND chain, the narrowest condition determines the result.
- Among defenses deployed on their own, coverage determines the ranking, not the broken
  condition: the item with 0.90 coverage brings 22 down to 1; the item with 0.30 coverage that
  breaks two conditions at once leaves it at 16.
- Two defenses bring the remainder to 0 for a legitimate-work cost of 9; the seven defenses' total cost is
  55, for zero additional endpoints dropped. Spare layers' function is not today's number, but
  absorbing the spike that occurs when a condition's rate changes.
- No trace is left through the first three conditions; after the fourth condition, the item has
  been written to disk. In the undefended fleet, the item is written at 73 endpoints, the chain
  completes at 22 — the 51 in between is the zone where the chain has not completed but still
  produces a cleanup work item.

## Next Step

This lesson counted an item reaching an endpoint, but never asked what that item **is**. Most of
the time, all the defense has in hand is that item: no source, no documentation, no known
author. The next lesson measures that question — which indicators can be pulled out of a binary,
which ones can never be pulled out at all, and how many items countermeasures hide. That lesson
is also this course's closing lesson: twenty-two lessons' chain steps, breakable link, and cost
of late breaking are gathered into a single table.
