Lesson 01 / 18
Open-Source Intelligence
From a publicly available item to an attack surface candidate: 29 items yielding 26 mentions and 20 unique candidates, 9 of which are evidence and 6 false positives (30%), the most accurate source producing 83% evidence and the least productive 0%, 12 of 45 validation steps returning zero evidence, the evidence count flattening after four sources while accuracy drops from 69% to 45%, and only 9 of a 13-item inventory being visible from outside.
Published Updated
In this lesson
- Counts the attack surface candidates extracted from publicly available sources and separates them into evidence, lead, and false positive
- Measures each source type's validation-step cost and the number of records it leaves on the target side
- Compares the return of adding a source against its effect on accuracy
Contents
The previous course counted, on paper, which link could be broken, and closed with a question: only someone authorized, actually trying, can show whether a defense truly holds. This course is that attempt itself. Its first step is knowing what to look at — and that list is not handed over by the organization itself; it is drawn out of items that are already sitting out in the open.
Open-source intelligence is the extraction, from publicly available items, of assets that may belong to the target. The critical word here is may. This course’s unit of measure is not the number of findings, it is the number of evidenced findings, and the distinction starts at the very first step: a name appearing in a list does not show that the name belongs to the organization.
Candidate, Evidence, and False Positive
This assessment is authorized: the scope owner has granted written authorization and defined which assets may be tested. Performing the same steps outside written authorization and a defined scope is both illegal and unprofessional.
RE1: each source type carries four numbers — how many items it takes, how many records it leaves on the target side, how many alerts it triggers, and how many validation steps it requires per candidate. RE2: a candidate appearing in a source is called a mention; the same candidate can appear in multiple sources, so the mention count is larger than the candidate count. RE3: the inventory the scope owner has approved is the referee of this measurement; a candidate in the inventory counts as evidence, one not in the inventory but seen to serve the organization counts as a lead, and one that does not overlap with the inventory at all counts as a false positive.
// recon/source.mjs — a fictional measurement network's set of publicly available items (a // model): no real domain name, no real public address, no real organization. No randomness. // RE1: a source type carries these four numbers — items taken, records it leaves on the // target, alerts it triggers, and validation steps required per candidate. A source sitting // on a third party produces no record on the target; an item taken from the organization's // own endpoint leaves an access record on every fetch. export const SOURCE = ` certificate-record 4 0 0 1 document-metadata 3 0 0 2 job-posting 2 0 0 2 public-code-repo 3 0 0 2 archived-copy 5 0 0 3 corporate-website 12 12 0 1` .trim().split("\n").map((r) => { const [type, items, record, alert, step] = r.trim().split(/\s+/); return { type, items: +items, record: +record, alert: +alert, step: +step }; }); // RE2: each row is one candidate appearing in one source; the same candidate can appear in // multiple sources. export const MENTION = ` certificate-record subscriber-portal certificate-record field-app certificate-record test-portal certificate-record shared-interface certificate-record meter-update-endpoint certificate-record old-report-endpoint document-metadata report-endpoint document-metadata shared-interface document-metadata branch-vpn-endpoint document-metadata old-report-endpoint job-posting work-order-endpoint job-posting branch-vpn-endpoint job-posting training-environment public-code-repo collector-gateway public-code-repo cloud-storage-bucket public-code-repo test-portal public-code-repo spare-domain archived-copy old-subscriber-portal archived-copy old-field-app archived-copy support-portal archived-copy dealer-portal corporate-website subscriber-portal corporate-website report-endpoint corporate-website marketing-page corporate-website identity-provider-endpoint corporate-website payment-provider-endpoint` .trim().split("\n").map((r) => { const [source, candidate] = r.trim().split(/\s+/); return { source, candidate }; }); // RE3: the fictional inventory is, in this assessment, the real asset list the scope owner // approved, and it is the referee of the measurement. A candidate not in the inventory is a // false positive. export const INVENTORY = [ "subscriber-portal", "field-app", "branch-vpn-endpoint", "collector-gateway", "report-endpoint", "test-portal", "shared-interface", "work-order-endpoint", "meter-update-endpoint", "internal-network-management-endpoint", "backup-endpoint", "measurement-store", "identity-store", ]; // Items whose ownership cannot be proven from outside, sitting on a third party but serving // the organization. export const LEAD = ["old-subscriber-portal", "marketing-page", "cloud-storage-bucket", "identity-provider-endpoint", "training-environment"]; export const CANDIDATE = [...new Set(MENTION.map((a) => a.candidate))]; export const classify = (a) => INVENTORY.includes(a) ? "evidence" : LEAD.includes(a) ? "lead" : "false-positive";
// recon/candidate.mjs — from a publicly available item to an attack surface candidate: how // many candidates come out, how many are evidence. import { SOURCE, MENTION, CANDIDATE, INVENTORY, classify } from "./source.mjs"; const write = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const count = (l, s) => l.filter((a) => classify(a) === s).length; const ITEMS = SOURCE.reduce((t, k) => t + k.items, 0); console.log(`${SOURCE.length} source types, ${ITEMS} publicly available items, ${MENTION.length} mentions, ` + `${CANDIDATE.length} unique candidates; inventory ${INVENTORY.length} items`); const A = [-19, 7, 6, 7, 7, 8, 9]; console.log("\n1. candidates and accuracy per source type"); write(A, "source type", "items", "ment", "evid", "leads", "falseP", "evid %"); for (const k of SOURCE) { const l = MENTION.filter((a) => a.source === k.type).map((a) => a.candidate); write(A, k.type, k.items, l.length, count(l, "evidence"), count(l, "lead"), count(l, "false-positive"), ((count(l, "evidence") / l.length) * 100).toFixed(0)); } write(A, "TOTAL (unique)", ITEMS, CANDIDATE.length, count(CANDIDATE, "evidence"), count(CANDIDATE, "lead"), count(CANDIDATE, "false-positive"), ((count(CANDIDATE, "evidence") / CANDIDATE.length) * 100).toFixed(0)); const B = [-19, 9, 7, 8, 12, 12]; console.log("\n2. the cost of validation and the trace left on the defense side"); write(B, "source type", "records", "alerts", "steps", "steps/evid", "rec/evid"); let stepTotal = 0; for (const k of SOURCE) { const l = MENTION.filter((a) => a.source === k.type).map((a) => a.candidate); const kn = count(l, "evidence"), st = l.length * k.step; stepTotal += st; write(B, k.type, k.record, k.alert, st, kn ? (st / kn).toFixed(2) : "-", kn ? (k.record / kn).toFixed(2) : "-"); } const TRACE = SOURCE.reduce((t, k) => t + k.record, 0); write(B, "TOTAL", TRACE, SOURCE.reduce((t, k) => t + k.alert, 0), stepTotal, (stepTotal / count(CANDIDATE, "evidence")).toFixed(2), (TRACE / count(CANDIDATE, "evidence")).toFixed(2)); const C = [-19, 10, 12, 13]; console.log("\n3. if one source type is dropped"); write(C, "dropped source", "evid left", "missed", "missed %"); const EVID = CANDIDATE.filter((a) => classify(a) === "evidence"); for (const k of SOURCE) { const remaining = [...new Set(MENTION.filter((a) => a.source !== k.type).map((a) => a.candidate))] .filter((a) => classify(a) === "evidence"); write(C, k.type, remaining.length, EVID.length - remaining.length, (((EVID.length - remaining.length) / EVID.length) * 100).toFixed(1)); } const D = [-19, 7, 7, 9, 9, 9]; console.log("\n4. as sources are added (from most to least accurate)"); write(D, "added source", "cand", "evid", "falseP", "evid %", "trace"); const sorted = [...SOURCE].sort((x, y) => { const o = (k) => { const l = MENTION.filter((a) => a.source === k.type).map((a) => a.candidate); return count(l, "evidence") / l.length; }; return o(y) - o(x); }); const accumulated = []; let traceTotal = 0; for (const k of sorted) { accumulated.push(k.type); traceTotal += k.record; const l = [...new Set(MENTION.filter((a) => accumulated.includes(a.source)).map((a) => a.candidate))]; write(D, k.type, l.length, count(l, "evidence"), count(l, "false-positive"), ((count(l, "evidence") / l.length) * 100).toFixed(0), traceTotal); } const visible = INVENTORY.filter((e) => MENTION.some((a) => a.candidate === e)).length; console.log(`\nexternally invisible inventory items: ` + INVENTORY.filter((e) => !MENTION.some((a) => a.candidate === e)).join(", ")); console.log(`${visible}/${INVENTORY.length} inventory items are visible ` + `(${((visible / INVENTORY.length) * 100).toFixed(1)}%); ` + `${count(CANDIDATE, "false-positive")}/${CANDIDATE.length} candidates are false positives ` + `(${((count(CANDIDATE, "false-positive") / CANDIDATE.length) * 100).toFixed(1)}%)`);
6 source types, 29 publicly available items, 26 mentions, 20 unique candidates; inventory 13 items 1. candidates and accuracy per source type source type items ment evid leads falseP evid % certificate-record 4 6 5 0 1 83 document-metadata 3 4 3 0 1 75 job-posting 2 3 2 1 0 67 public-code-repo 3 4 2 1 1 50 archived-copy 5 4 0 1 3 0 corporate-website 12 5 2 2 1 40 TOTAL (unique) 29 20 9 5 6 45 2. the cost of validation and the trace left on the defense side source type records alerts steps steps/evid rec/evid certificate-record 0 0 6 1.20 0.00 document-metadata 0 0 8 2.67 0.00 job-posting 0 0 6 3.00 0.00 public-code-repo 0 0 8 4.00 0.00 archived-copy 0 0 12 - - corporate-website 12 0 5 2.50 6.00 TOTAL 12 0 45 5.00 1.33 3. if one source type is dropped dropped source evid left missed missed % certificate-record 7 2 22.2 document-metadata 9 0 0.0 job-posting 8 1 11.1 public-code-repo 8 1 11.1 archived-copy 9 0 0.0 corporate-website 9 0 0.0 4. as sources are added (from most to least accurate) added source cand evid falseP evid % trace certificate-record 6 5 1 83 0 document-metadata 8 7 1 88 0 job-posting 10 8 1 80 0 public-code-repo 13 9 2 69 0 corporate-website 16 9 3 56 12 archived-copy 20 9 6 45 12 externally invisible inventory items: internal-network-management-endpoint, backup-endpoint, measurement-store, identity-store 9/13 inventory items are visible (69.2%); 6/20 candidates are false positives (30.0%)
The numbers belong to the measurement class; their inputs are RE1–RE3.
Twenty Candidates, Nine Evidence
Twenty-nine publicly available items produced twenty-six mentions; once mentions were deduplicated, 20 candidates remained. Of these twenty candidates, 9 are evidence, 5 are leads, and 6 are false positives. 30% of the raw list does not belong to the organization, and if it went into a report without validation, it would produce six items with no one on the other end to fix them.
The first table’s real information is the accuracy gap between source types. Certificate record produces evidence in five of six mentions — 83%. Archived copy produces four mentions and none of them is evidence; three are outright false positives. Both fall under the heading “publicly available source,” and both enter the candidate list the same way, but one is nearly pure evidence and the other is nearly pure noise.
The difference comes from the source’s nature. A certificate record shows that a certificate
was issued for a name, and the party who can request that is limited; an archived copy shows only
that something once appeared on a page, and something that was true in the past does not have to
be true today. The old-field-app item that came out of the archive once belonged to the
organization, was handed off, and is now the responsibility of another party. The
support-portal and dealer-portal items sitting in the same list, meanwhile, entered it only
because of name similarity.
The Step That Leaves a Trace and the One That Does not
The second table opens the column that will carry through this course: how many records a step produces on the defense side. Five of the six source types leave zero records on the target, because the item sits in a third party’s hands; there is no trace of these steps in the target’s log at all. Only the twelve items taken from the organization’s own endpoint produce twelve access records.
That these twelve records produce 0 alerts is the table’s second finding. Fetching a portal’s twelve pages in sequence is indistinguishable from what a normal visitor would do; on the defense side, these rows sit inside the noise. From the blue team’s perspective, the conclusion is this: most of the reconnaissance phase happens where the defense cannot see it, and the visible part stays below the threshold. Detecting this phase is not a matter of alerts, but a matter of retrospective correlation.
The same table’s step column gives the cost. Forty-five validation steps were spent, and nine
pieces of evidence came out of them: 5.00 steps per evidence item. The distribution is not
even. Certificate record spends 1.20 steps per evidence item; public code repo spends 4.00 steps;
archived copy spends 12 steps and produces no evidence at all. A reconnaissance effort that
orders sources by habit rather than accuracy sinks a quarter of its budget into a source with zero
return.
Two Sides of Adding a Source
The third table measures each source’s indispensability. When three of the six sources are dropped, no evidence is lost at all: document metadata, archived copy, and the organization’s own website share their evidence with other sources. Dropping certificate record loses 2 pieces of evidence (22.2%), and job posting and public code repo each take away one piece of evidence. So reaching all nine pieces of evidence does not require six sources — but the fact that three of them are individually dispensable does not mean the three of them can be dropped together; the next table shows that four are enough.
The fourth table shows this cumulatively, and it is the lesson’s sharpest finding. When sources are added one by one in order of accuracy, the evidence count moves 5 → 7 → 8 → 9 and stops growing entirely after the fourth source. The candidate count, by contrast, climbs from 13 to 16 to 20, and false positives rise from 2 to 6. Accuracy drops from 69% to 45% over those same two steps.
The last two sources do this: they add seven new candidates, bring zero new evidence, produce four new false positives, and leave 12 records on the defense side. The reconnaissance-phase assumption that “more sources means better coverage” breaks down in this table. Adding a source stops increasing coverage past a certain point; what it increases is the elimination burden.
The Lead Class Stops at the Scope Boundary
Five of the twenty candidates are neither evidence nor false positives. The old subscriber portal, marketing page, cloud storage bucket, identity provider endpoint, and training environment serve the organization but do not sit in the organization’s inventory; all of them run on third-party infrastructure. These five items are a quarter of the candidate list, and under a binary elimination rule — “evidence if it is in the inventory, noise if it is not” — they fall on the wrong side.
The reason this class is kept separate is not measurement, it is authorization. An item may well serve the organization, but acting on it means touching infrastructure that belongs to the third party who owns that item, and the written authorization granted does not cover that party. Each of the five lead items is an item that touches the scope boundary, and none of them can be tested without the scope owner’s approval. That is also where it differs from a false positive: a false positive drops out of the list, while a lead stays on the list and waits for a decision.
Numerically, it comes to this: nine pieces of evidence can be acted on directly, six false positives get eliminated, and five items move to a scope conversation. Reconnaissance’s output is not a single list, it is three separate lists; a report that collapses the three into one either works on an item it should not, or drops an item it should have worked on.
Four Items Invisible from Outside
The last line gives the measurement’s upper bound. There are thirteen items in the inventory, and open-source intelligence sees nine of them — 69.2%. The internal network management endpoint, backup endpoint, measurement store, and identity store do not appear in any publicly available item.
These four items say two things by their absence. First, open-source intelligence’s coverage is limited to the organization’s outward-facing side; assets that sit internally and leave no external signature do not enter the list at this stage. Second, the list produced at this stage is not an inventory, it is a projection of the inventory as seen from outside. In an assessment that cannot be compared against the inventory the scope owner provides, this 69.2% is never known at all; the only thing that is known is twenty candidates, and that nine of those twenty are evidence can only be said while a referee exists.
Summary
- This course’s unit of measure is not the candidate, it is the evidence: 29 publicly available items produced 26 mentions and 20 unique candidates; 9 are evidence, 5 are leads, 6 are false positives (30.0%).
- The accuracy gap between source types is large: certificate record produces 83% evidence while archived copy produces 0%, and three of its four mentions are false positives.
- Five of the six source types leave zero records on the target; the 12 items taken from the organization’s own endpoint produce 12 access records and 0 alerts — this phase stays below the threshold.
- The validation cost is 5.00 steps per evidence item and is not evenly distributed: certificate record spends 1.20 steps, archived copy spends 12 steps and produces no evidence at all.
- Evidence count flattens at 9 after four sources; the last two sources add 7 candidates, 0 evidence, 4 false positives, and 12 records, dropping accuracy from 69% to 45%.
- Only 9 of the inventory’s 13 items are visible from outside (69.2%); four items do not appear in any publicly available source.
Next Step
What separated nine of the twenty candidates as evidence and six as false positives in this lesson was a referee that came from outside: the inventory the scope owner approved. In a real assessment, that inventory is the assessment’s output, not its input. So the question is this: without a referee, how is it shown that a candidate belongs to the organization?
For some of them, it can be shown. Domain name and registration systems carry verifiable traces of who controls a name; some records can only be written by the name’s owner, while others point to a third party hosting the name and prove nothing about ownership. The next lesson treats records as inventory evidence: the same twenty candidates are run through registration lookup, and it is counted how many have ownership that can be proven, how many stay at the lead level only, and how many records this lookup leaves on the target side.
To keep your progress and take notes, Log in
My notes
Log in to take notes.