Skip to content
academia.sh

Lesson 04 / 18

Port Scanning

Comparing scan types on the trace axis: in a 96-port space, 23 open ports being definitively distinguished by type between 78.3% and 0%, the one-way probe producing 21 ambiguous candidates and 3 false positives, the trace per piece of evidence ranging from 3.13 to 8.00 records, the same type slowed down producing the same 18 pieces of evidence with the same 72 records but 0 alerts instead of 2, and 91.3% coverage requiring a combination of two types.

Published Updated

In this lesson

  • Compares scan types by the number of ports definitively distinguished, left ambiguous, and missed
  • Calculates the network record, application record, and alert count each type leaves on the defense side
  • Measures the balance between speed and trace, and how combining types contributes to coverage
Contents

At the end of three lessons the externally visible name list is ready, but a name is not an asset. A record resolves to a target; that target might accept no connections at all, or it might offer three services at once. The two look identical at the record level. This lesson covers the step that tests the difference, and the point where it departs from the previous three is this: requests go to the target directly and in large numbers.

This changes the weight of the measurement too. The trace accumulated so far was 136 records and 5 alerts. In this lesson a single scan type on its own can exceed half that number. The comparison is made on this axis: how many open ports each type definitively distinguishes, how many it leaves ambiguous, how many it misses, and how many records and alerts it produces on the defense side. This step can only be done against endpoints covered by written authorization and the scope definition; even a single request sent to an out-of-scope endpoint invalidates the authorization.

What Defines a Type Is What It Distinguishes

Scan types are referred to here not as a tool, a command, or an option, but as discriminating power. What defines a type is which port state it definitively distinguishes and which it leaves ambiguous.

RE10: the scan space is 9 connection-oriented and 3 connectionless ports per endpoint; the common port list is 4 ports per endpoint. A port is in the open, filtered, or closed state. RE11: a type is defined by which state of its space it definitively distinguishes and which it leaves ambiguous; among the ports left ambiguous, the ones that are not actually open are false positives. RE12: the trace is three items — every attempt is a network record, every open port whose connection completes is an application record, and an alert follows from the speed threshold and from whether an application record exists.

// recon/port.mjs — a fictional eight-endpoint port state and five scan types (a model): no real
// host, no real address, and no real service. No tool name, command, or flag sequence either:
// scan types are defined only by "which state it definitively distinguishes, which it leaves
// ambiguous."

// RE10: the scan space is 9 connection-oriented (conn) and 3 connectionless (disc) ports per endpoint; the
// common port list is 4 ports per endpoint. Only the non-closed ports are listed below, the rest
// are closed. "response" says whether the connectionless port answered the probe.
export const ENDPOINT = ["subscriber", "field", "vpn", "collector", "report", "shared", "monitoring", "log"];
export const SPACE = { conn: ENDPOINT.length * 9, disc: ENDPOINT.length * 3, common: ENDPOINT.length * 4 };

export const PORT = `
  subscriber   n1 conn open      1 -
  subscriber   n2 conn open      1 -
  subscriber   n3 conn open      0 -
  subscriber   n4 conn filtered  1 -
  field        n1 conn open      1 -
  field        n5 conn open      0 -
  field        n6 conn open      1 -
  field        b1 disc open      1 yes
  vpn          n1 conn filtered  1 -
  vpn          n6 conn open      1 -
  vpn          b2 disc open      0 no
  collector    n6 conn open      1 -
  collector    n7 conn open      0 -
  collector    n8 conn open      0 -
  collector    b1 disc open      1 yes
  report       n1 conn open      1 -
  report       n6 conn open      1 -
  report       n9 conn filtered  0 -
  shared       n1 conn open      1 -
  shared       n3 conn open      0 -
  monitoring   n1 conn open      1 -
  monitoring   n7 conn open      0 -
  monitoring   b1 disc open      1 yes
  log          n1 conn open      1 -
  log          n5 conn open      0 -
  log          b3 disc open      1 no`
  .trim().split("\n").map((r) => {
    const [endpoint, id, proto, state, common, response] = r.trim().split(/\s+/);
    return { endpoint, id, proto, state, common: +common, response };
  });

export const OPEN = PORT.filter((n) => n.state === "open");
const isOpen = (n) => n.state === "open";
const isResponsive = (n) => n.state === "open" && n.response === "yes";

// RE11: a scan type is defined by which state of its space it definitively distinguishes and
// which it leaves ambiguous. "unit" is a fictional time unit for one attempt, "app" is whether
// the connection completes and leaves a record at the application layer.
export const TYPE = [
  { name: "full connection", space: "conn", unit: 3, app: 1, confirmed: isOpen, ambiguous: () => false },
  { name: "half connection", space: "conn", unit: 1, app: 0, confirmed: isOpen, ambiguous: () => false },
  { name: "one-way probe", space: "conn", unit: 1, app: 0, confirmed: () => false,
    ambiguous: (n) => n.state !== "closed" },
  { name: "connectionless protocol", space: "disc", unit: 5, app: 0, confirmed: isResponsive,
    ambiguous: (n) => isOpen(n) && !isResponsive(n) },
  { name: "limited list", space: "common", unit: 3, app: 1, confirmed: isOpen,
    ambiguous: () => false },
  { name: "half connection (slow)", space: "conn", unit: 10, app: 0, confirmed: isOpen,
    ambiguous: () => false },
];

export const inScope = (t, n) =>
  t.space === "common" ? n.common === 1 : n.proto === t.space;

// RE12: the trace is three items — every attempt is a network record, every open port whose
// connection completes is an application record, and an alert follows from the speed threshold
// and from whether an application record exists.
export const measure = (t) => {
  const seen = PORT.filter((n) => inScope(t, n));
  const confirmed = seen.filter((n) => t.confirmed(n));
  const ambiguous = seen.filter((n) => t.ambiguous(n));
  const attempts = SPACE[t.space];
  const duration = attempts * t.unit;
  const rate = attempts / duration;
  const netRecords = attempts;
  const appRecords = t.app * confirmed.length;
  const alerts = (rate > 0.5 ? 2 : rate > 0.25 ? 1 : 0) + (appRecords > 0 ? 1 : 0);
  return { t, attempts, confirmed, ambiguous, duration, rate, netRecords, appRecords,
    records: netRecords + appRecords, alerts,
    falseP: ambiguous.filter((n) => n.state !== "open").length,
    missed: OPEN.length - confirmed.length - ambiguous.filter(isOpen).length };
};
// recon/scan.mjs — scan types on the trace axis: how many candidates, how many missed, how many
// records, how many alerts.
import { PORT, OPEN, TYPE, SPACE, measure } from "./port.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 M = TYPE.map(measure);

console.log(`${SPACE.conn} connection-oriented + ${SPACE.disc} connectionless port space, ` +
  `${PORT.length} not-closed ports: ${OPEN.length} open, ` +
  `${PORT.filter((n) => n.state === "filtered").length} filtered`);

const A = [-23, 8, 8, 10, 9, 11, 10];
console.log("\n1. found and missed per scan type");
write(A, "scan type", "attempts", "conf", "ambiguous", "falseP", "missed", "cover %");
for (const o of M)
  write(A, o.t.name, o.attempts, o.confirmed.length, o.ambiguous.length, o.falseP, o.missed,
    ((o.confirmed.length / OPEN.length) * 100).toFixed(1));

const B = [-23, 9, 10, 8, 9, 8, 7];
console.log("\n2. the trace left on the defense side");
write(B, "scan type", "net rec", "app rec", "records", "duration", "rate", "alerts");
for (const o of M)
  write(B, o.t.name, o.netRecords, o.appRecords, o.records, o.duration, o.rate.toFixed(2), o.alerts);

const C = [-23, 12, 12, 12];
console.log("\n3. cost per piece of evidence");
write(C, "scan type", "records/conf", "dur/conf", "alerts/conf");
for (const o of M) {
  const k = o.confirmed.length;
  write(C, o.t.name, k ? (o.records / k).toFixed(2) : "-", k ? (o.duration / k).toFixed(2) : "-",
    k ? (o.alerts / k).toFixed(3) : "-");
}

const D = [-50, 8, 10, 8, 9, 8];
console.log("\n4. combining two scan types");
write(D, "combination", "conf", "cover %", "records", "duration", "alerts");
const pair = [[1, 3], [4, 3], [5, 3], [0, 3]];
for (const [i, j] of pair) {
  const k = new Set([...M[i].confirmed, ...M[j].confirmed]).size;
  write(D, `${M[i].t.name} + ${M[j].t.name}`, k, ((k / OPEN.length) * 100).toFixed(1),
    M[i].records + M[j].records, M[i].duration + M[j].duration, M[i].alerts + M[j].alerts);
}
const slow = M[5], fast = M[1];
console.log(`same type slowed down: confirmed ${fast.confirmed.length} -> ${slow.confirmed.length}, ` +
  `records ${fast.records} -> ${slow.records}, duration ${fast.duration} -> ${slow.duration}, ` +
  `alerts ${fast.alerts} -> ${slow.alerts}`);
72 connection-oriented + 24 connectionless port space, 26 not-closed ports: 23 open, 3 filtered

1. found and missed per scan type
scan type              attempts    conf ambiguous   falseP     missed   cover %
full connection              72      18         0        0          5      78.3
half connection              72      18         0        0          5      78.3
one-way probe                72       0        21        3          5       0.0
connectionless protocol      24       3         2        0         18      13.0
limited list                 32      15         0        0          8      65.2
half connection (slow)       72      18         0        0          5      78.3

2. the trace left on the defense side
scan type                net rec   app rec records duration    rate alerts
full connection               72        18      90      216    0.33      2
half connection               72         0      72       72    1.00      2
one-way probe                 72         0      72       72    1.00      2
connectionless protocol       24         0      24      120    0.20      0
limited list                  32        15      47       96    0.33      2
half connection (slow)        72         0      72      720    0.10      0

3. cost per piece of evidence
scan type              records/conf    dur/conf alerts/conf
full connection                5.00       12.00       0.111
half connection                4.00        4.00       0.111
one-way probe                     -           -           -
connectionless protocol        8.00       40.00       0.000
limited list                   3.13        6.40       0.133
half connection (slow)         4.00       40.00       0.000

4. combining two scan types
combination                                           conf   cover % records duration  alerts
half connection + connectionless protocol               21      91.3      96      192       2
limited list + connectionless protocol                  15      65.2      71      216       2
half connection (slow) + connectionless protocol        21      91.3      96      840       0
full connection + connectionless protocol               21      91.3     114      336       2
same type slowed down: confirmed 18 -> 18, records 72 -> 72, duration 72 -> 720, alerts 2 -> 0

The numbers belong to the measurement class; their inputs are RE10–RE12.

Zero Confirmed, Twenty-One Ambiguous

The most striking row of the first table is the third one. The one-way probe makes 72 attempts and cannot definitively distinguish a single port: its coverage is 0%. Instead it produces 21 ambiguous candidates. The reason lies in the type’s definition — in this type, no response arriving is the same outcome for both an open and a filtered port; the two cannot be told apart. 3 of the 21 candidates produced are false positives; the three filtered ports sit in the same list as the open ones.

This row is the plainest example of this course’s unit of measure. The sentence “the scan found twenty-one open ports” is false for this type: twenty-one candidates were found, zero pieces of evidence. Closing that gap requires each candidate to be confirmed with a separate step, and the trace of those steps does not appear in this table at all.

At the other end, the full and half connection types definitively distinguish 18 ports, leave nothing ambiguous, and their coverage is 78.3%. The five ports they miss are on the connectionless protocol: a type that scans the connection-oriented space cannot see a single port in the connectionless space. The limited list finds 15 ports with 32 attempts (65.2%) and misses 8 — the ones it misses are the ports outside the common list, and those are exactly the ones most interesting in an assessment.

Same Evidence, Different Trace

The second table opens up the trace column. Full and half connection produce the same 18 pieces of evidence, but full connection leaves 90 records and half connection leaves 72 records. The difference is in the application record: full connection completes the connection at every open port, and the application at that port writes a line. Half connection does not complete the connection; 72 lines remain at the network layer, none at the application layer.

This distinction also determines the value of the two records from the blue team’s side. A network record only says that an attempt was made; an application record says who connected to which port on which endpoint, often together with an identity or a request line. A type chosen on the scanning side for “leaving less trace” means, on the defense side, trace that carries less information.

The third table gives cost per piece of evidence. The limited list is the cheapest type at 3.13 records per piece of evidence, but it never sees eight ports at all. The connectionless-protocol scan spends 8.00 records and 40.00 duration units per piece of evidence; it is the most expensive in the records column, ties the slow half connection in the duration column, and gives only 3 pieces of evidence in return. In this table, the cheapness ranking and the coverage ranking are each other’s reverse.

The Balance Between Speed and Trace

The last line of the second table is this lesson’s main finding. When the half connection type is slowed down tenfold, the confirmed count stays at 18, records stay at 72, and alerts drop from 2 to 0. Duration rises from 72 to 720.

In other words, the relationship between speed and trace is not direct. Slowing down does not reduce the number of records the defense sees at all; what it reduces is those records crossing a threshold and turning into an alert. The same 72 lines get written to the log in both runs; in one it triggers the rate rule because it fits inside a minute, in the other it does not because it is spread over a window ten times wider.

The defense side’s takeaway reads directly from this: a detection based on a speed threshold will never see a slow scan. The way to see a slow scan is to accumulate attempts from the same source over a wide window and tie the threshold not to speed but to spread — how many separate endpoints and how many separate ports were touched. In this run, both types touch 8 endpoints and 72 ports; the spread is the same in both runs.

The alerts/conf column of the third table writes the same thing from the reverse direction. The slow half connection and the connectionless-protocol scan produce 0.000 alerts; full and half connection produce 0.111, the limited list 0.133. The type producing the most alerts is not the type making the most attempts — the limited list gives the highest rate with 32 attempts, while the connectionless-protocol scan produces no alerts at all with 24 attempts; because an alert depends not on the number of attempts but on the attempt’s density and layer.

Twenty-One of Twenty-Three Ports

The fourth table shows that a single type is not enough. The highest single-type coverage of the twenty-three open ports is 78.3%; climbing to 91.3% requires combining two types that scan the connection-oriented and connectionless spaces separately. The remaining two ports are not found by any combination: the two open ports in the connectionless space that do not respond to the probe cannot be tied to a definitive result by any of these types.

The combinations’ cost differs. Half connection with connectionless protocol spends 96 records, 192 duration, and 2 alerts for 21 pieces of evidence. When the same evidence is obtained with full connection, records rise to 114 and duration to 336; alerts stay at 2. When obtained with the slow half connection, records stay at 96, duration is 840, and alerts are 0. The three options give the same 21 pieces of evidence; the difference between them is only in cost and trace.

Summary

  • What determines a scan type is the state it distinguishes: for 23 open ports in a 96-port space, the coverages are 78.3%, 78.3%, 0%, 13.0%, and 65.2%.
  • The one-way probe cannot definitively distinguish a single port; it produces 21 ambiguous candidates, 3 of them false positives — this type produces candidates, not evidence.
  • The same 18 pieces of evidence leave 90 records with full connection and 72 with half connection; the difference is at the application layer, and an application record carries more information on the defense side.
  • Cost per piece of evidence ranges from 3.13 to 8.00 records; the cheapest type never sees 8 ports, the most expensive gives only 3 pieces of evidence.
  • When the same type is slowed down tenfold, evidence stays at 18, records stay at 72, and alerts drop from 2 to 0: slowing down does not erase the trace, it keeps the trace from crossing the threshold.
  • 21 of the twenty-three open ports (91.3%) can only be reached by combining two types; two ports cannot be tied to a definitive result in any combination.

Next Step

This lesson definitively distinguished that a port is open: 21 ports were marked at the evidence level. Yet a port being open does not say what is running there. Each of the twenty-one open ports is, for now, only a number; what software sits behind it, which version it is running, and whether that version is associated with a known vulnerability have not been asked at all.

The step that asks this question is fingerprinting, and it brings a new measure: precision. The signatures collected from an endpoint point to a version set; the set sometimes has a single member, sometimes contains dozens of versions, and sometimes is the wrong set because of a banner the target deliberately leaves misleading. The next lesson counts how many candidates can be narrowed down to a single version, how much extra probing raises precision and how much it grows the trace, and how many false positives incorrect version information carries into the finding list. It is the topic’s closing lesson.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close