Skip to content
academia.sh

Lesson 08 / 11

Threat Classification Methods

Systematic threat generation's two numbers: applying six categories to 43 graph elements produces 156 raw threats, 108 of which (69.2%) are valid for this system and 48 of which turn out invalid, elimination spends 72 minutes for no return, the total session load comes to 666 minutes, and generation limited to exposed elements alone finds 92.6% of the valid threats 63.5 minutes cheaper.

Published Updated

In this lesson

  • Runs systematic generation, which applies a fixed category list per element type, over the graph
  • Separates the number of raw threats generated from the number valid for this system
  • Calculates elimination's per-minute cost and compares three separate generation sets by minutes per unit of coverage
Contents

The previous lesson derived 58 items from 23 boundary crossings. Those 58 items were not a threat list: each one stated a control type — “a source identity check is required here,” “authorization is required here.” It did not say what could go wrong.

The step that closes this gap is looking at a graph element and asking “what could happen here.” This question depends on the person and on their attention that day; two people produce two different lists. What makes it independent of the person is applying a fixed category list to every element type. This lesson runs that systematic generation and separates two numbers: how many threats the method produces, and how many of those are actually valid for this system.

A Fixed Question List per Element Type

The widely used method is a classification method that generates threats systematically for every data flow diagram element. It has six categories, and the categories are known by what they do: identity spoofing, tampering, repudiation, information disclosure, denial of service, and privilege escalation. The method has a single rule: every element is asked all of the categories applicable to its type — none is skipped, none is added on top.

The inventory below is a model; it is the same structure as the graph from the previous lesson and carries no attack step. Element properties are not written by hand — they are derived from the graph.

TM9: every element’s five properties are calculated from the graph — exposure, carrying a sensitive class, being in the work flow, having multiple roles, and producing a transaction. TM10: generation is the product of an element’s type and the categories applicable to that type. TM11: a generated threat is invalid for this system if the element’s property does not satisfy the relevant condition. TM12: every raw threat spends a fixed elimination time, and every threat found valid spends an additional writing time.

// threat/element.mjs — inventory of graph elements (model): the same structure as the graph in
// 02, this time as an element list. There is no real system; no attack step is carried.

const ZONE = { internet: 0, field: 1, edge: 2, internal: 3, management: 4, data: 5 };
const NODE = Object.fromEntries(`
  subscriber D internet | oversight-body D internet | meter-device D field | field-team D field
  subscriber-portal S edge | collection-endpoint S edge | field-app S field
  validator S internal | billing S internal | work-order S internal | report-generator S internal | identity-service S internal
  management-console S management | measurement-store P data | billing-store P data
  identity-store P data | audit-log P data`
  .trim().split(/\s*[|\n]\s*/).map((r) => {
    const [name, type, zone] = r.trim().split(/\s+/);
    return [name, { name, type, zone, level: ZONE[zone] }];
  }));
const FLOW = `
  subscriber          subscriber-portal   identity    | subscriber-portal   identity-service   identity
  identity-service    identity-store      identity    | subscriber-portal   billing            billing
  billing             billing-store       billing     | billing             subscriber-portal  billing
  meter-device        collection-endpoint measurement | collection-endpoint validator          measurement
  validator           measurement-store   measurement | validator           billing            measurement
  field-team          field-app           identity    | field-app           work-order         order
  work-order          field-app           order       | work-order          measurement-store  measurement
  field-app           collection-endpoint measurement | management-console  validator          setting
  management-console  collection-endpoint setting     | management-console  identity-service   setting
  report-generator    measurement-store   measurement | report-generator    billing-store      billing
  oversight-body      report-generator    identity    | report-generator    oversight-body     billing
  billing             audit-log           record      | identity-service    audit-log          record
  work-order          audit-log           record      | billing             work-order         order`
  .trim().split(/\s*[|\n]\s*/).map((r) => {
    const [source, target, cls] = r.trim().split(/\s+/);
    return { source, target, class: cls, name: `${source}->${target}` };
  });

// TM9: every element's property is derived from the graph; not written by hand.
const incoming = (d) => FLOW.filter((a) => a.target === d);
const outgoing = (d) => FLOW.filter((a) => a.source === d);
const neighbors = (d) => [...incoming(d), ...outgoing(d)];
const isSensitiveClass = (s) => s === "identity" || s === "billing";
const isCriticalClass = (s) => s === "measurement" || s === "billing" || s === "order";

export const ELEMENT = [
  ...Object.values(NODE).map((d) => ({
    name: d.name, type: d.type,
    exposed: d.type === "D" ? outgoing(d.name).length > 0
      : incoming(d.name).some((a) => NODE[a.source].level < d.level),
    sensitive: neighbors(d.name).some((a) => isSensitiveClass(a.class)),
    critical: neighbors(d.name).some((a) => isCriticalClass(a.class)),
    multiRole: new Set(incoming(d.name).map((a) => NODE[a.source].zone)).size >= 2,
    transacts: d.type === "P" || (d.type === "D" ? outgoing(d.name).length > 0
      : outgoing(d.name).some((a) => NODE[a.target].type === "P")),
  })),
  ...FLOW.map((a) => ({
    name: a.name, type: "A",
    exposed: NODE[a.source].zone !== NODE[a.target].zone,
    sensitive: isSensitiveClass(a.class), critical: isCriticalClass(a.class), multiRole: false, transacts: false,
  })),
];

// TM10: systematic generation applies to every element type the categories applicable to that type.
export const CATEGORY = ["identity-spoofing", "tampering", "repudiation", "information-disclosure",
  "denial-of-service", "privilege-escalation"];
export const APPLIES = {
  D: ["identity-spoofing", "repudiation"],
  S: CATEGORY,
  P: ["tampering", "repudiation", "information-disclosure", "denial-of-service"],
  A: ["tampering", "information-disclosure", "denial-of-service"],
};

// TM11: a generated threat is invalid if the element's property does not satisfy the condition below.
export const VALID_IF = {
  "identity-spoofing": (o) => o.exposed,
  "tampering": (o) => o.type !== "A" || o.exposed,
  "repudiation": (o) => o.transacts,
  "information-disclosure": (o) => o.sensitive,
  "denial-of-service": (o) => o.critical,
  "privilege-escalation": (o) => o.multiRole,
};

// TM12: every generated threat spends a fixed elimination time, and every threat found valid
// spends an additional writing time (fictional minutes).
export const ELIMINATION_MIN = 1.5, WRITE_MIN = 4;
// threat/generation.mjs — how many threats systematic generation gives, how many are invalid
// for this system, how many minutes elimination takes.
import { ELEMENT, CATEGORY, APPLIES, VALID_IF, ELIMINATION_MIN, WRITE_MIN } from "./element.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 generate = (els) => els.flatMap((o) =>
  APPLIES[o.type].map((k) => ({ element: o, category: k, valid: VALID_IF[k](o) })));
const RAW = generate(ELEMENT), VALID = RAW.filter((t) => t.valid);
const TYPE = { D: "external entity", S: "process", P: "data store", A: "data flow" };

console.log(`${ELEMENT.length} elements, ${CATEGORY.length} categories; raw generation ${RAW.length} threats, ` +
  `valid ${VALID.length} (${((VALID.length / RAW.length) * 100).toFixed(1)}%), ` +
  `invalid ${RAW.length - VALID.length}`);

const A = [-17, 10, 12, 5, 7, 9, 9];
console.log("\n1. generation per element type");
write(A, "element type", "elements", "categories", "raw", "valid", "invalid", "valid %");
for (const t of ["D", "S", "P", "A"]) {
  const o = ELEMENT.filter((x) => x.type === t), h = generate(o), g = h.filter((x) => x.valid);
  write(A, TYPE[t], o.length, APPLIES[t].length, h.length, g.length, h.length - g.length,
    ((g.length / h.length) * 100).toFixed(1));
}

const B = [-23, 5, 7, 9, 9, 2, -32];
console.log("\n2. generation and elimination per category");
write(B, "category", "raw", "valid", "invalid", "valid %", "", "validity condition");
const CONDITION = { "identity-spoofing": "exposed", "tampering": "if a flow, crosses a boundary",
  "repudiation": "transacts", "information-disclosure": "carries a sensitive class",
  "denial-of-service": "in the work flow", "privilege-escalation": "multi-role process" };
for (const k of CATEGORY) {
  const h = RAW.filter((t) => t.category === k), g = h.filter((t) => t.valid);
  write(B, k, h.length, g.length, h.length - g.length,
    ((g.length / h.length) * 100).toFixed(1), "", CONDITION[k]);
}

const C = [-23, 10, 5, 7, 11, 11];
console.log("\n3. three generation sets: all, exposed only, processes only");
write(C, "set", "elements", "raw", "valid", "total min", "valid/min");
const SET = { "all elements": ELEMENT, "exposed elements only": ELEMENT.filter((o) => o.exposed),
  "processes only": ELEMENT.filter((o) => o.type === "S") };
for (const [name, els] of Object.entries(SET)) {
  const h = generate(els), g = h.filter((x) => x.valid);
  const min = h.length * ELIMINATION_MIN + g.length * WRITE_MIN;
  write(C, name, els.length, h.length, g.length, min.toFixed(1), (g.length / min).toFixed(3));
}
const exposed = generate(ELEMENT.filter((o) => o.exposed)).filter((x) => x.valid);
console.log(`generation limited to exposed elements finds ` +
  `${((exposed.length / VALID.length) * 100).toFixed(1)}% of the valid threats; the ` +
  `${VALID.length - exposed.length} threats it misses arise only in internal elements`);

const CODE = Object.fromEntries(CATEGORY.map((k) => [k, k.slice(0, 2).toUpperCase()]));
const D = [-30, -11, 5, 7, 2, -19];
console.log("\n4. the three elements producing the most and fewest valid threats (" +
  CATEGORY.map((k) => `${CODE[k]}=${k}`).join(", ") + ")");
write(D, "element", "type", "raw", "valid", "", "valid categories");
const ranked = ELEMENT.map((o) => ({ o, h: APPLIES[o.type],
  g: APPLIES[o.type].filter((k) => VALID_IF[k](o)) })).sort((x, y) => y.g.length - x.g.length);
for (const s of [...ranked.slice(0, 3), ...ranked.slice(-3)])
  write(D, s.o.name, TYPE[s.o.type], s.h.length, s.g.length, "",
    s.g.map((k) => CODE[k]).join(" ") || "-");
for (const t of ["S", "A"]) {
  const g = VALID.filter((x) => x.element.type === t).length;
  console.log(`${TYPE[t]} elements: ` +
    `${((ELEMENT.filter((o) => o.type === t).length / ELEMENT.length) * 100).toFixed(1)}% of the elements, ` +
    `${((g / VALID.length) * 100).toFixed(1)}% of the valid threats`);
}
console.log(`eliminating the ${RAW.length - VALID.length} invalid threats spends ` +
  `${((RAW.length - VALID.length) * ELIMINATION_MIN).toFixed(1)} minutes and returns nothing`);
43 elements, 6 categories; raw generation 156 threats, valid 108 (69.2%), invalid 48

1. generation per element type
element type       elements  categories  raw  valid  invalid  valid %
external entity           4           2    8      8        0    100.0
process                   9           6   54     40       14     74.1
data store                4           4   16     12        4     75.0
data flow                26           3   78     48       30     61.5

2. generation and elimination per category
category                 raw  valid  invalid  valid %  validity condition
identity-spoofing         13     11        2     84.6  exposed
tampering                 39     36        3     92.3  if a flow, crosses a boundary
repudiation               17     13        4     76.5  transacts
information-disclosure    39     17       22     43.6  carries a sensitive class
denial-of-service         39     24       15     61.5  in the work flow
privilege-escalation       9      7        2     77.8  multi-role process

3. three generation sets: all, exposed only, processes only
set                      elements  raw  valid  total min  valid/min
all elements                   43  156    108      666.0      0.162
exposed elements only          38  135    100      602.5      0.166
processes only                  9   54     40      241.0      0.166
generation limited to exposed elements finds 92.6% of the valid threats; the 8 threats it misses arise only in internal elements

4. the three elements producing the most and fewest valid threats (ID=identity-spoofing, TA=tampering, RE=repudiation, IN=information-disclosure, DE=denial-of-service, PR=privilege-escalation)
element                       type         raw  valid  valid categories
billing                       process        6      6  ID TA RE IN DE PR
subscriber-portal             process        6      5  ID TA IN DE PR
validator                     process        6      5  ID TA RE DE PR
identity-service->audit-log   data flow      3      1  TA
work-order->audit-log         data flow      3      1  TA
billing->work-order           data flow      3      1  DE
process elements: 20.9% of the elements, 37.0% of the valid threats
data flow elements: 60.5% of the elements, 44.4% of the valid threats
eliminating the 48 invalid threats spends 72.0 minutes and returns nothing

The numbers are in the measurement class; their inputs are TM9–TM12.

One Hundred Fifty-Six Raw Threats

When the applicable ones of the six categories are asked of forty-three elements, 156 raw threats come out. Generation is cheap and automatic: nobody thinks about “what could happen here” — the table just gets filled in. This is where the method’s entire value comes from — no element is skipped, and the list does not change from person to person.

The first table shows where the generation comes from. The twenty-six data flow elements each take three categories, producing 78 threats — half of the raw generation. The nine process elements each take six categories and produce 54 threats. The four external entity elements take only two categories: an external entity is not asked about tampering or privilege escalation, because the element sits outside the system and holds no authority inside it.

The second table breaks the raw count down category by category. Tampering, information disclosure, and denial of service are the busiest three with 39 threats each; because all three are applicable to processes, stores, and flows alike, they produce a count close to the total element count. Privilege escalation produces only nine threats, because it is asked only of processes.

One Hundred Eight Valid

The second number comes out after elimination. Of the 156 raw threats, 108 are valid for this system (69.2%), and 48 are invalid. Invalidity is not arbitrary: every category has a validity condition, and the condition looks at a property of the element that is derived from the graph.

The split varies widely from category to category. 92.3% of tampering is valid; its condition only asks flows to cross a boundary, and 23 of the 26 flows do. Information disclosure, on the other hand, is only 43.6% valid: the condition is that the element carries a sensitive class — identity or billing — and the category falls flat on the 22 elements that carry a measurement, setting, or record class. Denial of service sits in the middle (61.5%): interrupting an element that is not in the work flow does not stop the work.

The fourth table shows the same split at the element level. billing produces a valid threat in all six of the six categories — it is exposed, carries a sensitive class, sits in the work flow, has multiple roles, and writes to a store. At the other end, flows like work-order->audit-log are valid in only one of three categories. The concentration is striking: the nine process elements, only 20.9% of the elements, carry 37.0% of the valid threats; the 26 data flow elements, 60.5% of the elements, carry 44.4% of the threats.

Elimination’s Minute

Generation’s cheapness is what makes elimination expensive. Every raw threat has to be assessed — the invalid ones too. In the model, eliminating a threat takes 1.5 fictional minutes, and writing up a threat found valid takes an additional 4 minutes.

The total is 666 minutes — 11.1 hours. Of this, 234 minutes go to elimination and 432 minutes to writing. 72 minutes of elimination are spent on the 48 invalid threats and return nothing — reading and discarding rows that will never turn into a countermeasure. A threat modeling session’s duration should therefore be read together with the number of threats eliminated, not the number generated.

The third table compares three separate generation sets. All elements: 156 raw, 108 valid, 666 minutes, 0.162 valid threats per minute. Exposed elements only: 135 raw, 100 valid, 602.5 minutes, 0.166 per minute. Processes only: 54 raw, 40 valid, 241 minutes, 0.166 again.

The three sets’ per-minute efficiency is nearly identical, and this runs against expectation. Prioritizing elements that cross a boundary saves 63.5 minutes and loses 8 threats — a saving of less than a tenth of the total, because 88% of the elements are already exposed. Looking only at processes, on the other hand, saves 425 minutes but leaves out 63% of the valid threats.

The conclusion that follows is this: in this method, a shortcut does not work by raising efficiency — it works by shrinking coverage. For a team that wants to cut the time down, the only honest path is to write down which set of elements is being left out.

Summary

  • Systematic generation asks 43 graph elements the categories applicable to their type and produces 156 raw threats; generation is cheap, does not vary by person, and skips no element.
  • 108 of the raw threats (69.2%) are valid for this system, and 48 are invalid; the validity condition looks at a property of the element derived from the graph.
  • Validity ranges from 43.6% to 92.3% depending on category; information disclosure falls flat on 22 elements, while tampering almost never does.
  • The session’s duration is 666 minutes; 72 minutes go to eliminating invalid threats and return nothing. Duration is read together with the threats eliminated, not the threats generated.
  • The three generation sets’ per-minute efficiency is nearly identical (0.162–0.166); a shortcut saves from coverage, not efficiency — looking only at processes misses 63% of the valid threats.

Next Step

Systematic generation starts from the element: the same questions are asked of every box, every arrow, and every store. This approach’s strength and its limit sit in the same place — it treats the element on its own. Every one of the 108 valid threats is the intersection of one element and one category; none of them arises from two elements being used together.

But real damage usually does not arise from a single element — it arises from several elements being used one after another: information obtained in one place becomes valid somewhere else. The way to find items of this kind is to start not from the element but from the goal — what the attacker wants to obtain and by what path they get there. The next lesson runs scenario-based modeling over the same graph and measures it on the coverage axis: how many threats the scenarios find, how many of the systematic method’s finds they miss, and what the intersection of the two is.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close