Lesson 21 / 22
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.
Published Updated
In this lesson
- 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
Contents
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
- Page reached. The user opens a page that carries content under the attacker’s control. Precondition: there is a path to that page.
- Third-party content. The page can load content from outside its own origin. Precondition: there is no source restriction.
- Unpatched component. There is an unfixed item in the loading component. Precondition: the browser or plugin is not up to date.
- Unapproved write. An item can be written to disk without user approval. Precondition: the download completes on its own.
- Executability. The written item can be run. Precondition: there is no app allowlist.
- 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.
// 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(""));
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.