Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESC9a Edge Composition #354

Merged
merged 46 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
dd48147
feat: esc9a post
rvazarkar Jan 23, 2024
5ea0324
test: add esc9 test
rvazarkar Jan 23, 2024
47eb582
chore: add harness files
rvazarkar Jan 23, 2024
0f99f61
Merge remote-tracking branch 'origin/main' into BED-4020
rvazarkar Jan 24, 2024
09f278f
fix: regen schema after merge
rvazarkar Jan 24, 2024
6c22570
chore: fix small nits
rvazarkar Jan 24, 2024
f20ae0d
chore: cleanup cert template new function
rvazarkar Jan 24, 2024
da18383
Merge remote-tracking branch 'origin/main' into BED-4020
rvazarkar Jan 24, 2024
0b29708
chore: add missing props
rvazarkar Jan 24, 2024
d653065
Merge branch 'main' into BED-4020
rvazarkar Jan 24, 2024
02c74a3
Merge branch 'main' into BED-4020
rvazarkar Jan 24, 2024
8fcd861
wip: 9a composition
rvazarkar Jan 25, 2024
2c1eb2e
Merge remote-tracking branch 'origin/main' into BED-4020
rvazarkar Jan 25, 2024
2866800
fix: treat failure to grab properties as true
rvazarkar Jan 25, 2024
ebee1c4
Merge remote-tracking branch 'origin/BED-4020' into BED-4023
rvazarkar Jan 25, 2024
5b95b3b
wip: esc9a composition
rvazarkar Jan 25, 2024
53283ce
wip: esc9a composition
rvazarkar Jan 25, 2024
b4f1d45
feat+chore: add depth controls to dawgs patterns
zinic Jan 25, 2024
fdb9614
wip: esc9a composition
rvazarkar Jan 25, 2024
9a1c50c
fix: do not drop the current segment if the next pattern is optional
zinic Jan 25, 2024
4317b72
wip: esc9a composition
rvazarkar Jan 25, 2024
2947746
fix: update other continuations to respect depth correctly
rvazarkar Jan 25, 2024
4e99b13
wip: edge comp
rvazarkar Jan 25, 2024
28c98a6
fix: swap
rvazarkar Jan 25, 2024
554390f
chore: remove unnecessary logs
rvazarkar Jan 25, 2024
c9385a9
feat: esc9a post
rvazarkar Jan 23, 2024
13923d2
test: add esc9 test
rvazarkar Jan 23, 2024
032f89c
chore: fix small nits
rvazarkar Jan 24, 2024
c52df5c
wip: 9a composition
rvazarkar Jan 25, 2024
869762f
wip: esc9a composition
rvazarkar Jan 25, 2024
10eda9e
wip: esc9a composition
rvazarkar Jan 25, 2024
60b5d48
feat+chore: add depth controls to dawgs patterns
zinic Jan 25, 2024
093e954
wip: esc9a composition
rvazarkar Jan 25, 2024
cac9e2f
fix: do not drop the current segment if the next pattern is optional
zinic Jan 25, 2024
61895bc
wip: esc9a composition
rvazarkar Jan 25, 2024
bd845e1
fix: update other continuations to respect depth correctly
rvazarkar Jan 25, 2024
7f0e94f
wip: edge comp
rvazarkar Jan 25, 2024
30d6253
fix: swap
rvazarkar Jan 25, 2024
8f83531
chore: remove unnecessary logs
rvazarkar Jan 25, 2024
61ca2ac
test: add test covering esc9a edge comp
rvazarkar Jan 26, 2024
84b66fa
Merge remote-tracking branch 'origin/BED-4023' into BED-4023
rvazarkar Jan 26, 2024
008b0df
chore: revert random re-ordering
rvazarkar Jan 26, 2024
bb9a9f8
Merge branch 'main' into BED-4023
rvazarkar Jan 29, 2024
e07a35a
Merge branch 'main' into BED-4023
rvazarkar Jan 29, 2024
f1bdadb
Merge branch 'main' into BED-4023
rvazarkar Jan 29, 2024
dba8876
chore: handle negative min/max depth on continuations
rvazarkar Jan 29, 2024
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
27 changes: 27 additions & 0 deletions cmd/api/src/analysis/ad/adcs_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,33 @@ func TestADCSESC9a(t *testing.T) {
}
return nil
})

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC9a)
})); err != nil {
t.Fatalf("error fetching esc9a edges in integration test; %v", err)
} else {
assert.Equal(t, 1, len(results))
edge := results[0]

if edgeComp, err := ad2.GetEdgeCompositionPath(context.Background(), db, edge); err != nil {
t.Fatalf("error getting edge composition for esc9: %v", err)
} else {
nodes := edgeComp.AllNodes().Slice()
assert.Contains(t, nodes, harness.ESC9AHarness.Attacker)
assert.Contains(t, nodes, harness.ESC9AHarness.Victim)
assert.Contains(t, nodes, harness.ESC9AHarness.Domain)
assert.Contains(t, nodes, harness.ESC9AHarness.NTAuthStore)
assert.Contains(t, nodes, harness.ESC9AHarness.RootCA)
assert.Contains(t, nodes, harness.ESC9AHarness.DC)
assert.Contains(t, nodes, harness.ESC9AHarness.EnterpriseCA)
assert.Contains(t, nodes, harness.ESC9AHarness.CertTemplate)
}
}

return nil
})
})

}
Expand Down
2 changes: 1 addition & 1 deletion cmd/ui/src/views/Explore/EdgeInfo/EdgeInfoContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const EdgeInfoContent: FC<{ selectedEdge: NonNullable<SelectedEdge> }> = ({ sele
const sendOnChange =
(selectedEdge.name === 'GoldenCert' ||
selectedEdge.name === 'ADCSESC1' ||
selectedEdge.name === 'ADCSESC3') &&
selectedEdge.name === 'ADCSESC3' || selectedEdge.name === 'ADCSESC9a') &&
section[0] === 'composition';

return (
Expand Down
263 changes: 256 additions & 7 deletions packages/go/analysis/ad/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,14 +549,20 @@ func GetEdgeCompositionPath(ctx context.Context, db graph.Database, edge *graph.
} else {
pathSet = results
}
} else if edge.Kind == ad.ADCSESC9a {
if results, err := GetADCSESC9aEdgeComposition(ctx, db, edge); err != nil {
return err
} else {
pathSet = results
}
}
return nil
})
}

func ADCSESC3Path1Pattern(domainId graph.ID, enterpriseCAs cardinality.Duplex[uint32]) traversal.PatternContinuation {
return traversal.NewPattern().
Outbound(query.And(
OutboundWithDepth(0, 0, query.And(
query.Kind(query.Relationship(), ad.MemberOf),
query.Kind(query.End(), ad.Group),
)).
Expand Down Expand Up @@ -591,7 +597,7 @@ func ADCSESC3Path1Pattern(domainId graph.ID, enterpriseCAs cardinality.Duplex[ui

func ADCSESC3Path2Pattern(domainId graph.ID, enterpriseCAs, candidateTemplates cardinality.Duplex[uint32]) traversal.PatternContinuation {
return traversal.NewPattern().
Outbound(query.And(
OutboundWithDepth(0, 0, query.And(
query.Kind(query.Relationship(), ad.MemberOf),
query.Kind(query.End(), ad.Group),
)).
Expand All @@ -618,7 +624,7 @@ func ADCSESC3Path2Pattern(domainId graph.ID, enterpriseCAs, candidateTemplates c

func ADCSESC3Path3Pattern() traversal.PatternContinuation {
return traversal.NewPattern().
Outbound(query.And(
OutboundWithDepth(0, 0, query.And(
query.Kind(query.Relationship(), ad.MemberOf),
query.Kind(query.End(), ad.Group),
)).
Expand Down Expand Up @@ -807,7 +813,7 @@ func getDelegatedEnrollmentAgentPath(ctx context.Context, startNode, certTemplat

func ADCSESC1Path1Pattern(domainID graph.ID) traversal.PatternContinuation {
return traversal.NewPattern().
Outbound(query.And(
OutboundWithDepth(0, 0, query.And(
query.Kind(query.Relationship(), ad.MemberOf),
query.Kind(query.End(), ad.Group),
)).
Expand Down Expand Up @@ -846,7 +852,7 @@ func ADCSESC1Path1Pattern(domainID graph.ID) traversal.PatternContinuation {

func ADCSESC1Path2Pattern(domainID graph.ID, enterpriseCAs cardinality.Duplex[uint32]) traversal.PatternContinuation {
return traversal.NewPattern().
Outbound(query.And(
OutboundWithDepth(0, 0, query.And(
query.Kind(query.Relationship(), ad.MemberOf),
query.Kind(query.End(), ad.Group),
)).
Expand Down Expand Up @@ -931,8 +937,6 @@ func GetADCSESC1EdgeComposition(ctx context.Context, db graph.Database, edge *gr
// Render paths from the segments
return paths, path1EnterpriseCAs.Each(func(value uint32) (bool, error) {
for _, segment := range candidateSegments[graph.ID(value)] {
log.Infof("Found ESC1 Path: %s", graph.FormatPathSegment(segment))

paths.AddPath(segment.Path())
}

Expand Down Expand Up @@ -975,3 +979,248 @@ func getGoldenCertEdgeComposition(tx graph.Transaction, edge *graph.Relationship
return finalPaths, nil
}
}

func adcsESC9aPath1Pattern(domainID graph.ID) traversal.PatternContinuation {
return traversal.NewPattern().
OutboundWithDepth(
1, 1,
query.And(
query.KindIn(query.Relationship(), ad.GenericWrite, ad.GenericAll, ad.Owns, ad.WriteOwner, ad.WriteDACL),
query.KindIn(query.End(), ad.Computer, ad.User),
),
).
OutboundWithDepth(
0, 0,
query.And(
query.Kind(query.Relationship(), ad.MemberOf),
query.Kind(query.End(), ad.Group),
),
).
Outbound(
query.And(
query.KindIn(query.Relationship(), ad.GenericAll, ad.Enroll, ad.AllExtendedRights),
query.Kind(query.End(), ad.CertTemplate),
query.Equals(query.EndProperty(ad.RequiresManagerApproval.String()), false),
query.Equals(query.EndProperty(ad.AuthenticationEnabled.String()), true),
query.Equals(query.EndProperty(ad.NoSecurityExtension.String()), true),
query.Equals(query.EndProperty(ad.EnrolleeSuppliesSubject.String()), false),
query.Or(
query.Equals(query.EndProperty(ad.SubjectAltRequireUPN.String()), true),
query.Equals(query.EndProperty(ad.SubjectAltRequireSPN.String()), true),
),
query.Or(
query.Equals(query.EndProperty(ad.SchemaVersion.String()), 1),
query.And(
query.GreaterThan(query.EndProperty(ad.SchemaVersion.String()), 1),
query.Equals(query.EndProperty(ad.AuthorizedSignatures.String()), 0),
),
),
),
).
Outbound(query.And(
query.KindIn(query.Relationship(), ad.PublishedTo, ad.IssuedSignedBy),
query.Kind(query.End(), ad.EnterpriseCA),
)).
Outbound(query.And(
query.KindIn(query.Relationship(), ad.IssuedSignedBy, ad.EnterpriseCAFor),
query.Kind(query.End(), ad.RootCA),
)).
Outbound(query.And(
query.KindIn(query.Relationship(), ad.RootCAFor),
query.Equals(query.EndID(), domainID),
))
}

func adcsESC9APath2Pattern(caNodes []graph.ID, domainId graph.ID) traversal.PatternContinuation {
return traversal.NewPattern().
OutboundWithDepth(0, 0, query.And(
query.Kind(query.Relationship(), ad.MemberOf),
query.Kind(query.End(), ad.Group),
)).
Outbound(query.And(
query.Kind(query.Relationship(), ad.Enroll),
query.InIDs(query.End(), caNodes...),
)).
Outbound(query.And(
query.KindIn(query.Relationship(), ad.TrustedForNTAuth),
query.Kind(query.End(), ad.NTAuthStore),
)).
Outbound(query.And(
query.KindIn(query.Relationship(), ad.NTAuthStoreFor),
query.Equals(query.EndID(), domainId),
))
}

func adcsESC9APath3Pattern(caIDs []graph.ID) traversal.PatternContinuation {
return traversal.NewPattern().
Inbound(
query.KindIn(query.Relationship(), ad.DCFor, ad.TrustedBy),
).
Inbound(query.And(
query.Kind(query.Relationship(), ad.CanAbuseWeakCertBinding),
query.InIDs(query.StartID(), caIDs...),
))
}

func GetADCSESC9aEdgeComposition(ctx context.Context, db graph.Database, edge *graph.Relationship) (graph.PathSet, error) {
/*
MATCH (n {objectid:'S-1-5-21-3933516454-2894985453-2515407000-500'})-[:ADCSESC9a]->(d:Domain {objectid:'S-1-5-21-3933516454-2894985453-2515407000'})
OPTIONAL MATCH p1 = (n)-[:GenericAll|GenericWrite|Owns|WriteOwner|WriteDacl]->(m)-[:MemberOf*0..]->()-[:GenericAll|Enroll|AllExtendedRights]->(ct)-[:PublishedTo]->(ca)-[:IssuedSignedBy|EnterpriseCAFor|RootCAFor*1..]->(d)
WHERE ct.requiresmanagerapproval = false
AND ct.authenticationenabled = true
AND ct.nosecurityextension = true
AND ct.enrolleesuppliessubject = false
AND (ct.subjectaltrequireupn = true OR ct.subjectaltrequirespn = true)
AND (
(ct.schemaversion > 1 AND ct.authorizedsignatures = 0)
OR ct.schemaversion = 1
)
AND (
m:Computer
OR (m:User AND ct.subjectaltrequiredns = false AND ct.subjectaltrequiredomaindns = false)
)
OPTIONAL MATCH p2 = (m)-[:MemberOf*0..]->()-[:Enroll]->(ca)-[:TrustedForNTAuth]->(nt)-[:NTAuthStoreFor]->(d)
OPTIONAL MATCH p3 = (ca)-[:CanAbuseWeakCertBinding|DCFor|TrustedBy*1..]->(d)
RETURN p1,p2,p3
*/

var (
startNode *graph.Node
endNode *graph.Node

traversalInst = traversal.New(db, analysis.MaximumDatabaseParallelWorkers)
paths = graph.PathSet{}
path1CandidateSegments = map[graph.ID][]*graph.PathSegment{}
victimCANodes = map[graph.ID][]graph.ID{}
path2CandidateSegments = map[graph.ID][]*graph.PathSegment{}
path3CandidateSegments = map[graph.ID][]*graph.PathSegment{}
p2canodes = make([]graph.ID, 0)
nodeMap = map[graph.ID]*graph.Node{}
lock = &sync.Mutex{}
)

if err := db.ReadTransaction(ctx, func(tx graph.Transaction) error {
if node, err := ops.FetchNode(tx, edge.StartID); err != nil {
return err
} else if eNode, err := ops.FetchNode(tx, edge.EndID); err != nil {
return err
} else {
startNode = node
endNode = eNode
return nil
}
}); err != nil {
return nil, err
}

//Fully manifest p1
if err := traversalInst.BreadthFirst(ctx, traversal.Plan{
Root: startNode,
Driver: adcsESC9aPath1Pattern(edge.EndID).Do(func(terminal *graph.PathSegment) error {
victimNode := terminal.Search(func(nextSegment *graph.PathSegment) bool {
return nextSegment.Depth() == 1
})

if victimNode.Kinds.ContainsOneOf(ad.User) {
certTemplate := terminal.Search(func(nextSegment *graph.PathSegment) bool {
return nextSegment.Node.Kinds.ContainsOneOf(ad.CertTemplate)
})

if !certTemplateValidForUserVictim(certTemplate) {
return nil
}
}

caNode := terminal.Search(func(nextSegment *graph.PathSegment) bool {
return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA)
})

lock.Lock()
path1CandidateSegments[victimNode.ID] = append(path1CandidateSegments[victimNode.ID], terminal)
nodeMap[victimNode.ID] = victimNode
victimCANodes[victimNode.ID] = append(victimCANodes[victimNode.ID], caNode.ID)
lock.Unlock()

return nil
}),
}); err != nil {
return nil, err
}

for victim, p1CANodes := range victimCANodes {
if err := traversalInst.BreadthFirst(ctx, traversal.Plan{
Root: nodeMap[victim],
Driver: adcsESC9APath2Pattern(p1CANodes, edge.EndID).Do(func(terminal *graph.PathSegment) error {
caNode := terminal.Search(func(nextSegment *graph.PathSegment) bool {
return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA)
})

lock.Lock()
path2CandidateSegments[caNode.ID] = append(path2CandidateSegments[caNode.ID], terminal)
p2canodes = append(p2canodes, caNode.ID)
lock.Unlock()

return nil
}),
}); err != nil {
return nil, err
}
}

if len(p2canodes) > 0 {
if err := traversalInst.BreadthFirst(ctx, traversal.Plan{
Root: endNode,
Driver: adcsESC9APath3Pattern(p2canodes).Do(func(terminal *graph.PathSegment) error {
caNode := terminal.Search(func(nextSegment *graph.PathSegment) bool {
return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA)
})

lock.Lock()
path3CandidateSegments[caNode.ID] = append(path3CandidateSegments[caNode.ID], terminal)
lock.Unlock()
return nil
}),
}); err != nil {
return nil, err
}
}

for _, p1paths := range path1CandidateSegments {
for _, p1path := range p1paths {
caNode := p1path.Search(func(nextSegment *graph.PathSegment) bool {
return nextSegment.Node.Kinds.ContainsOneOf(ad.EnterpriseCA)
})

if p2segments, ok := path2CandidateSegments[caNode.ID]; !ok {
continue
} else if p3segments, ok := path3CandidateSegments[caNode.ID]; !ok {
continue
} else {
paths.AddPath(p1path.Path())
for _, p2 := range p2segments {
paths.AddPath(p2.Path())
}

for _, p3 := range p3segments {
paths.AddPath(p3.Path())
}
}
}
}

return paths, nil
}

func certTemplateValidForUserVictim(certTemplate *graph.Node) bool {
if subjectAltRequireDNS, err := certTemplate.Properties.Get(ad.SubjectAltRequireDNS.String()).Bool(); err != nil {
return false
} else if subjectAltRequireDNS {
return false
} else if subjectAltRequireDomainDNS, err := certTemplate.Properties.Get(ad.SubjectAltRequireDomainDNS.String()).Bool(); err != nil {
return false
} else if subjectAltRequireDomainDNS {
return false
} else {
return true
}
}
Loading
Loading