Skip to content

Commit

Permalink
Got rid of the ForeignSecurityPrincipal nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
lkarlslund committed Nov 29, 2022
1 parent 67bfd01 commit a69c964
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 129 deletions.
23 changes: 23 additions & 0 deletions modules/engine/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,29 @@ var (
MetaLAPSInstalled = NewAttribute("_haslaps")
)

func init() {
AddMergeApprover("Merge SIDs", func(a, b *Object) (*Object, error) {
asid := a.SID()
bsid := b.SID()
if asid.IsBlank() || bsid.IsBlank() {
return nil, nil
}
if asid != bsid {
return nil, ErrDontMerge
}
if asid.Component(2) == 21 {
return nil, nil // Merge, these should be universally mappable !?
}
asource := a.OneAttr(DataSource)
bsource := b.OneAttr(DataSource)
if CompareAttributeValues(asource, bsource) {
// Stuff from GPOs can have non universal SIDs but should still be mapped
return nil, nil
}
return nil, ErrDontMerge
})
}

type Attribute uint16

type AttributePair struct {
Expand Down
3 changes: 3 additions & 0 deletions modules/engine/attributevalue.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ func CompareAttributeValues(a, b AttributeValue) bool {
}
default:
// Fallback
if a == nil || b == nil {
return a == b
}
return a.String() == b.String()
}

Expand Down
53 changes: 16 additions & 37 deletions modules/engine/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,52 +691,31 @@ func (os *Objects) FindOrAddAdjacentSID(s windowssecurity.SID, r *Object) *Objec
result, _ := os.FindMultiOrAdd(ObjectSid, AttributeValueSID(s), func() *Object {
no := NewObject(
ObjectSid, AttributeValueSID(s),
DataLoader, "FindOrAddAdjacentSID",
IgnoreBlanks,
DomainContext, r.Attr(DomainContext),
)
if !r.SID().IsNull() {
if r.SID().StripRID() == s.StripRID() {
// Same domain ... hmm!
} else {
// Other domain, then it's a foreign principal
no.SetFlex(ObjectCategorySimple, "Foreign-Security-Principal")
if domainContext := r.OneAttrString(DomainContext); domainContext != "" {
no.SetFlex(DistinguishedName, "CN="+s.String()+",CN=ForeignSecurityPrincipals,"+domainContext)
}
}
}
return no
})
return result.First()
default:
if r.HasAttr(DomainContext) {
// From outside, we need to find the domain part
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), DomainContext, r.OneAttr(DomainContext)); found {
return o.First()
}
}

if r.HasAttr(DomainContext) {
// From outside, we need to find the domain part
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), DomainContext, r.OneAttr(DomainContext)); found {
return o.First()
}
// From inside same source, that is easy
if r.HasAttr(DataSource) {
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), DataSource, r.OneAttr(DataSource)); found {
return o.First()
}
}
// From inside same source, that is easy
if r.HasAttr(DataSource) {
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), DataSource, r.OneAttr(DataSource)); found {
return o.First()
}

}

// Not found, we have write lock so create it
no := NewObject(ObjectSid, AttributeValueSID(s))

if s.Component(2) != 21 {
no.SetFlex(
IgnoreBlanks,
DomainContext, r.Attr(DomainContext),
DataSource, r.Attr(DataSource),
)
}

os.Add(no)
no, _ := os.FindOrAdd(ObjectSid, AttributeValueSID(s),
IgnoreBlanks,
DomainContext, r.Attr(DomainContext),
DataSource, r.Attr(DataSource),
)

return no
}
Expand Down
18 changes: 14 additions & 4 deletions modules/engine/processing.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,27 @@ func Merge(aos []*Objects) (*Objects, error) {
// We're grabbing the index directly for faster processing here
dnindex := globalobjects.GetIndex(DistinguishedName)

// Just add these. they have a DataSource so we're not merging them EXCEPT for ones with a DistinguishedName collition FML
mergeon := getMergeAttributes()

// Just add these. they have a DataSource so we're not merging them EXCEPT for ones with a DistinguishedName collision FML
sourcemap.Range(func(us string, usao sourceinfo) bool {
if us == "" {
return true // continue - not these, we'll try to merge at the very end
}
usao.shard.Iterate(func(addobject *Object) bool {
pb.Add(1)
// Here we'll deduplicate DNs, because sometimes schema and config context slips in twice
// Here we'll deduplicate DNs, because sometimes schema and config context slips in twice ...
aosid := addobject.SID()
if !aosid.IsBlank() && aosid.Component(2) == 21 {
// Always merge these, they might belong elsewhere
globalobjects.AddMerge(mergeon, addobject)
return true
}
if dn := addobject.OneAttr(DistinguishedName); dn != nil {
if existing, exists := dnindex.Lookup(AttributeValueToIndex(dn)); exists {
// UNLESS it's a predefined one with an objectSID, in that case done merge them at all WTF not a huge fan of this design, Microsoft
if !aosid.IsBlank() && aosid.Component(2) != 21 {
addobject.SetFlex(DistinguishedName, "mutated="+addobject.OneAttrString(DataSource)+","+dn.String())
} else if existing, exists := dnindex.Lookup(AttributeValueToIndex(dn)); exists {
existing.First().AbsorbEx(addobject, true)
return true
}
Expand All @@ -159,7 +170,6 @@ func Merge(aos []*Objects) (*Objects, error) {

nodatasource, _ := sourcemap.Load("")
var i int
mergeon := getMergeAttributes()
nodatasource.shard.Iterate(func(addobject *Object) bool {
pb.Add(1)
// Here we'll deduplicate DNs, because sometimes schema and config context slips in twice
Expand Down
4 changes: 4 additions & 0 deletions modules/integrations/activedirectory/analyze/adloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func (ld *ADLoader) Init() error {
continue // skip deleted object
}

if strings.Contains(o.DN(), ",CN=ForeignSecurityPrincipals,DC=") {
continue // skip all foreign security principals
}

if !o.HasAttr(engine.ObjectClass) {
if ld.warnhardened {
if strings.Contains(o.DN(), ",CN=MicrosoftDNS,") {
Expand Down
168 changes: 94 additions & 74 deletions modules/integrations/activedirectory/analyze/analyze-ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
AttributeProfilePathGUID, _ = uuid.FromString("{bf967a05-0de6-11d0-a285-00aa003049e2}")
AttributeScriptPathGUID, _ = uuid.FromString("{bf9679a8-0de6-11d0-a285-00aa003049e2}")
AttributeMSDSManagedPasswordId, _ = uuid.FromString("{0e78295a-c6d3-0a40-b491-d62251ffa0a6}")
AttributeUserAccountControlGUID, _ = uuid.FromString("{bf967a68-0de6-11d0-a285-00aa003049e2}")

ExtendedRightCertificateEnroll, _ = uuid.FromString("{0e10c968-78fb-11d2-90d4-00c04f79dc55}")
ExtendedRightCertificateAutoEnroll, _ = uuid.FromString("{a05b8cc2-17bc-4802-a710-e7c15ab866a2}")
Expand Down Expand Up @@ -1102,7 +1103,7 @@ func init() {
}

// Crude special handling for Everyone and Authenticated Users
if object.Type() == engine.ObjectTypeUser || object.Type() == engine.ObjectTypeComputer || object.Type() == engine.ObjectTypeManagedServiceAccount || object.Type() == engine.ObjectTypeForeignSecurityPrincipal || object.Type() == engine.ObjectTypeGroupManagedServiceAccount {
if object.Type() == engine.ObjectTypeUser || object.Type() == engine.ObjectTypeComputer || object.Type() == engine.ObjectTypeManagedServiceAccount || object.Type() == engine.ObjectTypeGroupManagedServiceAccount {
object.EdgeTo(authenticatedusers, activedirectory.EdgeMemberOfGroup)
}
authenticatedusers.EdgeTo(everyone, activedirectory.EdgeMemberOfGroup)
Expand Down Expand Up @@ -1310,50 +1311,50 @@ func init() {
engine.BeforeMerge,
)

Loader.AddProcessor(func(ao *engine.Objects) {
// Find all the DomainDNS objects, and find the domain object
domains := make(map[string]windowssecurity.SID)

domaindnsobjects, found := ao.FindMulti(engine.ObjectClass, engine.AttributeValueString("domainDNS"))

if !found {
ui.Error().Msg("Could not find any domainDNS objects")
}

domaindnsobjects.Iterate(func(domaindnsobject *engine.Object) bool {
domainSID, sidok := domaindnsobject.OneAttrRaw(activedirectory.ObjectSid).(windowssecurity.SID)
dn := domaindnsobject.OneAttrString(activedirectory.DistinguishedName)
if sidok {
domains[dn] = domainSID
}
return true
})

ao.Iterate(func(o *engine.Object) bool {
if o.HasAttr(engine.ObjectSid) && o.SID().Component(2) == 21 && !o.HasAttr(engine.DistinguishedName) && o.HasAttr(engine.DomainContext) {
// An unknown SID, is it ours or from another domain?
ourDomainDN := o.OneAttrString(engine.DomainContext)
ourDomainSid, domainfound := domains[ourDomainDN]
if !domainfound {
return true
}

if o.SID().StripRID() == ourDomainSid {
// ui.Debug().Msgf("Found a 'dangling' local SID object %v. This is either a SID from a deleted object (most likely) or hardened objects that are not readable with the account used to dump data.", o.SID())
} else {
// ui.Debug().Msgf("Found a 'lost' foreign SID object %v, adding it as a synthetic Foreign-Security-Principal", o.SID())
o.SetFlex(
engine.DistinguishedName, engine.AttributeValueString(o.SID().String()+",CN=ForeignSecurityPrincipals,"+ourDomainDN),
engine.ObjectCategorySimple, "Foreign-Security-Principal",
engine.DataLoader, "Autogenerated",
)
}
}
return true
})
},
"Creation of synthetic Foreign-Security-Principal objects",
engine.AfterMergeLow)
// Loader.AddProcessor(func(ao *engine.Objects) {
// // Find all the DomainDNS objects, and find the domain object
// domains := make(map[string]windowssecurity.SID)

// domaindnsobjects, found := ao.FindMulti(engine.ObjectClass, engine.AttributeValueString("domainDNS"))

// if !found {
// ui.Error().Msg("Could not find any domainDNS objects")
// }

// domaindnsobjects.Iterate(func(domaindnsobject *engine.Object) bool {
// domainSID, sidok := domaindnsobject.OneAttrRaw(activedirectory.ObjectSid).(windowssecurity.SID)
// dn := domaindnsobject.OneAttrString(activedirectory.DistinguishedName)
// if sidok {
// domains[dn] = domainSID
// }
// return true
// })

// ao.Iterate(func(o *engine.Object) bool {
// if o.HasAttr(engine.ObjectSid) && o.SID().Component(2) == 21 && !o.HasAttr(engine.DistinguishedName) && o.HasAttr(engine.DomainContext) {
// // An unknown SID, is it ours or from another domain?
// ourDomainDN := o.OneAttrString(engine.DomainContext)
// ourDomainSid, domainfound := domains[ourDomainDN]
// if !domainfound {
// return true
// }

// if o.SID().StripRID() == ourDomainSid {
// // ui.Debug().Msgf("Found a 'dangling' local SID object %v. This is either a SID from a deleted object (most likely) or hardened objects that are not readable with the account used to dump data.", o.SID())
// } else {
// // ui.Debug().Msgf("Found a 'lost' foreign SID object %v, adding it as a synthetic Foreign-Security-Principal", o.SID())
// o.SetFlex(
// engine.DistinguishedName, engine.AttributeValueString(o.SID().String()+",CN=ForeignSecurityPrincipals,"+ourDomainDN),
// engine.ObjectCategorySimple, "Foreign-Security-Principal",
// engine.DataLoader, "Autogenerated",
// )
// }
// }
// return true
// })
// },
// "Creation of synthetic Foreign-Security-Principal objects",
// engine.AfterMergeLow)

Loader.AddProcessor(func(ao *engine.Objects) {
ao.Iterate(func(machine *engine.Object) bool {
Expand Down Expand Up @@ -1559,21 +1560,18 @@ func init() {
memberobject, found := ao.Find(engine.DistinguishedName, member)
if !found {
var sid engine.AttributeValueSID
var category string
if stringsid, _, found := strings.Cut(member.String(), ",CN=ForeignSecurityPrincipals,"); found {
// We can figure out what the SID is
if c, err := windowssecurity.ParseStringSID(stringsid); err == nil {
sid = engine.AttributeValueSID(c)
category = "Foreign-Security-Principal"
member = nil
}
ui.Info().Msgf("Missing Foreign-Security-Principal: %v is a member of %v, which is not found - adding enhanced synthetic group", object.DN(), member)
} else {
ui.Warn().Msgf("Possible hardening? %v is a member of %v, which is not found - adding synthetic group. Your analysis will be degraded, try dumping with Domain Admin rights.", object.DN(), member)
}
memberobject = engine.NewObject(
engine.IgnoreBlanks,
engine.DistinguishedName, member,
engine.ObjectCategorySimple, category,
engine.ObjectSid, sid,
engine.DataLoader, "Autogenerated",
)
Expand All @@ -1589,6 +1587,26 @@ func init() {
engine.AfterMergeLow,
)

Loader.AddProcessor(func(ao *engine.Objects) {
ao.Iterate(func(o *engine.Object) bool {
// Only for containers and org units
if o.Type() != engine.ObjectTypeUser {
return true
}

sd, err := o.SecurityDescriptor()
if err != nil {
return true
}
for index, acl := range sd.DACL.Entries {
if sd.DACL.IsObjectClassAccessAllowed(index, o, engine.RIGHT_DS_WRITE_PROPERTY, AttributeUserAccountControlGUID, ao) {
ao.FindOrAddAdjacentSID(acl.SID, o).EdgeTo(o, activedirectory.EdgeWriteUserAccountControl)
}
}
return true
})
}, "Permissions that lets someone modify userAccountControl", engine.BeforeMergeFinal)

Loader.AddProcessor(func(ao *engine.Objects) {
ao.IterateParallel(func(o *engine.Object) bool {
// Object that is member of something
Expand All @@ -1610,32 +1628,34 @@ func init() {
engine.AfterMerge,
)

Loader.AddProcessor(func(ao *engine.Objects) {
ao.Filter(func(o *engine.Object) bool {
return o.Type() == engine.ObjectTypeForeignSecurityPrincipal
}).Iterate(func(foreign *engine.Object) bool {
sid := foreign.SID()
if sid.IsNull() {
ui.Error().Msgf("Found a foreign security principal with no SID %v", foreign.Label())
return true
}
if sid.Component(2) == 21 {
if sources, found := ao.FindMulti(engine.ObjectSid, engine.AttributeValueSID(sid)); found {
sources.Iterate(func(source *engine.Object) bool {
if source.Type() != engine.ObjectTypeForeignSecurityPrincipal {
source.EdgeToEx(foreign, activedirectory.EdgeForeignIdentity, true)
}
return true
})
/*
Loader.AddProcessor(func(ao *engine.Objects) {
ao.Filter(func(o *engine.Object) bool {
return o.Type() == engine.ObjectTypeForeignSecurityPrincipal
}).Iterate(func(foreign *engine.Object) bool {
sid := foreign.SID()
if sid.IsNull() {
ui.Error().Msgf("Found a foreign security principal with no SID %v", foreign.Label())
return true
}
} else {
ui.Warn().Msgf("Found a foreign security principal %v with an non type 21 SID %v", foreign.DN(), sid.String())
}
return true
})
}, "Link foreign security principals to their native objects",
engine.AfterMerge,
)
if sid.Component(2) == 21 {
if sources, found := ao.FindMulti(engine.ObjectSid, engine.AttributeValueSID(sid)); found {
sources.Iterate(func(source *engine.Object) bool {
if source.Type() != engine.ObjectTypeForeignSecurityPrincipal {
source.EdgeToEx(foreign, activedirectory.EdgeForeignIdentity, true)
}
return true
})
}
} else {
ui.Warn().Msgf("Found a foreign security principal %v with an non type 21 SID %v", foreign.DN(), sid.String())
}
return true
})
}, "Link foreign security principals to their native objects",
engine.AfterMerge,
)
*/

Loader.AddProcessor(func(ao *engine.Objects) {
var warnlines int
Expand Down
5 changes: 1 addition & 4 deletions modules/integrations/activedirectory/analyze/knownsids.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ func FindWellKnown(ao *engine.Objects, s windowssecurity.SID) *engine.Object {
results, _ := ao.FindMulti(engine.ObjectSid, engine.AttributeValueSID(s))
var result *engine.Object
results.Iterate(func(o *engine.Object) bool {
if result == nil || result.Type() == engine.ObjectTypeForeignSecurityPrincipal {
// Prefer non-FSP object
result = o
}
result = o
return true
})
return result
Expand Down
Loading

0 comments on commit a69c964

Please sign in to comment.