From e43b0d30f2fadd0ccef43be2ec7804ba3df120da Mon Sep 17 00:00:00 2001 From: Ulises Rangel Date: Tue, 30 Jan 2024 09:35:26 -0600 Subject: [PATCH] feat: esc6a edge composition (#359) * feat: esc6a edge composition * chore: allow composition accordion to show for 6a * fix: add trustedby rel to path4 pattern, use outboundwithdepth for optional memberof traversal * chore: update dcfor pattern to use outboundwithdepth for optional group membership --- .../src/analysis/ad/adcs_integration_test.go | 52 ++++ cmd/api/src/test/integration/harnesses.go | 2 +- .../Explore/EdgeInfo/EdgeInfoContent.tsx | 4 +- .../ExploreSearchCombobox.tsx | 8 +- packages/go/analysis/ad/ad.go | 289 +++++++++++++++++- .../HelpTexts/ADCSESC6a/ADCSESC6a.tsx | 2 + .../HelpTexts/ADCSESC6a/Composition.tsx | 54 ++++ .../src/components/HelpTexts/utils.ts | 5 +- 8 files changed, 408 insertions(+), 8 deletions(-) create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/Composition.tsx diff --git a/cmd/api/src/analysis/ad/adcs_integration_test.go b/cmd/api/src/analysis/ad/adcs_integration_test.go index d6f856be22..16dcb6fc40 100644 --- a/cmd/api/src/analysis/ad/adcs_integration_test.go +++ b/cmd/api/src/analysis/ad/adcs_integration_test.go @@ -839,6 +839,58 @@ func TestADCSESC6a(t *testing.T) { require.Contains(t, names, "Group1", "Group2") } + if edge, err := tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.ADCSESC6a), + query.Equals(query.StartProperty(common.Name.String()), "Group1"), + ) + }).First(); err != nil { + t.Fatalf("error fetching esc6a edges in integration test; %v", err) + } else { + composition, err := ad2.GetADCSESC6aEdgeComposition(context.Background(), db, edge) + require.Nil(t, err) + names := []string{} + for _, node := range composition.AllNodes() { + name, _ := node.Properties.Get(common.Name.String()).String() + names = append(names, name) + } + require.Equal(t, 8, len(composition.AllNodes())) + require.Contains(t, names, "Group1") + require.Contains(t, names, "Group0") + require.Contains(t, names, "CertTemplate1") + require.Contains(t, names, "EnterpriseCA") + require.Contains(t, names, "RootCA") + require.Contains(t, names, "NTAuthStore") + require.Contains(t, names, "DC") + require.Contains(t, names, "Domain") + } + + if edge, err := tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.ADCSESC6a), + query.Equals(query.StartProperty(common.Name.String()), "Group2"), + ) + }).First(); err != nil { + t.Fatalf("error fetching esc6a edges in integration test; %v", err) + } else { + composition, err := ad2.GetADCSESC6aEdgeComposition(context.Background(), db, edge) + require.Nil(t, err) + names := []string{} + for _, node := range composition.AllNodes() { + name, _ := node.Properties.Get(common.Name.String()).String() + names = append(names, name) + } + require.Equal(t, 8, len(composition.AllNodes())) + require.Contains(t, names, "Group2") + require.Contains(t, names, "Group0") + require.Contains(t, names, "CertTemplate2") + require.Contains(t, names, "EnterpriseCA") + require.Contains(t, names, "RootCA") + require.Contains(t, names, "NTAuthStore") + require.Contains(t, names, "DC") + require.Contains(t, names, "Domain") + } + return nil }) }) diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index a19dc30758..b9d5820473 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -2109,7 +2109,7 @@ func initHarnessNodeProperties(c *GraphTestContext, nodeMap map[string]*graph.No } //This is an exception for schemaVersion which should not be a boolean - if value == "1" || value == "0" { + if value == "1" || value == "0" || value == "2" { intValue, _ := strconv.ParseInt(value, 10, 32) nodeMap[node.ID].Properties.Set(strings.ToLower(key), float64(intValue)) } else if boolValue, err := strconv.ParseBool(value); err != nil { diff --git a/cmd/ui/src/views/Explore/EdgeInfo/EdgeInfoContent.tsx b/cmd/ui/src/views/Explore/EdgeInfo/EdgeInfoContent.tsx index 2f46bf7109..28972f3642 100644 --- a/cmd/ui/src/views/Explore/EdgeInfo/EdgeInfoContent.tsx +++ b/cmd/ui/src/views/Explore/EdgeInfo/EdgeInfoContent.tsx @@ -73,7 +73,9 @@ const EdgeInfoContent: FC<{ selectedEdge: NonNullable }> = ({ sele const sendOnChange = (selectedEdge.name === 'GoldenCert' || selectedEdge.name === 'ADCSESC1' || - selectedEdge.name === 'ADCSESC3' || selectedEdge.name === 'ADCSESC9a') && + selectedEdge.name === 'ADCSESC3' || + selectedEdge.name === 'ADCSESC6a' || + selectedEdge.name === 'ADCSESC9a') && section[0] === 'composition'; return ( diff --git a/cmd/ui/src/views/Explore/ExploreSearchCombobox/ExploreSearchCombobox.tsx b/cmd/ui/src/views/Explore/ExploreSearchCombobox/ExploreSearchCombobox.tsx index b4e807795f..a235fd1c4a 100644 --- a/cmd/ui/src/views/Explore/ExploreSearchCombobox/ExploreSearchCombobox.tsx +++ b/cmd/ui/src/views/Explore/ExploreSearchCombobox/ExploreSearchCombobox.tsx @@ -81,7 +81,13 @@ const ExploreSearchCombobox: React.FC<{ }, startAdornment: selectedItem?.type && , }} - {...getInputProps({ onFocus: openMenu, refKey: 'inputRef', onChange: (e) => {handleNodeEdited(e.currentTarget.value)} })} + {...getInputProps({ + onFocus: openMenu, + refKey: 'inputRef', + onChange: (e) => { + handleNodeEdited(e.currentTarget.value); + }, + })} data-testid='explore_search_input-search' />
0 { + if err := enterpriseCAs.Each(func(value uint32) (bool, error) { + for _, segment := range enterpriseCASegments[graph.ID(value)] { + paths.AddPath(segment.Path()) + } + return true, nil + + }); err != nil { + return paths, err + } + } + + return paths, nil +} + func GetADCSESC3EdgeComposition(ctx context.Context, db graph.Database, edge *graph.Relationship) (graph.PathSet, error) { var ( startNode *graph.Node diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/ADCSESC6a.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/ADCSESC6a.tsx index 35d0f7205c..a2013b7bc9 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/ADCSESC6a.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/ADCSESC6a.tsx @@ -19,6 +19,7 @@ import WindowsAbuse from './WindowsAbuse'; import LinuxAbuse from './LinuxAbuse'; import Opsec from './Opsec'; import References from './References'; +import Composition from './Composition'; const ADCSESC6a = { general: General, @@ -26,6 +27,7 @@ const ADCSESC6a = { linuxAbuse: LinuxAbuse, opsec: Opsec, references: References, + composition: Composition, }; export default ADCSESC6a; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/Composition.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/Composition.tsx new file mode 100644 index 0000000000..4a24ff5eab --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/ADCSESC6a/Composition.tsx @@ -0,0 +1,54 @@ +// Copyright 2024 Specter Ops, Inc. +// +// Licensed under the Apache License, Version 2.0 +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +import { FC } from 'react'; +import { Alert, Box, Skeleton, Typography } from '@mui/material'; +import { apiClient } from '../../../utils/api'; +import { EdgeInfoProps } from '..'; +import { useQuery } from 'react-query'; +import VirtualizedNodeList, { VirtualizedNodeListItem } from '../../VirtualizedNodeList'; + +const Composition: FC = ({ sourceDBId, targetDBId, edgeName }) => { + const { data, isLoading, isError } = useQuery(['edgeComposition', sourceDBId, targetDBId, edgeName], ({ signal }) => + apiClient.getEdgeComposition(sourceDBId!, targetDBId!, edgeName!).then((result) => result.data) + ); + + const nodesArray: VirtualizedNodeListItem[] = Object.values(data?.data.nodes || {}).map((node) => ({ + name: node.label, + objectId: node.objectId, + kind: node.kind, + })); + + return ( + <> + + The relationship represents the effective outcome of the configuration and relationships between several + different objects. All objects involved in the creation of this relationship are listed here: + + + {isLoading ? ( + + ) : isError ? ( + Couldn't load edge composition + ) : ( + + )} + + + ); +}; + +export default Composition; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/utils.ts b/packages/javascript/bh-shared-ui/src/components/HelpTexts/utils.ts index 7a6caf3abe..afa0cf9ccd 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/utils.ts +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/utils.ts @@ -14,7 +14,7 @@ // // SPDX-License-Identifier: Apache-2.0 -import { makeStyles } from "@mui/styles"; +import { makeStyles } from '@mui/styles'; export const groupSpecialFormat = (sourceType: string | undefined, sourceName: string | undefined) => { if (!sourceType || !sourceName) return 'This entity has'; @@ -44,7 +44,6 @@ export const typeFormat = (type: string | undefined): string => { } }; - export const useHelpTextStyles = makeStyles((theme) => ({ containsCodeEl: { '& code': { @@ -59,4 +58,4 @@ export const useHelpTextStyles = makeStyles((theme) => ({ whiteSpace: 'pre-wrap', }, }, -})); \ No newline at end of file +}));