Skip to content

Commit

Permalink
Improved merges across domains, and performance. Added experiemntal "…
Browse files Browse the repository at this point in the history
…limitattributes" on analysis for only loading attributes the engine *needs* into memory
  • Loading branch information
lkarlslund committed May 20, 2022
1 parent 22aad63 commit 18ee88a
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 122 deletions.
150 changes: 60 additions & 90 deletions modules/engine/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,118 +670,88 @@ func (os *Objects) FindOrAddSID(s windowssecurity.SID) *Object {
}

func (os *Objects) FindOrAddAdjacentSID(s windowssecurity.SID, r *Object) *Object {
os.objectmutex.RLock() // Prevent anyone from adding to objects while we're searching
result := os.FindAdjacentSID(s, r)
if result != nil {
os.objectmutex.RUnlock()
return result
}

os.objectmutex.RUnlock()

os.objectmutex.Lock()

// Did someone beat us to it?
result = os.FindAdjacentSID(s, r)
if result != nil {
os.objectmutex.Unlock()
return result
}

// Not found, we have write lock so create it
no := NewObject(
IgnoreBlanks,
ObjectSid, AttributeValueSID(s),
DomainPart, r.Attr(DomainPart),
UniqueSource, r.Attr(UniqueSource),
)

// Is it part of our own domain?
if s.Component(2) == 21 {
// Improve performance perhaps ...
dp := r.OneAttr(DomainPart)

domain, _ := os.FindTwoMulti(
DistinguishedName, dp,
ObjectClass, AttributeValueString("domain"),
)

if len(domain) == 1 {
ds := domain[0].SID()

if s.StripRID() != ds {
// Foreign security principal
no.SetFlex(DistinguishedName, s.String()+",CN=ForeignSecurityPrincipals,"+dp.String(),
ObjectCategorySimple, "Foreign-Security-Principal")
switch s.Component(2) {
case 21: // Full "domain" SID
result, _ := os.FindMultiOrAdd(ObjectSid, AttributeValueSID(s), func() *Object {
no := NewObject(
ObjectSid, AttributeValueSID(s),
MetaDataSource, "FindOrAddAdjacentSID",
IgnoreBlanks,
DomainPart, r.Attr(DomainPart),
)
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 dp := r.OneAttrString(DomainPart); dp != "" {
no.SetFlex(DistinguishedName, "CN="+s.String()+",CN=ForeignSecurityPrincipals,"+dp)
}
}
}
return no
})
return result[0]
default:
if r.HasAttr(DomainPart) {
// From outside, we need to find the domain part
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), DomainPart, r.OneAttr(DomainPart)); found {
return o[0]
}
}
// From inside same source, that is easy
if r.HasAttr(UniqueSource) {
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), UniqueSource, r.OneAttr(UniqueSource)); found {
return o[0]
}
}

}

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

if no2 := os.FindAdjacentSID(s, r); no2 == nil {
os.ReindexObject(no, false)
os.FindAdjacentSID(s, r)
panic("Failed to find newly created object")
if s.Component(2) != 21 {
no.SetFlex(
IgnoreBlanks,
DomainPart, r.Attr(DomainPart),
UniqueSource, r.Attr(UniqueSource),
)
}

os.objectmutex.Unlock()
os.Add(no)

return no
}

func (os *Objects) FindAdjacentSID(s windowssecurity.SID, r *Object) *Object {
/*
func (os *Objects) findAdjacentSID(s windowssecurity.SID, r *Object) *Object {
// These are the "local" groups shared between DCs
// We need to find the right one, and we'll use the DomainPart for this
if r.HasAttr(DomainPart) {
// From outside, we need to find the domain part
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), DomainPart, r.OneAttr(DomainPart)); found {
return findMostLocal(o)
}
}

// From inside same source, that is easy
if r.HasAttr(UniqueSource) {
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), UniqueSource, r.OneAttr(UniqueSource)); found {
return findMostLocal(o)
}
}

// Are we looking for a SID in our own domain?
if r.HasAttr(ObjectSid) {
if s.StripRID() == r.SID().StripRID() {
// Same domain
if os, found := os.FindMulti(ObjectSid, AttributeValueSID(s)); found {
for _, o := range os {
if o.Type() != ObjectTypeForeignSecurityPrincipal {
return o
}
}
switch s.Component(2) {
case 21: // Full "domain" SID
return os.Find(s)
default:
if r.HasAttr(DomainPart) {
// From outside, we need to find the domain part
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), DomainPart, r.OneAttr(DomainPart)); found {
return findMostLocal(o)
}
} else {
// Other domain ... hmm
if os, found := os.FindMulti(ObjectSid, AttributeValueSID(s)); found {
if len(os) == 1 {
return os[0]
}
for _, o := range os {
if o.Type() != ObjectTypeForeignSecurityPrincipal {
return o
}
if strings.Contains(o.DN(), ",CN=WellKnown Security Principals,") {
return o
}
}
log.Warn().Msgf("Found multiple SIDs for %s, returning first found", s.String())
return os[0]
}
// From inside same source, that is easy
if r.HasAttr(UniqueSource) {
if o, found := os.FindTwoMulti(ObjectSid, AttributeValueSID(s), UniqueSource, r.OneAttr(UniqueSource)); found {
return findMostLocal(o)
}
}
}
// Not found
return nil
}
*/

func findMostLocal(os []*Object) *Object {
if len(os) == 0 {
Expand Down
14 changes: 12 additions & 2 deletions modules/integrations/activedirectory/analyze/adloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ var (
importhardened = analyze.Command.Flags().Bool("importhardened", false, "Import hardened objects (without objectclass attribute)")
warnhardened = analyze.Command.Flags().Bool("warnhardened", false, "Warn about hardened objects (without objectclass attribute)")

limitattributes = analyze.Command.Flags().Bool("limitattributes", false, "Limit attributes to import (saves memory, experimental)")

adsource = engine.AttributeValueString("Active Directory dumps")
Loader = engine.AddLoader(func() engine.Loader { return (&ADLoader{}) })

defaultNamingContext = engine.NewAttribute("defaultNamingContext")
)

type convertqueueitem struct {
Expand Down Expand Up @@ -88,8 +92,13 @@ func (ld *ADLoader) Init() error {
}
}

if category, found := item.object.Attributes["objectCategory"]; found && strings.HasPrefix(category[0], "CN=Foreign-Security-Principal") {
// We don't want to import this
// continue
}

// Convert
o := item.object.ToObject()
o := item.object.ToObject(*limitattributes)

if !ld.importcnf && strings.Contains(o.DN(), "\\0ACNF:") {
continue // skip conflict object
Expand Down Expand Up @@ -202,9 +211,10 @@ func (ld *ADLoader) Close() ([]*engine.Objects, error) {
continue
}

domain := rootdse.OneAttrString(engine.A("defaultNamingContext"))
domain := rootdse.OneAttrString(defaultNamingContext)
domainval := engine.AttributeValueOne{Value: engine.AttributeValueString(domain)}

// Indicate from which domain we saw this
for _, o := range ao.Slice() {
o.Set(engine.UniqueSource, domainval)
}
Expand Down
59 changes: 38 additions & 21 deletions modules/integrations/activedirectory/analyze/analyze-ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,9 @@ func init() {
if len(part) < 3 || !strings.EqualFold("dc=", part[:3]) {
break
}
if strings.EqualFold("DC=ForestDNSZones", part) || strings.EqualFold("DC=DomainDNSZones", part) {
break
}
lastpart = i
}

Expand Down Expand Up @@ -1241,32 +1244,46 @@ func init() {

Loader.AddProcessor(func(ao *engine.Objects) {
// Find all the DomainDNS objects, and find the domain object
for _, domaindns := range ao.Filter(func(o *engine.Object) bool {
return o.Type() == engine.ObjectTypeDomainDNS && o.HasAttr(engine.ObjectSid)
}).Slice() {
ourDomainDN := domaindns.DN()
ourDomainSid := domaindns.SID()

for _, o := range ao.Slice() {
if o.HasAttr(engine.ObjectSid) && o.SID().Component(2) == 21 && !o.HasAttr(engine.DistinguishedName) {
// An unknown SID, is it ours or from another domain?
if o.SID().StripRID() == ourDomainSid {
log.Warn().Msgf("Found a 'lost' local SID object %v, but not taking action (don't know where to place it). This will affect your analysis results, try dumping as Domain Admin!", o.SID())
} else {
log.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.MetaDataSource, "Autogenerated",
)
}
}
domains := make(map[string]windowssecurity.SID)

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

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

for _, domaindnsobject := range domaindnsobjects {
domainSID, sidok := domaindnsobject.OneAttrRaw(activedirectory.ObjectSid).(windowssecurity.SID)
dn := domaindnsobject.OneAttrString(activedirectory.DistinguishedName)
if sidok {
domains[dn] = domainSID
}
}

for _, o := range ao.Slice() {
if o.HasAttr(engine.ObjectSid) && o.SID().Component(2) == 21 && !o.HasAttr(engine.DistinguishedName) && o.HasAttr(engine.DomainPart) {
// An unknown SID, is it ours or from another domain?
ourDomainDN := o.OneAttrString(engine.DomainPart)
ourDomainSid, domainfound := domains[ourDomainDN]
if !domainfound {
continue
}

if o.SID().StripRID() == ourDomainSid {
log.Warn().Msgf("Found a 'lost' local SID object %v, but not taking action (don't know where to place it). This will affect your analysis results, try dumping as Domain Admin!", o.SID())
} else {
log.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.MetaDataSource, "Autogenerated",
)
}
}
}
},
"Creation of synthetic Foreign-Security-Principal objects",
engine.BeforeMerge)
engine.AfterMergeLow)

Loader.AddProcessor(func(ao *engine.Objects) {
creatorowner, found := ao.Find(engine.ObjectSid, engine.AttributeValueSID(windowssecurity.CreatorOwnerSID))
Expand Down
20 changes: 13 additions & 7 deletions modules/integrations/activedirectory/rawobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,29 @@ type RawObject struct {
Attributes map[string][]string
}

func (r *RawObject) init() {
func (r *RawObject) Init() {
r.DistinguishedName = ""
r.Attributes = make(map[string][]string)
}

func (r *RawObject) ToObject() *engine.Object {
func (r *RawObject) ToObject(onlyKnownAttributes bool) *engine.Object {
result := engine.NewObject(
DistinguishedName, engine.AttributeValueString(r.DistinguishedName),
) // This is possibly repeated in member attributes, so dedup it
for name, values := range r.Attributes {
if len(values) == 0 || (len(values) == 1 && values[0] == "") {
continue
}
attribute := engine.NewAttribute(name)

var attribute engine.Attribute
if onlyKnownAttributes {
attribute = engine.LookupAttribute(name)
if attribute == engine.NonExistingAttribute {
continue
}
} else {
attribute = engine.NewAttribute(name)
}

encodedvals := EncodeAttributeData(attribute, values)
if len(encodedvals) > 0 {
Expand All @@ -44,10 +53,7 @@ func (r *RawObject) ToObject() *engine.Object {
}

func (r *RawObject) IngestLDAP(source *ldap.Entry) error {
r.init()
// if len(source.Attributes) == 0 {
// return errors.New("No attributes in object, ignoring")
// }
r.Init()
r.DistinguishedName = source.DN
for _, attr := range source.Attributes {
r.Attributes[attr.Name] = attr.Values
Expand Down
4 changes: 2 additions & 2 deletions modules/windowssecurity/wellknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ var (
"S-1-5-13": "Terminal Server Users",
"S-1-5-14": "Remote Interactive Logon",
"S-1-5-15": "This Organization",
"S-1-5-17": "This Organization",
"S-1-5-17": "IUSR",
"S-1-5-18": "Local System",
"S-1-5-19": "Local Service",
"S-1-5-2": "Network",
"S-1-5-20": "NT Authority",
"S-1-5-20": "Network Service",
"S-1-5-3": "Batch",
"S-1-5-32-544": "Administrators",
"S-1-5-32-545": "Users",
Expand Down

0 comments on commit 18ee88a

Please sign in to comment.