diff --git a/modules/analyze/html/index.html b/modules/analyze/html/index.html index 27e5cee..73344c6 100644 --- a/modules/analyze/html/index.html +++ b/modules/analyze/html/index.html @@ -197,7 +197,7 @@ + data-bs-title='Expanding these groups usually gives an unreasonable amount of data in the graph'>Don't expand "Authenticated Users" / "Everyone" @@ -347,7 +347,7 @@ diff --git a/modules/analyze/webservicefuncs.go b/modules/analyze/webservicefuncs.go index c4c8e05..76770bd 100644 --- a/modules/analyze/webservicefuncs.go +++ b/modules/analyze/webservicefuncs.go @@ -680,7 +680,7 @@ func analysisfuncs(ws *webservice) { if object.Type() == engine.ObjectTypeUser && object.OneAttrString(engine.MetaWorkstation) != "1" && object.OneAttrString(engine.MetaServer) != "1" && - object.OneAttrString(engine.MetaAccountDisabled) != "1" { + object.OneAttrString(engine.MetaAccountActive) == "1" { lastlogin, _ := object.AttrTime(activedirectory.LastLogon) lastlogints, _ := object.AttrTime(activedirectory.LastLogonTimestamp) last, _ := object.AttrTime(activedirectory.PwdLastSet) @@ -713,9 +713,9 @@ func analysisfuncs(ws *webservice) { Unconstrained: object.OneAttrString(engine.MetaUnconstrainedDelegation) == "1", Workstation: object.OneAttrString(engine.MetaWorkstation) == "1", Server: object.OneAttrString(engine.MetaServer) == "1", - Enabled: object.OneAttrString(engine.MetaAccountDisabled) != "1", + Enabled: object.OneAttrString(engine.MetaAccountActive) == "1", CantChangePwd: object.OneAttrString(engine.MetaPasswordCantChange) == "1", - NoExpirePwd: object.OneAttrString(engine.MetaPasswordNoExpire) == "1", + NoExpirePwd: object.OneAttrString(engine.MetaPasswordNeverExpires) == "1", NoRequirePwd: object.OneAttrString(engine.MetaPasswordNotRequired) == "1", HasLAPS: object.OneAttrString(engine.MetaLAPSInstalled) == "1", } diff --git a/modules/engine/attributes.go b/modules/engine/attributes.go index 05b8515..c9f4e63 100644 --- a/modules/engine/attributes.go +++ b/modules/engine/attributes.go @@ -41,6 +41,7 @@ const ( AttributeTypeSID AttributeTypeGUID AttributeTypeBlob + AttributeTypeSecurityDescriptor ) type mergeapproverinfo struct { @@ -58,7 +59,7 @@ var ( DistinguishedName = NewAttribute("distinguishedName").Single().Unique() ObjectClass = NewAttribute("objectClass") ObjectCategory = NewAttribute("objectCategory").Single() - ObjectCategorySimple = NewAttribute("objectCategorySimple").Single() + Type = NewAttribute("type").Single() Name = NewAttribute("name").Single() DisplayName = NewAttribute("displayName").Single() LDAPDisplayName = NewAttribute("lDAPDisplayName").Single() @@ -94,10 +95,10 @@ var ( MetaHasSPN = NewAttribute("_hasspn") MetaPasswordAge = NewAttribute("_passwordage") MetaLastLoginAge = NewAttribute("_lastloginage") - MetaAccountDisabled = NewAttribute("_accountdisabled") - MetaPasswordCantChange = NewAttribute("_passwordcantchange") - MetaPasswordNotRequired = NewAttribute("_passwordnotrequired") - MetaPasswordNoExpire = NewAttribute("_passwordnoexpire") + MetaAccountActive = NewAttribute("accountActive") + MetaPasswordCantChange = NewAttribute("passwordCantChange") + MetaPasswordNotRequired = NewAttribute("passwordNotRequired").Type(AttributeTypeBool) + MetaPasswordNeverExpires = NewAttribute("passwordNeverExpires").Type(AttributeTypeBool) MetaLinux = NewAttribute("_linux") MetaWindows = NewAttribute("_windows") MetaWorkstation = NewAttribute("_workstation") diff --git a/modules/engine/attributevalue.go b/modules/engine/attributevalue.go index 7cc29a9..a1e053a 100644 --- a/modules/engine/attributevalue.go +++ b/modules/engine/attributevalue.go @@ -275,3 +275,19 @@ func (as AttributeValueGUID) Raw() any { func (as AttributeValueGUID) IsZero() bool { return uuid.UUID(as).IsNil() } + +type AttributeValueSecurityDescriptor struct { + SD *SecurityDescriptor +} + +func (as AttributeValueSecurityDescriptor) String() string { + return as.SD.StringNoLookup() +} + +func (as AttributeValueSecurityDescriptor) Raw() any { + return as.SD +} + +func (as AttributeValueSecurityDescriptor) IsZero() bool { + return len(as.SD.DACL.Entries) == 0 +} diff --git a/modules/engine/globals.go b/modules/engine/globals.go deleted file mode 100644 index afe29fa..0000000 --- a/modules/engine/globals.go +++ /dev/null @@ -1,14 +0,0 @@ -package engine - -import ( - "sync" -) - -var ( - securitydescriptorcachemutex sync.RWMutex - securityDescriptorCache = make(map[uint64]*SecurityDescriptor) - - // AllRights = make(map[uuid.UUID]*Object) // Extented-Rights from Configuration - rightsGUID -> object - // AllSchemaClasses = make(map[uuid.UUID]*Object) // schemaIdGUID -> object - // AllSchemaAttributes = make(map[uuid.UUID]*Object) // attribute ... -) diff --git a/modules/engine/object.go b/modules/engine/object.go index b228969..b588949 100644 --- a/modules/engine/object.go +++ b/modules/engine/object.go @@ -11,7 +11,6 @@ import ( "sync/atomic" "time" - "github.com/OneOfOne/xxhash" gsync "github.com/SaveTheRbtz/generic-sync-map-go" "github.com/gofrs/uuid" "github.com/icza/gox/stringsx" @@ -439,7 +438,7 @@ func (o *Object) Type() ObjectType { return o.objecttype } - category := o.Attr(ObjectCategorySimple) + category := o.Attr(Type) if category.Len() == 0 { return ObjectTypeOther @@ -467,8 +466,8 @@ func (o *Object) AttrString(attr Attribute) []string { } func (o *Object) AttrRendered(attr Attribute) AttributeValues { - if attr == ObjectCategory && o.HasAttr(ObjectCategorySimple) { - return o.Attr(ObjectCategorySimple) + if attr == ObjectCategory && o.HasAttr(Type) { + return o.Attr(Type) } return o.Attr(attr) } @@ -795,6 +794,10 @@ func (o *Object) set(a Attribute, values AttributeValues) { ui.Warn().Msgf("Setting multiple values on non-multival attribute %v: %v", a.String(), strings.Join(values.StringSlice(), ", ")) } + if a == NTSecurityDescriptor { + o.sdcache = values.First().Raw().(*SecurityDescriptor) + } + if a == DownLevelLogonName { // There's been so many problems with DLLN that we're going to just check for these if values.Len() != 1 { @@ -835,24 +838,11 @@ func (o *Object) set(a Attribute, values AttributeValues) { }) } - if a == ObjectCategory || a == ObjectCategorySimple { + if a == ObjectCategory || a == Type { // Clear objecttype cache attribute o.objecttype = 0 } - // Cache the NTSecurityDescriptor - if a == NTSecurityDescriptor { - if values.Len() != 1 { - panic(fmt.Sprintf("Asked to set %v security descriptors on %v", values.Len(), o.Label())) - } else { - sd := values.First() - if err := o.cacheSecurityDescriptor([]byte(sd.Raw().(string))); err != nil { - ui.Error().Msgf("Problem parsing security descriptor for %v: %v", o.DN(), err) - } - } - return // We dont store the raw version, just the decoded one, KTHX - } - // Deduplication of values switch vs := values.(type) { case AttributeValueSlice: @@ -1008,32 +998,6 @@ func (o *Object) SecurityDescriptor() (*SecurityDescriptor, error) { var ErrEmptySecurityDescriptorAttribute = errors.New("empty nTSecurityDescriptor attribute!?") -// Parse and cache security descriptor -func (o *Object) cacheSecurityDescriptor(rawsd []byte) error { - if len(rawsd) == 0 { - return ErrEmptySecurityDescriptorAttribute - } - - securitydescriptorcachemutex.RLock() - cacheindex := xxhash.Checksum64(rawsd) - if sd, found := securityDescriptorCache[cacheindex]; found { - securitydescriptorcachemutex.RUnlock() - o.sdcache = sd - return nil - } - securitydescriptorcachemutex.RUnlock() - - securitydescriptorcachemutex.Lock() - sd, err := ParseSecurityDescriptor([]byte(rawsd)) - if err == nil { - o.sdcache = &sd - securityDescriptorCache[cacheindex] = &sd - } - - securitydescriptorcachemutex.Unlock() - return err -} - // Return the object's SID func (o *Object) SID() windowssecurity.SID { if !o.sidcached.Load() { diff --git a/modules/engine/processing.go b/modules/engine/processing.go index 059ac65..5bf91f9 100644 --- a/modules/engine/processing.go +++ b/modules/engine/processing.go @@ -47,7 +47,7 @@ func Merge(aos []*Objects) (*Objects, error) { globalobjects := NewObjects() globalroot := NewObject( Name, AttributeValueString("Adalanche root node"), - ObjectCategorySimple, AttributeValueString("Root"), + Type, AttributeValueString("Root"), ) globalobjects.SetRoot(globalroot) orphancontainer := NewObject(Name, AttributeValueString("Orphans")) diff --git a/modules/engine/securitydescriptor.go b/modules/engine/securitydescriptor.go index 935c6b9..1f05385 100644 --- a/modules/engine/securitydescriptor.go +++ b/modules/engine/securitydescriptor.go @@ -316,6 +316,14 @@ func (a ACL) String(ao *Objects) string { return result } +func (a ACL) StringNoLookup() string { + result := fmt.Sprintf("ACL revision %v:\n", a.Revision) + for _, ace := range a.Entries { + result += "ACE: " + ace.StringNoLookup() + "\n" + } + return result +} + func ParseACLentry(odata []byte) (ACE, []byte, error) { var ace ACE var err error @@ -620,6 +628,113 @@ func (a ACE) String(ao *Objects) string { return result } +func (a ACE) StringNoLookup() string { + var result string + switch a.Type { + case ACETYPE_ACCESS_ALLOWED: + result += "Allow" + case ACETYPE_ACCESS_ALLOWED_OBJECT: + result += "Allow object" + case ACETYPE_ACCESS_DENIED: + result += "Deny" + case ACETYPE_ACCESS_DENIED_OBJECT: + result += "Deny object" + default: + result += fmt.Sprintf("Unknown %v", a.Type) + } + + result += " " + a.SID.String() + + if a.Flags&OBJECT_TYPE_PRESENT != 0 { + // ui.Debug().Msgf("Looking for right %v", a.ObjectType) + result += " OBJECT_TYPE_PRESENT" + result += " " + a.ObjectType.String() + } + if a.Flags&INHERITED_OBJECT_TYPE_PRESENT != 0 { + // ui.Debug().Msgf("Looking for right %v", a.InheritedObjectType) + // if o, found := AllRights[a.InheritedObjectType]; found { + // result += fmt.Sprintf(" inherited RIGHT %v (%v)", o.OneAttr(Name), a.InheritedObjectType) + // } else + result += "INHERITED_OBJECT_TYPE_PRESENT " + if a.InheritedObjectType.IsNil() { + result += a.InheritedObjectType.String() + } else { + result += " " + a.InheritedObjectType.String() + } + } + + result += fmt.Sprintf("MASK %08x", a.Mask) + + var rights []string + if a.Mask&RIGHT_GENERIC_READ == RIGHT_GENERIC_READ { + rights = append(rights, "GENERIC_READ") + } + if a.Mask&RIGHT_GENERIC_WRITE == RIGHT_GENERIC_WRITE { + rights = append(rights, "GENERIC_WRITE") + } + if a.Mask&RIGHT_GENERIC_EXECUTE == RIGHT_GENERIC_EXECUTE { + rights = append(rights, "GENERIC_EXECUTE") + } + if a.Mask&RIGHT_GENERIC_ALL == RIGHT_GENERIC_ALL { + rights = append(rights, "GENERIC_ALL") + } + if a.Mask&RIGHT_MAXIMUM_ALLOWED == RIGHT_MAXIMUM_ALLOWED { + rights = append(rights, "MAXIMUM_ALLOWED") + } + if a.Mask&RIGHT_ACCESS_SYSTEM_SECURITY == RIGHT_ACCESS_SYSTEM_SECURITY { + rights = append(rights, "ACCESS_SYSTEM_SECURITY") + } + if a.Mask&RIGHT_SYNCRONIZE == RIGHT_SYNCRONIZE { + rights = append(rights, "SYNCRONIZE") + } + if a.Mask&RIGHT_WRITE_OWNER == RIGHT_WRITE_OWNER { + rights = append(rights, "WRITE_OWNER") + } + if a.Mask&RIGHT_WRITE_DACL == RIGHT_WRITE_DACL { + rights = append(rights, "WRITE_DACL") + } + if a.Mask&RIGHT_READ_CONTROL == RIGHT_READ_CONTROL { + rights = append(rights, "READ_CONTROL") + } + if a.Mask&RIGHT_DELETE == RIGHT_DELETE { + rights = append(rights, "DELETE") + } + if a.Mask&RIGHT_DS_CONTROL_ACCESS == RIGHT_DS_CONTROL_ACCESS { + rights = append(rights, "DS_CONTROL_ACCESS") + } + + if a.Mask&RIGHT_DS_VOODOO_BIT == RIGHT_DS_VOODOO_BIT { + rights = append(rights, "DS_VOODOO_BIT") + } + + if a.Mask&RIGHT_DS_LIST_OBJECT == RIGHT_DS_LIST_OBJECT { + rights = append(rights, "DS_LIST_OBJECT") + } + if a.Mask&RIGHT_DS_DELETE_TREE == RIGHT_DS_DELETE_TREE { + rights = append(rights, "DS_DELETE_TREE") + } + if a.Mask&RIGHT_DS_WRITE_PROPERTY == RIGHT_DS_WRITE_PROPERTY { + rights = append(rights, "DS_WRITE_PROPERTY") + } + if a.Mask&RIGHT_DS_READ_PROPERTY == RIGHT_DS_READ_PROPERTY { + rights = append(rights, "DS_READ_PROPERTY") + } + if a.Mask&RIGHT_DS_WRITE_PROPERTY_EXTENDED == RIGHT_DS_WRITE_PROPERTY_EXTENDED { + rights = append(rights, "DS_WRITE_PROPERTY_EXTENDED") + } + if a.Mask&RIGHT_DS_LIST_CONTENTS == RIGHT_DS_LIST_CONTENTS { + rights = append(rights, "DS_LIST_CONTENTS") + } + if a.Mask&RIGHT_DS_DELETE_CHILD == RIGHT_DS_DELETE_CHILD { + rights = append(rights, "DS_DELETE_CHILD") + } + if a.Mask&RIGHT_DS_CREATE_CHILD == RIGHT_DS_CREATE_CHILD { + rights = append(rights, "DS_CREATE_CHILD") + } + result += " " + strings.Join(rights, " | ") + return result +} + type SecurityDescriptor struct { Owner windowssecurity.SID Group windowssecurity.SID @@ -741,3 +856,52 @@ func (sd SecurityDescriptor) String(ao *Objects) string { } return result } + +func (sd SecurityDescriptor) StringNoLookup() string { + var result string + var flags []string + if sd.Control&CONTROLFLAG_OWNER_DEFAULTED != 0 { + flags = append(flags, "OWNER_DEFAULTED") + } + if sd.Control&CONTROLFLAG_GROUP_DEFAULTED != 0 { + flags = append(flags, "GROUP_DEFAULTED") + } + if sd.Control&CONTROLFLAG_DACL_PRESENT != 0 { + flags = append(flags, "DACL_PRESENT") + } + if sd.Control&CONTROLFLAG_DACL_DEFAULTED != 0 { + flags = append(flags, "DACL_DEFAULTED") + } + if sd.Control&CONTROLFLAG_SACL_PRESENT != 0 { + flags = append(flags, "SACL_PRESENT") + } + if sd.Control&CONTROLFLAG_SACL_DEFAULTED != 0 { + flags = append(flags, "SACL_DEFAULTED") + } + if sd.Control&CONTROLFLAG_DACL_AUTO_INHERITED != 0 { + flags = append(flags, "DACL_AUTO_INHERITED") + } + if sd.Control&CONTROLFLAG_SACL_AUTO_INHERITED != 0 { + flags = append(flags, "SACL_AUTO_INHERITED") + } + if sd.Control&CONTROLFLAG_DACL_PROTECTED != 0 { + flags = append(flags, "DACL_PROTECTED") + } + if sd.Control&CONTROLFLAG_SACL_PROTECTED != 0 { + flags = append(flags, "SACL_PROTECTED") + } + result = "SecurityDescriptor: " + strings.Join(flags, " | ") + "\n" + if !sd.Owner.IsNull() { + result += "Owner: " + sd.Owner.String() + "\n" + } + if !sd.Group.IsNull() { + result += "Group: " + sd.Group.String() + "\n" + } + if sd.Control&CONTROLFLAG_DACL_PRESENT != 0 { + result += "DACL:\n" + sd.DACL.StringNoLookup() + } + if sd.Control&CONTROLFLAG_SACL_PRESENT != 0 { + result += "DACL:\n" + sd.SACL.StringNoLookup() + } + return result +} diff --git a/modules/engine/securitydescriptorcache.go b/modules/engine/securitydescriptorcache.go new file mode 100644 index 0000000..ca2bc06 --- /dev/null +++ b/modules/engine/securitydescriptorcache.go @@ -0,0 +1,36 @@ +package engine + +import ( + "sync" + + "github.com/OneOfOne/xxhash" +) + +var ( + securitydescriptorcachemutex sync.RWMutex + securityDescriptorCache = make(map[uint64]*SecurityDescriptor) +) + +// Parse and cache security descriptor +func CacheOrParseSecurityDescriptor(rawsd []byte) (*SecurityDescriptor, error) { + if len(rawsd) == 0 { + return nil, ErrEmptySecurityDescriptorAttribute + } + + securitydescriptorcachemutex.RLock() + cacheindex := xxhash.Checksum64(rawsd) + if sd, found := securityDescriptorCache[cacheindex]; found { + securitydescriptorcachemutex.RUnlock() + return sd, nil + } + securitydescriptorcachemutex.RUnlock() + + securitydescriptorcachemutex.Lock() + sd, err := ParseSecurityDescriptor([]byte(rawsd)) + if err == nil { + securityDescriptorCache[cacheindex] = &sd + } + + securitydescriptorcachemutex.Unlock() + return &sd, err +} diff --git a/modules/integrations/activedirectory/analyze/adloader.go b/modules/integrations/activedirectory/analyze/adloader.go index 03e7097..2c20a42 100644 --- a/modules/integrations/activedirectory/analyze/adloader.go +++ b/modules/integrations/activedirectory/analyze/adloader.go @@ -84,7 +84,7 @@ func (ld *ADLoader) Init() error { if dnc, found := item.object.Attributes["defaultNamingContext"]; found { // There's a special place for people who do this item.object.DistinguishedName = "cn=RootDSE," + dnc[0] - item.object.Attributes["objectCategorySimple"] = []string{"rootdse"} + item.object.Attributes["type"] = []string{"rootdse"} } else { // We want the RootDSE KTHX, but ignore everything else ui.Warn().Msg("Empty DN, ignoring!") diff --git a/modules/integrations/activedirectory/analyze/analyze-ad.go b/modules/integrations/activedirectory/analyze/analyze-ad.go index e4dc960..aa3dab7 100644 --- a/modules/integrations/activedirectory/analyze/analyze-ad.go +++ b/modules/integrations/activedirectory/analyze/analyze-ad.go @@ -23,20 +23,23 @@ var ( DSReplicationSyncronize = uuid.UUID{0x11, 0x31, 0xf6, 0xab, 0x9c, 0x07, 0x11, 0xd1, 0xf7, 0x9f, 0x00, 0xc0, 0x4f, 0xc2, 0xdc, 0xd2} DSReplicationGetChangesInFilteredSet, _ = uuid.FromString("{89e95b76-444d-4c62-991a-0facbeda640c}") - AttributeMember = uuid.UUID{0xbf, 0x96, 0x79, 0xc0, 0x0d, 0xe6, 0x11, 0xd0, 0xa2, 0x85, 0x00, 0xaa, 0x00, 0x30, 0x49, 0xe2} - AttributeSetGroupMembership, _ = uuid.FromString("{BC0AC240-79A9-11D0-9020-00C04FC2D4CF}") - AttributeSIDHistory = uuid.UUID{0x17, 0xeb, 0x42, 0x78, 0xd1, 0x67, 0x11, 0xd0, 0xb0, 0x02, 0x00, 0x00, 0xf8, 0x03, 0x67, 0xc1} + AttributeMember = uuid.UUID{0xbf, 0x96, 0x79, 0xc0, 0x0d, 0xe6, 0x11, 0xd0, 0xa2, 0x85, 0x00, 0xaa, 0x00, 0x30, 0x49, 0xe2} + AttributeSetGroupMembership, _ = uuid.FromString("{BC0AC240-79A9-11D0-9020-00C04FC2D4CF}") + AttributeSIDHistory = uuid.UUID{0x17, 0xeb, 0x42, 0x78, 0xd1, 0x67, 0x11, 0xd0, 0xb0, 0x02, 0x00, 0x00, 0xf8, 0x03, 0x67, 0xc1} + AttributeAllowedToActOnBehalfOfOtherIdentity, _ = uuid.FromString("{3F78C3E5-F79A-46BD-A0B8-9D18116DDC79}") - AttributeMSDSGroupMSAMembership = uuid.UUID{0x88, 0x8e, 0xed, 0xd6, 0xce, 0x04, 0xdf, 0x40, 0xb4, 0x62, 0xb8, 0xa5, 0x0e, 0x41, 0xba, 0x38} - AttributeGPLink, _ = uuid.FromString("{F30E3BBE-9FF0-11D1-B603-0000F80367C1}") - AttributeMSDSKeyCredentialLink, _ = uuid.FromString("{5B47D60F-6090-40B2-9F37-2A4DE88F3063}") - AttributeSecurityGUIDGUID, _ = uuid.FromString("{bf967924-0de6-11d0-a285-00aa003049e2}") - AttributeAltSecurityIdentitiesGUID, _ = uuid.FromString("{00FBF30C-91FE-11D1-AEBC-0000F80367C1}") - 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}") - AttributePwdLastSetGUID, _ = uuid.FromString("{bf967a0a-0de6-11d0-a285-00aa003049e2}") + AttributeAllowedToDelegateTo, _ = uuid.FromString("{800d94d7-b7a1-42a1-b14d-7cae1423d07f}") + + AttributeMSDSGroupMSAMembership = uuid.UUID{0x88, 0x8e, 0xed, 0xd6, 0xce, 0x04, 0xdf, 0x40, 0xb4, 0x62, 0xb8, 0xa5, 0x0e, 0x41, 0xba, 0x38} + AttributeGPLink, _ = uuid.FromString("{F30E3BBE-9FF0-11D1-B603-0000F80367C1}") + AttributeMSDSKeyCredentialLink, _ = uuid.FromString("{5B47D60F-6090-40B2-9F37-2A4DE88F3063}") + AttributeSecurityGUIDGUID, _ = uuid.FromString("{bf967924-0de6-11d0-a285-00aa003049e2}") + AttributeAltSecurityIdentitiesGUID, _ = uuid.FromString("{00FBF30C-91FE-11D1-AEBC-0000F80367C1}") + 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}") + AttributePwdLastSetGUID, _ = uuid.FromString("{bf967a0a-0de6-11d0-a285-00aa003049e2}") ExtendedRightCertificateEnroll, _ = uuid.FromString("{0e10c968-78fb-11d2-90d4-00c04f79dc55}") ExtendedRightCertificateAutoEnroll, _ = uuid.FromString("{a05b8cc2-17bc-4802-a710-e7c15ab866a2}") @@ -64,9 +67,10 @@ var ( EdgePublishesCertificateTemplate = engine.NewEdge("PublishCertTmpl").Tag("Informative").RegisterProbabilityCalculator(activedirectory.NotAChance) - NetBIOSName = engine.NewAttribute("nETBIOSName") - NCName = engine.NewAttribute("nCName") - DNSRoot = engine.NewAttribute("dnsRoot") + NetBIOSName = engine.NewAttribute("nETBIOSName") + NCName = engine.NewAttribute("nCName") + DNSRoot = engine.NewAttribute("dnsRoot") + MemberOfRecursive = engine.NewAttribute("memberOfRecursive") ObjectTypeMachine = engine.NewObjectType("Machine", "Machine") DomainJoinedSID = engine.NewAttribute("domainJoinedSid").Merge() @@ -445,10 +449,11 @@ func init() { }) }, "Indicator that a user can change the ServicePrincipalName attribute (validate write), and then Kerberoast the account", engine.BeforeMergeFinal) + // https://blog.harmj0y.net/activedirectory/the-most-dangerous-user-right-you-probably-have-never-heard-of/ Loader.AddProcessor(func(ao *engine.Objects) { ao.Iterate(func(o *engine.Object) bool { // Only computers - if o.Type() != engine.ObjectTypeComputer { + if o.Type() != engine.ObjectTypeComputer && o.Type() != engine.ObjectTypeUser { return true } sd, err := o.SecurityDescriptor() @@ -457,13 +462,58 @@ func init() { } for index, acl := range sd.DACL.Entries { if sd.DACL.IsObjectClassAccessAllowed(index, o, engine.RIGHT_DS_WRITE_PROPERTY, AttributeAllowedToActOnBehalfOfOtherIdentity, ao) { - ao.FindOrAddAdjacentSID(acl.SID, o).EdgeTo(o, activedirectory.EdgeWriteAllowedToAct) // Success rate? + // This does NOT requires the SeEnableDelegationPrivilege set on the DC for the user doing it!! + ao.FindOrAddAdjacentSID(acl.SID, o).EdgeTo(o, activedirectory.EdgeWriteAllowedToAct) } } return true }) - }, `Modify the msDS-AllowedToActOnBehalfOfOtherIdentity on a computer to enable any SPN enabled user to impersonate anyone else`, engine.BeforeMergeFinal) + }, `Modify the msDS-AllowedToActOnBehalfOfOtherIdentity (Resource Based Constrained Delegation) on an account to enable any SPN enabled user to impersonate it`, engine.BeforeMergeFinal) + EdgeRBCD := engine.NewEdge("RBCD") + Loader.AddProcessor(func(ao *engine.Objects) { + ao.Iterate(func(o *engine.Object) bool { + // Only computers + if o.Type() != engine.ObjectTypeComputer && o.Type() != engine.ObjectTypeUser { + return true + } + o.Attr(activedirectory.MSDSAllowedToActOnBehalfOfOtherIdentity).Iterate(func(val engine.AttributeValue) bool { + // Each of these is a SID, so find that SID and add an edge + sd := val.Raw().(*engine.SecurityDescriptor) + ui.Debug().Msgf("Found msDS-AllowedToActOnBehalfOfOtherIdentity on %v as %v", o.DN(), sd.String(ao)) + for _, acl := range sd.DACL.Entries { + if acl.Type == engine.ACETYPE_ACCESS_ALLOWED { + ao.FindOrAddAdjacentSID(acl.SID, o).EdgeTo(o, EdgeRBCD) + } + } + // o.EdgeTo(ao.FindOrAddAdjacentSID(sid, o), EdgeRBCD) + return true + }) + return true + }) + }, `Someone is listed in the msDS-AllowedToActOnBehalfOfOtherIdentity (Resource Based Constrained Delegation) on an account`, engine.BeforeMergeFinal) + /* + // https://blog.harmj0y.net/activedirectory/the-most-dangerous-user-right-you-probably-have-never-heard-of/ + Loader.AddProcessor(func(ao *engine.Objects) { + ao.Iterate(func(o *engine.Object) bool { + // Only computers + if o.Type() != engine.ObjectTypeComputer { + 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, AttributeAllowedToDelegateTo, ao) { + // Also requires the SeEnableDelegationPrivilege set on the DC for the user doing it!! + ao.FindOrAddAdjacentSID(acl.SID, o).EdgeTo(o, activedirectory.EdgeWriteAllowedToDelegateTo) // Success rate? + } + } + return true + }) + }, `Modify the msDS-AllowedToDelegateTo (Constrained Delegation) on a computer to enable any SPN enabled user to impersonate anyone else`, engine.BeforeMergeFinal) + */ Loader.AddProcessor(func(ao *engine.Objects) { ao.Iterate(func(o *engine.Object) bool { // Only for groups @@ -732,6 +782,8 @@ func init() { }, "Has the Voodoo Bit set", engine.BeforeMergeFinal) Loader.AddProcessor(func(ao *engine.Objects) { + // DCsyncObject := ao.AddNew() + ao.Iterate(func(o *engine.Object) bool { if o.Type() != engine.ObjectTypeDomainDNS { return true @@ -786,7 +838,7 @@ func init() { DomainJoinedSID, sid, engine.IgnoreBlanks, engine.Name, computeraccount.Attr(engine.Name), - activedirectory.ObjectCategorySimple, "Machine", + activedirectory.Type, "Machine", DnsHostName, computeraccount.Attr(DnsHostName), ) // ui.Debug().Msgf("Added machine for SID %v", sid.String()) @@ -1047,7 +1099,7 @@ func init() { engine.Name, engine.AttributeValueString(name), engine.ObjectSid, engine.AttributeValueSID(binsid), engine.ObjectClass, engine.AttributeValueString("person"), engine.AttributeValueString("user"), engine.AttributeValueString("top"), - engine.ObjectCategorySimple, engine.AttributeValueString("Group"), + engine.Type, engine.AttributeValueString("Group"), )) } } @@ -1138,22 +1190,23 @@ func init() { // Also they can DCsync because of this membership ... FIXME } - if uac&engine.UAC_ACCOUNTDISABLE != 0 { - object.SetValues(engine.MetaAccountDisabled, engine.AttributeValueInt(1)) - } - if uac&engine.UAC_PASSWD_CANT_CHANGE != 0 { - object.SetValues(engine.MetaPasswordCantChange, engine.AttributeValueInt(1)) - } - if uac&engine.UAC_DONT_EXPIRE_PASSWORD != 0 { - object.SetValues(engine.MetaPasswordNoExpire, engine.AttributeValueInt(1)) - } - if uac&engine.UAC_PASSWD_NOTREQD != 0 { - object.SetValues(engine.MetaPasswordNotRequired, engine.AttributeValueInt(1)) + + accountActive := uac&engine.UAC_ACCOUNTDISABLE == 0 + if exp, ok := object.Attr(activedirectory.AccountExpires).First().Raw().(time.Time); ok && accountActive { + if !exp.IsZero() && time.Now().After(exp) { + accountActive = false + } } + + object.SetValues(engine.MetaAccountActive, engine.AttributeValueBool(accountActive)) + object.SetValues(engine.MetaPasswordCantChange, engine.AttributeValueBool(uac&engine.UAC_PASSWD_CANT_CHANGE != 0)) + object.SetValues(engine.MetaPasswordNeverExpires, engine.AttributeValueBool(uac&engine.UAC_DONT_EXPIRE_PASSWORD != 0)) + object.SetValues(engine.MetaPasswordNotRequired, engine.AttributeValueBool(uac&engine.UAC_PASSWD_NOTREQD != 0)) + if uac&engine.UAC_SERVER_TRUST_ACCOUNT != 0 { // Domain Controller // find the machine object for this - machine, found := ao.FindTwo(engine.ObjectCategorySimple, engine.AttributeValueString("Machine"), + machine, found := ao.FindTwo(engine.Type, engine.AttributeValueString("Machine"), DomainJoinedSID, engine.AttributeValueSID(object.SID())) if !found { ui.Warn().Msgf("Can not find machine object for DC %v", object.DN()) @@ -1279,12 +1332,12 @@ func init() { object.SetFlex( engine.ObjectCategoryGUID, objectcategoryguid, - engine.ObjectCategorySimple, simple, + engine.Type, simple, ) return true }) }, - "Set ObjectCategorySimple (for Type call) to Active Directory objects", + "Set type (for Type call) to Active Directory objects", engine.BeforeMergeLow, ) @@ -1536,7 +1589,7 @@ func init() { group = engine.NewObject( engine.IgnoreBlanks, engine.DistinguishedName, memberof, - engine.ObjectCategorySimple, engine.AttributeValueString("Group"), + engine.Type, engine.AttributeValueString("Group"), engine.ObjectClass, engine.AttributeValueString("top"), engine.AttributeValueString("group"), engine.Name, engine.AttributeValueString("Synthetic group "+memberof.String()), engine.Description, engine.AttributeValueString("Synthetic group"), @@ -1617,6 +1670,24 @@ func init() { }) return true }, 0) + + ao.IterateParallel(func(o *engine.Object) bool { + var groups []engine.AttributeValue + o.Edges(engine.Out).Range(func(target *engine.Object, edge engine.EdgeBitmap) bool { + if edge.IsSet(activedirectory.EdgeMemberOfGroupIndirect) || edge.IsSet(activedirectory.EdgeMemberOfGroup) { + dn := target.Attr(engine.DistinguishedName) + if dn.First() != nil { + groups = append(groups, dn.First()) + } + } + return true + }) + if len(groups) > 0 { + o.SetValues(MemberOfRecursive, groups...) + } + return true + }, 0) + }, "MemberOfIndirect resolution", engine.AfterMerge, @@ -1686,8 +1757,8 @@ func init() { nativeobject := nativeObjects.First() nativeobject.EdgeTo(object, activedirectory.EdgeForeignIdentity) // Inherit the type from the original - if !object.HasAttr(activedirectory.ObjectCategorySimple) { - object.SetFlex(activedirectory.ObjectCategorySimple, nativeobject.Attr(activedirectory.ObjectCategorySimple)) + if !object.HasAttr(activedirectory.Type) { + object.SetFlex(activedirectory.Type, nativeobject.Attr(activedirectory.Type)) } } } diff --git a/modules/integrations/activedirectory/analyze/gpoimport.go b/modules/integrations/activedirectory/analyze/gpoimport.go index 7abbadf..364061e 100644 --- a/modules/integrations/activedirectory/analyze/gpoimport.go +++ b/modules/integrations/activedirectory/analyze/gpoimport.go @@ -68,7 +68,7 @@ func ImportGPOInfo(ginfo activedirectory.GPOdump, ao *engine.Objects) error { AbsolutePath, absolutepath, RelativePath, relativepath, engine.DisplayName, relativepath, - engine.ObjectCategorySimple, objecttype, + engine.Type, objecttype, BinarySize, item.Size, activedirectory.WhenChanged, item.Timestamp, ) @@ -152,7 +152,7 @@ func ImportGPOInfo(ginfo activedirectory.GPOdump, ao *engine.Objects) error { for _, e := range exposed { // New object to contain the sensitive data expobj := ao.AddNew( - engine.ObjectCategorySimple, "ExposedPassword", + engine.Type, "ExposedPassword", engine.DisplayName, "Exposed password for "+e.Username, engine.Description, "Password is exposed in GPO with GUID "+ginfo.GUID.String(), engine.ObjectGUID, ginfo.GUID, @@ -260,7 +260,7 @@ func ImportGPOInfo(ginfo activedirectory.GPOdump, ao *engine.Objects) error { } // Create new synthetic object sob := engine.NewObject( - engine.ObjectCategorySimple, engine.AttributeValueString("Script"), + engine.Type, engine.AttributeValueString("Script"), engine.DistinguishedName, engine.AttributeValueString(fmt.Sprintf("CN=Startup Script %v from GPO %v,CN=synthetic", scriptnum, ginfo.GUID)), engine.Name, engine.AttributeValueString("Machine startup script "+strings.Trim(k1.String()+" "+k2.String(), " ")), ) @@ -280,7 +280,7 @@ func ImportGPOInfo(ginfo activedirectory.GPOdump, ao *engine.Objects) error { // Create new synthetic object sob := engine.NewObject( engine.DistinguishedName, engine.AttributeValueString(fmt.Sprintf("CN=Shutdown Script %v from GPO %v,CN=synthetic", scriptnum, ginfo.GUID)), - engine.ObjectCategorySimple, engine.AttributeValueString("Script"), + engine.Type, engine.AttributeValueString("Script"), engine.Name, engine.AttributeValueString("Machine shutdown script "+strings.Trim(k1.String()+" "+k2.String(), " ")), ) ao.Add(sob) diff --git a/modules/integrations/activedirectory/attributes.go b/modules/integrations/activedirectory/attributes.go index ee7b04f..ce39de4 100644 --- a/modules/integrations/activedirectory/attributes.go +++ b/modules/integrations/activedirectory/attributes.go @@ -7,92 +7,95 @@ import ( var ( EdgeForeignIdentity = engine.NewEdge("ForeignIdentity") - DistinguishedName = engine.NewAttribute("distinguishedName").Tag("AD").Unique().Single() - ObjectClass = engine.NewAttribute("objectClass").Tag("AD") - ObjectCategory = engine.NewAttribute("objectCategory").Tag("AD").Single() - ObjectCategorySimple = engine.NewAttribute("objectCategorySimple").Single() - StructuralObjectClass = engine.NewAttribute("structuralObjectClass").Tag("AD") - NTSecurityDescriptor = engine.NewAttribute("nTSecurityDescriptor").Tag("AD").Single().Type(engine.AttributeTypeBlob) - SAMAccountType = engine.NewAttribute("sAMAccountType").Tag("AD").Single() - GroupType = engine.NewAttribute("groupType").Tag("AD").Single() - MemberOf = engine.NewAttribute("memberOf").Tag("AD") - Member = engine.NewAttribute("member").Tag("AD") - BadPasswordTime = engine.NewAttribute("badPasswordTime").Tag("AD").Type(engine.AttributeTypeTime100NS) - CreationTime = engine.NewAttribute("creationTime").Tag("AD").Type(engine.AttributeTypeTime100NS) - AccountExpires = engine.NewAttribute("accountExpires").Tag("AD").Type(engine.AttributeTypeTime100NS) - RepsTo = engine.NewAttribute("repsTo").Tag("AD") - InstanceType = engine.NewAttribute("instanceType").Tag("AD") - ModifiedCount = engine.NewAttribute("modifiedCount").Tag("AD") - MinPwdAge = engine.NewAttribute("minPwdAge").Tag("AD") - MinPwdLength = engine.NewAttribute("minPwdLength").Tag("AD").Type(engine.AttributeTypeInt) - PwdProperties = engine.NewAttribute("pwdProperties").Tag("AD") - LockOutDuration = engine.NewAttribute("lockoutDuration").Tag("AD") - PwdHistoryLength = engine.NewAttribute("pwdHistoryLength").Tag("AD").Type(engine.AttributeTypeInt) - IsCriticalSystemObject = engine.NewAttribute("isCriticalSystemObject").Tag("AD") - FSMORoleOwner = engine.NewAttribute("fSMORoleOwner").Tag("AD") - NTMixedDomain = engine.NewAttribute("nTMixedDomain").Tag("AD") - SystemFlags = engine.NewAttribute("systemFlags").Tag("AD") - PrimaryGroupID = engine.NewAttribute("primaryGroupID").Tag("AD") - LogonCount = engine.NewAttribute("logonCount").Tag("AD") - UserAccountControl = engine.NewAttribute("userAccountControl").Tag("AD") - LocalPolicyFlags = engine.NewAttribute("localPolicyFlags").Tag("AD") - CodePage = engine.NewAttribute("codePage").Tag("AD") - CountryCode = engine.NewAttribute("countryCode").Tag("AD") - OperatingSystem = engine.NewAttribute("operatingSystem").Tag("AD") - OperatingSystemHotfix = engine.NewAttribute("operatingSystemHotfix").Tag("AD") - OperatingSystemVersion = engine.NewAttribute("operatingSystemVersion").Tag("AD") - OperatingSystemServicePack = engine.NewAttribute("operatingSystemServicePack").Tag("AD") - AdminCount = engine.NewAttribute("adminCount").Tag("AD") - LogonHours = engine.NewAttribute("logonHours").Tag("AD") - BadPwdCount = engine.NewAttribute("badPwdCount").Tag("AD").Type(engine.AttributeTypeInt) - GPCFileSysPath = engine.NewAttribute("gPCFileSysPath").Tag("AD").Merge() - SchemaIDGUID = engine.NewAttribute("schemaIDGUID").Tag("AD").Type(engine.AttributeTypeGUID) - PossSuperiors = engine.NewAttribute("possSuperiors") - SystemPossSuperiors = engine.NewAttribute("systemPossSuperiors") - SubClassOf = engine.NewAttribute("subClassOf").Tag("AD") - SystemMayContain = engine.NewAttribute("systemMayContain") - SystemMustContain = engine.NewAttribute("systemMustContain") - ServicePrincipalName = engine.NewAttribute("servicePrincipalName").Tag("AD") - Name = engine.NewAttribute("name").Tag("AD") - DisplayName = engine.NewAttribute("displayName").Tag("AD") - LDAPDisplayName = engine.NewAttribute("lDAPDisplayName").Tag("AD") // Attribute-Schema - Description = engine.NewAttribute("description").Tag("AD") - SAMAccountName = engine.NewAttribute("sAMAccountName").Tag("AD") - ObjectSid = engine.NewAttribute("objectSid").Tag("AD").Merge().Single().Type(engine.AttributeTypeSID) - CreatorSID = engine.NewAttribute("mS-DS-CreatorSID").Tag("AD").Single().Type(engine.AttributeTypeSID) - - ObjectGUID = engine.NewAttribute("objectGUID").Tag("AD").Merge() - PwdLastSet = engine.NewAttribute("pwdLastSet").Tag("AD").Type(engine.AttributeTypeTime) - WhenCreated = engine.NewAttribute("whenCreated").Type(engine.AttributeTypeTime) - WhenChanged = engine.NewAttribute("whenChanged").Type(engine.AttributeTypeTime) - DsCorePropagationData = engine.NewAttribute("dsCorePropagationData").Type(engine.AttributeTypeTime) - MsExchLastUpdateTime = engine.NewAttribute("msExchLastUpdateTime").Type(engine.AttributeTypeTime) - GWARTLastModified = engine.NewAttribute("gWARTLastModified").Type(engine.AttributeTypeTime) - SpaceLastComputed = engine.NewAttribute("spaceLastComputed").Type(engine.AttributeTypeTime) - MsExchPolicyLastAppliedTime = engine.NewAttribute("msExchPolicyLastAppliedTime").Type(engine.AttributeTypeTime) - MsExchWhenMailboxCreated = engine.NewAttribute("msExchWhenMailboxCreated").Type(engine.AttributeTypeTime) - SIDHistory = engine.NewAttribute("sIDHistory").Tag("AD").Type(engine.AttributeTypeSID) - LastLogon = engine.NewAttribute("lastLogon").Type(engine.AttributeTypeTime) - LastLogonTimestamp = engine.NewAttribute("lastLogonTimestamp").Type(engine.AttributeTypeTime) - MSDSGroupMSAMembership = engine.NewAttribute("msDS-GroupMSAMembership").Tag("AD") - MSDSHostServiceAccount = engine.NewAttribute("msDS-HostServiceAccount").Tag("AD") - MSDSHostServiceAccountBL = engine.NewAttribute("msDS-HostServiceAccountBL").Tag("AD") - MSmcsAdmPwdExpirationTime = engine.NewAttribute("ms-mcs-AdmPwdExpirationTime").Tag("AD").Type(engine.AttributeTypeTime) // LAPS password timeout - SecurityIdentifier = engine.NewAttribute("securityIdentifier").Type(engine.AttributeTypeSID) - TrustDirection = engine.NewAttribute("trustDirection").Type(engine.AttributeTypeInt) - TrustAttributes = engine.NewAttribute("trustAttributes") - TrustPartner = engine.NewAttribute("trustPartner") - TrustType = engine.NewAttribute("trustType") - DsHeuristics = engine.NewAttribute("dsHeuristics").Tag("AD") - AttributeSecurityGUID = engine.NewAttribute("attributeSecurityGUID").Tag("AD") - MSDSConsistencyGUID = engine.NewAttribute("mS-DS-ConsistencyGuid") - RightsGUID = engine.NewAttribute("rightsGUID").Tag("AD").Type(engine.AttributeTypeGUID) - GPLink = engine.NewAttribute("gPLink").Tag("AD") - GPOptions = engine.NewAttribute("gPOptions").Tag("AD") - ScriptPath = engine.NewAttribute("scriptPath").Tag("AD").Single() - MSPKICertificateNameFlag = engine.NewAttribute("msPKI-Certificate-Name-Flag").Tag("AD").Type(engine.AttributeTypeInt) - PKIExtendedUsage = engine.NewAttribute("pKIExtendedKeyUsage").Tag("AD") - PKIExpirationPeriod = engine.NewAttribute("pKIExpirationPeriod").Tag("AD") - PKIOverlapPeriod = engine.NewAttribute("pKIOverlapPeriod").Tag("AD") - MsDSBehaviourVersion = engine.NewAttribute("msDS-Behavior-Version").Type(engine.AttributeTypeInt) + DistinguishedName = engine.NewAttribute("distinguishedName").Tag("AD").Unique().Single() + ObjectClass = engine.NewAttribute("objectClass").Tag("AD") + ObjectCategory = engine.NewAttribute("objectCategory").Tag("AD").Single() + Type = engine.NewAttribute("type").Single() + StructuralObjectClass = engine.NewAttribute("structuralObjectClass").Tag("AD") + NTSecurityDescriptor = engine.NewAttribute("nTSecurityDescriptor").Tag("AD").Single().Type(engine.AttributeTypeSecurityDescriptor) + SAMAccountType = engine.NewAttribute("sAMAccountType").Tag("AD").Single() + GroupType = engine.NewAttribute("groupType").Tag("AD").Single() + MemberOf = engine.NewAttribute("memberOf").Tag("AD") + Member = engine.NewAttribute("member").Tag("AD") + BadPasswordTime = engine.NewAttribute("badPasswordTime").Tag("AD").Type(engine.AttributeTypeTime100NS) + CreationTime = engine.NewAttribute("creationTime").Tag("AD").Type(engine.AttributeTypeTime100NS) + AccountExpires = engine.NewAttribute("accountExpires").Tag("AD").Type(engine.AttributeTypeTime100NS) + RepsTo = engine.NewAttribute("repsTo").Tag("AD") + InstanceType = engine.NewAttribute("instanceType").Tag("AD") + ModifiedCount = engine.NewAttribute("modifiedCount").Tag("AD") + MinPwdAge = engine.NewAttribute("minPwdAge").Tag("AD") + MinPwdLength = engine.NewAttribute("minPwdLength").Tag("AD").Type(engine.AttributeTypeInt) + PwdProperties = engine.NewAttribute("pwdProperties").Tag("AD") + LockOutDuration = engine.NewAttribute("lockoutDuration").Tag("AD") + PwdHistoryLength = engine.NewAttribute("pwdHistoryLength").Tag("AD").Type(engine.AttributeTypeInt) + IsCriticalSystemObject = engine.NewAttribute("isCriticalSystemObject").Tag("AD") + FSMORoleOwner = engine.NewAttribute("fSMORoleOwner").Tag("AD") + NTMixedDomain = engine.NewAttribute("nTMixedDomain").Tag("AD") + SystemFlags = engine.NewAttribute("systemFlags").Tag("AD") + PrimaryGroupID = engine.NewAttribute("primaryGroupID").Tag("AD") + LogonCount = engine.NewAttribute("logonCount").Tag("AD") + UserAccountControl = engine.NewAttribute("userAccountControl").Tag("AD") + LocalPolicyFlags = engine.NewAttribute("localPolicyFlags").Tag("AD") + CodePage = engine.NewAttribute("codePage").Tag("AD") + CountryCode = engine.NewAttribute("countryCode").Tag("AD") + OperatingSystem = engine.NewAttribute("operatingSystem").Tag("AD") + OperatingSystemHotfix = engine.NewAttribute("operatingSystemHotfix").Tag("AD") + OperatingSystemVersion = engine.NewAttribute("operatingSystemVersion").Tag("AD") + OperatingSystemServicePack = engine.NewAttribute("operatingSystemServicePack").Tag("AD") + AdminCount = engine.NewAttribute("adminCount").Tag("AD") + LogonHours = engine.NewAttribute("logonHours").Tag("AD") + BadPwdCount = engine.NewAttribute("badPwdCount").Tag("AD").Type(engine.AttributeTypeInt) + GPCFileSysPath = engine.NewAttribute("gPCFileSysPath").Tag("AD").Merge() + SchemaIDGUID = engine.NewAttribute("schemaIDGUID").Tag("AD").Type(engine.AttributeTypeGUID) + PossSuperiors = engine.NewAttribute("possSuperiors") + SystemPossSuperiors = engine.NewAttribute("systemPossSuperiors") + SubClassOf = engine.NewAttribute("subClassOf").Tag("AD") + SystemMayContain = engine.NewAttribute("systemMayContain") + SystemMustContain = engine.NewAttribute("systemMustContain") + ServicePrincipalName = engine.NewAttribute("servicePrincipalName").Tag("AD") + Name = engine.NewAttribute("name").Tag("AD") + DisplayName = engine.NewAttribute("displayName").Tag("AD") + LDAPDisplayName = engine.NewAttribute("lDAPDisplayName").Tag("AD") // Attribute-Schema + Description = engine.NewAttribute("description").Tag("AD") + SAMAccountName = engine.NewAttribute("sAMAccountName").Tag("AD") + ObjectSid = engine.NewAttribute("objectSid").Tag("AD").Merge().Single().Type(engine.AttributeTypeSID) + CreatorSID = engine.NewAttribute("mS-DS-CreatorSID").Tag("AD").Single().Type(engine.AttributeTypeSID) + MSDSAllowedToActOnBehalfOfOtherIdentity = engine.NewAttribute("msDS-AllowedToActOnBehalfOfOtherIdentity").Tag("AD").Type(engine.AttributeTypeSecurityDescriptor) + FRSRootSecurity = engine.NewAttribute("fRSRootSecurity").Tag("AD").Type(engine.AttributeTypeSecurityDescriptor) + MSDFSLinkSecurityDescriptorv2 = engine.NewAttribute("msDFS-LinkSecurityDescriptorv2").Tag("AD").Type(engine.AttributeTypeSecurityDescriptor) + PKIEnrollmentAccess = engine.NewAttribute("pKIEnrollmentAccess").Tag("AD") + ObjectGUID = engine.NewAttribute("objectGUID").Tag("AD").Merge() + PwdLastSet = engine.NewAttribute("pwdLastSet").Tag("AD").Type(engine.AttributeTypeTime) + WhenCreated = engine.NewAttribute("whenCreated").Type(engine.AttributeTypeTime) + WhenChanged = engine.NewAttribute("whenChanged").Type(engine.AttributeTypeTime) + DsCorePropagationData = engine.NewAttribute("dsCorePropagationData").Type(engine.AttributeTypeTime) + MsExchLastUpdateTime = engine.NewAttribute("msExchLastUpdateTime").Type(engine.AttributeTypeTime) + GWARTLastModified = engine.NewAttribute("gWARTLastModified").Type(engine.AttributeTypeTime) + SpaceLastComputed = engine.NewAttribute("spaceLastComputed").Type(engine.AttributeTypeTime) + MsExchPolicyLastAppliedTime = engine.NewAttribute("msExchPolicyLastAppliedTime").Type(engine.AttributeTypeTime) + MsExchWhenMailboxCreated = engine.NewAttribute("msExchWhenMailboxCreated").Type(engine.AttributeTypeTime) + SIDHistory = engine.NewAttribute("sIDHistory").Tag("AD").Type(engine.AttributeTypeSID) + LastLogon = engine.NewAttribute("lastLogon").Type(engine.AttributeTypeTime) + LastLogonTimestamp = engine.NewAttribute("lastLogonTimestamp").Type(engine.AttributeTypeTime) + MSDSGroupMSAMembership = engine.NewAttribute("msDS-GroupMSAMembership").Tag("AD").Type(engine.AttributeTypeSecurityDescriptor) + MSDSHostServiceAccount = engine.NewAttribute("msDS-HostServiceAccount").Tag("AD") + MSDSHostServiceAccountBL = engine.NewAttribute("msDS-HostServiceAccountBL").Tag("AD") + MSmcsAdmPwdExpirationTime = engine.NewAttribute("ms-mcs-AdmPwdExpirationTime").Tag("AD").Type(engine.AttributeTypeTime) // LAPS password timeout + SecurityIdentifier = engine.NewAttribute("securityIdentifier").Type(engine.AttributeTypeSID) + TrustDirection = engine.NewAttribute("trustDirection").Type(engine.AttributeTypeInt) + TrustAttributes = engine.NewAttribute("trustAttributes") + TrustPartner = engine.NewAttribute("trustPartner") + TrustType = engine.NewAttribute("trustType") + DsHeuristics = engine.NewAttribute("dsHeuristics").Tag("AD") + AttributeSecurityGUID = engine.NewAttribute("attributeSecurityGUID").Tag("AD") + MSDSConsistencyGUID = engine.NewAttribute("mS-DS-ConsistencyGuid") + RightsGUID = engine.NewAttribute("rightsGUID").Tag("AD").Type(engine.AttributeTypeGUID) + GPLink = engine.NewAttribute("gPLink").Tag("AD") + GPOptions = engine.NewAttribute("gPOptions").Tag("AD") + ScriptPath = engine.NewAttribute("scriptPath").Tag("AD").Single() + MSPKICertificateNameFlag = engine.NewAttribute("msPKI-Certificate-Name-Flag").Tag("AD").Type(engine.AttributeTypeInt) + PKIExtendedUsage = engine.NewAttribute("pKIExtendedKeyUsage").Tag("AD") + PKIExpirationPeriod = engine.NewAttribute("pKIExpirationPeriod").Tag("AD") + PKIOverlapPeriod = engine.NewAttribute("pKIOverlapPeriod").Tag("AD") + MsDSBehaviourVersion = engine.NewAttribute("msDS-Behavior-Version").Type(engine.AttributeTypeInt) ) diff --git a/modules/integrations/activedirectory/pwns.go b/modules/integrations/activedirectory/pwns.go index 833c4fc..21b5ab9 100644 --- a/modules/integrations/activedirectory/pwns.go +++ b/modules/integrations/activedirectory/pwns.go @@ -27,24 +27,25 @@ var ( EdgeWriteSPN = engine.NewEdge("WriteSPN").RegisterProbabilityCalculator(func(source, target *engine.Object) engine.Probability { if uac, ok := target.AttrInt(UserAccountControl); ok && uac&0x0002 /*UAC_ACCOUNTDISABLE*/ != 0 { // Account is disabled - return 0 + return 25 } return 50 }).Tag("Pivot") EdgeWriteValidatedSPN = engine.NewEdge("WriteValidatedSPN").RegisterProbabilityCalculator(func(source, target *engine.Object) engine.Probability { if uac, ok := target.AttrInt(UserAccountControl); ok && uac&0x0002 /*UAC_ACCOUNTDISABLE*/ != 0 { // Account is disabled - return 0 + return 25 } return 50 }).Tag("Pivot") - EdgeWriteAllowedToAct = engine.NewEdge("WriteAllowedToAct").Tag("Pivot") - EdgeAddMember = engine.NewEdge("AddMember").Tag("Pivot") - EdgeAddMemberGroupAttr = engine.NewEdge("AddMemberGroupAttr").Tag("Pivot") - EdgeAddSelfMember = engine.NewEdge("AddSelfMember").Tag("Pivot") - EdgeReadMSAPassword = engine.NewEdge("ReadMSAPassword").Tag("Pivot") - EdgeHasMSA = engine.NewEdge("HasMSA").Tag("Granted") - EdgeWriteUserAccountControl = engine.NewEdge("WriteUserAccountControl").Describe("Allows attacker to set ENABLE and set DONT_REQ_PREAUTH and then to do AS_REP Kerberoasting").RegisterProbabilityCalculator(func(source, target *engine.Object) engine.Probability { + EdgeWriteAllowedToAct = engine.NewEdge("WriteAllowedToAct").Tag("Pivot") + EdgeWriteAllowedToDelegateTo = engine.NewEdge("WriteAllowedToDelegTo").Tag("Pivot") + EdgeAddMember = engine.NewEdge("AddMember").Tag("Pivot") + EdgeAddMemberGroupAttr = engine.NewEdge("AddMemberGroupAttr").Tag("Pivot") + EdgeAddSelfMember = engine.NewEdge("AddSelfMember").Tag("Pivot") + EdgeReadMSAPassword = engine.NewEdge("ReadMSAPassword").Tag("Pivot") + EdgeHasMSA = engine.NewEdge("HasMSA").Tag("Granted") + EdgeWriteUserAccountControl = engine.NewEdge("WriteUserAccountControl").Describe("Allows attacker to set ENABLE and set DONT_REQ_PREAUTH and then to do AS_REP Kerberoasting").RegisterProbabilityCalculator(func(source, target *engine.Object) engine.Probability { /*if uac, ok := target.AttrInt(activedirectory.UserAccountControl); ok && uac&0x0002 != 0 { //UAC_ACCOUNTDISABLE // Account is disabled return 0 diff --git a/modules/integrations/activedirectory/rawobject.go b/modules/integrations/activedirectory/rawobject.go index 3f40fa5..3a89abf 100644 --- a/modules/integrations/activedirectory/rawobject.go +++ b/modules/integrations/activedirectory/rawobject.go @@ -29,9 +29,9 @@ func (r *RawObject) Init() { } func (r *RawObject) ToObject(onlyKnownAttributes bool) *engine.Object { - result := engine.NewPreload(len(r.Attributes)) + newobject := engine.NewPreload(len(r.Attributes)) - result.SetFlex( + newobject.SetFlex( DistinguishedName, engine.AttributeValueString(r.DistinguishedName), ) // This is possibly repeated in member attributes, so dedup it @@ -52,11 +52,11 @@ func (r *RawObject) ToObject(onlyKnownAttributes bool) *engine.Object { encodedvals := EncodeAttributeData(attribute, values) if encodedvals != nil { - result.Set(attribute, encodedvals) + newobject.Set(attribute, encodedvals) } } - return result + return newobject } func (item *RawObject) IngestLDAP(source *ldap.Entry) error { @@ -176,6 +176,14 @@ func EncodeAttributeData(attribute engine.Attribute, values []string) engine.Att case ObjectSid, SIDHistory, SecurityIdentifier, CreatorSID: sid, _, _ := windowssecurity.BytesToSID([]byte(value)) attributevalue = engine.AttributeValueSID(sid) + case MSDSAllowedToActOnBehalfOfOtherIdentity, FRSRootSecurity, MSDFSLinkSecurityDescriptorv2, + MSDSGroupMSAMembership, NTSecurityDescriptor, PKIEnrollmentAccess: + sd, err := engine.CacheOrParseSecurityDescriptor([]byte(value)) + if err == nil { + attributevalue = engine.AttributeValueSecurityDescriptor{sd} + } else { + ui.Warn().Msgf("Failed to convert attribute %v value %2x to security descriptor: %v", attribute.String(), []byte(value), err) + } default: // AUTO CONVERSION - WHAT COULD POSSIBLY GO WRONG if value == "true" || value == "TRUE" { diff --git a/modules/integrations/localmachine/analyze/analyzers.go b/modules/integrations/localmachine/analyze/analyzers.go index 2cb27f3..b972d65 100644 --- a/modules/integrations/localmachine/analyze/analyzers.go +++ b/modules/integrations/localmachine/analyze/analyzers.go @@ -20,7 +20,7 @@ func LinkSCCM(ao *engine.Objects) { for _, host := range hosts { servers, found := ao.FindTwoMulti( DNSHostname, engine.AttributeValueString(host), - engine.ObjectCategorySimple, engine.AttributeValueString("Machine"), + engine.Type, engine.AttributeValueString("Machine"), ) if !found { ui.Warn().Msgf("Could not find controlling WSUS or SCCM server %v for %v", host, o.Label()) diff --git a/modules/integrations/localmachine/analyze/import.go b/modules/integrations/localmachine/analyze/import.go index 7a6faf4..33c77aa 100644 --- a/modules/integrations/localmachine/analyze/import.go +++ b/modules/integrations/localmachine/analyze/import.go @@ -78,7 +78,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O engine.NewAttribute("productSuite"), cinfo.Machine.ProductSuite, engine.NewAttribute("productType"), cinfo.Machine.ProductType, engine.ObjectSid, localsid, - engine.ObjectCategorySimple, engine.AttributeValueString("Machine"), + engine.Type, engine.AttributeValueString("Machine"), engine.NewAttribute("connectivity"), cinfo.Network.InternetConnectivity, ) @@ -139,12 +139,12 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O machine.SetFlex(engine.DataSource, uniquesource) everyone, _, _ := ri.GetSIDObject(windowssecurity.EveryoneSID, Auto) - everyone.SetFlex(engine.ObjectCategorySimple, "Group") // This could go wrong + everyone.SetFlex(engine.Type, "Group") // This could go wrong everyone.ChildOf(machine) authenticatedusers, _, _ := ri.GetSIDObject(windowssecurity.AuthenticatedUsersSID, Auto) - authenticatedusers.SetFlex(engine.ObjectCategorySimple, "Group") // This could go wrong + authenticatedusers.SetFlex(engine.Type, "Group") // This could go wrong authenticatedusers.EdgeTo(everyone, activedirectory.EdgeMemberOfGroup) authenticatedusers.ChildOf(machine) @@ -221,6 +221,10 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O pwn = EdgeSeTrustedCredManAccess case "SeTcbPrivilege": pwn = EdgeSeTcb + case "SeIncreaseQuotaPrivilege", "SeSystemProfilePrivilege", "SeSecurityPrivilege", "SeSystemtimePrivilege", "SeProfileSingleProcessPrivilege", "SeIncreaseBasePriorityPrivilege", "SeCreatePagefilePrivilege", "SeShutdownPrivilege", "SeAuditPrivilege", "SeSystemEnvironmentPrivilege", "SeChangeNotifyPrivilege", "SeRemoteShutdownPrivilege", "SeUndockPrivilege", "SeCreateGlobalPrivilege", "SeIncreaseWorkingSetPrivilege", "SeTimeZonePrivilege", "SeCreateSymbolicLinkPrivilege", "SeInteractiveLogonRight", "SeDenyInteractiveLogonRight", "SeDenyRemoteInteractiveLogonRight", "SeBatchLogonRight", "SeServiceLogonRight", "SeDelegateSessionUserImpersonatePrivilege", "SeLockMemoryPrivilege", "SeDenyNetworkLogonRight", "SeTrustedCredManAccessPrivilege", "SeDenyBatchLogonRight", "SeDenyServiceLogonRight", "SeRelabelPrivilege": + // No edge + case "SeEnableDelegationPrivilege": + ui.Trace().Msgf("SeEnableDelegationPrivilege hit") default: continue } @@ -255,7 +259,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O user := ao.AddNew( engine.IgnoreBlanks, activedirectory.ObjectSid, engine.AttributeValueSID(usid), - activedirectory.ObjectCategorySimple, "Person", + activedirectory.Type, "Person", activedirectory.DisplayName, user.FullName, activedirectory.Name, user.Name, activedirectory.UserAccountControl, uac, @@ -285,7 +289,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O activedirectory.ObjectSid, engine.AttributeValueSID(groupsid), activedirectory.Name, group.Name, activedirectory.Description, group.Comment, - engine.ObjectCategorySimple, "Group", + engine.Type, "Group", engine.DataSource, uniquesource, ) groupobject.ChildOf(groupscontainer) @@ -360,7 +364,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O user := ao.AddNew( activedirectory.ObjectSid, engine.AttributeValueSID(usersid), - engine.ObjectCategorySimple, "Person", + engine.Type, "Person", ) if usersid.StripRID() == localsid || usersid.Component(2) != 21 { user.SetFlex( @@ -442,7 +446,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O engine.NetbiosDomain, engine.AttributeValueString(cinfo.Machine.DefaultDomain), activedirectory.SAMAccountName, cinfo.Machine.DefaultUsername, engine.DownLevelLogonName, cinfo.Machine.DefaultDomain+"\\"+cinfo.Machine.DefaultUsername, - activedirectory.ObjectCategorySimple, "Person", + activedirectory.Type, "Person", ) machine.EdgeTo(user, EdgeHasAutoAdminLogonCredentials) } @@ -468,7 +472,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O activedirectory.Description, service.Description, ServiceStart, int64(service.Start), ServiceType, int64(service.Type), - activedirectory.ObjectCategorySimple, "Service", + activedirectory.Type, "Service", ) ao.Add(serviceobject) @@ -514,7 +518,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O svcaccount.ChildOf(serviceobject) } if serviceaccountSID.Component(2) < 21 { - svcaccount.SetFlex(activedirectory.ObjectCategorySimple, "Group") + svcaccount.SetFlex(activedirectory.Type, "Group") } } if svcaccount == nil { @@ -615,7 +619,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O serviceimageobject := engine.NewObject( activedirectory.DisplayName, filepath.Base(service.ImageExecutable), AbsolutePath, service.ImageExecutable, - engine.ObjectCategorySimple, "Executable", + engine.Type, "Executable", ) ao.Add(serviceimageobject) serviceimageobject.EdgeTo(serviceobject, EdgeExecuted) @@ -680,7 +684,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O // SHARES if len(cinfo.Shares) > 0 { computershares := ao.AddNew( - activedirectory.ObjectCategorySimple, "Container", + activedirectory.Type, "Container", activedirectory.DisplayName, "Shares", ) computershares.ChildOf(machine) @@ -692,7 +696,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O AbsolutePath, share.Path, engine.Description, share.Remark, ShareType, share.Type, - engine.ObjectCategorySimple, "Share", + engine.Type, "Share", ) machine.EdgeTo(shareobject, EdgeShares) @@ -740,7 +744,7 @@ func ImportCollectorInfo(ao *engine.Objects, cinfo localmachine.Info) (*engine.O engine.IgnoreBlanks, activedirectory.DisplayName, share.Path, AbsolutePath, share.Path, - engine.ObjectCategorySimple, "Directory", + engine.Type, "Directory", ) pathobject.ChildOf(machine) diff --git a/readme.MD b/readme.MD index bc55903..7e284e9 100644 --- a/readme.MD +++ b/readme.MD @@ -219,9 +219,9 @@ The tool has its own LDAP query parser, and makes it easy to search for other ob - custom extensible match: length - matches on length of attribute values (name:length:>20 gives you objects with long names) - custom extensible match: since - parses the attribute as a timestamp and your value as a duration - pwdLastSet:since:<-6Y5M4D3h2m1s (pawLastSet is less than the time 6 years, 5 months, 4 days, 3 hours, 2 minutes and 1 second ago - or just pass an integer that represents seconds directly) - synthetic attribute: _limit (_limit=10) returns true on the first 10 hits, false on the rest giving you a max output of 10 items -- synthetic attribute: _random100 (_random100<10) allows you to return a random percentage of results (&(objectCategory=Person)(_random100<1)) gives you 1% of users -- synthetic attribute: _canpwn - allows you to select objects based on what they can pwn *directly* (&(objectCategory=Group)(_canpwn=ResetPassword)) gives you all groups that are assigned the reset password right -- synthetic attribute: _pwnable - allows you to select objects based on how they can be pwned *directly* (&(objectCategory=Person)(_pwnable=ResetPassword)) gives you all users that can have their password reset +- synthetic attribute: _random100 (_random100<10) allows you to return a random percentage of results (&(type=Person)(_random100<1)) gives you 1% of users +- synthetic attribute: out - allows you to select objects based on what they can pwn *directly* (&(type=Group)(_canpwn=ResetPassword)) gives you all groups that are assigned the reset password right +- synthetic attribute: in - allows you to select objects based on how they can be pwned *directly* (&(type=Person)(_pwnable=ResetPassword)) gives you all users that can have their password reset - glob matching on the attribute name - searching for (*name=something) is possible - also just * to search all attributes - custom extensible match: timediff - allows you to search for accounts not in use or password changes relative to other attributes - e.g. lastLogonTimestamp:timediff(pwdLastSet):>6M finds all objects where the lastLogonTimestamp is 6 months or more recent than pwdLastSet - custom extensible match: caseExactMatch - switches text searches (exact, glob) to case sensitive mode