diff --git a/cmd/api/src/analysis/ad/queries.go b/cmd/api/src/analysis/ad/queries.go index 372a815726..9fdcff056c 100644 --- a/cmd/api/src/analysis/ad/queries.go +++ b/cmd/api/src/analysis/ad/queries.go @@ -170,6 +170,18 @@ func GraphStats(ctx context.Context, db graph.Database) (model.ADDataQualityStat stat.IssuancePolicies = int(count) aggregation.IssuancePolicies += int(count) + case ad.Site: + stat.Sites = int(count) + aggregation.Sites += int(count) + + case ad.SiteServer: + stat.SiteServers = int(count) + aggregation.SiteServers += int(count) + + case ad.SiteSubnet: + stat.SiteSubnets = int(count) + aggregation.SiteSubnets += int(count) + case ad.Domain: // Do nothing. Only ADDataQualityAggregation stats have domain stats and the domain stats are handled in the outer domain loop } diff --git a/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go b/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go index b86bf5f74b..4891b8a628 100644 --- a/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go +++ b/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go @@ -258,6 +258,18 @@ func (s *BloodHoundGraphNode) SetIcon(nType string) { s.FontIcon = &BloodHoundGraphFontIcon{ Text: "fas fa-clipboard-check", } + case "Site": + s.FontIcon = &BloodHoundGraphFontIcon{ + Text: "fas fa-map-signs", + } + case "SiteServer": + s.FontIcon = &BloodHoundGraphFontIcon{ + Text: "fas fa-map-marker", + } + case "SiteSubnet": + s.FontIcon = &BloodHoundGraphFontIcon{ + Text: "fas fa-map", + } case "Meta": if tier, ok := s.Data["admintier"]; ok { if tier.(int64) == 0 { diff --git a/cmd/api/src/api/registration/v2.go b/cmd/api/src/api/registration/v2.go index 5e1d196e48..829ae8306b 100644 --- a/cmd/api/src/api/registration/v2.go +++ b/cmd/api/src/api/registration/v2.go @@ -276,6 +276,7 @@ func NewV2API(resources v2.Resources, routerInst *router.Router) { routerInst.GET(fmt.Sprintf("/api/v2/gpos/{%s}", api.URIPathVariableObjectID), resources.GetGPOEntityInfo).RequirePermissions(permissions.GraphDBRead), routerInst.GET(fmt.Sprintf("/api/v2/gpos/{%s}/computers", api.URIPathVariableObjectID), resources.ListADGPOAffectedComputers).RequirePermissions(permissions.GraphDBRead), routerInst.GET(fmt.Sprintf("/api/v2/gpos/{%s}/users", api.URIPathVariableObjectID), resources.ListADGPOAffectedUsers).RequirePermissions(permissions.GraphDBRead), + routerInst.GET(fmt.Sprintf("/api/v2/gpos/{%s}/sites", api.URIPathVariableObjectID), resources.ListADGPOAffectedSites).RequirePermissions(permissions.GraphDBRead), routerInst.GET(fmt.Sprintf("/api/v2/gpos/{%s}/controllers", api.URIPathVariableObjectID), resources.ListADEntityControllers).RequirePermissions(permissions.GraphDBRead), routerInst.GET(fmt.Sprintf("/api/v2/gpos/{%s}/tier-zero", api.URIPathVariableObjectID), resources.ListADGPOAffectedTierZero).RequirePermissions(permissions.GraphDBRead), routerInst.GET(fmt.Sprintf("/api/v2/gpos/{%s}/ous", api.URIPathVariableObjectID), resources.ListADGPOAffectedContainers).RequirePermissions(permissions.GraphDBRead), @@ -343,6 +344,20 @@ func NewV2API(resources v2.Resources, routerInst *router.Router) { routerInst.GET(fmt.Sprintf("/api/v2/issuancepolicies/{%s}/controllers", api.URIPathVariableObjectID), resources.ListADEntityControllers).RequirePermissions(permissions.GraphDBRead), routerInst.GET(fmt.Sprintf("/api/v2/issuancepolicies/{%s}/linkedtemplates", api.URIPathVariableObjectID), resources.ListADIssuancePolicyLinkedCertTemplates).RequirePermissions(permissions.GraphDBRead), + // Site Entity API + routerInst.GET(fmt.Sprintf("/api/v2/sites/{%s}", api.URIPathVariableObjectID), resources.GetSiteEntityInfo).RequirePermissions(permissions.GraphDBRead), + routerInst.GET(fmt.Sprintf("/api/v2/sites/{%s}/controllers", api.URIPathVariableObjectID), resources.ListADEntityControllers).RequirePermissions(permissions.GraphDBRead), + routerInst.GET(fmt.Sprintf("/api/v2/sites/{%s}/siteservers", api.URIPathVariableObjectID), resources.ListADSiteLinkedServers).RequirePermissions(permissions.GraphDBRead), + routerInst.GET(fmt.Sprintf("/api/v2/sites/{%s}/sitesubnets", api.URIPathVariableObjectID), resources.ListADSiteLinkedSubnets).RequirePermissions(permissions.GraphDBRead), + + // SiteServer Entity API + routerInst.GET(fmt.Sprintf("/api/v2/siteservers/{%s}", api.URIPathVariableObjectID), resources.GetSiteServerEntityInfo).RequirePermissions(permissions.GraphDBRead), + routerInst.GET(fmt.Sprintf("/api/v2/siteservers/{%s}/controllers", api.URIPathVariableObjectID), resources.ListADEntityControllers).RequirePermissions(permissions.GraphDBRead), + + // SiteSubnet Entity API + routerInst.GET(fmt.Sprintf("/api/v2/sitesubnets/{%s}", api.URIPathVariableObjectID), resources.GetSiteSubnetEntityInfo).RequirePermissions(permissions.GraphDBRead), + routerInst.GET(fmt.Sprintf("/api/v2/sitesubnets/{%s}/controllers", api.URIPathVariableObjectID), resources.ListADEntityControllers).RequirePermissions(permissions.GraphDBRead), + // Data Quality Stats API routerInst.GET(fmt.Sprintf("/api/v2/ad-domains/{%s}/data-quality-stats", api.URIPathVariableDomainID), resources.GetADDataQualityStats).RequirePermissions(permissions.GraphDBRead), routerInst.GET(fmt.Sprintf("/api/v2/azure-tenants/{%s}/data-quality-stats", api.URIPathVariableTenantID), resources.GetAzureDataQualityStats).RequirePermissions(permissions.GraphDBRead), diff --git a/cmd/api/src/api/v2/ad_entity.go b/cmd/api/src/api/v2/ad_entity.go index 6e70401a0f..2b21a7088a 100644 --- a/cmd/api/src/api/v2/ad_entity.go +++ b/cmd/api/src/api/v2/ad_entity.go @@ -160,6 +160,7 @@ func (s *Resources) GetGPOEntityInfo(response http.ResponseWriter, request *http "ous": adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectGPOContainerCandidateFilter), "computers": adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectComputersCandidateFilter), "users": adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectUsersCandidateFilter), + "sites": adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectSitesCandidateFilter), "controllers": adAnalysis.FetchInboundADEntityControllers, "tierzero": adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectGPOTierZeroCandidateFilter), } @@ -282,3 +283,33 @@ func (s *Resources) GetIssuancePolicyEntityInfo(response http.ResponseWriter, re s.handleAdEntityInfoQuery(response, request, ad.IssuancePolicy, countQueries) } + +func (s *Resources) GetSiteEntityInfo(response http.ResponseWriter, request *http.Request) { + var ( + countQueries = map[string]any{ + "controllers": adAnalysis.FetchInboundADEntityControllers, + } + ) + + s.handleAdEntityInfoQuery(response, request, ad.Site, countQueries) +} + +func (s *Resources) GetSiteServerEntityInfo(response http.ResponseWriter, request *http.Request) { + var ( + countQueries = map[string]any{ + "controllers": adAnalysis.FetchInboundADEntityControllers, + } + ) + + s.handleAdEntityInfoQuery(response, request, ad.SiteServer, countQueries) +} + +func (s *Resources) GetSiteSubnetEntityInfo(response http.ResponseWriter, request *http.Request) { + var ( + countQueries = map[string]any{ + "controllers": adAnalysis.FetchInboundADEntityControllers, + } + ) + + s.handleAdEntityInfoQuery(response, request, ad.SiteSubnet, countQueries) +} diff --git a/cmd/api/src/api/v2/ad_related_entity.go b/cmd/api/src/api/v2/ad_related_entity.go index 55cc4533d5..82fd43cfe5 100644 --- a/cmd/api/src/api/v2/ad_related_entity.go +++ b/cmd/api/src/api/v2/ad_related_entity.go @@ -205,6 +205,10 @@ func (s *Resources) ListADGPOAffectedUsers(response http.ResponseWriter, request s.handleAdRelatedEntityQuery(response, request, "ListADGPOAffectedUsers", adAnalysis.CreateGPOAffectedIntermediariesPathDelegate(ad.User), adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectUsersCandidateFilter)) } +func (s *Resources) ListADGPOAffectedSites(response http.ResponseWriter, request *http.Request) { + s.handleAdRelatedEntityQuery(response, request, "ListADGPOAffectedSites", adAnalysis.FetchGPOAffectedSitePaths, adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectSitesCandidateFilter)) +} + func (s *Resources) ListADGPOAffectedComputers(response http.ResponseWriter, request *http.Request) { s.handleAdRelatedEntityQuery(response, request, "ListADGPOAffectedComputers", adAnalysis.CreateGPOAffectedIntermediariesPathDelegate(ad.Computer), adAnalysis.CreateGPOAffectedIntermediariesListDelegate(adAnalysis.SelectComputersCandidateFilter)) } @@ -240,3 +244,11 @@ func (s *Resources) ListTrustedCAs(response http.ResponseWriter, request *http.R func (s *Resources) ListADCSEscalations(response http.ResponseWriter, request *http.Request) { s.handleAdRelatedEntityQuery(response, request, "ListADCSEscalations", adAnalysis.CreateADCSEscalationsPathDelegate, adAnalysis.CreateADCSEscalationsListDelegate) } + +func (s *Resources) ListADSiteLinkedServers(response http.ResponseWriter, request *http.Request) { + s.handleAdRelatedEntityQuery(response, request, "ListADSiteLinkedServers", adAnalysis.CreateSiteContainedPathDelegate(ad.SiteServer), adAnalysis.CreateSiteContainedListDelegate(ad.SiteServer)) +} + +func (s *Resources) ListADSiteLinkedSubnets(response http.ResponseWriter, request *http.Request) { + s.handleAdRelatedEntityQuery(response, request, "ListADSiteLinkedSubnets", adAnalysis.CreateSiteContainedPathDelegate(ad.SiteSubnet), adAnalysis.CreateSiteContainedListDelegate(ad.SiteSubnet)) +} diff --git a/cmd/api/src/database/migration/migrations/v8.4.0.sql b/cmd/api/src/database/migration/migrations/v8.4.0.sql index cbdbc4dd26..635a6e3257 100644 --- a/cmd/api/src/database/migration/migrations/v8.4.0.sql +++ b/cmd/api/src/database/migration/migrations/v8.4.0.sql @@ -42,3 +42,58 @@ JOIN permissions p )) ) ON CONFLICT DO NOTHING; + +-- Adding site specific columns to ad_data_quality_aggregations and ad_data_quality_stats tables +ALTER TABLE "ad_data_quality_aggregations" ADD COLUMN IF NOT EXISTS sites BIGINT DEFAULT 0; +ALTER TABLE "ad_data_quality_aggregations" ADD COLUMN IF NOT EXISTS siteservers BIGINT DEFAULT 0; +ALTER TABLE "ad_data_quality_aggregations" ADD COLUMN IF NOT EXISTS sitesubnets BIGINT DEFAULT 0; + +ALTER TABLE "ad_data_quality_stats" ADD COLUMN IF NOT EXISTS sites BIGINT DEFAULT 0; +ALTER TABLE "ad_data_quality_stats" ADD COLUMN IF NOT EXISTS siteservers BIGINT DEFAULT 0; +ALTER TABLE "ad_data_quality_stats" ADD COLUMN IF NOT EXISTS sitesubnets BIGINT DEFAULT 0; + + +-- Add Sites default selector to Tier Zero +WITH src_data AS ( + SELECT * FROM (VALUES +-- START +('Sites', true, true, E'MATCH (n:Site) \nRETURN n;', E'Control over an Active Directory site may allow users to compromise all assets associated with the site through the application of Group Policy Objects. Since AD Sites contain at least a Domain Controller as a Site Server, this results in the potential compromise of at least one domain in the forest. Therefore, Active Directory Site objects are Tier Zero.') +-- END + ) AS s (name, enabled, allow_disable, cypher, description) +), inserted_selectors AS ( +INSERT INTO asset_group_tag_selectors ( + asset_group_tag_id, + created_at, + created_by, + updated_at, + updated_by, + disabled_at, + disabled_by, + name, + description, + is_default, + allow_disable, + auto_certify +) +SELECT + (SELECT id FROM asset_group_tags WHERE type = 1 and position = 1 LIMIT 1), + current_timestamp, + 'SYSTEM', + current_timestamp, + 'SYSTEM', + CASE WHEN NOT d.enabled THEN current_timestamp ELSE NULL END, + CASE WHEN NOT d.enabled THEN 'SYSTEM' ELSE NULL END, + d.name, + d.description, + true, + d.allow_disable, + 2 +FROM src_data d WHERE NOT EXISTS(SELECT 1 FROM asset_group_tag_selectors WHERE name = d.name) + RETURNING id, name +) +INSERT INTO asset_group_tag_selector_seeds (selector_id, type, value) +SELECT + s.id, + 2, + d.cypher +FROM inserted_selectors s JOIN src_data d ON d.name = s.name; diff --git a/cmd/api/src/model/adquality.go b/cmd/api/src/model/adquality.go index 4ee6ea9cc4..2380a5244d 100644 --- a/cmd/api/src/model/adquality.go +++ b/cmd/api/src/model/adquality.go @@ -34,6 +34,9 @@ type ADDataQualityStat struct { NTAuthStores int `json:"ntauthstores" gorm:"column:ntauthstores"` CertTemplates int `json:"certtemplates" gorm:"column:certtemplates"` IssuancePolicies int `json:"issuancepolicies" gorm:"column:issuancepolicies"` + Sites int `json:"sites" gorm:"column:sites"` + SiteServers int `json:"siteservers" gorm:"column:siteservers"` + SiteSubnets int `json:"sitesubnets" gorm:"column:sitesubnets"` ACLs int `json:"acls" gorm:"column:acls"` Sessions int `json:"sessions"` Relationships int `json:"relationships"` @@ -58,6 +61,9 @@ type ADDataQualityAggregation struct { NTAuthStores int `json:"ntauthstores" gorm:"column:ntauthstores"` CertTemplates int `json:"certtemplates" gorm:"column:certtemplates"` IssuancePolicies int `json:"issuancepolicies" gorm:"column:issuancepolicies"` + Sites int `json:"sites" gorm:"column:sites"` + SiteServers int `json:"siteservers" gorm:"column:siteservers"` + SiteSubnets int `json:"sitesubnets" gorm:"column:sitesubnets"` Acls int `json:"acls" gorm:"column:acls"` Sessions int `json:"sessions"` Relationships int `json:"relationships"` diff --git a/cmd/api/src/model/ingest/ingest.go b/cmd/api/src/model/ingest/ingest.go index 345e993817..22b13feedd 100644 --- a/cmd/api/src/model/ingest/ingest.go +++ b/cmd/api/src/model/ingest/ingest.go @@ -78,8 +78,14 @@ func (s Metadata) MatchKind() (graph.Kind, bool) { return ad.CertTemplate, true case DataTypeIssuancePolicy: return ad.IssuancePolicy, true - } + case DataTypeSite: + return ad.Site, true + case DataTypeSiteServer: + return ad.SiteServer, true + case DataTypeSiteSubnet: + return ad.SiteSubnet, true + } return nil, false } @@ -103,6 +109,9 @@ const ( DataTypeCertTemplate DataType = "certtemplates" DataTypeAzure DataType = "azure" DataTypeIssuancePolicy DataType = "issuancepolicies" + DataTypeSite DataType = "sites" + DataTypeSiteServer DataType = "siteservers" + DataTypeSiteSubnet DataType = "sitesubnets" DataTypeOpenGraph DataType = "opengraph" ) @@ -125,6 +134,9 @@ func AllIngestDataTypes() []DataType { DataTypeCertTemplate, DataTypeAzure, DataTypeIssuancePolicy, + DataTypeSite, + DataTypeSiteServer, + DataTypeSiteSubnet, } } diff --git a/cmd/api/src/services/graphify/convertors.go b/cmd/api/src/services/graphify/convertors.go index 1acab6af23..35b211f47c 100644 --- a/cmd/api/src/services/graphify/convertors.go +++ b/cmd/api/src/services/graphify/convertors.go @@ -299,3 +299,37 @@ func convertIssuancePolicy(issuancePolicy ein.IssuancePolicy, converted *Convert converted.RelProps = append(converted.RelProps, container) } } + +func convertSiteData(site ein.Site, converted *ConvertedData, ingestTime time.Time) { + baseNodeProp := ein.ConvertObjectToNode(site.IngestBase, ad.Site, ingestTime) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, site.Aces, site.ObjectIdentifier, ad.Site)...) + + if rel := ein.ParseObjectContainer(site.IngestBase, ad.Site); rel.IsValid() { + converted.RelProps = append(converted.RelProps, rel) + } + + if len(site.Links) > 0 { + converted.RelProps = append(converted.RelProps, ein.ParseGpLinks(site.Links, site.ObjectIdentifier, ad.Site)...) + } +} + +func convertSiteServerData(siteServer ein.SiteServer, converted *ConvertedData, ingestTime time.Time) { + baseNodeProp := ein.ConvertObjectToNode(ein.IngestBase(siteServer), ad.SiteServer, ingestTime) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, siteServer.Aces, siteServer.ObjectIdentifier, ad.SiteServer)...) + + if rel := ein.ParseObjectContainer(ein.IngestBase(siteServer), ad.SiteServer); rel.IsValid() { + converted.RelProps = append(converted.RelProps, rel) + } +} + +func convertSiteSubnetData(siteSubnet ein.SiteSubnet, converted *ConvertedData, ingestTime time.Time) { + baseNodeProp := ein.ConvertObjectToNode(ein.IngestBase(siteSubnet), ad.SiteSubnet, ingestTime) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, siteSubnet.Aces, siteSubnet.ObjectIdentifier, ad.SiteSubnet)...) + + if rel := ein.ParseObjectContainer(ein.IngestBase(siteSubnet), ad.SiteSubnet); rel.IsValid() { + converted.RelProps = append(converted.RelProps, rel) + } +} diff --git a/cmd/api/src/services/graphify/ingest.go b/cmd/api/src/services/graphify/ingest.go index 2d95c63cdd..e0cb14dda5 100644 --- a/cmd/api/src/services/graphify/ingest.go +++ b/cmd/api/src/services/graphify/ingest.go @@ -339,6 +339,9 @@ var basicHandlers = map[ingest.DataType]basicIngestHandler{ ingest.DataTypeNTAuthStore: defaultBasicHandler(convertNTAuthStoreData), ingest.DataTypeCertTemplate: defaultBasicHandler(convertCertTemplateData), ingest.DataTypeIssuancePolicy: defaultBasicHandler(convertIssuancePolicy), + ingest.DataTypeSite: defaultBasicHandler(convertSiteData), + ingest.DataTypeSiteServer: defaultBasicHandler(convertSiteServerData), + ingest.DataTypeSiteSubnet: defaultBasicHandler(convertSiteSubnetData), } var sourceKindHandlers = map[ingest.DataType]sourceKindIngestHandler{ diff --git a/cmd/ui/src/ducks/graph/graphutils.ts b/cmd/ui/src/ducks/graph/graphutils.ts index a408d92aeb..98bf024c35 100644 --- a/cmd/ui/src/ducks/graph/graphutils.ts +++ b/cmd/ui/src/ducks/graph/graphutils.ts @@ -236,6 +236,9 @@ const ICONS: { [id in GraphNodeTypes]: string } = { [GraphNodeTypes.NTAuthStore]: 'fa-store', [GraphNodeTypes.CertTemplate]: 'fa-id-card', [GraphNodeTypes.IssuancePolicy]: 'fa-clipboard-check', + [GraphNodeTypes.Site]: 'fa-clipboard-check', + [GraphNodeTypes.SiteServer]: 'fa-clipboard-check', + [GraphNodeTypes.SiteSubnet]: 'fa-clipboard-check', }; const setFontIcons = (data: Items): void => { diff --git a/cmd/ui/src/ducks/graph/types.ts b/cmd/ui/src/ducks/graph/types.ts index 3ee5002d3b..1248f1d225 100644 --- a/cmd/ui/src/ducks/graph/types.ts +++ b/cmd/ui/src/ducks/graph/types.ts @@ -49,6 +49,9 @@ export enum GraphNodeTypes { NTAuthStore = 'NTAuthStore', CertTemplate = 'CertTemplate', IssuancePolicy = 'IssuancePolicy', + Site = 'Site', + SiteServer = 'SiteServer', + SiteSubnet = 'SiteSubnet', } export interface GraphNodeData { diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index d245a53c56..d834cbc2e8 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -1261,6 +1261,21 @@ IssuancePolicy: types.#Kind & { schema: "active_directory" } +Site: types.#Kind & { + symbol: "Site" + schema: "active_directory" +} + +SiteServer: types.#Kind & { + symbol: "SiteServer" + schema: "active_directory" +} + +SiteSubnet: types.#Kind & { + symbol: "SiteSubnet" + schema: "active_directory" +} + NodeKinds: [ Entity, User, @@ -1278,6 +1293,9 @@ NodeKinds: [ NTAuthStore, CertTemplate, IssuancePolicy, + Site, + SiteServer, + SiteSubnet ] Owns: types.#Kind & { diff --git a/packages/go/analysis/ad/filters.go b/packages/go/analysis/ad/filters.go index c58d0c929f..5ccacfa8c3 100644 --- a/packages/go/analysis/ad/filters.go +++ b/packages/go/analysis/ad/filters.go @@ -154,6 +154,10 @@ func SelectUsersCandidateFilter(node *graph.Node) bool { return node.Kinds.ContainsOneOf(ad.User) } +func SelectSitesCandidateFilter(node *graph.Node) bool { + return node.Kinds.ContainsOneOf(ad.Site) +} + func SelectComputersCandidateFilter(node *graph.Node) bool { return node.Kinds.ContainsOneOf(ad.Computer) } diff --git a/packages/go/analysis/ad/queries.go b/packages/go/analysis/ad/queries.go index 646b1e1153..1c05de59f0 100644 --- a/packages/go/analysis/ad/queries.go +++ b/packages/go/analysis/ad/queries.go @@ -210,7 +210,7 @@ func getGPOLinks(tx graph.Transaction, node *graph.Node) ([]*graph.Relationship, return query.And( query.Equals(query.StartID(), node.ID), query.Kind(query.Relationship(), ad.GPLink), - query.KindIn(query.End(), ad.Domain, ad.OU), + query.KindIn(query.End(), ad.Domain, ad.OU, ad.Site), ) })); err != nil { return nil, err @@ -334,6 +334,10 @@ func FetchGPOAffectedContainerPaths(tx graph.Transaction, node *graph.Node) (gra if _, end, err := ops.FetchRelationshipNodes(tx, rel); err != nil { return nil, err } else { + if end.Kinds.ContainsOneOf(ad.Site) { + // We don't want to handle Sites here, only domain and OUs + continue + } var descentFilter ops.SegmentFilter // Set our descent filter based on enforcement status @@ -373,6 +377,30 @@ func FetchGPOAffectedContainerPaths(tx graph.Transaction, node *graph.Node) (gra } } +func FetchGPOAffectedSitePaths(tx graph.Transaction, node *graph.Node) (graph.PathSet, error) { + pathSet := graph.NewPathSet() + + if gpLinks, err := getGPOLinks(tx, node); err != nil { + return nil, err + } else { + for _, rel := range gpLinks { + if _, end, err := ops.FetchRelationshipNodes(tx, rel); err != nil { + return nil, err + } else { + // Not bothering with enforcement status here since inheritance does not affect Sites + // We only want Sites here. We won't traverse further down. + if end.Kinds.ContainsOneOf(ad.Site) { + pathSet.AddPath(graph.Path{ + Nodes: []*graph.Node{node, end}, + Edges: []*graph.Relationship{rel}, + }) + } + } + } + return pathSet, nil + } +} + func CreateGPOAffectedIntermediariesPathDelegate(targetKinds ...graph.Kind) analysis.PathDelegate { return func(tx graph.Transaction, node *graph.Node) (graph.PathSet, error) { pathSet := graph.NewPathSet() @@ -525,7 +553,7 @@ func FetchEnforcedGPOsPaths(ctx context.Context, db graph.Database, target *grap // Walk the GPO path to see if any of the nodes between the GPO and the enforcement target block GPO // inheritance. This walk starts at the GPO and moves down, with end being the GPO to start segment.Path().WalkReverse(func(start, end *graph.Node, relationship *graph.Relationship) bool { - if !start.Kinds.ContainsOneOf(ad.OU, ad.Domain) { + if !start.Kinds.ContainsOneOf(ad.OU, ad.Domain, ad.Site) { // If we run into anything that isn't an OU or a Domain node then we're done checking for // inheritance blocking return false @@ -583,7 +611,7 @@ func FetchACLInheritancePath(ctx context.Context, db graph.Database, edge *graph Direction: graph.DirectionInbound, BranchQuery: func() graph.Criteria { return query.And( - query.KindIn(query.Start(), ad.Domain, ad.OU, ad.Container), + query.KindIn(query.Start(), ad.Domain, ad.OU, ad.Site, ad.Container), query.KindIn(query.Relationship(), ad.Contains), ) }, @@ -699,6 +727,42 @@ func CreateOUContainedPathDelegate(kind graph.Kind) analysis.PathDelegate { } } +func CreateSiteContainedListDelegate(kind graph.Kind) analysis.ListDelegate { + return func(tx graph.Transaction, node *graph.Node, skip, limit int) (graph.NodeSet, error) { + return ops.AcyclicTraverseTerminals(tx, ops.TraversalPlan{ + Root: node, + Direction: graph.DirectionOutbound, + BranchQuery: func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.Contains), + ) + }, + PathFilter: func(ctx *ops.TraversalContext, segment *graph.PathSegment) bool { + return segment.Node.Kinds.ContainsOneOf(kind) + }, + Skip: skip, + Limit: limit, + }) + } +} + +func CreateSiteContainedPathDelegate(kind graph.Kind) analysis.PathDelegate { + return func(tx graph.Transaction, node *graph.Node) (graph.PathSet, error) { + return ops.TraversePaths(tx, ops.TraversalPlan{ + Root: node, + Direction: graph.DirectionOutbound, + BranchQuery: func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.Contains), + ) + }, + PathFilter: func(ctx *ops.TraversalContext, segment *graph.PathSegment) bool { + return segment.Node.Kinds.ContainsOneOf(kind) + }, + }) + } +} + func CreateDomainTrustListDelegate(direction graph.Direction) analysis.ListDelegate { return func(tx graph.Transaction, node *graph.Node, skip, limit int) (graph.NodeSet, error) { return ops.AcyclicTraverseNodes(tx, ops.TraversalPlan{ diff --git a/packages/go/ein/incoming_models.go b/packages/go/ein/incoming_models.go index 28e7d133c7..8c31fbae81 100644 --- a/packages/go/ein/incoming_models.go +++ b/packages/go/ein/incoming_models.go @@ -174,6 +174,15 @@ type IssuancePolicy struct { GroupLink TypedPrincipal } +type Site struct { + IngestBase + Links []GPLink +} + +type SiteServer IngestBase + +type SiteSubnet IngestBase + type RootCA struct { IngestBase DomainSID string diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 4e21691829..2056efaf18 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -41,6 +41,9 @@ var ( NTAuthStore = graph.StringKind("NTAuthStore") CertTemplate = graph.StringKind("CertTemplate") IssuancePolicy = graph.StringKind("IssuancePolicy") + Site = graph.StringKind("Site") + SiteServer = graph.StringKind("SiteServer") + SiteSubnet = graph.StringKind("SiteSubnet") Owns = graph.StringKind("Owns") GenericAll = graph.StringKind("GenericAll") GenericWrite = graph.StringKind("GenericWrite") @@ -1144,7 +1147,7 @@ func (s Property) Is(others ...graph.Kind) bool { return false } func Nodes() []graph.Kind { - return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy} + return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy, Site, SiteServer, SiteSubnet} } func Relationships() []graph.Kind { return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, CrossForestTrust, SameForestTrust, SpoofSIDHistory, AbuseTGTDelegation, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC6a, ADCSESC6b, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, CoerceAndRelayNTLMToSMB, CoerceAndRelayNTLMToADCS, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw, ClaimSpecialIdentity, CoerceAndRelayNTLMToLDAP, CoerceAndRelayNTLMToLDAPS, ContainsIdentity, PropagatesACEsTo, GPOAppliesTo, CanApplyGPO, HasTrustKeys, ProtectAdminGroups} @@ -1173,5 +1176,5 @@ func IsACLKind(s graph.Kind) bool { return false } func NodeKinds() []graph.Kind { - return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy} + return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy, Site, SiteServer, SiteSubnet} } diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/LinuxAbuse.tsx index bcbe1caa9a..d87bd6915f 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/LinuxAbuse.tsx @@ -22,19 +22,53 @@ const LinuxAbuse: FC = () => { return ( <> - With full control of a GPO, you may make modifications to that GPO which will then apply to the users - and computers affected by the GPO. Select the target object you wish to push an evil policy down to, - then use the gpedit GUI to modify the GPO, using an evil policy that allows item-level targeting, such - as a new immediate scheduled task. Then wait at least 2 hours for the group policy client to pick up and - execute the new evil policy. See the references tab for a more detailed write up on this abuse. + If you control a GPO linked to a target object, you may make modifications to that GPO in order to inject malicious configurations into it. + You could for instance add a Scheduled Task that will then be executed by all of the computers and/or users to which the GPO applies, + thus compromising them. Note that some configurations (such as Scheduled Tasks) implement item-level targeting, allowing + to precisely target a specific object. + GPOs are applied every 90 minutes for standard objects (with a random offset of 0 to 30 minutes), and every 5 minutes for domain controllers. - - - pyGPOAbuse.py - {' '} - can be used for that purpose. - + + The + GroupPolicyBackdoor.py + {' '} + tool can be used to perform the attack from a Linux machine. First, define a module file that describes the configuration to inject. + The following one defines a computer configuration, with an immediate Scheduled Task adding a domain user as local administrator. + A filter is defined, so that it only applies to a specific target. + + + + { + '[MODULECONFIG]\n' + + 'name = Scheduled Tasks\n' + + 'type = computer\n' + + '\n' + + '[MODULEOPTIONS]\n' + + 'task_type = immediate\n' + + 'program = cmd.exe\n' + + 'arguments = /c "net localgroup Administrators corp.com\john /add"\n' + + '\n' + + '[MODULEFILTERS]\n' + + 'filters = [{ "operator": "AND", "type": "Computer Name", "value": "srv1.corp.com"}]' + } + + + + Place the described configuration into the Scheduled_task_add.ini file, and inject it into the target GPO with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + + Alternatively, + pyGPOAbuse.py + {' '} + can be used for that purpose. + ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/WindowsAbuse.tsx index 2935f9acfb..d6c12dcc23 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GPLink/WindowsAbuse.tsx @@ -14,7 +14,7 @@ // // SPDX-License-Identifier: Apache-2.0 -import { Typography } from '@mui/material'; +import { Link, Typography } from '@mui/material'; import { FC } from 'react'; import { EdgeInfoProps } from '../index'; @@ -22,11 +22,23 @@ const WindowsAbuse: FC = () => { return ( <> - With full control of a GPO, you may make modifications to that GPO which will then apply to the users - and computers affected by the GPO. Select the target object you wish to push an evil policy down to, - then use the gpedit GUI to modify the GPO, using an evil policy that allows item-level targeting, such - as a new immediate scheduled task. Then wait for the group policy client to pick up and execute the new - evil policy. See the references tab for a more detailed write up on this abuse. + If you control a GPO linked to a target object, you may make modifications to that GPO in order to inject malicious configurations into it. + You could for instance add a Scheduled Task that will then be executed by all of the computers and/or users to which the GPO applies, + thus compromising them. Note that some configurations (such as Scheduled Tasks) implement item-level targeting, allowing + to precisely target a specific object. + GPOs are applied every 90 minutes for standard objects (with a random offset of 0 to 30 minutes), and every 5 minutes for domain controllers. + See the references tab for a more detailed write up on this abuse. + + + + On a domain-joined Windows machine, the native Group Policy Management Console (GPMC) may be used to edit GPOs. + On a non-domain joined Windows Machine, the{' '} + + DRSAT (Disconnected RSAT) + tool can be used. ); diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx index 99ac0c545e..7c67f31e56 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx @@ -394,20 +394,19 @@ const LinuxAbuse: FC = ( - In such a situation, it may still be possible to exploit GenericAll permissions on a domain - object through an alternative attack vector. Indeed, with GenericAll permissions over a domain - object, you may make modifications to the gPLink attribute of the domain. The ability to alter - the gPLink attribute of a domain may allow an attacker to apply a malicious Group Policy Object - (GPO) to all of the domain user and computer objects (including the ones located in nested OUs). - This can be exploited to make said child objects execute arbitrary commands through an immediate + In such a situation, it may still be possible to exploit GenericAll permissions on a domain + object. Indeed, with GenericAll permissions over a domain object, it is possible to modify its + gPLink attribute. This may be abused to apply a malicious Group Policy Object + (GPO) to all of the domain's user and computer objects (including the ones located in nested OUs). + This can be exploited to make said child objects execute arbitrary commands e.g. through an immediate scheduled task, thus compromising them. - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -424,18 +423,31 @@ const LinuxAbuse: FC = ( . - - Be mindful of the number of users and computers that are in the given domain as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target domain object through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + You can then link the modified GPO to the domain, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "DC=corp,DC=com" -n "TARGETGPO"' + } - Alternatively, the ability to modify the gPLink attribute of a domain can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target domain - through its gPLink attribute, making all child users and computers apply the malicious GPO and - execute arbitrary commands. + Be mindful of the number of users and computers that are in the given OU as they all will + attempt to fetch and apply the malicious GPO. ); @@ -443,19 +455,53 @@ const LinuxAbuse: FC = ( return ( <> - With full control of a GPO, you may make modifications to that GPO which will then apply to the - users and computers affected by the GPO. Select the target object you wish to push an evil - policy down to, then use the gpedit GUI to modify the GPO, using an evil policy that allows - item-level targeting, such as a new immediate scheduled task. Then wait at least 2 hours for the - group policy client to pick up and execute the new evil policy. See the references tab for a - more detailed write up on this abuse. + With full control over a GPO, you may make modifications to that GPO in order to inject malicious configurations into it. + You could for instance add a Scheduled Task that will then be executed by all of the computers and/or users to which the GPO applies, + thus compromising them. Note that some configurations (such as Scheduled Tasks) implement item-level targeting, allowing + to precisely target a specific object. + GPOs are applied every 90 minutes for standard objects (with a random offset of 0 to 30 minutes), and every 5 minutes for domain controllers. + See the references tab for a more detailed write up on this abuse. - - pyGPOAbuse.py + The + GroupPolicyBackdoor.py {' '} - can be used for that purpose. + tool can be used to perform the attack from a Linux machine. First, define a module file that describes the configuration to inject. + The following one defines a computer configuration, with an immediate Scheduled Task adding a domain user as local administrator. + A filter is defined, so that it only applies to a specific target. + + + + { + '[MODULECONFIG]\n' + + 'name = Scheduled Tasks\n' + + 'type = computer\n' + + '\n' + + '[MODULEOPTIONS]\n' + + 'task_type = immediate\n' + + 'program = cmd.exe\n' + + 'arguments = /c "net localgroup Administrators corp.com\john /add"\n' + + '\n' + + '[MODULEFILTERS]\n' + + 'filters = [{ "operator": "AND", "type": "Computer Name", "value": "srv1.corp.com"}]' + } + + + + Place the described configuration into the Scheduled_task_add.ini file, and inject it into the target GPO with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + + Alternatively, + pyGPOAbuse.py + {' '} + can be used for that purpose. ); @@ -498,27 +544,26 @@ const LinuxAbuse: FC = ( Objects for which ACL inheritance is disabled - It is important to note that the compromise vector described above relies on ACL inheritance and - will not work for objects with ACL inheritance disabled, such as objects protected by - AdminSDHolder (attribute adminCount=1). This observation applies to any OU child user or - computer with ACL inheritance disabled, including objects located in nested sub-OUs. + The compromise vector described above relies on ACL inheritance and will not work for objects + with ACL inheritance disabled, such as objects protected by AdminSDHolder (attribute + adminCount=1). This observation applies to any user or computer with inheritance disabled, + including objects located in nested OUs. - In such a situation, it may still be possible to exploit GenericAll permissions on an OU through - an alternative attack vector. Indeed, with GenericAll permissions over an OU, you may make - modifications to the gPLink attribute of the OU. The ability to alter the gPLink attribute of an - OU may allow an attacker to apply a malicious Group Policy Object (GPO) to all of the OU's child - user and computer objects (including the ones located in nested sub-OUs). This can be exploited - to make said child objects execute arbitrary commands through an immediate scheduled task, thus - compromising them. + In such a situation, it may still be possible to exploit GenericAll permissions on an OU + object. Indeed, with GenericAll permissions over an OU, it is possible to modify its + gPLink attribute. This may be abused to apply a malicious Group Policy Object + (GPO) to all of the OU's user and computer objects (including the ones located in nested OUs). + This can be exploited to make said child objects execute arbitrary commands e.g. through an immediate + scheduled task, thus compromising them. - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -535,18 +580,31 @@ const LinuxAbuse: FC = ( . - - Be mindful of the number of users and computers that are in the given OU as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target OU through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + You can then link the modified GPO to the OU, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "OU=SERVERS,DC=corp,DC=com" -n "TARGETGPO"' + } - Alternatively, the ability to modify the gPLink attribute of an OU can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target OU through - its gPLink attribute, making all child users and computers apply the malicious GPO and execute - arbitrary commands. + Be mindful of the number of users and computers that are in the given OU as they all will + attempt to fetch and apply the malicious GPO. ); @@ -639,6 +697,70 @@ const LinuxAbuse: FC = ( ); + case 'Site': + return ( + <> + + GenericAll permissions over a Site object allow modifying the gPLink attribute of the site. + The ability to alter the gPLink attribute of a site may allow an attacker to apply a malicious Group Policy Object + (GPO) to all of the objects associated with the site. This can be exploited to make said objects execute + arbitrary commands e.g. through an immediate scheduled task, thus compromising them. + In the case of a site, the affected objects are the computers that have an IP address included in one of the site's subnets + (or computers that do not belong to any site if this is the default site), as well as users connecting to these computers. + Note that Server objects associated with the Site should be located in the Site. + + + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the + domain and to create machine accounts. Alternatively, an already compromised domain-joined + machine may be used to perform the attack. Note that the attack vector implementation is not + trivial and will require some setup. + + + + From a Linux machine, the gPLink manipulation attack vector may be exploited using the{' '} + + OUned.py + {' '} + tool. For a detailed outline of exploit requirements and implementation, you can refer to{' '} + + the article associated to the OUned.py tool + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) in that GPO, and then link the GPO to the target Site through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + Now you can link the modified GPO to the Site object, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=corp,DC=com" -n "TARGETGPO"' + } + + + + Be mindful of the number of users and computers that are in the given site as they all will + attempt to fetch and apply the malicious GPO. + + + ); default: return ( diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/References.tsx index af34772838..c9915268c7 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/References.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/References.tsx @@ -207,6 +207,12 @@ const References: FC = () => { https://github.com/CravateRouge/bloodyAD + + https://www.synacktiv.com/publications/site-unseen-enumerating-and-attacking-active-directory-sites + ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx index 785594a90c..ab724f9389 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx @@ -556,20 +556,19 @@ const WindowsAbuse: FC = - In such a situation, it may still be possible to exploit GenericAll permissions on a domain - object through an alternative attack vector. Indeed, with GenericAll permissions over a domain - object, you may make modifications to the gPLink attribute of the domain. The ability to alter - the gPLink attribute of a domain may allow an attacker to apply a malicious Group Policy Object - (GPO) to all of the domain user and computer objects (including the ones located in nested OUs). - This can be exploited to make said child objects execute arbitrary commands through an immediate + In such a situation, it may still be possible to exploit GenericAll permissions on a domain + object. Indeed, with GenericAll permissions over a domain object, it is possible to modify its + gPLink attribute. This may be abused to apply a malicious Group Policy Object + (GPO) to all of the domain's user and computer objects (including the ones located in nested OUs). + This can be exploited to make said child objects execute arbitrary commands e.g. through an immediate scheduled task, thus compromising them. - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -586,29 +585,37 @@ const WindowsAbuse: FC = - Be mindful of the number of users and computers that are in the given domain as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target domain object through its gPLink attribute. - Alternatively, the ability to modify the gPLink attribute of a domain can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target domain - through its gPLink attribute, making all child users and computers apply the malicious GPO and - execute arbitrary commands. + Be mindful of the number of users and computers that are in the given domain as they all will + attempt to fetch and apply the malicious GPO. ); case 'GPO': return ( <> + + With full control over a GPO, you may make modifications to that GPO in order to inject malicious configurations into it. + You could for instance add a Scheduled Task that will then be executed by all of the computers and/or users to which the GPO applies, + thus compromising them. Note that some configurations (such as Scheduled Tasks) implement item-level targeting, allowing + to precisely target a specific object. + GPOs are applied every 90 minutes for standard objects (with a random offset of 0 to 30 minutes), and every 5 minutes for domain controllers. + See the references tab for a more detailed write up on this abuse. + + - With full control of a GPO, you may make modifications to that GPO which will then apply to the - users and computers affected by the GPO. Select the target object you wish to push an evil - policy down to, then use the gpedit GUI to modify the GPO, using an evil policy that allows - item-level targeting, such as a new immediate scheduled task. Then wait for the group policy - client to pick up and execute the new evil policy. See the references tab for a more detailed - write up on this abuse. + On a domain-joined Windows machine, the native Group Policy Management Console (GPMC) may be used to edit GPOs. + On a non-domain joined Windows Machine, the{' '} + + DRSAT (Disconnected RSAT) + tool can be used. ); @@ -697,27 +704,26 @@ const WindowsAbuse: FC = Objects for which ACL inheritance is disabled - It is important to note that the compromise vector described above relies on ACL inheritance and - will not work for objects with ACL inheritance disabled, such as objects protected by - AdminSDHolder (attribute adminCount=1). This observation applies to any OU child user or - computer with ACL inheritance disabled, including objects located in nested sub-OUs. + The compromise vector described above relies on ACL inheritance and will not work for objects + with ACL inheritance disabled, such as objects protected by AdminSDHolder (attribute + adminCount=1). This observation applies to any user or computer with inheritance disabled, + including objects located in nested OUs. - In such a situation, it may still be possible to exploit GenericAll permissions on an OU through - an alternative attack vector. Indeed, with GenericAll permissions over an OU, you may make - modifications to the gPLink attribute of the OU. The ability to alter the gPLink attribute of an - OU may allow an attacker to apply a malicious Group Policy Object (GPO) to all of the OU's child - user and computer objects (including the ones located in nested sub-OUs). This can be exploited - to make said child objects execute arbitrary commands through an immediate scheduled task, thus - compromising them. + In such a situation, it may still be possible to exploit GenericAll permissions on an OU. + Indeed, with GenericAll permissions over an OU, it is possible to modify its + gPLink attribute. This may be abused to apply a malicious Group Policy Object + (GPO) to all of the OU's user and computer objects (including the ones located in nested OUs). + This can be exploited to make said child objects execute arbitrary commands e.g. through an immediate + scheduled task, thus compromising them. - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -734,16 +740,13 @@ const WindowsAbuse: FC = - Be mindful of the number of users and computers that are in the given OU as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target OU object through its gPLink attribute. - Alternatively, the ability to modify the gPLink attribute of an OU can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target OU through - its gPLink attribute, making all child users and computers apply the malicious GPO and execute - arbitrary commands. + Be mindful of the number of users and computers that are in the given domain as they all will + attempt to fetch and apply the malicious GPO. ); @@ -830,6 +833,51 @@ const WindowsAbuse: FC = ); + case 'Site': + return ( + <> + + GenericAll permissions over a Site object allow modifying the gPLink attribute of the site. + The ability to alter the gPLink attribute of a site may allow an attacker to apply a malicious Group Policy Object + (GPO) to all of the objects associated with the site. This can be exploited to make said objects execute + arbitrary commands e.g. through an immediate scheduled task, thus compromising them. + In the case of a site, the affected objects are the computers that have an IP address included in one of the site's subnets + (or computers that do not belong to any site if this is the default site), as well as users connecting to these computers. + Note that Server objects associated with the Site should be located in the Site. + + + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the + domain and to create machine accounts. Alternatively, an already compromised domain-joined + machine may be used to perform the attack. Note that the attack vector implementation is not + trivial and will require some setup. + + + + From a domain-joined compromised Windows machine, the gPLink manipulation attack vector may be + exploited through Powermad, PowerView and native Windows functionalities. For a detailed outline + of exploit requirements and implementation, you can refer to{' '} + + this article + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) in that GPO, and then link the GPO to the target Site through its gPLink attribute. + + + + Be mindful of the number of users and computers that are in the given site as they all will + attempt to fetch and apply the malicious GPO. + + + ); default: return ( <> diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/LinuxAbuse.tsx index 8750ebc47c..da8955cf3f 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/LinuxAbuse.tsx @@ -180,19 +180,53 @@ const LinuxAbuse: FC = ({ targetType }) => { return ( <> - With GenericWrite over a GPO, you may make modifications to that GPO which will then apply to - the users and computers affected by the GPO. Select the target object you wish to push an evil - policy down to, then use the gpedit GUI to modify the GPO, using an evil policy that allows - item-level targeting, such as a new immediate scheduled task. Then wait at least 2 hours for the - group policy client to pick up and execute the new evil policy. See the references tab for a - more detailed write up on this abuse. + With GenericWrite over a GPO, you may make modifications to that GPO in order to inject malicious configurations into it. + You could for instance add a Scheduled Task that will then be executed by all of the computers and/or users to which the GPO applies, + thus compromising them. Note that some configurations (such as Scheduled Tasks) implement item-level targeting, allowing + to precisely target a specific object. + GPOs are applied every 90 minutes for standard objects (with a random offset of 0 to 30 minutes), and every 5 minutes for domain controllers. + See the references tab for a more detailed write up on this abuse. - - pyGPOAbuse.py + The + GroupPolicyBackdoor.py {' '} - can be used for that purpose. + tool can be used to perform the attack from a Linux machine. First, define a module file that describes the configuration to inject. + The following one defines a computer configuration, with an immediate Scheduled Task adding a domain user as local administrator. + A filter is defined, so that it only applies to a specific target. + + + + { + '[MODULECONFIG]\n' + + 'name = Scheduled Tasks\n' + + 'type = computer\n' + + '\n' + + '[MODULEOPTIONS]\n' + + 'task_type = immediate\n' + + 'program = cmd.exe\n' + + 'arguments = /c "net localgroup Administrators corp.com\john /add"\n' + + '\n' + + '[MODULEFILTERS]\n' + + 'filters = [{ "operator": "AND", "type": "Computer Name", "value": "srv1.corp.com"}]' + } + + + + Place the described configuration into the Scheduled_task_add.ini file, and inject it into the target GPO with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + + Alternatively, + pyGPOAbuse.py + {' '} + can be used for that purpose. This edge can be a false positive in rare scenarios. If you have GenericWrite on the GPO with @@ -216,10 +250,10 @@ const LinuxAbuse: FC = ({ targetType }) => { - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -236,18 +270,31 @@ const LinuxAbuse: FC = ({ targetType }) => { . - - Be mindful of the number of users and computers that are in the given OU as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target OU through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + You can then link the modified GPO to the OU, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "OU=SERVERS,DC=corp,DC=com" -n "TARGETGPO"' + } - Alternatively, the ability to modify the gPLink attribute of an OU can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target OU through - its gPLink attribute, making all child users and computers apply the malicious GPO and execute - arbitrary commands. + Be mindful of the number of users and computers that are in the given OU as they all will + attempt to fetch and apply the malicious GPO. ); @@ -259,14 +306,14 @@ const LinuxAbuse: FC = ({ targetType }) => { attribute of the domain. The ability to alter the gPLink attribute of a domain may allow an attacker to apply a malicious Group Policy Object (GPO) to all of the domain user and computer objects (including the ones located in nested OUs). This can be exploited to make said child - items execute arbitrary commands through an immediate scheduled task, thus compromising them. + items execute arbitrary commands through e.g. an immediate scheduled task, thus compromising them. - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -283,18 +330,31 @@ const LinuxAbuse: FC = ({ targetType }) => { . - - Be mindful of the number of users and computers that are in the given domain as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target domain object through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + You can then link the modified GPO to the domain, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "DC=corp,DC=com" -n "TARGETGPO"' + } - Alternatively, the ability to modify the gPLink attribute of a domain can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target domain - through its gPLink attribute, making all child users and computers apply the malicious GPO and - execute arbitrary commands. + Be mindful of the number of users and computers that are in the given OU as they all will + attempt to fetch and apply the malicious GPO. ); @@ -350,6 +410,70 @@ const LinuxAbuse: FC = ({ targetType }) => { ); + case 'Site': + return ( + <> + + GenericWrite permissions over a Site object allow modifying the gPLink attribute of the site. + The ability to alter the gPLink attribute of a site may allow an attacker to apply a malicious Group Policy Object + (GPO) to all of the objects associated with the site. This can be exploited to make said objects execute + arbitrary commands e.g. through an immediate scheduled task, thus compromising them. + In the case of a site, the affected objects are the computers that have an IP address included in one of the site's subnets + (or computers that do not belong to any site if this is the default site), as well as users connecting to these computers. + Note that Server objects associated with the Site should be located in the Site. + + + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the + domain and to create machine accounts. Alternatively, an already compromised domain-joined + machine may be used to perform the attack. Note that the attack vector implementation is not + trivial and will require some setup. + + + + From a Linux machine, the gPLink manipulation attack vector may be exploited using the{' '} + + OUned.py + {' '} + tool. For a detailed outline of exploit requirements and implementation, you can refer to{' '} + + the article associated to the OUned.py tool + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) in that GPO, and then link the GPO to the target Site through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + Now you can link the modified GPO to the Site object, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=corp,DC=com" -n "TARGETGPO"' + } + + + + Be mindful of the number of users and computers that are in the given site as they all will + attempt to fetch and apply the malicious GPO. + + + ); default: return ( <> diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/References.tsx index de5af1bcde..f98726c0d8 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/References.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/References.tsx @@ -122,6 +122,12 @@ const References: FC = () => { href='https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53'> https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53 + + https://www.synacktiv.com/publications/site-unseen-enumerating-and-attacking-active-directory-sites + ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx index eb111f2e89..bd60cdcd7f 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx @@ -139,12 +139,23 @@ const WindowsAbuse: FC = ({ sourceName, sourceType, targetName, t return ( <> - With GenericWrite on a GPO, you may make modifications to that GPO which will then apply to the - users and computers affected by the GPO. Select the target object you wish to push an evil - policy down to, then use the gpedit GUI to modify the GPO, using an evil policy that allows - item-level targeting, such as a new immediate scheduled task. Then wait for the group policy - client to pick up and execute the new evil policy. See the references tab for a more detailed - write up on this abuse. + With GenericWrite permissions over a GPO, you may make modifications to that GPO in order to inject malicious configurations into it. + You could for instance add a Scheduled Task that will then be executed by all of the computers and/or users to which the GPO applies, + thus compromising them. Note that some configurations (such as Scheduled Tasks) implement item-level targeting, allowing + to precisely target a specific object. + GPOs are applied every 90 minutes for standard objects (with a random offset of 0 to 30 minutes), and every 5 minutes for domain controllers. + See the references tab for a more detailed write up on this abuse. + + + + On a domain-joined Windows machine, the native Group Policy Management Console (GPMC) may be used to edit GPOs. + On a non-domain joined Windows Machine, the{' '} + + DRSAT (Disconnected RSAT) + tool can be used. This edge can be a false positive in rare scenarios. If you have GenericWrite on the GPO with @@ -261,18 +272,18 @@ const WindowsAbuse: FC = ({ sourceName, sourceType, targetName, t return ( <> - With GenericWrite permissions over an OU, you may make modifications to the gPLink attribute of - the OU. The ability to alter the gPLink attribute of an OU may allow an attacker to apply a - malicious Group Policy Object (GPO) to all of the OU's child user and computer objects - (including the ones located in nested sub-OUs). This can be exploited to make said child items - execute arbitrary commands through an immediate scheduled task, thus compromising them. + With GenericWrite permissions over an OU, it is possible to modify its + gPLink attribute. This may be abused to apply a malicious Group Policy Object + (GPO) to all of the OU's user and computer objects (including the ones located in nested OUs). + This can be exploited to make said child objects execute arbitrary commands e.g. through an immediate + scheduled task, thus compromising them. - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -289,16 +300,13 @@ const WindowsAbuse: FC = ({ sourceName, sourceType, targetName, t - Be mindful of the number of users and computers that are in the given OU as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target OU object through its gPLink attribute. - Alternatively, the ability to modify the gPLink attribute of an OU can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target OU through - its gPLink attribute, making all child users and computers apply the malicious GPO and execute - arbitrary commands. + Be mindful of the number of users and computers that are in the given domain as they all will + attempt to fetch and apply the malicious GPO. ); @@ -306,18 +314,18 @@ const WindowsAbuse: FC = ({ sourceName, sourceType, targetName, t return ( <> - With GenericWrite permission over a domain object, you may make modifications to the gPLink - attribute of the domain. The ability to alter the gPLink attribute of a domain may allow an - attacker to apply a malicious Group Policy Object (GPO) to all of the domain user and computer - objects (including the ones located in nested OUs). This can be exploited to make said child - items execute arbitrary commands through an immediate scheduled task, thus compromising them. + With GenericWrite permission over a domain object, it is possible to modify its + gPLink attribute. This may be abused to apply a malicious Group Policy Object + (GPO) to all of the domain's user and computer objects (including the ones located in nested OUs). + This can be exploited to make said child objects execute arbitrary commands e.g. through an immediate + scheduled task, thus compromising them. - Successful exploitation will require the possibility to add non-existing DNS records to the - domain and to create machine accounts. Alternatively, an already compromised domain-joined - machine may be used to perform the attack. Note that the attack vector implementation is not - trivial and will require some setup. + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. @@ -334,16 +342,13 @@ const WindowsAbuse: FC = ({ sourceName, sourceType, targetName, t - Be mindful of the number of users and computers that are in the given domain as they all will - attempt to fetch and apply the malicious GPO. + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target domain object through its gPLink attribute. - Alternatively, the ability to modify the gPLink attribute of a domain can be exploited in - conjunction with write permissions on a GPO. In such a situation, an attacker could first inject - a malicious scheduled task in the controlled GPO, and then link the GPO to the target domain - through its gPLink attribute, making all child users and computers apply the malicious GPO and - execute arbitrary commands. + Be mindful of the number of users and computers that are in the given domain as they all will + attempt to fetch and apply the malicious GPO. ); @@ -399,6 +404,51 @@ const WindowsAbuse: FC = ({ sourceName, sourceType, targetName, t ); + case 'Site': + return ( + <> + + GenericWrite permissions over a Site object allow modifying the gPLink attribute of the site. + The ability to alter the gPLink attribute of a site may allow an attacker to apply a malicious Group Policy Object + (GPO) to all of the objects associated with the site. This can be exploited to make said objects execute + arbitrary commands e.g. through an immediate scheduled task, thus compromising them. + In the case of a site, the affected objects are the computers that have an IP address included in one of the site's subnets + (or computers that do not belong to any site if this is the default site), as well as users connecting to these computers. + Note that Server objects associated with the Site should be located in the Site. + + + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the + domain and to create machine accounts. Alternatively, an already compromised domain-joined + machine may be used to perform the attack. Note that the attack vector implementation is not + trivial and will require some setup. + + + + From a domain-joined compromised Windows machine, the gPLink manipulation attack vector may be + exploited through Powermad, PowerView and native Windows functionalities. For a detailed outline + of exploit requirements and implementation, you can refer to{' '} + + this article + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) in that GPO, and then link the GPO to the target Site through its gPLink attribute. + + + + Be mindful of the number of users and computers that are in the given site as they all will + attempt to fetch and apply the malicious GPO. + + + ); default: return ( <> diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/General.tsx index d721ea5b30..143d16771f 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/General.tsx @@ -28,24 +28,16 @@ const General: FC = ({ sourceName, sourceType, targetType, target - The ability to alter the gPLink attribute may allow an attacker to apply a malicious Group Policy Object - (GPO) to all child user and computer objects (including the ones located in nested OUs). This can be - exploited to make said child objects execute arbitrary commands through an immediate scheduled task, + The ability to alter the gPLink attribute of an object may allow an attacker to apply a malicious Group Policy Object + (GPO) to all child user and computer objects. This can be + exploited to make said child objects execute arbitrary commands through e.g. an immediate scheduled task, thus compromising them. - - - Successful exploitation will require the possibility to add non-existing DNS records to the domain and - to create machine accounts. Alternatively, an already compromised domain-joined machine may be used to - perform the attack. Note that the attack vector implementation is not trivial and will require some - setup. - - - Alternatively, the ability to modify the gPLink attribute can be exploited in conjunction with write - permissions on a GPO. In such a situation, an attacker could first inject a malicious scheduled task in - the controlled GPO, and then link the GPO to the target through its gPLink attribute, making all child - users and computers apply the malicious GPO and execute arbitrary commands. + For domain and OU objects, child user/computer objects are the ones belonging to the domain/OU (including the ones located in nested OUs). + In the case of a site, the affected objects are the computers that have an IP address included in one of the site's subnets + (or computers that do not belong to any site if this is the default site), as well as users connecting to these computers. + Note that Server objects associated with the Site should be located in the Site. ); diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/LinuxAbuse.tsx index 8f106d2deb..8aab63491b 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/LinuxAbuse.tsx @@ -16,31 +16,175 @@ import { Link, Typography } from '@mui/material'; import { FC } from 'react'; +import { EdgeInfoProps } from '../index'; -const LinuxAbuse: FC = () => { - return ( - <> - - From a Linux machine, the WriteGPLink permission may be abused using the{' '} - - OUned.py - {' '} - exploitation tool. For a detailed outline of exploit requirements and implementation, you can refer to{' '} - - the article associated to the OUned.py tool - - . - - - - Be mindful of the number of users and computers that are in the given domain as they all will attempt to - fetch and apply the malicious GPO. - - - ); +const LinuxAbuse: FC = ({ targetType }) => { + switch (targetType) { + case 'Domain': + return ( + <> + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. + + + + From a Linux machine, the gPLink manipulation attack vector may be exploited using the{' '} + + OUned.py + {' '} + tool. For a detailed outline of exploit requirements and implementation, you can refer to{' '} + + the article associated to the OUned.py tool + + . + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target domain object through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + You can then link the modified GPO to the domain, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "DC=corp,DC=com" -n "TARGETGPO"' + } + + + + Be mindful of the number of users and computers that are in the given OU as they all will + attempt to fetch and apply the malicious GPO. + + + ); + case 'OU': + return ( + <> + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. + + + + From a Linux machine, the gPLink manipulation attack vector may be exploited using the{' '} + + OUned.py + {' '} + tool. For a detailed outline of exploit requirements and implementation, you can refer to{' '} + + the article associated to the OUned.py tool + + . + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target OU through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + You can then link the modified GPO to the OU, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "OU=SERVERS,DC=corp,DC=com" -n "TARGETGPO"' + } + + + + Be mindful of the number of users and computers that are in the given OU as they all will + attempt to fetch and apply the malicious GPO. + + + ); + case 'Site': + return ( + <> + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the + domain and to create machine accounts. Alternatively, an already compromised domain-joined + machine may be used to perform the attack. Note that the attack vector implementation is not + trivial and will require some setup. + + + + From a Linux machine, the gPLink manipulation attack vector may be exploited using the{' '} + + OUned.py + {' '} + tool. For a detailed outline of exploit requirements and implementation, you can refer to{' '} + + the article associated to the OUned.py tool + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) in that GPO, and then link the GPO to the target Site through its gPLink attribute. + To do so, you can use the + GroupPolicyBackdoor.py + {' '} + tool. You may for instance first inject the malicious configuration with the 'inject' command. + + + { + 'python3 gpb.py gpo inject -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -m Scheduled_task_add.ini -n "TARGETGPO"' + } + + + Now you can link the modified GPO to the Site object, through the 'link' command. + + + { + 'python3 gpb.py links link -d "corp.com" --dc "dc.corp.com" -u "user" -p "password" -o "CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=corp,DC=com" -n "TARGETGPO"' + } + + + + Be mindful of the number of users and computers that are in the given site as they all will + attempt to fetch and apply the malicious GPO. + + + ); + default: + return ( + <> + No abuse information available for this node type. + + ); + } }; export default LinuxAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/References.tsx index 8a5ce5fdfa..502dfcc574 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/References.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/References.tsx @@ -33,6 +33,12 @@ const References: FC = () => { href='https://www.synacktiv.com/publications/ounedpy-exploiting-hidden-organizational-units-acl-attack-vectors-in-active-directory'> https://www.synacktiv.com/publications/ounedpy-exploiting-hidden-organizational-units-acl-attack-vectors-in-active-directory + + https://www.synacktiv.com/publications/site-unseen-enumerating-and-attacking-active-directory-sites + ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/WindowsAbuse.tsx index e43b110d96..68aea5001b 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteGPLink/WindowsAbuse.tsx @@ -16,29 +16,120 @@ import { Link, Typography } from '@mui/material'; import { FC } from 'react'; +import { EdgeInfoProps } from '../index'; -const WindowsAbuse: FC = () => { - return ( - <> - - From a domain-joined compromised Windows machine, the WriteGPLink permission may be abused through - Powermad, PowerView and native Windows functionalities. For a detailed outline of exploit requirements - and implementation, you can refer to{' '} - - this article - - . - - - - Be mindful of the number of users and computers that are in the given domain as they all will attempt to - fetch and apply the malicious GPO. - - - ); +const WindowsAbuse: FC = ({ targetType }) => { + switch (targetType) { + case 'Domain': + return ( + <> + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. + + + + From a domain-joined compromised Windows machine, the gPLink manipulation attack vector may be + exploited through Powermad, PowerView and native Windows functionalities. For a detailed outline + of exploit requirements and implementation, you can refer to{' '} + + this article + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target domain object through its gPLink attribute. + + + + Be mindful of the number of users and computers that are in the given domain as they all will + attempt to fetch and apply the malicious GPO. + + + ); + case 'OU': + return ( + <> + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the domain and to create machine accounts. + Alternatively, an already compromised domain-joined machine may be used to perform the attack. Note that the + attack vector implementation is not trivial and will require some setup. + + + + From a domain-joined compromised Windows machine, the gPLink manipulation attack vector may be + exploited through Powermad, PowerView and native Windows functionalities. For a detailed outline + of exploit requirements and implementation, you can refer to{' '} + + this article + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) into a controlled GPO, and then link the GPO to the target OU object through its gPLink attribute. + + + + Be mindful of the number of users and computers that are in the given domain as they all will + attempt to fetch and apply the malicious GPO. + + + ); + case 'Site': + return ( + <> + + If you do not have control over an existing GPO (or the ability to create new ones), successful exploitation + will require the possibility to add non-existing DNS records to the + domain and to create machine accounts. Alternatively, an already compromised domain-joined + machine may be used to perform the attack. Note that the attack vector implementation is not + trivial and will require some setup. + + + + From a domain-joined compromised Windows machine, the gPLink manipulation attack vector may be + exploited through Powermad, PowerView and native Windows functionalities. For a detailed outline + of exploit requirements and implementation, you can refer to{' '} + + this article + + . + + + + If you have control over an existing GPO (or the ability to create new ones), the attack is simpler. You can inject a malicious + configuration (e.g. an immediate scheduled task) in that GPO, and then link the GPO to the target Site through its gPLink attribute. + + + + Be mindful of the number of users and computers that are in the given site as they all will + attempt to fetch and apply the malicious GPO. + + + ); + default: + return ( + <> + No abuse information available for this node type. + + ); + } }; export default WindowsAbuse; diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index 7ede0cf403..315cdde1fb 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -30,6 +30,9 @@ export enum ActiveDirectoryNodeKind { NTAuthStore = 'NTAuthStore', CertTemplate = 'CertTemplate', IssuancePolicy = 'IssuancePolicy', + Site = 'Site', + SiteServer = 'SiteServer', + SiteSubnet = 'SiteSubnet', } export function ActiveDirectoryNodeKindToDisplay(value: ActiveDirectoryNodeKind): string | undefined { switch (value) { @@ -65,6 +68,12 @@ export function ActiveDirectoryNodeKindToDisplay(value: ActiveDirectoryNodeKind) return 'CertTemplate'; case ActiveDirectoryNodeKind.IssuancePolicy: return 'IssuancePolicy'; + case ActiveDirectoryNodeKind.Site: + return 'Site'; + case ActiveDirectoryNodeKind.SiteServer: + return 'SiteServer'; + case ActiveDirectoryNodeKind.SiteSubnet: + return 'SiteSubnet'; default: return undefined; } diff --git a/packages/javascript/bh-shared-ui/src/utils/content.ts b/packages/javascript/bh-shared-ui/src/utils/content.ts index 71be8561fe..b02ca37176 100644 --- a/packages/javascript/bh-shared-ui/src/utils/content.ts +++ b/packages/javascript/bh-shared-ui/src/utils/content.ts @@ -161,6 +161,11 @@ export const entityInformationEndpoints: Record apiClient.getUserV2(id, false, options), [ActiveDirectoryNodeKind.IssuancePolicy]: (id: string, options?: RequestOptions) => apiClient.getIssuancePolicyV2(id, false, options), + [ActiveDirectoryNodeKind.Site]: (id: string, options?: RequestOptions) => apiClient.getSiteV2(id, false, options), + [ActiveDirectoryNodeKind.SiteServer]: (id: string, options?: RequestOptions) => + apiClient.getSiteServerV2(id, false, options), + [ActiveDirectoryNodeKind.SiteSubnet]: (id: string, options?: RequestOptions) => + apiClient.getSiteSubnetV2(id, false, options), Meta: apiClient.getMetaV2, }; @@ -896,6 +901,11 @@ export const allSections: Partial EntityInfo label: 'Users', queryType: 'gpo-users', }, + { + id, + label: 'Sites', + queryType: 'gpo-sites', + }, { id, label: 'Tier Zero Objects', @@ -1020,6 +1030,37 @@ export const allSections: Partial EntityInfo queryType: 'issuancepolicy-linked_certificate_templates', }, ], + [ActiveDirectoryNodeKind.Site]: (id: string) => [ + { + id, + label: 'Inbound Object Control', + queryType: 'site-inbound_object_control', + }, + { + id, + label: 'Linked Site Servers', + queryType: 'site-linked_siteservers', + }, + { + id, + label: 'Linked Site Subnets', + queryType: 'site-linked_sitesubnets', + }, + ], + [ActiveDirectoryNodeKind.SiteServer]: (id: string) => [ + { + id, + label: 'Inbound Object Control', + queryType: 'siteserver-inbound_object_control', + }, + ], + [ActiveDirectoryNodeKind.SiteSubnet]: (id: string) => [ + { + id, + label: 'Inbound Object Control', + queryType: 'sitesubnet-inbound_object_control', + }, + ], [ActiveDirectoryNodeKind.User]: (id: string) => [ { id, @@ -1740,6 +1781,8 @@ export const entityRelationshipEndpoints = { apiClient.getGPOComputersV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), 'gpo-users': ({ id, skip, limit, type }) => apiClient.getGPOUsersV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), + 'gpo-sites': ({ id, skip, limit, type }) => + apiClient.getGPOSitesV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), 'gpo-tier_zero_objects': ({ id, skip, limit, type }) => apiClient.getGPOTierZeroV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), 'gpo-inbound_object_control': ({ id, skip, limit, type }) => @@ -1792,6 +1835,20 @@ export const entityRelationshipEndpoints = { apiClient .getIssuancePolicyLinkedTemplatesV2(id, skip, limit, type, { signal: controller.signal }) .then((res) => res.data), + 'site-inbound_object_control': ({ id, skip, limit, type }) => + apiClient.getSiteControllersV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), + 'site-linked_siteservers': ({ id, skip, limit, type }) => + apiClient.getSiteLinkedServersV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), + 'site-linked_sitesubnets': ({ id, skip, limit, type }) => + apiClient.getSiteLinkedSubnetsV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), + 'siteserver-inbound_object_control': ({ id, skip, limit, type }) => + apiClient + .getSiteServerControllersV2(id, skip, limit, type, { signal: controller.signal }) + .then((res) => res.data), + 'sitesubnet-inbound_object_control': ({ id, skip, limit, type }) => + apiClient + .getSiteSubnetControllersV2(id, skip, limit, type, { signal: controller.signal }) + .then((res) => res.data), 'user-sessions': ({ id, skip, limit, type }) => apiClient.getUserSessionsV2(id, skip, limit, type, { signal: controller.signal }).then((res) => res.data), 'user-member_of': ({ id, skip, limit, type }) => diff --git a/packages/javascript/bh-shared-ui/src/utils/icons.ts b/packages/javascript/bh-shared-ui/src/utils/icons.ts index f86a9c94cd..5010c789c7 100644 --- a/packages/javascript/bh-shared-ui/src/utils/icons.ts +++ b/packages/javascript/bh-shared-ui/src/utils/icons.ts @@ -35,6 +35,9 @@ import { faLandmark, faList, faLock, + faMapSigns, + faMap, + faMapMarker, faObjectGroup, faQuestion, faRobot, @@ -140,6 +143,21 @@ export const NODE_ICONS: IconDictionary = { color: '#99B2DD', }, + [ActiveDirectoryNodeKind.Site]: { + icon: faMapSigns, + color: '#bababdff', + }, + + [ActiveDirectoryNodeKind.SiteServer]: { + icon: faMapMarker, + color: '#dcdce6ff', + }, + + [ActiveDirectoryNodeKind.SiteSubnet]: { + icon: faMap, + color: '#fdfdfdff', + }, + [ActiveDirectoryNodeKind.OU]: { icon: faSitemap, color: '#FFAA00', diff --git a/packages/javascript/bh-shared-ui/src/views/DataQuality/DomainInfo.tsx b/packages/javascript/bh-shared-ui/src/views/DataQuality/DomainInfo.tsx index 87ce459f63..3d1219ac93 100644 --- a/packages/javascript/bh-shared-ui/src/views/DataQuality/DomainInfo.tsx +++ b/packages/javascript/bh-shared-ui/src/views/DataQuality/DomainInfo.tsx @@ -54,6 +54,9 @@ export const DomainMap = { ntauthstores: { displayText: 'NTAuthStores', kind: ActiveDirectoryNodeKind.NTAuthStore }, certtemplates: { displayText: 'CertTemplates', kind: ActiveDirectoryNodeKind.CertTemplate }, issuancepolicies: { displayText: 'IssuancePolicies', kind: ActiveDirectoryNodeKind.IssuancePolicy }, + sites: { displayText: 'Sites', kind: ActiveDirectoryNodeKind.Site }, + siteservers: { displayText: 'SiteServers', kind: ActiveDirectoryNodeKind.SiteServer }, + sitesubnets: { displayText: 'SiteSubnet', kind: ActiveDirectoryNodeKind.SiteSubnet }, containers: { displayText: 'Containers', kind: ActiveDirectoryNodeKind.Container, diff --git a/packages/javascript/js-client-library/src/client.ts b/packages/javascript/js-client-library/src/client.ts index ee77b2c09a..bbcf64460a 100644 --- a/packages/javascript/js-client-library/src/client.ts +++ b/packages/javascript/js-client-library/src/client.ts @@ -1847,6 +1847,21 @@ class BHEAPIClient { ) ); + getGPOSitesV2 = (id: string, skip?: number, limit?: number, type?: string, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/gpos/${id}/sites`, + Object.assign( + { + params: { + skip, + limit, + type, + }, + }, + options + ) + ); + getGPOControllersV2 = (id: string, skip?: number, limit?: number, type?: string, options?: RequestOptions) => this.baseClient.get( `/api/v2/gpos/${id}/controllers`, @@ -2616,6 +2631,118 @@ class BHEAPIClient { ) ); + getSiteV2 = (id: string, counts?: boolean, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/sites/${id}`, + Object.assign( + { + params: { + counts, + }, + }, + options + ) + ); + + getSiteControllersV2 = (id: string, skip?: number, limit?: number, type?: string, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/sites/${id}/controllers`, + Object.assign( + { + params: { + skip, + limit, + type, + }, + }, + options + ) + ); + getSiteLinkedServersV2 = (id: string, skip?: number, limit?: number, type?: string, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/sites/${id}/siteservers`, + Object.assign( + { + params: { + skip, + limit, + type, + }, + }, + options + ) + ); + getSiteLinkedSubnetsV2 = (id: string, skip?: number, limit?: number, type?: string, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/sites/${id}/sitesubnets`, + Object.assign( + { + params: { + skip, + limit, + type, + }, + }, + options + ) + ); + + getSiteServerV2 = (id: string, counts?: boolean, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/siteservers/${id}`, + Object.assign( + { + params: { + counts, + }, + }, + options + ) + ); + + getSiteServerControllersV2 = (id: string, skip?: number, limit?: number, type?: string, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/siteservers/${id}/controllers`, + Object.assign( + { + params: { + skip, + limit, + type, + }, + }, + options + ) + ); + + getSiteSubnetV2 = (id: string, counts?: boolean, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/sitesubnets/${id}`, + Object.assign( + { + params: { + counts, + }, + }, + options + ) + ); + + getSiteSubnetControllersV2 = (id: string, skip?: number, limit?: number, type?: string, options?: RequestOptions) => + this.baseClient.get( + `/api/v2/sitesubnets/${id}/controllers`, + Object.assign( + { + params: { + skip, + limit, + type, + }, + }, + options + ) + ); + getMetaV2 = (id: string, options?: RequestOptions) => this.baseClient.get(`/api/v2/meta/${id}`, options); getShortestPathV2 = (startNode: string, endNode: string, relationshipKinds?: string, options?: RequestOptions) =>