Skip to content

Commit

Permalink
Attribute objectCategorySimple -> type, parsing of securitydescriptor…
Browse files Browse the repository at this point in the history
…s moved to rawobject, added securitydescriptor parsing to 5 other attributes, ACLs can now print without resolving SIDs, added RBCD edge, renamed some of the meta attributes, added predefined search for Unconstrained delegation computers and Constrained delegation
  • Loading branch information
lkarlslund committed Dec 15, 2023
1 parent 25921ed commit de7730b
Show file tree
Hide file tree
Showing 18 changed files with 503 additions and 245 deletions.
38 changes: 21 additions & 17 deletions modules/analyze/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
<input class="form-check-input" id="dont-expand-au-eo" type="checkbox" name="dont-expand-au-eo" autocomplete="off"
preference="analysis.dontexpandaueo" defaultpref=true>
<label class="form-check-label" for="dont-expand-au-eo" data-bs-toggle='tooltip'
data-bs-title='Expanding these groups usually gives an unreasonable amount of data in the graph'>Don't expand "Domain Users" / "Everyone"</label>
data-bs-title='Expanding these groups usually gives an unreasonable amount of data in the graph'>Don't expand "Authenticated Users" / "Everyone"</label>
</div>

</div>
Expand Down Expand Up @@ -347,7 +347,7 @@
<ul id="predefinedqueries" class="dropdown-menu max-vh-75 overflow-y-auto" style="max-height:75vh"
aria-labelledby="queriesbutton">
<li id="defaultquery" class="dropdown-item"
query="(&(dataLoader=Active Directory)(objectCategory=Group)(|(objectSid=S-1-5-32-544)(objectSid=S-1-5-21-*-512)(objectSid=S-1-5-21-*-519)))"
query="(&(dataLoader=Active Directory)(type=Group)(|(objectSid=S-1-5-32-544)(objectSid=S-1-5-21-*-512)(objectSid=S-1-5-21-*-519)))"
mode="Normal" depth=99 methods="default">Who owns your AD? (Reach Domain Admin etc)</li>
<li class="dropdown-item"
query="(_canpwn=DCsync,(!(distinguishedName=*DnsZones,DC=*)))"
Expand All @@ -364,49 +364,53 @@
query="(&(dataLoader=Active Directory)(|(objectSid=S-1-5-32-551)(objectSid=S-1-5-32-549)))"
mode="Normal" depth=99 methods="default">Who can dump SAM/SYSTEM or your ntds.dit remotely or via RDP? (Server and Backup Operators)</li>
<li class="dropdown-item"
query="(&(objectCategory=PKI-Certificate-Template)(msPKI-Certificate-Name-Flag:and:=1)(|(pKIExtendedKeyUsage=1.3.6.1.5.5.7.3.2)(pKIExtendedKeyUsage=1.3.5.1.5.2.3.4)(pKIExtendedKeyUsage=1.3.6.1.4.1.311.20.2.2)(pKIExtendedKeyUsage=2.5.29.37.0)(pKIExtendedKeyUsage:count:=0)))"
mode="Normal" depth=99 methods="default">Client cert templates with custom SAN (pose as anyone)</li>
query="(&(type=PKI-Certificate-Template)(msPKI-Certificate-Name-Flag:and:=1)(|(pKIExtendedKeyUsage=1.3.6.1.5.5.7.3.2)(pKIExtendedKeyUsage=1.3.5.1.5.2.3.4)(pKIExtendedKeyUsage=1.3.6.1.4.1.311.20.2.2)(pKIExtendedKeyUsage=2.5.29.37.0)(pKIExtendedKeyUsage:count:=0)))"
mode="Normal" depth=99 methods="default">ESC1 vulnerable certificate templates (pose as anyone)</li>


<li class="dropdown-item" query="(&(dataLoader=Active Directory)(|(objectSid=S-1-5-21-*-513)(objectSid=S-1-5-11)(objectSid=S-1-1-0)))" mode="Reverse"
depth=99 methods="default">What can Domain Users, Authenticated Users and Everyone do?</li>
<li class="dropdown-item"
query="(&(dataLoader=Active Directory)(objectCategory=Group)(|(name=*vcenter*)(name=*vmware*)(name=*esxi*)(name=*vsan*)(name=*simplivity*)))"
query="(&(dataLoader=Active Directory)(type=Group)(|(name=*vcenter*)(name=*vmware*)(name=*esxi*)(name=*vsan*)(name=*simplivity*)))"
mode="Normal" depth=99 methods="default">Who can dump a virtual DC? (hypervisor/SAN sounding groups)</li>
<li class="dropdown-item"
query="(&(dataLoader=Active Directory)(objectCategory=Group)(|(name=*backup*)(name=*veeam*)(name=*tsm*)(name=*tivoli storage*)(name=*rubrik*)(name=*commvault*))),(|(objectSid=S-1-5-32-544)(objectSid=S-1-5-21-*-512)(objectSid=S-1-5-21-*-519))"
query="(&(dataLoader=Active Directory)(type=Group)(|(name=*backup*)(name=*veeam*)(name=*tsm*)(name=*tivoli storage*)(name=*rubrik*)(name=*commvault*))),(|(objectSid=S-1-5-32-544)(objectSid=S-1-5-21-*-512)(objectSid=S-1-5-21-*-519))"
mode="Normal" depth=99 methods="default">Who can wipe or access your backups? (backup sounding groups)</li>
<li class="dropdown-item"
query="(objectCategory=Group-Policy-Container)"
query="(type=Group-Policy-Container)"
mode="Normal" depth=99 methods="default">Who can change GPOs?</li>
<li class="dropdown-item"
query="(&(dataLoader=Active Directory)(objectCategory=Person)(userAccountControl:1.2.840.113556.1.4.803:=32))" mode="Normal" depth=99
query="(&(dataLoader=Active Directory)(type=Person)(userAccountControl:1.2.840.113556.1.4.803:=32))" mode="Normal" depth=99
methods="default">Users not required to have a password</li>
<li class="dropdown-item"
query="(&(objectCategory=Person)(userAccountControl:1.2.840.113556.1.4.803:=64))" mode="Normal" depth=99
query="(&(type=Person)(userAccountControl:1.2.840.113556.1.4.803:=64))" mode="Normal" depth=99
methods="default">Users that can't change password</li>
<li class="dropdown-item"
query="(&(objectCategory=Person)(userAccountControl:1.2.840.113556.1.4.803:=65536))" mode="Normal"
query="(&(type=Person)(userAccountControl:1.2.840.113556.1.4.803:=65536))" mode="Normal"
depth=99 methods="default">Users where password never expire</li>
<li class="dropdown-item"
query="(&(objectClass=Person)(!(pwdLastSet=0))(pwdLastSet:since:<-5Y)(!(userAccountControl:and:=2)))"
mode="Reverse" depth=99 methods="default">Accounts that has a password older than 5 years</li>
<li class="dropdown-item"
query="(&(dataLoader=Active Directory)(objectClass=Person)(pwdLastSet=0)(|(logonCount=0)(!(logonCount=*)))(!(userAccountControl:and:=2)))"
mode="Reverse" depth=99 methods="default">New accounts with initial password</li>
<li class="dropdown-item" query="(&(objectCategory=Person)(memberOf=CN=Protected Users,*))"
<li class="dropdown-item" query="(&(type=Person)(memberOf=CN=Protected Users,*))"
mode="Normal" depth=99 methods="default">Who can pwn Protected Users?</li>
<li class="dropdown-item" query="(&(objectCategory=Person)(memberOf:count:>10))" mode="Normal"
depth=1 methods="default">Users that are direct members of more than 10 groups</li>
<li class="dropdown-item" query="(&(objectCategory=Person)(servicePrincipalName=*))" mode="Normal"
<li class="dropdown-item" query="(&(type=Person)(servicePrincipalName=*))" mode="Normal"
depth=1 methods="HasSPN">Users with SPNs (can be Kerberoasted)</li>
<li class="dropdown-item" query="(&(objectCategory=Group)(member:count:>100))" mode="Normal"
<li class="dropdown-item" query="(&(type=Group)(member:count:>100))" mode="Normal"
depth=99 methods="default">Groups that have more than 100 direct members</li>
<li class="dropdown-item"
query="(&(objectCategory=Computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" mode="Normal"
query="(&(type=Computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" mode="Normal"
depth=99>Domain Controllers</li>
<li class="dropdown-item" query="(&(type=Computer)(userAccountControl:1.2.840.113556.1.4.803:=524288)(!userAccountControl:1.2.840.113556.1.4.803:=8192))" mode="Normal"
depth=99>Computers with Unconstrained Delegation (non DCs)</li>
<li class="dropdown-item" query="(&(objectCategory=computer)(msds-allowedtodelegateto=*))" mode="Normal" depth=99>
Computers with Constrained Delegation</li>
<li class="dropdown-item" query="(&(type=Person)(memberOf:count:>10))" mode="Normal" depth=1 methods="default">Users
that are direct members of more than 10 groups</li>
<li class="dropdown-item"
query="(&(objectCategory=Machine)(out=MachineAccount,(userAccountControl:1.2.840.113556.1.4.803:=4096))(_limit=100))"
query="(&(type=Machine)(out=MachineAccount,(userAccountControl:1.2.840.113556.1.4.803:=4096))(_limit=100))"
mode="Normal" depth=99>Servers or Workstations (100 random)</li>
</ul>
</div>
Expand Down
6 changes: 3 additions & 3 deletions modules/analyze/webservicefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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",
}
Expand Down
11 changes: 6 additions & 5 deletions modules/engine/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
AttributeTypeSID
AttributeTypeGUID
AttributeTypeBlob
AttributeTypeSecurityDescriptor
)

type mergeapproverinfo struct {
Expand All @@ -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()
Expand Down Expand Up @@ -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")
Expand Down
16 changes: 16 additions & 0 deletions modules/engine/attributevalue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
14 changes: 0 additions & 14 deletions modules/engine/globals.go

This file was deleted.

52 changes: 8 additions & 44 deletions modules/engine/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion modules/engine/processing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Loading

0 comments on commit de7730b

Please sign in to comment.