Skip to content
academia.sh

Lesson 11 / 18

Exploitation Frameworks

Measuring an exploitation framework strictly by its validation limit: of 16 candidates 9 are actually open while only 3 turn into evidence, only one of four validation options has a negative result that shows the vulnerability's absence, two of the three candidates that come back negative are actually open, three candidates get wrongly closed if negative and weak results are counted as closure grounds, and two targets are never touched at all because of scope.

Published Updated

In this lesson

  • Classifies validation options by risk level, evidence strength, and the meaning of a negative result
  • Separates what a framework proves and does not prove by counting candidate items
  • Counts how scope and hosting class narrow the validation option
Contents

Up to this point five tool classes have hit the same limit: every time, the last step that would show an item is really open was left undone. The exploitation framework is the tool class that takes that last step, and for exactly that reason it is used within the narrowest limits.

This lesson does not describe how a framework is used; no tool, module, step sequence, command, or payload is written. Three things are measured: what a validation attempt proves, what it does not prove, and under what condition it damages the target. Pointing a framework at a target without written authorization and a defined scope is unauthorized access regardless of the outcome, and it carries legal consequences.

Four Options, Two Separate Questions

There is more than one way to validate a candidate, and these are separated by two independent properties.

The first is evidence strength. Passive indicator matching — comparing configuration and version information against a known definition — does not touch the target and never produces conclusive evidence; it only strengthens a candidate. Side-effect-free validation deterministically tests whether the condition needed for the vulnerability to exist is met. State-changing and disruption-risk options produce conclusive evidence and in return leave a trace and risk on the target.

The second, and the less-discussed one, is the meaning of a negative result. When a condition test comes back negative, it is known that the condition is not met; this is proof of the vulnerability’s absence. When an attempt comes back negative, nothing is known. The attempt may have failed because of the target’s state at that moment, its load, a middle layer’s behavior, or timing. A failed validation attempt does not prove the vulnerability does not exist. Losing this distinction is the report’s most expensive mistake.

  • AT36. The option catalog, candidate list, and hosting classes are a model; there is no real target, no real vulnerability identity, and no real tool.
  • AT37. The open field is the candidate’s actual state; it is not known before validation and is written only so the model can compute the result.
  • AT38. In conditional options the result depends on the target’s environment condition at that moment; if the condition is not met, the attempt comes back negative even on an open item.
  • AT39. The hosting class narrows the option set. managed is a service operated by a third party, and no attempt is in scope there.
  • AT40. The rules of engagement set the highest risk level; the selection is the option with the highest evidence strength among those allowed, and the lowest risk in case of a tie.
  • AT41. Duration, record, and alert counts were written by hand per option.
// cerceve/limit.mjs — this is a model of an exploitation framework's VALIDATION LIMIT. No tool,
// module, step sequence, command, or payload is written; options are referred to only by their
// type, risk level, evidence strength, and THE MEANING OF A NEGATIVE RESULT.
// negative: 'absence' = a negative result shows the vulnerability's absence; 'inconclusive' = it shows nothing.
// conditional = the result depends on the target's environment condition at that moment; if the
// condition is not met, the attempt comes back negative even on an open item. A conditional
// attempt's negative result shows NOTHING.
export const OPTION = {
  'indicator-match':             { risk: 0, evidence: 'weak',       negative: 'inconclusive', conditional: false, min: 5,  record: 1, alert: 0 },
  'side-effect-free-validation': { risk: 1, evidence: 'conclusive', negative: 'absence',       conditional: false, min: 15, record: 2, alert: 1 },
  'state-changing':              { risk: 2, evidence: 'conclusive', negative: 'inconclusive',  conditional: true,  min: 30, record: 4, alert: 3 },
  'disruption-risk':             { risk: 3, evidence: 'conclusive', negative: 'inconclusive',  conditional: true,  min: 45, record: 6, alert: 5 },
};

// Hosting class: up to which risk level an operation can be performed on the target.
// managed = a service operated by a third party; no attempt is in scope there.
export const HOSTING = { own: 3, shared: 1, managed: -1 };
export const ALLOWED_RISK = 2;   // the highest risk level the rules of engagement allow

// row: candidate | is it open | passive indicator | is the environment condition met | hosting | options
export const CANDIDATE = `
  c01 open   yes no    own     indicator-match,side-effect-free-validation
  c02 open   yes yes   own     indicator-match,state-changing
  c03 open   no  yes   own     indicator-match,side-effect-free-validation
  c04 open   yes no    own     indicator-match,state-changing
  c05 open   yes yes   shared  indicator-match,state-changing
  c06 open   no  no    own     indicator-match,state-changing
  c07 open   yes yes   managed indicator-match,side-effect-free-validation
  c08 open   yes yes   own     indicator-match
  c09 closed yes yes   own     indicator-match,side-effect-free-validation
  c10 closed yes yes   own     indicator-match,state-changing
  c11 closed no  yes   own     indicator-match,side-effect-free-validation
  c12 closed yes yes   own     indicator-match
  c13 closed yes yes   shared  indicator-match,side-effect-free-validation
  c14 closed no  yes   own     indicator-match,disruption-risk
  c15 closed yes yes   managed indicator-match,state-changing
  c16 open   no  yes   own     indicator-match,disruption-risk
`.trim().split('\n').map((r) => {
  const [name, state, indicator, cond, hosting, opts] = r.trim().split(/\s+/);
  return { name, open: state === 'open', indicator: indicator === 'yes', condition: cond === 'yes',
    hosting, options: opts.split(',') };
});

export const allowed = (a, s) => OPTION[s].risk <= Math.min(ALLOWED_RISK, HOSTING[a.hosting]);

// Selected option: among those allowed, the one with the highest evidence strength, and the lowest risk in case of a tie.
export function selection(a) {
  const fit = a.options.filter((s) => allowed(a, s));
  if (fit.length === 0) return null;
  return fit.sort((x, y) => {
    const g = (s) => (OPTION[s].evidence === 'conclusive' ? 1 : 0);
    return g(y) - g(x) || OPTION[x].risk - OPTION[y].risk;
  })[0];
}

// Result: evidence | absence-proof | inconclusive-negative | strong-candidate | weak-candidate | unverifiable
export function result(a) {
  const s = selection(a);
  if (!s) return { option: '-', result: 'unverifiable', min: 0, record: 0, alert: 0 };
  const o = OPTION[s];
  let d;
  if (o.evidence === 'conclusive') {
    if (a.open && (a.condition || !o.conditional)) d = 'evidence';
    else if (a.open) d = 'inconclusive-negative';        // open, but the attempt came back negative
    else d = o.negative === 'absence' ? 'absence-proof' : 'inconclusive-negative';
  } else d = a.indicator ? 'strong-candidate' : 'weak-candidate';
  return { option: s, result: d, min: o.min, record: o.record, alert: o.alert };
}

export function total() {
  const t = { evidence: 0, 'absence-proof': 0, 'inconclusive-negative': 0, 'strong-candidate': 0,
    'weak-candidate': 0, unverifiable: 0, min: 0, record: 0, alert: 0, unprovenOpen: 0 };
  for (const a of CANDIDATE) {
    const r = result(a);
    t[r.result] += 1; t.min += r.min; t.record += r.record; t.alert += r.alert;
    if (r.result !== 'evidence' && a.open) t.unprovenOpen += 1;
  }
  return t;
}

Sixteen Candidates, Six Result Types

// cerceve/measure.mjs — sixteen candidates, what the framework proves and does not prove.
import { OPTION, HOSTING, ALLOWED_RISK, CANDIDATE, selection, result, total } from './limit.mjs';

const y = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join('').trimEnd());

console.log(`${CANDIDATE.length} candidates (${CANDIDATE.filter((a) => a.open).length} actually open), ` +
  `${Object.keys(OPTION).length} validation options, highest allowed risk ${ALLOWED_RISK}`);

const A = [-28, 6, 6, 7, 7, 13, 14];
console.log('\n1. option types');
y(A, 'option', 'risk', 'min', 'record', 'alert', 'conditional', 'negative');
for (const [s, o] of Object.entries(OPTION))
  y(A, s, o.risk, o.min, o.record, o.alert, o.conditional ? 'yes' : 'no', o.negative);

const B = [-10, 8, 11, 11, 10, 30, 23];
console.log('\n2. option selected and result per candidate');
y(B, 'candidate', 'actual', 'indicator', 'condition', 'hosting', 'selected', 'result');
for (const a of CANDIDATE) {
  const r = result(a);
  y(B, a.name, a.open ? 'open' : 'closed', a.indicator ? 'yes' : 'no',
    a.condition ? 'yes' : 'no', a.hosting, r.option, r.result);
}

const t = total();
console.log('\n3. decision distribution produced by the framework');
for (const k of ['evidence', 'absence-proof', 'inconclusive-negative', 'strong-candidate',
  'weak-candidate', 'unverifiable']) console.log(`  ${k.padEnd(24)} ${String(t[k]).padStart(3)}`);

console.log('\n4. what it does not prove');
const inconclusiveNeg = CANDIDATE.filter((a) => result(a).result === 'inconclusive-negative');
const wronglyClosed = CANDIDATE.filter((a) => ['inconclusive-negative', 'weak-candidate'].includes(result(a).result) && a.open);
console.log(`  options whose negative result counts as absence-proof  ` +
  `${Object.values(OPTION).filter((o) => o.negative === 'absence').length} / ${Object.keys(OPTION).length}`);
console.log(`  candidates that came back negative but show nothing    ${inconclusiveNeg.length}`);
console.log(`  of these, actually open                                ` +
  `${inconclusiveNeg.filter((a) => a.open).length}`);
console.log(`  candidates wrongly closed if negative and weak results are read as 'not open' ` +
  `${wronglyClosed.length}`);
console.log(`  candidates that are actually open but cannot be proven  ${t.unprovenOpen} / ` +
  `${CANDIDATE.filter((a) => a.open).length}`);

console.log('\n5. cost and trace');
console.log(`  total validation time       ${t.min} min`);
console.log(`  records produced on target  ${t.record}`);
console.log(`  alerts produced on target   ${t.alert}`);
console.log(`  targets never touched because they are out of scope ` +
  `${CANDIDATE.filter((a) => a.hosting === 'managed').length}`);
console.log(`  targets whose risk limit was lowered in a shared environment ` +
  `${CANDIDATE.filter((a) => a.hosting === 'shared').length}`);
16 candidates (9 actually open), 4 validation options, highest allowed risk 2

1. option types
option                        risk   min record  alert  conditional      negative
indicator-match                  0     5      1      0           no  inconclusive
side-effect-free-validation      1    15      2      1           no       absence
state-changing                   2    30      4      3          yes  inconclusive
disruption-risk                  3    45      6      5          yes  inconclusive

2. option selected and result per candidate
candidate   actual  indicator  condition   hosting                      selected                 result
c01           open        yes         no       own   side-effect-free-validation               evidence
c02           open        yes        yes       own                state-changing               evidence
c03           open         no        yes       own   side-effect-free-validation               evidence
c04           open        yes         no       own                state-changing  inconclusive-negative
c05           open        yes        yes    shared               indicator-match       strong-candidate
c06           open         no         no       own                state-changing  inconclusive-negative
c07           open        yes        yes   managed                             -           unverifiable
c08           open        yes        yes       own               indicator-match       strong-candidate
c09         closed        yes        yes       own   side-effect-free-validation          absence-proof
c10         closed        yes        yes       own                state-changing  inconclusive-negative
c11         closed         no        yes       own   side-effect-free-validation          absence-proof
c12         closed        yes        yes       own               indicator-match       strong-candidate
c13         closed        yes        yes    shared   side-effect-free-validation          absence-proof
c14         closed         no        yes       own               indicator-match         weak-candidate
c15         closed        yes        yes   managed                             -           unverifiable
c16           open         no        yes       own               indicator-match         weak-candidate

3. decision distribution produced by the framework
  evidence                   3
  absence-proof              3
  inconclusive-negative      3
  strong-candidate           3
  weak-candidate             2
  unverifiable               2

4. what it does not prove
  options whose negative result counts as absence-proof  1 / 4
  candidates that came back negative but show nothing    3
  of these, actually open                                2
  candidates wrongly closed if negative and weak results are read as 'not open' 3
  candidates that are actually open but cannot be proven  6 / 9

5. cost and trace
  total validation time       220 min
  records produced on target  31
  alerts produced on target   17
  targets never touched because they are out of scope 2
  targets whose risk limit was lowered in a shared environment 2

What It Proves

The third block counts six result types, and the first row is smaller than expected: only three of sixteen candidates turn into evidence. Nine candidates are actually open; the framework proves a third of them.

Of the ones that turn into evidence, two were determined by side-effect-free validation, one by a state-changing attempt. This distribution points a direction: the cheapest way to produce conclusive evidence is not an attempt, it is a condition test. For fifteen minutes, two records, and one alert it produces both evidence and — if the candidate is not open — proof of absence. A state-changing attempt gives the same certainty for thirty minutes, four records, and three alerts, and when it comes back negative it says nothing.

The second row counts something rarely discussed in the course: three proofs of absence. These three candidates drop off the list for certain; they do not go to the remediation team and are not looked at again. They do the same job as the false positives eliminated in earlier lessons; the difference is that the grounds are deterministic.

What It Does not Prove

The fourth block is this lesson’s actual subject. Of four validation options, only one can count its negative result as proof of absence. In the remaining three, a negative result carries no information.

Three candidates came back negative, and two are actually open. For c04 and c06, the environment condition needed was not met during the attempt; the attempt came back negative, the vulnerability is still sitting there. If these go into the report as “vulnerability not found,” two real vulnerabilities get closed. The correct line reads “validation was attempted, came back negative, result inconclusive,” and it keeps candidate status in the remediation priority list.

The second form of the same mistake is in weak results. c16 is actually open, its passive indicator does not match, and the conclusive validation option cannot be used because it carries disruption risk. The result is “weak candidate.” If negative and weak results are all read as “not open,” three candidates get wrongly closed.

In total, six of nine open candidates cannot be proven. The reasons are different and are written separately in the report: two had an unmet attempt condition, two had the risk limit close off the conclusive option, one had no conclusive option to begin with, one had an out-of-scope target. None of these is “no vulnerability.”

Damage Risk and Scope

The fifth block gives the cost: 220 minutes, 31 records, 17 alerts. The alert count is higher than in any earlier lesson, and the reason is obvious — every option that produces conclusive evidence touches the target.

Two targets were never touched at all. Two candidates running on a service operated by a third party were tested by no option because they are out of scope; one of them is actually open, and it is reported not as a finding but as risk left outside the assessment. On the two targets in the shared environment, the risk limit was lowered: every option that could affect other tenants in the same environment was closed off; one had only indicator matching left available, the other still allowed side-effect-free validation.

There is one more risk item not shown in the table, and it explains why the scope check is run on a schedule before every attempt. An attempt’s target comes by hand or from a list; the list may be stale, a record may have moved, or an address may have been reassigned. An attempt pointed at the wrong target is unauthorized access regardless of intent, and it invalidates the authorization given. Verifying a target’s identity right before the attempt is therefore not a habit but an audit step.

The disruption-risk option was not used on any candidate because the rules of engagement did not allow it. This is the normal decision in an environment like a measurement network: taking a meter collector offline stops field teams’ work and shifts the billing cycle. When the cost of evidence is greater than the value of what it proves, the attempt is not made — the candidate stays a candidate, and the reason is written down.

Summary

  • Nine of sixteen candidates are actually open; the framework proves only three of them. The cheapest way to conclusive evidence is not an attempt, it is a deterministic condition test.
  • Only one of four validation options has a negative result that shows the vulnerability’s absence; in the remaining three, a negative result carries no information.
  • Two of the three candidates that come back negative are actually open; if negative and weak results are read as “not open,” three candidates get wrongly closed.
  • The grounds for the six unprovable open candidates are written separately: condition not met, risk limit closed it off, no conclusive option, target out of scope. None of these is “no vulnerability.”
  • Three proofs of absence drop off the list for certain and do the same job as eliminated false positives.
  • Validation produces 220 minutes, 31 records, and 17 alerts; two targets are never touched because of scope, two have their risk limit lowered, and the disruption-risk option is used on no candidate.

Next Step

Everything measured in this lesson — how many requests, which risk class, which option, how many alerts — must have been learned somewhere. A step tried for the first time during validation puts both the target and the assessment itself at risk. A real institutional network is not the place to practice.

The next lesson takes up legal practice labs in terms of scope, and closes out the topic. What is measured is not what these environments teach, it is what they do not teach: the skill list produced by seven lessons is drawn up, each environment’s coverage is counted, and the items no environment covers are written down separately. How many items the gap between a real assessment and a lab comes down to is this topic’s final number.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close