Skip to content

Commit

Permalink
feat: add conversion logic to EIN to fix badly typed properties (#909)
Browse files Browse the repository at this point in the history
* feat: add conversion logic to EIN to fix badly typed properties

https://specterops.atlassian.net/browse/BED-4928

* chore: add test

* chore: add test

* fix: set trustattributes value properly in error case

* fix: set trustattributes value properly in error case
chore: refactor conversion code
  • Loading branch information
rvazarkar authored Oct 28, 2024
1 parent f8813f8 commit 010bf3e
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 4 deletions.
1 change: 0 additions & 1 deletion cmd/api/src/daemons/datapipe/convertors.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ func convertDomainData(domain ein.Domain, converted *ConvertedData) {
domainTrustData := ein.ParseDomainTrusts(domain)
converted.RelProps = append(converted.RelProps, domainTrustData.TrustRelationships...)
converted.NodeProps = append(converted.NodeProps, domainTrustData.ExtraNodeProps...)

}

func convertGPOData(gpo ein.GPO, converted *ConvertedData) {
Expand Down
74 changes: 72 additions & 2 deletions packages/go/ein/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package ein

import (
"strconv"
"strings"

"github.com/specterops/bloodhound/analysis"
Expand All @@ -40,13 +41,66 @@ func ConvertObjectToNode(item IngestBase, itemType graph.Kind) IngestibleNode {
itemProps = make(map[string]any)
}

if itemType == ad.Domain {
convertInvalidDomainProperties(itemProps)
}

return IngestibleNode{
ObjectID: item.ObjectIdentifier,
PropertyMap: itemProps,
Label: itemType,
}
}

func convertInvalidDomainProperties(itemProps map[string]any) {
convertProperty(itemProps, "machineaccountquota", stringToInt)
convertProperty(itemProps, "minpwdlength", stringToInt)
convertProperty(itemProps, "pwdproperties", stringToInt)
convertProperty(itemProps, "pwdhistorylength", stringToInt)
convertProperty(itemProps, "lockoutthreshold", stringToInt)
convertProperty(itemProps, "expirepasswordsonsmartcardonlyaccounts", stringToBool)
}

func convertProperty(itemProps map[string]any, keyName string, conversionFunction func(map[string]any, string)) {
conversionFunction(itemProps, keyName)
}

func stringToBool(itemProps map[string]any, keyName string) {
if rawProperty, ok := itemProps[keyName]; ok {
switch converted := rawProperty.(type) {
case string:
if final, err := strconv.ParseBool(converted); err != nil {
delete(itemProps, keyName)
} else {
itemProps[keyName] = final
}
case bool:
//pass
default:
log.Debugf("Removing %s with type %T", converted)
delete(itemProps, keyName)
}
}
}

func stringToInt(itemProps map[string]any, keyName string) {
if rawProperty, ok := itemProps[keyName]; ok {
switch converted := rawProperty.(type) {
case string:
if final, err := strconv.Atoi(converted); err != nil {
delete(itemProps, keyName)
} else {
itemProps[keyName] = final
}
case int:
//pass
default:
log.Debugf("Removing %s with type %T", keyName, converted)
delete(itemProps, keyName)
}
}
}

func ParseObjectContainer(item IngestBase, itemType graph.Kind) IngestibleRelationship {
containingPrincipal := item.ContainedBy
if containingPrincipal.ObjectIdentifier != "" {
Expand Down Expand Up @@ -284,6 +338,22 @@ func ParseGpLinks(links []GPLink, itemIdentifier string, itemType graph.Kind) []
func ParseDomainTrusts(domain Domain) ParsedDomainTrustData {
parsedData := ParsedDomainTrustData{}
for _, trust := range domain.Trusts {
var finalTrustAttributes int
switch converted := trust.TrustAttributes.(type) {
case string:
if i, err := strconv.Atoi(converted); err != nil {
log.Errorf("Error converting trust attributes %s to int", converted)
finalTrustAttributes = 0
} else {
finalTrustAttributes = i
}
case int:
finalTrustAttributes = converted
default:
log.Errorf("Error converting trust attributes %s to int", converted)
finalTrustAttributes = 0
}

parsedData.ExtraNodeProps = append(parsedData.ExtraNodeProps, IngestibleNode{
PropertyMap: map[string]any{"name": trust.TargetDomainName},
ObjectID: trust.TargetDomainSid,
Expand All @@ -306,7 +376,7 @@ func ParseDomainTrusts(domain Domain) ParsedDomainTrustData {
"isacl": false,
"sidfiltering": trust.SidFilteringEnabled,
"tgtdelegationenabled": trust.TGTDelegationEnabled,
"trustattributes": trust.TrustAttributes,
"trustattributes": finalTrustAttributes,
"trusttype": trust.TrustType,
"transitive": trust.IsTransitive},
RelType: ad.TrustedBy,
Expand All @@ -329,7 +399,7 @@ func ParseDomainTrusts(domain Domain) ParsedDomainTrustData {
"isacl": false,
"sidfiltering": trust.SidFilteringEnabled,
"tgtdelegationenabled": trust.TGTDelegationEnabled,
"trustattributes": trust.TrustAttributes,
"trustattributes": finalTrustAttributes,
"trusttype": trust.TrustType,
"transitive": trust.IsTransitive},
RelType: ad.TrustedBy,
Expand Down
118 changes: 118 additions & 0 deletions packages/go/ein/ad_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package ein_test

import (
"github.com/specterops/bloodhound/ein"
"github.com/specterops/bloodhound/graphschema/ad"
"github.com/stretchr/testify/assert"
"testing"
)

func TestConvertObjectToNode_DomainInvalidProperties(t *testing.T) {
baseItem := ein.IngestBase{
ObjectIdentifier: "ABC123",
Properties: map[string]any{
"machineaccountquota": "1",
"minpwdlength": "1",
"pwdproperties": "1",
"pwdhistorylength": "1",
"lockoutthreshold": "1",
"expirepasswordsonsmartcardonlyaccounts": "false",
},
Aces: nil,
IsDeleted: false,
IsACLProtected: false,
ContainedBy: ein.TypedPrincipal{},
}

result := ein.ConvertObjectToNode(baseItem, ad.Domain)
props := result.PropertyMap
assert.Contains(t, props, "machineaccountquota")
assert.Contains(t, props, "minpwdlength")
assert.Contains(t, props, "pwdproperties")
assert.Contains(t, props, "pwdhistorylength")
assert.Contains(t, props, "lockoutthreshold")
assert.Contains(t, props, "expirepasswordsonsmartcardonlyaccounts")
assert.Equal(t, 1, props["machineaccountquota"])
assert.Equal(t, 1, props["minpwdlength"])
assert.Equal(t, 1, props["pwdproperties"])
assert.Equal(t, 1, props["pwdhistorylength"])
assert.Equal(t, 1, props["lockoutthreshold"])
assert.Equal(t, false, props["expirepasswordsonsmartcardonlyaccounts"])

baseItem = ein.IngestBase{
ObjectIdentifier: "ABC123",
Properties: map[string]any{
"machineaccountquota": 1,
"minpwdlength": 1,
"pwdproperties": 1,
"pwdhistorylength": 1,
"lockoutthreshold": 1,
"expirepasswordsonsmartcardonlyaccounts": false,
},
Aces: nil,
IsDeleted: false,
IsACLProtected: false,
ContainedBy: ein.TypedPrincipal{},
}

result = ein.ConvertObjectToNode(baseItem, ad.Domain)
props = result.PropertyMap
assert.Contains(t, props, "machineaccountquota")
assert.Contains(t, props, "minpwdlength")
assert.Contains(t, props, "pwdproperties")
assert.Contains(t, props, "pwdhistorylength")
assert.Contains(t, props, "lockoutthreshold")
assert.Contains(t, props, "expirepasswordsonsmartcardonlyaccounts")
assert.Equal(t, 1, props["machineaccountquota"])
assert.Equal(t, 1, props["minpwdlength"])
assert.Equal(t, 1, props["pwdproperties"])
assert.Equal(t, 1, props["pwdhistorylength"])
assert.Equal(t, 1, props["lockoutthreshold"])
assert.Equal(t, false, props["expirepasswordsonsmartcardonlyaccounts"])
}

func TestParseDomainTrusts_TrustAttributesFix(t *testing.T) {
domainObject := ein.Domain{
IngestBase: ein.IngestBase{},
ChildObjects: nil,
Trusts: make([]ein.Trust, 0),
Links: nil,
}

domainObject.Trusts = append(domainObject.Trusts, ein.Trust{
TargetDomainSid: "abc123",
IsTransitive: false,
TrustDirection: ein.TrustDirectionInbound,
TrustType: "abc",
SidFilteringEnabled: false,
TargetDomainName: "abc456",
TGTDelegationEnabled: false,
TrustAttributes: "12345",
})

result := ein.ParseDomainTrusts(domainObject)
assert.Len(t, result.TrustRelationships, 1)

rel := result.TrustRelationships[0]
assert.Contains(t, rel.RelProps, "trustattributes")
assert.Equal(t, rel.RelProps["trustattributes"], 12345)

domainObject.Trusts = make([]ein.Trust, 0)
domainObject.Trusts = append(domainObject.Trusts, ein.Trust{
TargetDomainSid: "abc123",
IsTransitive: false,
TrustDirection: ein.TrustDirectionInbound,
TrustType: "abc",
SidFilteringEnabled: false,
TargetDomainName: "abc456",
TGTDelegationEnabled: false,
TrustAttributes: 12345,
})

result = ein.ParseDomainTrusts(domainObject)
assert.Len(t, result.TrustRelationships, 1)

rel = result.TrustRelationships[0]
assert.Contains(t, rel.RelProps, "trustattributes")
assert.Equal(t, rel.RelProps["trustattributes"], 12345)
}
2 changes: 1 addition & 1 deletion packages/go/ein/incoming_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ type Trust struct {
SidFilteringEnabled bool
TargetDomainName string
TGTDelegationEnabled bool
TrustAttributes string
TrustAttributes any
}

type GPLink struct {
Expand Down

0 comments on commit 010bf3e

Please sign in to comment.