diff --git a/cmd/api/src/daemons/datapipe/convertors.go b/cmd/api/src/daemons/datapipe/convertors.go index 949056d465..19b014386f 100644 --- a/cmd/api/src/daemons/datapipe/convertors.go +++ b/cmd/api/src/daemons/datapipe/convertors.go @@ -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) { diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 48a0205d30..6bfd64480f 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -17,6 +17,7 @@ package ein import ( + "strconv" "strings" "github.com/specterops/bloodhound/analysis" @@ -40,6 +41,10 @@ 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, @@ -47,6 +52,55 @@ func ConvertObjectToNode(item IngestBase, itemType graph.Kind) IngestibleNode { } } +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 != "" { @@ -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, @@ -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, @@ -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, diff --git a/packages/go/ein/ad_test.go b/packages/go/ein/ad_test.go new file mode 100644 index 0000000000..88cb6d6654 --- /dev/null +++ b/packages/go/ein/ad_test.go @@ -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) +} diff --git a/packages/go/ein/incoming_models.go b/packages/go/ein/incoming_models.go index b4564a6d3f..4d5fd580c9 100644 --- a/packages/go/ein/incoming_models.go +++ b/packages/go/ein/incoming_models.go @@ -208,7 +208,7 @@ type Trust struct { SidFilteringEnabled bool TargetDomainName string TGTDelegationEnabled bool - TrustAttributes string + TrustAttributes any } type GPLink struct {