Skip to content

Commit

Permalink
Source->Target search added to UI again, reworked AdminSDHolder analy…
Browse files Browse the repository at this point in the history
…sis (WIP, needs some fixes)
  • Loading branch information
lkarlslund committed Feb 21, 2022
1 parent 40752a7 commit 8266894
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 96 deletions.
8 changes: 7 additions & 1 deletion modules/analyze/html/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ function makePopper(ele) {
});
}

window.onpopstate = function (event) {
$('body').html(event.state);
}

function setquery(
query,
depth,
Expand Down Expand Up @@ -183,7 +187,7 @@ function analyze(e) {
url: 'cytograph.json',
contentType: 'charset=utf-8',
data: JSON.stringify(
$('#queryform, #optionsform')
$('#queryform, #analysisoptionsform, #analysispwnform, #analysistypeform')
.serializeArray()
.reduce(function (m, o) {
m[o.name] = o.value;
Expand Down Expand Up @@ -235,6 +239,8 @@ function analyze(e) {
}

initgraph(data.elements);

history.pushState($('body').html(), 'adalanche')
}
},
error: function (xhr, status, error) {
Expand Down
16 changes: 11 additions & 5 deletions modules/analyze/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,22 @@
</div>
<div id="optionswrap" class="fw-250 accordion pointer-events-auto">
<div id="optionscontent" class="fw-250 collapse-group bg-dark h-full overflow-y-auto">
<form id="optionsform">

<details class="collapse-panel">
<summary class="collapse-header" id="headingTwo">
Analysis options
</summary>
<form id="analysisoptionsform">
<div class="collapse-content">
<div class="row justify-content-between">
<label class="col" for="querymode_group">Direction</label>
<label class="col" for="querymode_group">Mode</label>
<div class="col btn-group btn-group-sm checkbox-button" id="querymode_group" role="group"
aria-label="Mode">
<input type="radio" name="mode" id="querymode_normal" value="normal" autocomplete="off" checked />
<label class="btn btn-sm" for="querymode_normal">Normal</label>
<input type="radio" name="mode" id="querymode_reverse" value="inverted" autocomplete="off" />
<label class="btn btn-sm" for="querymode_reverse">Reverse</label>
<input type="radio" name="mode" id="querymode_sourcetarget" value="sourcetarget" autocomplete="off" />
<label class="btn btn-sm" for="querymode_sourcetarget">SrcTgt</label>
</div>
</div>

Expand Down Expand Up @@ -137,28 +138,33 @@
</div>

</div>
</form>
</details>
<details class="collapse-panel">
<summary class="collapse-header">
Analysis methods
</summary>
<div class="collapse-content">
<form id="analysispwnform">
<div id="pwnfilter">
Loading ...
</div>
</form>
</div>
</details>
<details class="collapse-panel">
<summary class="collapse-header">
Analysis object types
</summary>
<div class="collapse-content">
<form id="analysistypeform">
<div id="objecttypefilter">
Loading ...
</div>
</form>
</div>
</details>
</form>
</details>
<!-- end of data submitted on analysis -->
<details class="collapse-panel">
<summary class="collapse-header">
Expand Down Expand Up @@ -244,7 +250,7 @@
mode="Normal" depth=99 methods="default">Who can DCsync?</a>
<a class="dropdown-item" href="#"
query="(|(objectSid=S-1-5-32-551)(objectSid=S-1-5-32-549))"
mode="Normal" depth=99 methods="default">Who can dump your ntds.dit? (Server and Backup Operators)</a>
mode="Normal" depth=99 methods="default">Who can dump SAM/SYSTEM or your ntds.dit remotely or via RDP? (Server and Backup Operators)</a>
<a class="dropdown-item" href="#"
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)</a>
Expand Down
10 changes: 8 additions & 2 deletions modules/analyze/webservicefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,20 @@ func analysisfuncs(ws *webservice) {

var pg engine.PwnGraph
if mode == "sourcetarget" {
if len(includeobjects.Slice()) == 0 || excludeobjects == nil || len(excludeobjects.Slice()) == 0 {
if includeobjects.Len() == 0 || excludeobjects == nil || excludeobjects.Len() == 0 {
fmt.Fprintf(w, "You must use two queries (source and target), seperated by commas. Each must return at least one object.")
}

// We dont support this yet, so merge all of them
combinedmethods := methods_f.Merge(methods_m).Merge(methods_l)

pg = engine.AnalyzePaths(includeobjects.Slice()[0], excludeobjects.Slice()[0], ws.Objs, combinedmethods, engine.Probability(minprobability), 1)
for _, source := range includeobjects.Slice() {
for _, target := range excludeobjects.Slice() {
newpg := engine.AnalyzePaths(source, target, ws.Objs, combinedmethods, engine.Probability(minprobability), maxdepth)
pg.Merge(newpg)
}
}
// pg = engine.AnalyzePaths(includeobjects.Slice()[0], excludeobjects.Slice()[0], ws.Objs, combinedmethods, engine.Probability(minprobability), 1)
} else {
opts := engine.NewAnalyzeObjectsOptions()
opts.IncludeObjects = includeobjects
Expand Down
11 changes: 8 additions & 3 deletions modules/engine/analyzeobjects.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"github.com/rs/zerolog/log"
)

var PwnMemberOfGroup = NewPwn("MemberOfGroup") // FIXME, this should be generalized to expand-anyway-priority somehoe

type ProbabilityCalculatorFunction func(source, target *Object) Probability

var pcfs = make(map[PwnMethod]ProbabilityCalculatorFunction)
Expand Down Expand Up @@ -181,6 +183,8 @@ func AnalyzeObjects(opts AnalyzeObjectsOptions) (pg PwnGraph) {
!opts.Backlinks &&
// It's found
found &&
// This is not the first round
processinground > 1 &&
// It was found in an earlier round
tri.roundadded+opts.Fuzzlevel <= processinground &&
// If SIDs match between objects, it's a cross forest link and we want to see it
Expand Down Expand Up @@ -211,9 +215,9 @@ func AnalyzeObjects(opts AnalyzeObjectsOptions) (pg PwnGraph) {
} else {
log.Debug().Msgf("Outgoing expansion limit hit %v for object %v, there was %v connections", opts.MaxOutgoingConnections, object.Label(), len(newconnectionsmap))
var groupcount int
for pwnpair := range newconnectionsmap {
for _, detectedmethods := range newconnectionsmap {
// We assume the number of groups are limited and add them anyway
if pwnpair.Target.Type() == ObjectTypeGroup {
if detectedmethods.IsSet(PwnMemberOfGroup) {
groupcount++
}
}
Expand All @@ -222,14 +226,15 @@ func AnalyzeObjects(opts AnalyzeObjectsOptions) (pg PwnGraph) {
var addedanyway int
for pwnpair, detectedmethods := range newconnectionsmap {
// We assume the number of groups are limited and add them anyway
if pwnpair.Target.Type() == ObjectTypeGroup {
if detectedmethods.IsSet(PwnMemberOfGroup) {
connectionsmap[pwnpair] = detectedmethods
if _, found := implicatedobjectsmap[pwnpair.Target]; !found {
newimplicatedobjects[pwnpair.Target] = struct{}{} // Add this to work map as non-processed
}
addedanyway++
}
}
log.Debug().Msgf("Expansion limit compromise - added %v groups as they fit under the expansion limit %v", addedanyway, opts.MaxOutgoingConnections)
ri.canexpand = len(newconnectionsmap) - addedanyway
}
}
Expand Down
64 changes: 64 additions & 0 deletions modules/engine/graph.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package engine

import "log"

type GraphObject struct {
*Object
Target bool
Expand All @@ -20,6 +22,68 @@ type PwnConnection struct {
PwnMethodBitmap
}

func (pg *PwnGraph) Merge(npg PwnGraph) {
nodemap := make(map[*Object]GraphObject)
for _, node := range pg.Nodes {
nodemap[node.Object] = node
}

if len(nodemap) != len(pg.Nodes) {
log.Panic("Nodes not equal")
}

pairmap := make(map[PwnPair]PwnMethodBitmap)
for _, connection := range pg.Connections {
pairmap[PwnPair{connection.Source, connection.Target}] = connection.PwnMethodBitmap
}

if len(pairmap) != len(pg.Connections) {
log.Panic("Connections not equal")
}

for _, node := range npg.Nodes {
if e, ok := nodemap[node.Object]; ok {
if node.Target {
e.Target = true
nodemap[node.Object] = e
}
if node.CanExpand > e.CanExpand {
e.CanExpand = node.CanExpand
nodemap[node.Object] = e
}
} else {
nodemap[node.Object] = node
}
}

for _, connection := range npg.Connections {
if e, ok := pairmap[PwnPair{connection.Source, connection.Target}]; ok {
e.Merge(connection.PwnMethodBitmap)
pairmap[PwnPair{connection.Source, connection.Target}] = e
} else {
pairmap[PwnPair{connection.Source, connection.Target}] = connection.PwnMethodBitmap
}
}

pg.Nodes = make([]GraphObject, len(nodemap))
i := 0
for _, node := range nodemap {
pg.Nodes[i] = node
i++
}

pg.Connections = make([]PwnConnection, len(pairmap))
i = 0
for connection, methods := range pairmap {
pg.Connections[i] = PwnConnection{
Source: connection.Source,
Target: connection.Target,
PwnMethodBitmap: methods,
}
i++
}
}

func (pg PwnGraph) SCC() [][]*Object {
offsetmap := make(map[*Object]int)
for i, o := range pg.Nodes {
Expand Down
2 changes: 1 addition & 1 deletion modules/engine/loaders.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func Load(path string, cb ProgressCallbackFunc) ([]loaderobjects, error) {
}

for _, loader := range loaders {
log.Debug().Msgf("Initializing loader %v", loader.Name())
log.Debug().Msgf("Initializing loader for %v", loader.Name())
err := loader.Init()
if err != nil {
return nil, err
Expand Down
51 changes: 44 additions & 7 deletions modules/engine/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const threadbuckets = 1024

var threadsafeobjectmutexes [threadbuckets]sync.RWMutex

func SetThreadsafe(enable bool) {
func setThreadsafe(enable bool) {
if enable {
threadsafeobject++
} else {
Expand Down Expand Up @@ -187,6 +187,8 @@ func (o *Object) Absorb(source *Object) {
target.members = append(target.members, newmember)
}
}
} else {
target.members = source.members
}

if len(source.memberof) > 0 {
Expand All @@ -199,6 +201,8 @@ func (o *Object) Absorb(source *Object) {
target.memberof = append(target.memberof, newmemberof)
}
}
} else {
target.memberof = source.memberof
}

for _, child := range source.children {
Expand All @@ -208,6 +212,8 @@ func (o *Object) Absorb(source *Object) {
// Move the securitydescriptor, as we dont have the attribute saved to regenerate it (we throw it away at import after populating the cache)
if target.sdcache == nil && source.sdcache != nil {
target.sdcache = source.sdcache
} else {
target.sdcache = nil
}

// If the source has a parent, but the target doesn't we assimilate that role (muhahaha)
Expand Down Expand Up @@ -473,7 +479,7 @@ func (o *Object) Members(recursive bool) []*Object {
}

members := make(map[*Object]struct{})
o.recursemembers(members)
o.recursemembers(&members)

membersarray := make([]*Object, len(members))
var i int
Expand All @@ -484,19 +490,45 @@ func (o *Object) Members(recursive bool) []*Object {
return membersarray
}

func (o *Object) recursemembers(members map[*Object]struct{}) {
func (o *Object) recursemembers(members *map[*Object]struct{}) {
for _, directmember := range o.members {
if _, found := members[directmember]; found {
if _, found := (*members)[directmember]; found {
// endless loop, not today thanks
continue
}
members[directmember] = struct{}{}
(*members)[directmember] = struct{}{}
directmember.recursemembers(members)
}
}

func (o *Object) MemberOf() []*Object {
return o.memberof
func (o *Object) MemberOf(recursive bool) []*Object {
o.lock()
defer o.unlock()
if !recursive {
return o.memberof
}

memberof := make(map[*Object]struct{})
o.recursememberof(&memberof)

memberofarray := make([]*Object, len(memberof))
var i int
for member := range memberof {
memberofarray[i] = member
i++
}
return memberofarray
}

func (o *Object) recursememberof(memberof *map[*Object]struct{}) {
for _, directmemberof := range o.memberof {
if _, found := (*memberof)[directmemberof]; found {
// endless loop, not today thanks
continue
}
(*memberof)[directmemberof] = struct{}{}
directmemberof.recursemembers(memberof)
}
}

// Wrapper for Set - easier to call
Expand Down Expand Up @@ -570,6 +602,11 @@ func (o *Object) SetFlex(flexinit ...interface{}) {
data = append(data, AttributeValueBool(*v))
case bool:
data = append(data, AttributeValueBool(v))
case int64:
if ignoreblanks && v == 0 {
continue
}
data = append(data, AttributeValueInt(v))
case Attribute:
if attribute != 0 && (!ignoreblanks || len(data) > 0) {
o.set(attribute, data)
Expand Down
2 changes: 1 addition & 1 deletion modules/engine/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (os *Objects) SetThreadsafe(enable bool) {
if os.threadsafe < 0 {
panic("threadsafe is negative")
}
SetThreadsafe(enable) // Do this globally for individial objects too
setThreadsafe(enable) // Do this globally for individial objects too
}

func (os *Objects) lock() {
Expand Down
6 changes: 4 additions & 2 deletions modules/engine/objecttype.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ type objecttypeinfo struct {
DefaultEnabledL bool
}

var objecttypenums []objecttypeinfo
var objecttypenums = []objecttypeinfo{
{Name: "#OBJECT_TYPE_NOT_FOUND_ERROR#"},
}

var objecttypemutex sync.RWMutex

Expand All @@ -71,7 +73,7 @@ func NewObjectType(name, lookup string) ObjectType {
return objecttype
}

newindex := ObjectType(len(objecttypenames))
newindex := ObjectType(len(objecttypenums))
objecttypenames[lookup] = newindex
objecttypenums = append(objecttypenums, objecttypeinfo{
Name: name,
Expand Down
Loading

0 comments on commit 8266894

Please sign in to comment.