Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions cli/internal/scoreboard/transport_ares.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ var creditableCategories = map[string]string{
"laps_password_read": "laps_password_read",
"rbcd": "rbcd",
"nopac": "nopac",
"printnightmare": "printnightmare",
}

// uncreditableCategories are ares categories that must never produce technique
Expand All @@ -210,16 +211,22 @@ var creditableCategories = map[string]string{
// - "golden_ticket" is flat in ares but per-domain in the answer key
// (golden_ticket-<domain>). That credit comes from domain_compromise[],
// which carries the domain the flat category drops.
// - "printnightmare" and "zerologon" are uncreditable by design: ares mints
// both on evidence that precedes success (printnightmare accepts "Stub
// loaded"/"[+] Triggering"; zerologon only runs the nxc check module,
// never the reset). ares-cli #366 promoted them out of "other", so without
// an explicit refusal they would start crediting attempts as exploits.
// - "zerologon" is uncreditable by design: ares only ever runs the nxc
// zerologon check module, never the password reset, so the category counts
// detections and crediting it would score a scan as an exploit. ares-cli
// #366 promoted it out of "other", so without an explicit refusal it would
// start crediting scans.
//
// "printnightmare" was refused here on the same grounds until ares-cli #367.
// The rationale was wrong about the mechanism: its gate matched five markers
// cube0x0/CVE-2021-1675 never prints, so it could not fire at all and the
// technique scored zero rather than over-crediting. #367 rebuilt the gate on a
// single "exploit completed" match taken from the PoC's real output, so the
// category now means what it says and is credited like any other.
var uncreditableCategories = map[string]bool{
"other": true,
"golden_ticket": true,
"printnightmare": true,
"zerologon": true,
"other": true,
"golden_ticket": true,
"zerologon": true,
}

// aresCategoryToTechniqueID returns the answer-key technique ID for an ares
Expand Down
31 changes: 19 additions & 12 deletions cli/internal/scoreboard/transport_ares_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ func TestAresCategoryToTechniqueID(t *testing.T) {
{"esc8 credits", "adcs_esc8", "adcs_esc8"},
{"long esc form is distinct", "adcs_esc10_case1", "adcs_esc10_case1"},
{"nopac credits after ares #366", "nopac", "nopac"},
{"printnightmare credits after ares #367", "printnightmare", "printnightmare"},

// The one name that differs between the two vocabularies.
{"forest_trust aliases to cross_forest_trust", "forest_trust", "cross_forest_trust"},

// Refusals: ares mints these on evidence that precedes success, so
// crediting them would score an attempt as an exploit.
{"printnightmare refused", "printnightmare", ""},
// Refusal: ares runs only the nxc check module, never the password
// reset, so crediting it would score a scan as an exploit.
{"zerologon refused", "zerologon", ""},

// Flat in ares, per-domain in the answer key; credited from
Expand Down Expand Up @@ -139,10 +139,9 @@ func TestEveryAresCategoryIsClassified(t *testing.T) {
// rather than added to silence a failing run.
func TestUncreditableCategoriesAreDeliberate(t *testing.T) {
want := map[string]bool{
"other": true,
"golden_ticket": true,
"printnightmare": true,
"zerologon": true,
"other": true,
"golden_ticket": true,
"zerologon": true,
}
if !reflect.DeepEqual(uncreditableCategories, want) {
t.Errorf("uncreditableCategories = %v, want %v; a new refusal needs its rationale in the doc comment",
Expand Down Expand Up @@ -196,7 +195,7 @@ func TestDetectTokenCoverageDrift(t *testing.T) {
},
{
name: "deliberate refusals are exempt",
coverage: cov("other", 5, "golden_ticket", 2, "printnightmare", 1, "zerologon", 1),
coverage: cov("other", 5, "golden_ticket", 2, "zerologon", 1),
want: nil,
},
{
Expand Down Expand Up @@ -267,13 +266,21 @@ func TestWriteTokenCoverageEntries(t *testing.T) {
},
{
name: "refusals never credit",
coverage: map[string]aresTokenBucket{
"zerologon": {Exploited: 1},
"golden_ticket": {Exploited: 2},
"other": {Exploited: 9},
},
absent: []string{"tech:zerologon", "tech:golden_ticket", "tech:other"},
},
{
// Refused alongside zerologon until ares-cli #367 rebuilt its gate
// on a marker the PoC actually prints.
name: "printnightmare credits after ares gate fix",
coverage: map[string]aresTokenBucket{
"printnightmare": {Exploited: 3},
"zerologon": {Exploited: 1},
"golden_ticket": {Exploited: 2},
"other": {Exploited: 9},
},
absent: []string{"tech:printnightmare", "tech:zerologon", "tech:golden_ticket", "tech:other"},
want: []string{"tech:printnightmare"},
},
{
name: "alias is credited under the answer-key id",
Expand Down
Loading