Skip to content

Commit

Permalink
Fixed object merges does not cross forests, creates synthetic Foreign…
Browse files Browse the repository at this point in the history
…-Security-Principals, and returns better results for cross forest takeovers in graph. It's a beast of an update!
  • Loading branch information
lkarlslund committed Jan 26, 2022
1 parent 7c1183d commit 7a06fb3
Show file tree
Hide file tree
Showing 12 changed files with 315 additions and 218 deletions.
14 changes: 11 additions & 3 deletions modules/engine/analyzeobjects.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,19 @@ func AnalyzeObjects(opts AnalyzeObjectsOptions) (pg PwnGraph) {
// If pwner is already processed, we don't care what it can pwn someone more far away from targets
// If pwner is our attacker, we always want to know what it can do
targetprocessinground, found := implicatedobjectsmap[pwntarget]
if !opts.Backlinks &&

// SKIP THIS IF
if
// We're not including backlinks
!opts.Backlinks &&
// It's found
found &&
// pwntarget != Attacker && FIXME
// This is not the first round
targetprocessinground != 0 &&
targetprocessinground < processinground {
// It was found in an earlier round
targetprocessinground < processinground &&
// If SIDs match between objects, it's a cross forest link and we want to see it
(object.SID().IsNull() || pwntarget.SID().IsNull() || object.SID().Component(2) != 21 || object.SID() != pwntarget.SID()) {
// skip it
continue
}
Expand Down
34 changes: 24 additions & 10 deletions modules/engine/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ type AttributeGetFunc func(o *Object, a Attribute) (v AttributeValues, found boo
type AttributeSetFunc func(o *Object, a Attribute, v AttributeValues) error

type attributeinfo struct {
name string
tags []string
multi bool
unique bool
merge bool
mf mergefunc
onset AttributeSetFunc
onget AttributeGetFunc
name string
tags []string
multi bool // If true, this attribute can have multiple values
nonunique bool // Doing a Find on this attribute will return multiple results
merge bool // If true, objects can be merged on this attribute
mf mergefunc
onset AttributeSetFunc
onget AttributeGetFunc
}

var attributenums []attributeinfo
Expand Down Expand Up @@ -52,6 +52,13 @@ var (
ObjectCategoryGUID = NewAttribute("objectCategoryGUID") // Used for caching the GUIDs

MetaDataSource = NewAttribute("_datasource").Multi()
UniqueSource = NewAttribute("_source").Merge(func(attr Attribute, a, b *Object) (*Object, error) {
// Prevents objects from vastly different sources to join across them
if a.HasAttr(attr) && b.HasAttr(attr) && a.OneAttrString(attr) != b.OneAttrString(attr) {
return nil, ErrDontMerge
}
return nil, ErrMergeOnOtherAttr
})

IPAddress = NewAttribute("IPAddress")
Hostname = NewAttribute("Hostname").Merge(nil)
Expand Down Expand Up @@ -128,14 +135,21 @@ func (a Attribute) Multi() Attribute {
return a
}

func (a Attribute) Unique() Attribute {
func (a Attribute) IsNonUnique() bool {
ai := attributenums[a]
return ai.nonunique
}

func (a Attribute) NonUnique() Attribute {
ai := attributenums[a]
ai.unique = true
ai.nonunique = true
attributenums[a] = ai
return a
}

var ErrDontMerge = errors.New("Dont merge objects using any methods")
var ErrMergeOnOtherAttr = errors.New("Merge on other attribute")
var ErrMergeOnThis = errors.New("Merge on this attribute")

type mergefunc func(attr Attribute, a, b *Object) (*Object, error)

Expand Down
2 changes: 1 addition & 1 deletion modules/engine/loaders.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func AddLoader(loader Loader) LoaderID {
func NewLoaderObjects(ld Loader) *Objects {
aos := &Objects{}
aos.Init()
aos.SetDefaultSource(AttributeValueString(ld.Name()))
aos.AddDefaultFlex(MetaDataSource, AttributeValueString(ld.Name()))

// Add the root node
rootnode := NewObject(Name, AttributeValueString(ld.Name()))
Expand Down
3 changes: 3 additions & 0 deletions modules/engine/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,9 @@ func (o *Object) set(a Attribute, values AttributeValues) {
panic("DownLevelLogon ends with \\")
}
}
if a == ObjectCategory || a == ObjectCategorySimple {
o.objectcategoryguid = NullGUID
}
if a == NTSecurityDescriptor {
for _, sd := range values.Slice() {
if err := o.cacheSecurityDescriptor([]byte(sd.Raw().(string))); err != nil {
Expand Down
154 changes: 113 additions & 41 deletions modules/engine/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,24 @@ type typestatistics [OBJECTTYPEMAX]int

type Objects struct {
threadsafemutex sync.RWMutex
DefaultSource AttributeValue
DefaultValues []interface{}
root *Object
idindex map[uint32]*Object
index map[Attribute]map[interface{}]*Object
uniqueindex map[Attribute]map[interface{}]*Object
multiindex map[Attribute]map[interface{}][]*Object
asarray []*Object
typecount typestatistics
threadsafe int
}

func (os *Objects) Init() {
os.index = make(map[Attribute]map[interface{}]*Object)
os.uniqueindex = make(map[Attribute]map[interface{}]*Object)
os.multiindex = make(map[Attribute]map[interface{}][]*Object)
os.idindex = make(map[uint32]*Object)
}

func (os *Objects) SetDefaultSource(source AttributeValue) {
os.DefaultSource = source
func (os *Objects) AddDefaultFlex(data ...interface{}) {
os.DefaultValues = append(os.DefaultValues, data...)
}

func (os *Objects) SetThreadsafe(enable bool) {
Expand Down Expand Up @@ -71,16 +73,29 @@ func (os *Objects) runlock() {

func (os *Objects) addIndex(attribute Attribute) {
// create index
os.index[attribute] = make(map[interface{}]*Object)
if attribute.IsNonUnique() {
os.multiindex[attribute] = make(map[interface{}][]*Object)
} else {
os.uniqueindex[attribute] = make(map[interface{}]*Object)
}

// add all existing stuff to index
for _, o := range os.asarray {
value := o.OneAttr(attribute)
if value != nil {
var key interface{}
// If it's a string, lowercase it before adding to index, we do the same on lookups
if vs, ok := value.(AttributeValueString); ok {
os.index[attribute][strings.ToLower(string(vs))] = o
key = strings.ToLower(string(vs))
} else {
os.index[attribute][value.Raw()] = o
key = value.Raw()
}

// Add to index
if attribute.IsNonUnique() {
os.multiindex[attribute][key] = append(os.multiindex[attribute][key], o)
} else {
os.uniqueindex[attribute][key] = o
}
}
}
Expand All @@ -90,13 +105,24 @@ func (os *Objects) SetRoot(ro *Object) {
os.root = ro
}

func (os *Objects) DropIndexes() {
// Clear all indexes
os.lock()
defer os.unlock()
os.uniqueindex = make(map[Attribute]map[interface{}]*Object)
os.multiindex = make(map[Attribute]map[interface{}][]*Object)
}

// Force reindex after changing data in Objects
func (os *Objects) Reindex() {
// Clear all indexes
os.lock()
defer os.unlock()
for a := range os.index {
os.index[a] = make(map[interface{}]*Object)
for a := range os.uniqueindex {
os.uniqueindex[a] = make(map[interface{}]*Object)
}
for a := range os.multiindex {
os.multiindex[a] = make(map[interface{}][]*Object)
}
// Put all objects in index
for _, o := range os.asarray {
Expand All @@ -111,7 +137,7 @@ func (os *Objects) ReindexObject(o *Object) {
}

func (os *Objects) updateIndex(o *Object, warn bool) {
for attribute := range os.index {
for attribute := range os.uniqueindex {
values := o.Attr(attribute)
if values.Len() > 1 && !attributenums[attribute].multi {
log.Warn().Msgf("Encountered multiple values on attribute %v, but is not declared as multival", attribute.String())
Expand All @@ -123,15 +149,30 @@ func (os *Objects) updateIndex(o *Object, warn bool) {
value = AttributeValueString(strings.ToLower(string(vs)))
}
if warn {
existing, dupe := os.index[attribute][value.Raw()]
existing, dupe := os.uniqueindex[attribute][value.Raw()]
if dupe && existing != o {
log.Warn().Msgf("Duplicate index %v value %v when trying to add %v, already exists as %v, index still points to original object", attribute.String(), value.String(), o.Label(), existing.Label())
// log.Debug().Msgf("NEW: %v", o.String(os))
// log.Debug().Msgf("EXISTING: %v", existing.String(os))
continue
}
}
os.index[attribute][value.Raw()] = o
os.uniqueindex[attribute][value.Raw()] = o
}
}

for attribute := range os.multiindex {
values := o.Attr(attribute)
if values.Len() > 1 && !attributenums[attribute].multi {
log.Warn().Msgf("Encountered multiple values on attribute %v, but is not declared as multival", attribute.String())
log.Debug().Msgf("Object dump:\n%s", o.String(os))
}
for _, value := range values.Slice() {
// If it's a string, lowercase it before adding to index, we do the same on lookups
if vs, ok := value.(AttributeValueString); ok {
value = AttributeValueString(strings.ToLower(string(vs)))
}
os.multiindex[attribute][value.Raw()] = append(os.multiindex[attribute][value.Raw()], o)
}
}
}
Expand Down Expand Up @@ -159,6 +200,9 @@ func (os *Objects) AddMerge(attrtomerge []Attribute, obs ...*Object) {

func (os *Objects) AddNew(flexinit ...interface{}) *Object {
o := NewObject(flexinit...)
if os.DefaultValues != nil {
o.SetFlex(os.DefaultValues...)
}
os.lock()
os.addmerge(nil, o)
os.unlock()
Expand Down Expand Up @@ -189,6 +233,7 @@ func (os *Objects) Merge(attrtomerge []Attribute, o *Object) bool {

func (os *Objects) merge(attrtomerge []Attribute, o *Object) bool {
if len(attrtomerge) > 0 {
outerattrloop:
for _, mergeattr := range attrtomerge {
for _, lookfor := range o.Attr(mergeattr).Slice() {
if lookfor == nil {
Expand All @@ -199,10 +244,14 @@ func (os *Objects) merge(attrtomerge []Attribute, o *Object) bool {
mf := attributenums[mergeattr].mf
if mf != nil {
res, err := mf(mergeattr, o, mergetarget)
if err == ErrDontMerge {
switch err {
case ErrDontMerge:
return false
}
if err != nil {
case ErrMergeOnOtherAttr:
continue outerattrloop
case ErrMergeOnThis, nil:
// Let the code below do the merge
default:
log.Warn().Msgf("Error merging %v: %v", o.Label(), err)
return false
}
Expand All @@ -225,12 +274,8 @@ func (os *Objects) merge(attrtomerge []Attribute, o *Object) bool {

func (os *Objects) add(o *Object) {
// Add this to the iterator array
if !o.HasAttr(MetaDataSource) {
if os.DefaultSource != nil {
o.SetValues(MetaDataSource, os.DefaultSource)
} else {
log.Warn().Msgf("Object %v, missing data source", o.Label())
}
if os.DefaultValues != nil {
o.SetFlex(os.DefaultValues...)
}

// Do chunked extensions for speed
Expand Down Expand Up @@ -290,8 +335,9 @@ func (os *Objects) MergeOrAdd(attribute Attribute, value AttributeValue, flexini
if o, found := os.find(attribute, value); found {
flexinit = append(flexinit, attribute, value)
eatme := NewObject(flexinit...)
o.Absorb(eatme)
return o, true
// Use the first one found
o[0].Absorb(eatme)
return o[0], true
}
no := NewObject(append(flexinit, attribute, value)...)
os.addmerge(nil, no)
Expand All @@ -302,7 +348,8 @@ func (os *Objects) FindOrAdd(attribute Attribute, value AttributeValue, flexinit
os.lock()
defer os.unlock()
if o, found := os.find(attribute, value); found {
return o, true
// Use the first one found
return o[0], true
}
no := NewObject(append(flexinit, attribute, value)...)
os.addmerge(nil, no)
Expand All @@ -312,26 +359,46 @@ func (os *Objects) FindOrAdd(attribute Attribute, value AttributeValue, flexinit
func (os *Objects) Find(attribute Attribute, value AttributeValue) (o *Object, found bool) {
os.lock()
defer os.unlock()
return os.find(attribute, value)
v, found := os.find(attribute, value)
if len(v) != 1 {
return nil, false
}
return v[0], found
}

func (os *Objects) find(attribute Attribute, value AttributeValue) (o *Object, found bool) {
index, found := os.index[attribute]
if !found {
os.addIndex(attribute)
index = os.index[attribute]
}
func (os *Objects) FindMulti(attribute Attribute, value AttributeValue) (o []*Object, found bool) {
os.lock()
defer os.unlock()
return os.find(attribute, value)
}

var result *Object
func (os *Objects) find(attribute Attribute, value AttributeValue) ([]*Object, bool) {
var lookup interface{}

// If it's a string, lowercase it before adding to index, we do the same on lookups
if vs, ok := value.(AttributeValueString); ok {
result, found = index[strings.ToLower(string(vs))]
lookup = strings.ToLower(string(vs))
} else {
result, found = index[value.Raw()]
lookup = value.Raw()
}

return result, found
if attribute.IsNonUnique() {
index, found := os.multiindex[attribute]
if !found {
os.addIndex(attribute)
index = os.multiindex[attribute]
}
result, found := index[lookup]
return result, found
}

index, found := os.uniqueindex[attribute]
if !found {
os.addIndex(attribute)
index = os.uniqueindex[attribute]
}
result, found := index[lookup]
return []*Object{result}, found
}

func (os *Objects) DistinguishedParent(o *Object) (*Object, bool) {
Expand Down Expand Up @@ -383,14 +450,19 @@ func (os *Objects) FindOrAddSID(s windowssecurity.SID) *Object {
defer os.unlock()
o, found := os.find(ObjectSid, AttributeValueSID(s))
if found {
return o
// Use the first one found
return o[0]
}
o = NewObject(

no := NewObject(
ObjectSid, AttributeValueSID(s),
)
log.Trace().Msgf("Auto-adding unknown SID %v", s)
os.addmerge(nil, o)
return o
if os.DefaultValues != nil {
no.SetFlex(os.DefaultValues...)
}

os.addmerge(nil, no)
return no
}

func (os *Objects) FindGUID(g uuid.UUID) (o *Object, found bool) {
Expand Down
Loading

0 comments on commit 7a06fb3

Please sign in to comment.