From 22ff46c666df80917e6f7bc41a867110b30bc0e9 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Sat, 1 Aug 2026 13:56:50 -0700 Subject: [PATCH] fix: rewrite compound share names in generated variants **Added:** - Compound share name handling via a new `shareAliases` map that rewrites forms like `thewallserver` that word-boundary matching cannot reach from the bare share name - `cli/internal/variant/generator.go` - `TestCompoundShareNameRewritten` covering the archived kerberoasting script's `thewallserver` case - `cli/internal/variant/generator_test.go` **Changed:** - Renamed the `thewall` share to `records` across GOAD variant-1 config, dev overlay, mapping, and delegation/kerberoasting scripts to align share naming - Updated user `patrick` credentials and description, adjusting the corresponding password mapping in `mapping.json` - Extended `mapShares` to emit compound alias forms into Misc for plain longest-first replacement so they resolve before the bare share name --- ad/GOAD-variant-1/data/config.json | 10 +++--- ad/GOAD-variant-1/data/dev-overlay.json | 4 +-- ad/GOAD-variant-1/mapping.json | 6 +++- .../scripts/archives/kerberoasting.ps1 | 2 +- .../constrained_delegation_use_any.ps1 | 2 +- cli/internal/variant/generator.go | 19 +++++++++++ cli/internal/variant/generator_test.go | 34 +++++++++++++++++++ 7 files changed, 67 insertions(+), 10 deletions(-) diff --git a/ad/GOAD-variant-1/data/config.json b/ad/GOAD-variant-1/data/config.json index 4ebc26ad..6673580f 100644 --- a/ad/GOAD-variant-1/data/config.json +++ b/ad/GOAD-variant-1/data/config.json @@ -208,11 +208,11 @@ } }, "shares": { - "thewall": { + "records": { "change": "CLOUD\\anthony.green,CLOUD\\carol.peterson", "full": "CLOUD\\AdministrationSquad", "list": "yes", - "path": "C:\\thewall", + "path": "C:\\records", "read": "Users" } } @@ -358,7 +358,7 @@ ], "path": "CN=Users,DC=Cloud,DC=sigmatech,DC=local", "spns": [ - "HTTP/thewall.cloud.sigmatech.local" + "HTTP/records.cloud.sigmatech.local" ] }, "carol.peterson": { @@ -443,9 +443,9 @@ "patrick": { "firstname": "patrick", "surname": "patrick", - "password": "kkzof", + "password": "patrick", "city": "Atlanta", - "description": "Brainless Giant", + "description": "Patrick", "groups": [ "AdministrationSquad" ], diff --git a/ad/GOAD-variant-1/data/dev-overlay.json b/ad/GOAD-variant-1/data/dev-overlay.json index 57bf6d0b..65bb6206 100644 --- a/ad/GOAD-variant-1/data/dev-overlay.json +++ b/ad/GOAD-variant-1/data/dev-overlay.json @@ -39,8 +39,8 @@ ], "vulns_vars": { "shares": { - "thewall": { - "path": "C:\\thewall", + "records": { + "path": "C:\\records", "list": "yes", "full": "CLOUD\\AdministrationSquad", "change": "CLOUD\\anthony.green,CLOUD\\carol.peterson", diff --git a/ad/GOAD-variant-1/mapping.json b/ad/GOAD-variant-1/mapping.json index ca6a128d..073cdff6 100644 --- a/ad/GOAD-variant-1/mapping.json +++ b/ad/GOAD-variant-1/mapping.json @@ -111,7 +111,7 @@ "_W1sper_$": "SGW7xb\u0026$h", "cersei": "ttimzd", "fr3edom": "1g16kx7", - "hodor": "kkzof", + "hodor": "patrick", "horse": "fdmzy", "iamthekingoftheworld": "ludexcyrpmgdaekcwrsw", "iknownothing": "sigpubdmehqv", @@ -156,6 +156,9 @@ "Westerlands": "Northern" }, "acls": {}, + "shares": { + "thewall": "records" + }, "misc": { "Arya": "frank", "BRAAVOS": "AURORA2", @@ -248,6 +251,7 @@ "stark": "phillips", "targaryen": "wright", "tarly": "peterson", + "thewallserver": "recordsserver", "tyron": "raymond", "tywin": "catherine", "varys": "collins", diff --git a/ad/GOAD-variant-1/scripts/archives/kerberoasting.ps1 b/ad/GOAD-variant-1/scripts/archives/kerberoasting.ps1 index fb8cdc01..795589ac 100644 --- a/ad/GOAD-variant-1/scripts/archives/kerberoasting.ps1 +++ b/ad/GOAD-variant-1/scripts/archives/kerberoasting.ps1 @@ -1 +1 @@ -Set-ADUser -Identity "anthony.green" -ServicePrincipalNames @{Add='HTTP/thewallserver'} +Set-ADUser -Identity "anthony.green" -ServicePrincipalNames @{Add='HTTP/recordsserver'} diff --git a/ad/GOAD-variant-1/scripts/constrained_delegation_use_any.ps1 b/ad/GOAD-variant-1/scripts/constrained_delegation_use_any.ps1 index 9c983952..2000b1bc 100644 --- a/ad/GOAD-variant-1/scripts/constrained_delegation_use_any.ps1 +++ b/ad/GOAD-variant-1/scripts/constrained_delegation_use_any.ps1 @@ -1,5 +1,5 @@ $identity = 'anthony.green' -$spn = 'CIFS/thewall.cloud.sigmatech.local' +$spn = 'CIFS/records.cloud.sigmatech.local' $delegateTo = @('CIFS/delta.cloud.sigmatech.local', 'CIFS/delta') # Re-adding a value a multi-valued attribute already holds is an LDAP constraint diff --git a/cli/internal/variant/generator.go b/cli/internal/variant/generator.go index 3105751f..40252908 100644 --- a/cli/internal/variant/generator.go +++ b/cli/internal/variant/generator.go @@ -213,6 +213,16 @@ var preservedUsernames = map[string]bool{"sql_svc": true} // in unrelated contexts. const minShareNameLength = 5 +// shareAliases maps a share name to compound forms of it that appear elsewhere +// in upstream GOAD. Share replacement is word-boundary matched so the JSON key +// and the path (C:\thewall) move together without touching substrings, but that +// same boundary means \bthewall\b cannot reach "thewallserver" in the archived +// kerberoasting script. Each alias is rewritten to the new share name plus +// whatever the alias appended, keeping the parallel form. +var shareAliases = map[string][]string{ + "thewall": {"thewallserver"}, +} + // hostnameAliases maps canonical hostnames to known typos/aliases in upstream GOAD. var hostnameAliases = map[string][]string{ "braavos": {"Bravos"}, @@ -697,6 +707,15 @@ func (g *Generator) mapShares(config *LabConfig) { newName := g.nameGen.GenerateShareName() g.mappings.Shares[shareName] = newName fmt.Printf(" %s -> %s\n", shareName, newName) + + // Compound forms go to Misc for plain (non-boundary) replacement. + // They are longer than the bare share name, and the replacement + // list is sorted longest-first, so they match before it does. + for _, alias := range shareAliases[shareName] { + suffix := strings.TrimPrefix(alias, shareName) + g.mappings.Misc[alias] = newName + suffix + fmt.Printf(" %s -> %s (compound)\n", alias, newName+suffix) + } } } } diff --git a/cli/internal/variant/generator_test.go b/cli/internal/variant/generator_test.go index bcf7a20a..0e13917c 100644 --- a/cli/internal/variant/generator_test.go +++ b/cli/internal/variant/generator_test.go @@ -627,3 +627,37 @@ func TestPasswordEqualToUsernamePreservesPairing(t *testing.T) { } } } + +// TestCompoundShareNameRewritten covers "thewallserver" in the archived +// kerberoasting script. Share replacement is word-boundary matched, so the bare +// share mapping cannot reach a compound form and it survived into variants. +func TestCompoundShareNameRewritten(t *testing.T) { + sourceDir, targetDir := setupTestSource(t) + scriptPath := filepath.Join(sourceDir, "scripts", "kerberoasting.ps1") + if err := os.WriteFile(scriptPath, + []byte(`Set-ADUser -Identity "jon.snow" -ServicePrincipalNames @{Add='HTTP/thewallserver'}`+"\n"), + 0o644); err != nil { + t.Fatal(err) + } + + gen := NewGenerator(sourceDir, targetDir, "test-compound") + if err := gen.Run(); err != nil { + t.Fatalf("generator failed: %v", err) + } + + out, err := os.ReadFile(filepath.Join(targetDir, "scripts", "kerberoasting.ps1")) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(out), "thewall") { + t.Errorf("compound share name survived: %s", out) + } + + newShare := gen.mappings.Shares["thewall"] + if newShare == "" { + t.Fatal("thewall share was not mapped") + } + if !strings.Contains(string(out), newShare+"server") { + t.Errorf("got %q, want it to contain %q", out, newShare+"server") + } +}