Skip to content

Commit

Permalink
Added cPassword analysis to GPO
Browse files Browse the repository at this point in the history
  • Loading branch information
lkarlslund committed Feb 24, 2022
1 parent 1e79f62 commit 687e318
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 26 deletions.
87 changes: 72 additions & 15 deletions modules/engine/object.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package engine

import (
"encoding/xml"
"errors"
"fmt"
"strconv"
Expand Down Expand Up @@ -39,18 +40,21 @@ func setThreadsafe(enable bool) {
var UnknownGUID = uuid.UUID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}

type Object struct {
values AttributeValueMap
PwnableBy PwnConnections
CanPwn PwnConnections
parent *Object
sdcache *SecurityDescriptor
sid windowssecurity.SID
children []*Object
members []*Object
values AttributeValueMap
PwnableBy PwnConnections
CanPwn PwnConnections
parent *Object
sdcache *SecurityDescriptor
sid windowssecurity.SID
children []*Object
members []*Object
membersrecursive []*Object

// objectclassguids []uuid.UUID
memberof []*Object
id uint32
guid uuid.UUID
memberof []*Object
memberofrecursive []*Object
id uint32
guid uuid.UUID
// objectcategoryguid uuid.UUID
guidcached bool
sidcached bool
Expand Down Expand Up @@ -223,6 +227,8 @@ func (o *Object) Absorb(source *Object) {
}

target.objecttype = 0 // Recalculate this
target.memberofrecursive = nil
target.membersrecursive = nil
}

func (o *Object) AttributeValueMap() AttributeValueMap {
Expand All @@ -237,8 +243,48 @@ func (o *Object) AttributeValueMap() AttributeValueMap {
return val
}

type StringMap map[string][]string

func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {

tokens := []xml.Token{start}

for key, values := range s {
t := xml.StartElement{Name: xml.Name{"", key}}
for _, value := range values {
tokens = append(tokens, t, xml.CharData(value), xml.EndElement{t.Name})
}
}

tokens = append(tokens, xml.EndElement{start.Name})

for _, t := range tokens {
err := e.EncodeToken(t)
if err != nil {
return err
}
}

// flush to ensure tokens are written
return e.Flush()
}

func (o *Object) NameStringMap() StringMap {
o.lock()
defer o.unlock()
result := make(StringMap)
for attr, values := range o.values {
result[attr.String()] = values.StringSlice()
}
return result
}

func (o *Object) MarshalJSON() ([]byte, error) {
return jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(o.AttributeValueMap())
return jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(o.NameStringMap())
}

func (o *Object) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return o.NameStringMap().MarshalXML(e, start)
}

func (o *Object) IDString() string {
Expand Down Expand Up @@ -504,10 +550,14 @@ func (o *Object) recursemembers(members *map[*Object]struct{}) {
func (o *Object) MemberOf(recursive bool) []*Object {
o.lock()
defer o.unlock()
if !recursive {
if !recursive || len(o.memberof) == 0 {
return o.memberof
}

if o.memberofrecursive != nil {
return o.memberofrecursive
}

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

Expand All @@ -517,18 +567,25 @@ func (o *Object) MemberOf(recursive bool) []*Object {
memberofarray[i] = member
i++
}
o.memberofrecursive = memberofarray
return memberofarray
}

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

// Wrapper for Set - easier to call
Expand Down
97 changes: 88 additions & 9 deletions modules/integrations/activedirectory/analyze/gpoimport.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ import (
var (
gPCFileSysPath = engine.NewAttribute("gPCFileSysPath").Merge()

AbsolutePath = engine.NewAttribute("AbsolutePath")
RelativePath = engine.NewAttribute("RelativePath")
PwnOwns = engine.NewPwn("Owns")
PwnFSPartOfGPO = engine.NewPwn("FSPartOfGPO")
PwnFileCreate = engine.NewPwn("FileCreate")
PwnDirCreate = engine.NewPwn("DirCreate")
PwnFileWrite = engine.NewPwn("FileWrite")
PwnTakeOwnership = engine.NewPwn("FileTakeOwnership")
PwnModifyDACL = engine.NewPwn("FileModifyDACL")
AbsolutePath = engine.NewAttribute("absolutePath")
RelativePath = engine.NewAttribute("relativePath")
BinarySize = engine.NewAttribute("binarySize")
ExposedPassword = engine.NewAttribute("exposedPassword")

PwnExposesPassword = engine.NewPwn("ExposesPassword")
PwnContainsSensitiveData = engine.NewPwn("ContainsSensitiveData")
PwnReadSensitiveData = engine.NewPwn("ReadSensitiveData")
PwnOwns = engine.NewPwn("Owns")
PwnFSPartOfGPO = engine.NewPwn("FSPartOfGPO")
PwnFileCreate = engine.NewPwn("FileCreate")
PwnDirCreate = engine.NewPwn("DirCreate")
PwnFileWrite = engine.NewPwn("FileWrite")
PwnTakeOwnership = engine.NewPwn("FileTakeOwnership")
PwnModifyDACL = engine.NewPwn("FileModifyDACL")
)

func init() {
Expand All @@ -38,6 +44,9 @@ func init() {
})
}

var cpasswordusername = regexp.MustCompile(`cpassword="(?P<password>[^"]+)[^>]+(runAs|userName)="(?P<username>[^"]+)"`)
var usernamecpassword = regexp.MustCompile(`(runAs|userName)="(?P<username>[^"]+)[^>]+cpassword="(?P<password>[^"]+)"`)

func ImportGPOInfo(ginfo activedirectory.GPOdump, ao *engine.Objects) error {
if ginfo.DomainDN != "" {
ao.AddDefaultFlex(engine.UniqueSource, ginfo.DomainDN)
Expand All @@ -61,10 +70,13 @@ func ImportGPOInfo(ginfo activedirectory.GPOdump, ao *engine.Objects) error {
}

itemobject := ao.AddNew(
engine.IgnoreBlanks,
AbsolutePath, engine.AttributeValueString(absolutepath),
RelativePath, engine.AttributeValueString(relativepath),
engine.DisplayName, engine.AttributeValueString(relativepath),
engine.ObjectCategorySimple, engine.AttributeValueString(objecttype),
BinarySize, engine.AttributeValueInt(item.Size),
activedirectory.WhenChanged, engine.AttributeValueTime(item.Timestamp),
)

if relativepath == "/" {
Expand Down Expand Up @@ -114,6 +126,73 @@ func ImportGPOInfo(ginfo activedirectory.GPOdump, ao *engine.Objects) error {
}
}

var exposed []struct{ Username, Password string }

for _, line := range strings.Split(string(item.Contents), "\n") {
var unhandledpass bool

// FIXME: Handle other formats, adding something to catch this here
if strings.Contains(line, "cpassword=") && !strings.Contains(line, "cpassword=\"\"") {
log.Debug().Msgf("Found cpassword in %s", item.RelativePath)
log.Debug().Msgf("GPO Dump\n%s", item.Contents)
unhandledpass = true
}
for _, match := range cpasswordusername.FindAllStringSubmatch(line, -1) {
log.Debug().Msgf("Found password in %s", item.RelativePath)
log.Debug().Msgf("Password: %v", match)
log.Debug().Msgf("GPO Dump\n%s", item.Contents)
exposed = append(exposed, struct{ Username, Password string }{match[cpasswordusername.SubexpIndex("username")], match[cpasswordusername.SubexpIndex("password")]})
unhandledpass = false
}
for _, match := range usernamecpassword.FindAllStringSubmatch(line, -1) {
log.Debug().Msgf("Found username in %s", item.RelativePath)
log.Debug().Msgf("Password: %v", match)
log.Debug().Msgf("GPO Dump\n%s", item.Contents)
exposed = append(exposed, struct{ Username, Password string }{match[usernamecpassword.SubexpIndex("username")], match[usernamecpassword.SubexpIndex("password")]})
unhandledpass = false
}
if unhandledpass {
log.Error().Msgf("Unhandled password in %s", item.RelativePath)
log.Error().Msgf("GPO Dump\n%s", item.Contents)
log.Panic().Msg("Please submit bugreport on Github with redacted account name and redacted password")
}
}
for _, e := range exposed {
// New object to contain the sensitive data
expobj := ao.AddNew(
engine.ObjectCategorySimple, "ExposedPassword",
engine.DisplayName, "Exposed password for "+e.Username,
ExposedPassword, e.Password,
)

// The account targeted
target, _ := ao.FindOrAdd(
engine.DownLevelLogonName, engine.AttributeValueString(e.Username),
)

// GPO exposes this object
itemobject.Pwns(expobj, PwnContainsSensitiveData)
// Exposed password leaks this object
expobj.Pwns(target, PwnExposesPassword)

// Everyone that can read the file can then read the password
if item.DACL != nil {
dacl, err := engine.ParseACL(item.DACL)
if err != nil {
return err
}
for _, entry := range dacl.Entries {
entrysidobject, _ := ao.FindOrAdd(activedirectory.ObjectSid, engine.AttributeValueSID(entry.SID))

if entry.Type == engine.ACETYPE_ACCESS_ALLOWED && entry.SID.Component(2) == 21 {
if entry.Mask&engine.FILE_READ_DATA != 0 {
entrysidobject.Pwns(expobj, PwnReadSensitiveData)
}
}
}
}

}
switch relativepath {
case "/machine/preferences/groups/groups.xml", "/machine/microsoft/windows nt/secedit/gpttmpl.inf":
var pairs []SIDpair
Expand Down
6 changes: 6 additions & 0 deletions modules/integrations/activedirectory/collect/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ func Execute(cmd *cobra.Command, args []string) error {

var fileinfo activedirectory.GPOfileinfo
fileinfo.IsDir = d.IsDir()
if !fileinfo.IsDir {
if info, err := d.Info(); err == nil {
fileinfo.Timestamp = info.ModTime()
fileinfo.Size = info.Size()
}
}
fileinfo.RelativePath = curpath[offset:]

if gppath == originalpath {
Expand Down
8 changes: 6 additions & 2 deletions modules/integrations/activedirectory/gpo.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package activedirectory

import (
"time"

"github.com/gofrs/uuid"
"github.com/lkarlslund/adalanche/modules/basedata"
"github.com/lkarlslund/adalanche/modules/windowssecurity"
Expand All @@ -22,8 +24,10 @@ type GPOfileinfo struct {
RelativePath string `json:",omitempty"`
IsDir bool `json:",omitempty"`

OwnerSID windowssecurity.SID `json:",omitempty"`
DACL []byte `json:",omitempty"`
Size int64 `json:",omitempty"`
Timestamp time.Time `json:",omitempty"`
OwnerSID windowssecurity.SID `json:",omitempty"`
DACL []byte `json:",omitempty"`

Contents []byte `json:",omitempty"`
}

0 comments on commit 687e318

Please sign in to comment.