Normal view

There are new articles available, click to refresh the page.
Before yesterdayeXploit

Crossing Trusts 4 Delegation

By: 0xe7
4 April 2020 at 22:34

The purpose of this post is to attempt to explain some research I did not long ago on performing S4U across a domain trust. There doesn't seem to be much research in this area and very little information about the process of requesting the necessary tickets.

I highly recommend reading Elad Shamir's Wagging the Dog post before reading this, as here I'll primarily focus on the differences between performing S4U within a single domain and performing it across a domain trust but I won't be going into a huge amount of depth on the basics of S4U and it's potential for attack, as Elad has already done that so well.

Motivation

I first thought of the ability to perform cross domain S4U when looking at the following Microsoft advisory. It states:

“To re-enable delegation across trusts and return to the original unsafe configuration until constrained or resource-based delegation can be enabled, set the EnableTGTDelegation flag to Yes.”

This makes it clear that it is possible to perform cross domain constrained delegation. The problem was I couldn't find anywhere that gave any real detail as to how it is performed, and the tools used to take advantage of constrained delegation did not support it.

Luckily Will Schroeder published how to simulate real delegation traffic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# translated from the C# example at https://msdn.microsoft.com/en-us/library/ff649317.aspx

# load the necessary assembly
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')

# execute S4U2Self w/ WindowsIdentity to request a forwardable TGS for the specified user
$Ident = New-Object System.Security.Principal.WindowsIdentity @('[email protected]')

# actually impersonate the next context
$Context = $Ident.Impersonate()

# implicitly invoke S4U2Proxy with the specified action
ls \\PRIMARY.TESTLAB.LOCAL\C$

# undo the impersonation context
$Context.Undo()

This allowed me to figure out how it works and implement it into Rubeus.

Recap

To perform standard constrained delegation, 3 requests and responses are required: 1. AS-REQ and AS-REP, which is just the standard Kerberos authentication. 2. S4U2Self TGS-REQ and TGS-REP, which is the first step in the S4U process. 3. S4U2Proxy TGS-REQ and TGS-REP, which is the actual impersonation to the target service.

I created a visual representation as the ones I've seen previously weren't the easiest to understand:

In this it's the ticket contained within the final TGS_REP that is used to access the target service as the impersonated user.

Some Theory

After hours of using Will's Powershell to generate S4U traffic and staring at packet dumps, this is how I understood cross domain S4U to work:

Clearly there's a lot more going on here, so let me try to explain.

  1. The first step is still the same, a standard Kerberos authentication with the local domain controller. (1 and 2)

  2. A service ticket is requested for the foreign domains krbtgt service from the local domain controller. (3 and 4)

    • The users real TGT is required for this request.
    • This is known as the inter-realm TGT or cross domain TGT. This resulting service ticket is used to request service tickets for services on the foreign domain from the foreign domain controller.

    Here's where things start to get a little complicated. And the S4U2Self starts.

  3. A service ticket for yourself as the target user you want to impersonate is requested from the foreign domain controller. (5 and 6)

    • This requires the cross domain TGT.
    • This is the first step in the cross domain S4U2Self process.
  4. A service ticket for yourself as the user you want to impersonate is now requested from the local domain controller. (7 and 8)

    • This request includes the users normal TGT as well as having the S4U2Self ticket, received from the foreign domain in step 3, attached as an additional ticket.
    • This is the final step in the cross domain S4U2Self process.

    And finally the S4U2Proxy requests. As with S4U2Self, it involves 2 requests, 1 to the local DC and 1 to the foreign DC.

  5. A service ticket for the target service (on the foreign domain) is requested from the local domain controller. (9 and 10)

    • This requires the users real TGT as well as the S4U2Self ticket, received from the local domain controller in step 4, attached as an additional ticket.
    • This is the first step in the cross domain S4U2Proxy process.
  6. A service ticket for the target service is requested from the foreign domain controller. (11 and 12)

    • This requires the cross domain TGT as well as the S4U2Proxy ticket, received from the local domain controller in step 5, as an additional ticket.
    • This is the service ticket used to access the target service and the final step in the cross domain S4U2Proxy process.

I implemented this full process into Rubeus with this PR, which means that the whole process can be carried out with a single command.

The implementation primarily involves the CrossDomainS4U(), CrossDomainKRBTGT(), CrossDomainS4U2Self() and CrossDomainS4U2Proxy() functions, along with the addition of 2 new command line switches, /targetdomain and /targetdc, and some other little modifications.

Basically when /targetdomain and /targetdc are passed on the commandline, Rubeus executes a cross domain S4U, otherwise a standard one is performed.

What's The Point?

Good question. This could be a useful attack path in some unusual situations. Let me try to explain one.

Consider the following infrastructure setup:

There are 2 domains, in a single forest. internal.zeroday.lab (the parent and root of the forest) and child1.internal.zeroday.lab (a child domain).

We've compromised a standard user, child.user, on child1.internal.zeroday.lab, this user can also authenticate against the SQL server ISQL1 in internal.zeroday.lab as a low privileged user:

As Elad mentions in the MSSQL section of his blog post, if the SQL server has the WebDAV client installed and running, xp_dirtree can be used to coerce an authentication to port 80.

What is important here is that the machine account quota for internal.zeroday.lab is 0:

This means that the standard method of creating a new machine account using the relayed credentials will not work:

The machine account quota for child1.internal.zeroday.lab is still the default 10 though:

So the user child.user can be used to create a machine account within the child1.internal.zeroday.lab domain:

As the machine account belongs to another domain, ntlmrelayx.py is not able to resolve the name to a SID:

For this reason I made a small modification which allows you to manually specify the SID, rather than a name. First we need the SID of the newly created machine account:

Now the --sid switch can be used to specify the SID of the machine account to delegate access to:

The configuration can be verified using Get-ADComputer:

Impersonation

So now everything is in place to perform the S4U and impersonate users to access ISQL1.

The NTLM hash of the newly created machine account is the ast thing that is required:

The following command can be used to perform the full attack and inject the service ticket for immediate use:

1
.\Rubeus.exe s4u /user:TestChildSPN$ /rc4:C4B0E1B10C7CE2C4723B4E2407EF81A2 /domain:child1.internal.zeroday.lab /dc:IC1DC1.child1.internal.zeroday.lab /impersonateuser:internal.admin /targetdomain:internal.zeroday.lab /targetdc:IDC1.internal.zeroday.lab /msdsspn:http/ISQL1.internal.zeroday.lab /ptt

This command does a number of things but simply put, it authenticates as TestChildSPN$ from child1.internal.zeroday.lab against IC1DC1.child1.internal.zeroday.lab and impersonates internal.admin from internal.zeroday.lab to access http/ISQL1.internal.zeroday.lab.

Now let's look at this in a bit more detail.

As described previously, the first step is to perform a standard Kerberos authentication and recieve the account's TGT that has been delegated access (TestChildSPN in this case):

This TGT is then used to request the cross domain TGT from IC1DC1.child1.internal.zeroday.lab (the local domain controller):

This is simply a service ticket to krbtgt/internal.zeroday.lab. This cross domain TGT is then used on the foreign domain in exactly the same manner the users real TGT is used on the local domain.

It is this ticket that is then used to request the S4U2Self service ticket for TestChildSPN$ for the user internal.admin from IDC1.internal.zeroday.lab (the foreign domain controller):

To complete the S4U2Self process, the S4U2Self service ticket is requested from IC1DC1.child1.internal.zeroday.lab, again for TestChildSPN$ for the user internal.admin, but this time the users real TGT is used and the S4U2Self service ticket retrieved from the foreign domain in the previous step is attached as an additional ticket within the TGS-REQ:

To begin the impersonation, a S4U2Proxy service ticket is requested for the target service (http/ISQL1.internal.zeroday.lab in this case) from IC1DC1.child1.internal.zeroday.lab. As this request is to the local domain controller the users real TGT is used and the local S4U2Self, received in the previous step, is atached as an additional ticket in the TGS-REQ:

Lastly, a S4U2Proxy service ticket is also requested for http/ISQL1.internal.zeroday.lab from IDC1.internal.zeroday.lab. As this request is to the foreign domain controller, the cross domain TGT is used, and the local S4U2Proxy service ticket received in the previous step is attached as an additional ticket in the TGS-REQ. Once the final ticket is received, Rubeus automatically imports the ticket so it can be used immediately:

Now that the final service ticket has been imported it's possible to get code execution on the target server:

Conclusion

While it was possible to perform this across trusts within a single forest, I didn't manage to get this to work across external trusts. It would probably be possible but would require a non-standard trust configuration.

With most configurations this wouldn't be required as you could either create a machine account within the target domain or delegate to the same machine account, as I've discussed in a previous post, but it's important to understand the limits of what is possible with these types of attacks.

The mitigations are exactly the same as Elad discusses in his blog post as the attack is exactly the same, the only difference is here I'm performing it across a domain trust.

Acknowledgements

A big thaks to Will Schroeder for all of his work on delegation attacks and Rubeus. Also Elad Shamir for his detailed work on resource-based constrained delegation attacks and contributions to Rubeus which helped me greatly when trying to implement this. Benjamin Delpy for all of his work on Kerberos tickets in mimikatz and kekeo.

I'm sure there are many more too, without these guys work, research in this area would be much further behind where it currently is!

A Strange Case of Trusts, Machine Accounts and DNS

By: 0xe7
1 May 2020 at 11:28

While playing about with my Active directory (AD) lab infrastructure, I discovered I was able to create machine accounts across domain trusts. This led to the following bit of research.

There's been a lot of research into the impact of users creating machine accounts, including by Kevin Robertson's here and by Elad Shamir here but not much discussed about doing this across a domain trust.

The Setup

The lab I'm using for this blog post is setup as follows:

So here there are 3 forests and 4 domains (1 child domain). For the sake of simplicity this is a completely flat network, so all machines can access all other machines.

We have access to a low privileged user in the other.zeroday.lab domain.

This domain has a bidirectional external trust with the child domain child1.internal.zeroday.lab:

The child1.internal.zeroday.lab domain has an addition trust with it's parent domain internal.zeroday.lab:

Limitations Of The Current Position

It is not possible to authenticate against the internal.zeroday.lab domain using the current user:

And therefore, it is not possible to perform any real enumeration against that domain.

Also, it is not possible to create DNS records in the child1.internal.zeroday.lab domain:

Enter Machine Accounts

So while trying some things on this lab I attempted to create a machine account in child1.internal.zeroday.lab using the other.user user from other.zeroday.lab, and it worked:

The reason I tried this initially was because I wanted to create a DNS record in the trusted domain, which is now possible using this newly created machine account:

This shows that I now have more privileges on the trusted domain than I did otherwise.

But the implications were bigger, I can now use this machine account to query other trusted domains. So I can now enumerate trusts for the trusted domain internal.zeroday.lab:

And as a result, it's also possible to perform attacks such as kerberoasting against those trusted domains:

As well as triggering the printer bug discovered by Lee Christensen.

So we now clearly have much greater privileges across the enterprise than we did with only the user account we started out with.

Limitations Of Machine Accounts

While the machine accounts can create other machine account within the same domain (as I mentioned in my delegate to thyself post, a machine account is not able to create machine accounts across a trust:

This means we cannot use this machine account to pivot across the whole enterprise and gain access to the external.zeroday.lab domain without compromising another user account.

Conclusion

For me at least, this makes clear that the machine account quota configuration is more important than previously thought. By leaving this configuration at anything but 0, you allow for attackers on any domains that you have trust relationships with, to perform attacks against all other domains you have trust relationships with. Clearly it's not a huge impact if your domain only has 1 trust, then the main impact I can see is the ability for an attacker on the other domain to create DNS records within your domain.

More research is definitely needed in this area, I can't help but feel that this opens up new attacks that I'm not currently seeing.

Revisiting 'Delegate 2 Thyself'

By: 0xe7
18 August 2020 at 18:18

So while still trying to ingest the great blog post by Elad Shamir Wagging the Dog, I discovered a section called Solving a Sensitive Problem which improves on the method I used in my Delegate 2 Thyself post. This post is about that improvement and an abuse case using it.

I highly recommend reading both of those posts before reading this if you haven't done already.

The Improvement

An S4U2Self service ticket can be retrieved by any machine account, without any prior configuration.

As shown below, this machine account for EIIS1 is not configured for any type of delegation:

Also, the external.admin user is marked as Account is sensitive and cannot be delegated:

Elad says that it is still possible to impersonate as that user on the requesting system (EIIS1), all that's required are the machine account credentials or TGT.

Automating the Process

In the Wagging the Dog post, Elad was using an ASN.1 editor to modify the S4U2Self ticket obtained using Rubeus. I decided to modify Rubeus so that this process was fully automated.

All this modification does is add's a /self flag to Rubeus's s4u command, when that's used and the /altservice flag is also used, the value to /altservice (in the format of the full SPN, eg. host/computer.domain.com) is subsituted into the sname field in the returned S4U2Self ticket.

Trying It Out

In the previous post I started with code execution to the IIS server EIIS1, which is the same position here:

We already know that this user can use the tgtdeleg trick to get a usable TGT:

Now it's important to understand the rest I perform from a separate non domain-joined system. I tend to see a lot of questions about performing attacks from systems that aren't domain-joined but due to the nature of most of my work, I almost always perform the attacks from my own, non domain-joined system, so I do in my blog posts too.

So next I perform the S4U2Self using the TGT we just recieved from a different system:

As you can see here, I've requested the S4U2Self ticket then rewrote the sname to http/eiis1.external.zeroday.lab as the user that cannot de delegated external.admin, the resulting ticket can be seen in the following klist output:

This ticket can then be used to execute commands over PowerShell remoting:

Conclusion

Obviously, the only way you can take advantage of this is if you can get access to the credentials or TGT of the target system.

But it can still be used for privilege escalation and, in the case of gaining access to a system configured for unconstrained delegation, remote code exectution (because you will be able to gather usable TGT's for remote systems).

The advantages of this over the full S4U2Proxy approach is that no configuration changes are required and you are able to impersonate protected users.

The advantages of this over a traditional silver ticket is that the resulting ticket contains a valid PAC.

PowerView - A New Hope

By: 0xe7
10 November 2020 at 23:56

I'd been wanting to add some features to PowerView for a while, it's arguably the tool I use most on infrastructure assessments, and when @harmj0y officially discontinued PowerSploit I decided to fork it and start adding them.

For anyone that doesn't know, PowerView is an amazing tool written in PowerShell that can be used for playing with Active Directory and particually performing recon of Active Directory.

This post is about some new features I've added to it. My forked version can be found here.

RBCD Support

Until now dealing with RBCD (or the msds-allowedtoactonbehalfofotheridentity attribute) using PowerView was a manual process. Using Security.AccessControl.RawSecurityDescriptor with an security descriptor definition language (SDDL) string as an argument and manually converting it, as documented here and here.

I wanted to automate this so I created the Get-DomainRBCD and Set-DomainRBCD functions.

Get-DomainRBCD

Get-DomainRBCD by default finds all accounts, user and computer, that have the msds-allowedtoactonbehalfofotheridentity. It returns a custom PS object where the SID's have been resolved if possible. If identities are specified then only the RBCD configuration of those identities are returned:

It also tells you whether the account (either source account or account that's been granted delegation rights) is a user or machine account. This is useful to know because only 1 type of account can be configured on the msds-allowedtoactonbehalfofotheridentity security descriptor at once. So either all computer accounts or all user accounts, but a mixture of the 2.

Set-DomainRBCD

To compliment Get-DomainRBCD, I created Set-DomainRBCD, which can be used to configured RBCD on an account.

The Identity parameter is the account(s) where RBCD is to be configured, it can be done on multiple accounts at once and works the same way as in the other PowerView functions, like Get-DomainUser. The DelegateFrom parameter is a pipe ('|') delimited list of identities to delegate access to. The argument to DelegateFrom can be any format also supported by the Identity parameter:

Configuring RBCD the same as in the previous screenshot can be done like this:

Here, I configure RBCD on the computer account ISQL1 and delegate access to ISQL1 and ISQL2. This results in the same configured shown previously:

Finally, it is possible to easily remove this configuration using the -Clear switch to Set-DomainRBCD:

This makes dealing with RBCD using only PowerView much easier.

Ownership

A small addition to the Get-DomainUser function in PowerView was the -Owner switch. With this switch it return 2 extra object members, OwnerSID and OwnerName:

As shown here, the owner of the testsd user has the SID of S-1-5-21-2042794111-3163024120-2630140754-512 and the SamAccountName of Domain Admins. This is important for the next section.

Security Descriptors

While coding Set-DomainRBCD I realised that the msds-allowedtoactonbehalfofotheridentity attribute is just a security descriptor (SD) and it reminded me of a conversation I had in the BloodHound slack regarding the AdminCount attribute.

As discussed here members of protected groups have their AdminCount attribute set to 1 by the SD Propagator (SDProp). At the same time the security descriptor (SD) from AdminSDHolder gets applied, which is basically a hardened SD for protected objects. The problem here is that when the object is removed from having protected status, the AdminCount attribute value as well as the hardened SD remains.

It is often required to escalate accounts during assessments to perform certain attack paths, but it is always best to leave the client infrastructure in as similar state as before the assessment. So a method of viewing and restoring object SD's was required.

Enter Get-DomainObjectSD and Set-DomainObjectSD.

Get-DomainObjectSD

Get-DomainObjectSD can be used to retrieve an object's SD. By default it will output a custom PS object with 2 members (ObjectSID and ObjectSDDL).

  1. ObjectSID is the objects security idenfitier (i.e S-1-5-21-2042794111-3163024120-2630140754-1113).
  2. ObjectSDDL is the security descriptor of the object in SDDL string format.

There is also an -OutFile parameter that can be used to output the SD's and SID's to a file:

The -OutFile argument appends when the file exists so different SD's can be added dynamically:

It is also possible to retrieve several SD's at the same time, by piping the identities into Get-DomainObjectSD, like with other PowerView functions:

A -Check parameter exists which allows the current SD to be compared to a supplied one. If it's the same just a warning will be thrown but if the SD is different, a warning will be thrown and the object will be returned:

The -Check parameter also takes a file containing multiple account SD's to be checked:

Escalating A User

So after adding the testsd user to the Domain Admins group, the AdminCount attribute was set as expected:

After a while and retreiving the SD using Get-DomainObjectSD function and diffing the 2 SD's shows that they are significantly different:

Removing testsd from the Domain Admins group, leaves the AdminCount set to 1, as shown below:

The AdminCount attribute can easily be cleared using Set-DomainObject's -Clear parameter:

Set-DomainObjectSD

This is where Set-DomainObjectSD comes in. Set-DomainObjectSD can only be used from an account that has owner privileges on the object, this is partly why the -Owner switch was added to Get-DomainUser.

There are 2 ways to set object SD's with Set-DomainObjectSD, firstly using an input file with the -InputFile parameter, this takes a csv file in the same format created by Get-DomainObjectSD:

This will apply all SD's contained within the provided file. The other way is to specify the object identity and the SDDL string manually with -SDDLString:

If multiple identities are specified here, the same SD will be applied to them, unlike if an input file is provided.

Some Other Useful Features

I have added 2 other useful functions, Find-HighValueAccounts and Get-DomainDCSync.

Find-HighValueAccounts

As mentioned above, the AdminCount attribute remains even after the user has been removed from the protected group. I wanted a way to find all current members of these groups. For this I created Find-HighValueAccounts, by default it returned the full user and computer objects (I've selected just the samaccountname so it can be displayed better):

It gets group membership recursively. You can specify which type of object to return with the -Users and -Computers switches. It also supports -SPN for returning accounts with service principal names, -Enabled and -Disabled, -AllowDelegation and -DisallowDelegation; and -PassNotExpire to search for accounts that are configured to not require a regular password reset.

Get-DomainDCSync

Another useful function I created is Get-DomainDCSync which gets the ACL from the domain head, and determines which result in the ability to perform a DCSync. This is primarily 2 different types of ACE's, GenericAll or both DS-Replication-Get-Changes and DS-Replication-Get-Changes-All. This function again returns the full object and by default returns user and computer objects:

This function also gets group membership recursively and also provides the ability to filter by object type, with -Users and -Computers but this time also includes the ability to filter by groups too with -Groups.

Get-DomainUser

I've also added a few features to some standard PowerView functions, including Get-DomainUser.

Along with the already mentioned -Owner switch, I added -Enabled, -Disabled -PassNotExpire switches which are pretty self-explainatory. A -Unconstrained switch which filters user accounts that are configured for unconstrained delegation. A -RBCD switch which returns user accounts that have a non empty msds-allowedtoactonbehalfofotheridentity attribute. A -PassLastSet parameter which will only return user accounts that have not changed their password for at least a number of days:

I've also added the -Locked and -Unlocked switches. These take into account the domain lockout duration policy into account. So if a user account has been locked 31 minutes ago but the lockout duration policy is set to 30, the account will be returned as unlocked.

Get-DomainComputer

For Get-DomainComputer I've added 2 switches, -RBCD and -ExcludeDCs. The -RBCD switch which returns computer accounts that have a non empty msds-allowedtoactonbehalfofotheridentity attribute. -ExcludeDCs allows you to filter out domain controllers from the results, useful for searching for computer accounts configured for unconstrained delegation:

Conclusion

I do plan on making more additions to PowerView but hopefully these will be useful on assessments.

Until then you can grab my fork of PowerView here.

Another Delegation Edge Case

By: 0xe7
17 May 2021 at 16:43

While coding the cross domain S4U into Rubeus I only really considered the situation where the user that was to be impersonated was in the target/foreign domain, but not if the user was in the source/local domain. After looking at how the process of requesting delegation worked when impersonating a user on the local domain, I decided to write this post detailing how it works and when it might be useful.

The information in this post is reasonably complex and I won't be going over previous work on how S4U works, the best places to see this is probably the Microsoft Documentation and Elad Shamir's post. It might also be helpful to check out my original post on cross domain S4U if you haven't already.

So let's get to it.

The Standard Process

I've created a diagram for showing how these tickets are requested when the OS performs this type of delegation:

This can understandably look a little intimidating, so let's break it down:

  1. The service account gets its standard TGT from the local DC, this is nothing interesting and included for the sake of completion. (1 and 2)

  2. The account connects to the service accounts SPN using a standard service ticket, which is forwardable. (3)

  3. The service account uses its TGT to request a referral TGT from the local DC for the foreign domain where the end service resides (krbtgt/Domain2). (4 and 5)

  4. The service account uses its TGT, with the standard service ticket (provided by the connecting account) as an additional ticket, to request a service ticket for the end service SPN from the local DC. This results in what I'm calling a delegated referral TGT for the foreign domain being issued by the local DC. (6 and 7)

  5. The service account uses its referral TGT to request a service ticket for itself for the end service from the foreign DC. (8 and 9)

  6. The service account finally requests the delegated service ticket for the end service as the connecting user account from the foreign DC using the service accounts referral TGT and the delegated referral TGT (obtained in step 4 in this list or 6 and 7 in the image) as an additional ticket. (10 and 11)

From this we can determine that the main thing that is required to impersonate a local account on a service on a foreign domain is a forwardable service ticket from that account.

With this in mind, I decided to try to find a scenario this might be useful in.

The Situation

The following diagram shows the relevant part of the lab setup for this demonstration:

The position we are currently in is we have compromised the low privileged user child.user in the child domain child1.internal.zeroday.lab.

For the sake of simplicity, this user has an SPN set:

There are many ways to obtain an account with an SPN, including creating a machine account, compromising an account with an SPN, adding an SPN to an account you have Write privileges on. The other important thing here is that the user child.user has GenericWrite privileges on the machine account ISQL1 on the parent domain (internal.zeroday.lab):

The last thing to note here is the machine account quota for the parent domain (internal.zeroday.lab) is set to 0:

So there's no creating machine accounts to hop across the trust, as I demonstrated in a previous blog post.

Gaining Access to ISQL1

Due to having GenericWrite privileges on ISQL1 it is possible to configure resource-based constrained delegation (RBCD) to allow ourselves the ability to delegate to it:

At this point child1.internal.zeroday.lab\child.user can delegate to internal.zeroday.lab\ISQL1$.

The first thing we might try is to delegate to an administrative user on the foreign domain (internal.zeroday.lab), internal.admin is a member of the Domain Admins group so might be a good option. Using the Rubeus additions I did here I can try the following command to impersonate internal.admin:

1
Rubeus.exe s4u /user:child.user /rc4:C4B0E1B10C7CE2C4723B4E2407EF81A2 /domain:child1.internal.zeroday.lab /dc:ic1dc1.child1.internal.zeroday.lab /impersonateuser:internal.admin /targetdomain:internal.zeroday.lab /targetdc:idc1.internal.zeroday.lab /msdsspn:CIFS/ISQL1.internal.zeroday.lab /nowrap

This results in a KDC_ERR_BADOPTION error, as shown below:

We can see the reason for this by looking at the S4U2Self ticket returned by the local DC (IC1DC1.child1.internal.zeroday.lab) with Rubeus' describe command:

This ticket does not have the forwardable flag set, meaning it can't be used to perform the S4U2Proxy extension. Looking at the user internal.admin we can see that it is protected from delegation:

So the question becomes, what can we do if all users in the foreign domain with the desired privileges on the target system are protected from delegation? Well, there are several potential attack paths but the one we're going to focus on here is impersonating a user in the local domain that has administrative privileges on the target system. Again for simplicity sake, I've just added a user (child1.internal.zeroday.lab\sql.admin) to the local Administrators group on ISQL1:

With what we know about the process of obtaining a service ticket for child1.internal.zeroday.lab\sql.admin to internal.zeroday.lab\ISQL1, all we require is a forwardable service ticket to our account (child1.internal.zeroday.lab\child.user). Using a trick Elad mentions in the A Forwardable Result section of his Wagging the Dog post, all service tickets produced by S4U2Proxy is always forwardable.

This means that providing the user sql.admin can be delegated (shown below), we can obtain a forwardable service ticket using RBCD.

So configuring RBCD on ourselves (child.user) so we can delegate to ourselves:

The following Rubeus command was used to retrieve a forwardable service ticket as the user sql.admin for the SPN blah/foobar (just a junk one for demonstration purposes) which belongs to the account child.user:

1
Rubeus.exe s4u /user:child.user /rc4:C4B0E1B10C7CE2C4723B4E2407EF81A2 /domain:child1.internal.zeroday.lab /dc:ic1dc1.child1.internal.zeroday.lab /impersonateuser:sql.admin /msdsspn:blah/foobar /nowrap

Using Rubeus' describe command shows that this ticket is forwardable:

Now everything is in place to gain access to the remote system ISQL1. The following requests is the process in full described earlier to obtain the desired delegated service ticket for CIFS/ISQL1.internal.zeroday.lab as the user child1.internal.zeroday.lab, with any requests not required omitted. Each request, except gaining the TGT initially, are made manually using the asktgs Rubeus command.

Firstly the TGT for the service account (child.user) is required:

We already have the forwardable service ticket to child.user, so we don't need to worry about that. Next we have to obtain a referral TGT as the service account (child.user) for the foreign domain (internal.zeroday.lab), for this we only require the TGT just obtained. We can use the following command for that:

1
Rubeus.exe asktgs /service:krbtgt/internal.zeroday.lab /dc:ic1dc1.child1.internal.zeroday.lab /nowrap /ticket:doIFq...(snip)...

The last thing we require from the local DC is the delegated referral TGT, to get this I made another PR to Rubeus which allows for including additional tickets when using asktgs by providing the /tgs:X argument. Using the following command and including the primary TGT for the service account (child.user) as the /ticket:X argument and the forwardable service ticket as the /tgs:X argument, it is possible to request this delegated referral TGT:

1
Rubeus.exe asktgs /service:CIFS/ISQL1.internal.zeroday.lab /nowrap /dc:ic1dc1.child1.internal.zeroday.lab /ticket:doIFqjCCB...(snip)... /tgs:doIGPDCCB...(snip)...

We can skip requesting a service ticket for the end service using only the referral TGT for child.user as we won't be using that ticket. The last thing we need to do it request the final service ticket for CIFS/ISQL1.internal.zeroday.lab from the DC for the domain internal.zeroday.lab (IDC1.internal.zeroday.lab), this is the ticket we can use to impersonate sql.admin on the target service. To do this we use a similar command to the one we just run, except instead of the TGT and fowardable service ticket, we use the referral TGT and delegated referral TGT, and instead of the local DC we request it from the foreign DC, you also have to pass Rubeus the /usesvcdomain switch because cross-domain stuff is hard:

1
Rubeus.exe asktgs /service:CIFS/ISQL1.internal.zeroday.lab /nowrap /dc:idc1.internal.zeroday.lab /usesvcdomain /ticket:doIFpjCCB...(snip)... /tgs:doIHJjCCB...(snip)...

And finally we get the service ticket we're after:

Using this ticket gives as access to the CIFS service on the target ISQL1:

Conclusion

While this attack path is probably not normally required, due to other easier attack paths being likely possible, it does show that unsual edge cases exist that could allow for privilege escalation within a domain or even across domain trusts. Defenders should therefore ensure that they are fully aware of the configuration of their whole enterprise and the implications any of those configurations could have on the security of the infrastructure as a whole.

CVE-2021-42287/CVE-2021-42278 Weaponisation

By: 0xe7
9 December 2021 at 23:13

So on 9th November 2021, Cliff Fisher tweeted about a bunch of CVE's to do with Active Directory that caught a lot of people's eyes. These included CVE-2021-42278, CVE-2021-42291, CVE-2021-42287 and CVE-2021-42282. The one that caught my eye the most was CVE-2021-42287 as it related to PAC confusion and impersonation of domain controllers, also having just worked on PAC forging with Rubeus 2.0.

This post discusses my quest to figure out how to exploit this issue and some things I discovered along the way.

I just want to highlight that there's no new research here, this issue was discovered by Andrew Bartlett of Catalyst IT. I just found one way to weaponise it, there may well be others in the issues he found.

A Little Digging

So immediately upon seeing Cliff's tweet, Ceri Coburn and I started tryng to figure out how this could be exploited. We (perhaps incorrectly) latched onto the text on Microsofts description of CVE-2021-42287 which seemed to be based around the idea of TGT's being issued without PACs. This led me to modify Rubeus to allow for requesting TGT's without a PAC.

After Ceri debugging the Windows KDC and us digging through the leaked XP source we were convinced that to trigger the codepath we needed to go down (to insert a PAC into a ST when it was requested with a TGT lacking a PAC) required a cross domain S4U2self but was unable to get it to work. The only way we could get a DC to add a PAC when an service ticket (ST) was requested using a TGT without a PAC was by configuring altSecurityIdentities.

This process involves modifying the altSecurityIdentities attribute of an account in a foreign domain to Kerberos:[samaccountname]@[domain] to impersonate that user.

So below you can see a low privileged user (internal.user) of the local doamin (internal.zeroday.lab) has GenericAll over a high privileged user (external.admin) of a different domain (external.zeroday.lab):

As this user we can add ourselves to the altSecurityIdentities attribute as shown below:

Now we can get a TGT from our local DC and request it without a PAC using the new /nopac switch:

This results in an obviously small TGT. We then use that TGT to request a referral to the target domain (external.zeroday.lab) from our local DC:

That referral can then be used to request ST's for services on our target domain (external.zeroday.lab). Here I'm requesting a ST for LDAP/EDC1.external.zeroday.lab the DC's LDAP service:

The size of the ST is very large compared to the previous 2 tickets, this is because (as we'll see) a PAC has been added. As shown in the klist output below, this ST is for the original user internal.user which has no special privileges on either domain:

Using this ST, however, we can DCSync:

So what happened here is the DC has searched for the account in the local database, it hasn't found it so it's then searched to see if any accounts have this account listed in their AltSecurityIdentities attribute, which external.admin does because we added it earlier, and if so, the DC adds a PAC belonging to that account. This can be verified using Rubeus' describe command and the AES256 key we just DCsync'd:

We now effectly have the privileges of the external.admin user on the external.zeroday.lab domain.

This didn't help us exploiting the issue we wanted but I did find it interesting.

Some Progress

Then along came this tweet from Clément Notin which actually mentioned the Samba information regarding these issues and led me to CVE-2020-25719 and this patch. What particularly caught my attention was this paragraph:

Delegated administrators with the right to create other user or machine accounts can abuse the race between the time of ticket issue and the time of presentation (back to the AD DC) to impersonate a different account, including a highly privileged account.

Suddenly I realised that to make the local lookup fail, we didn't need to attack a foreign domain but perhaps remove the account after retrieving the TGT.

I started playing with naming a machine account the same as the local DC (minus the $), requesting a TGT (still without a PAC), removing the machine account and using that TGT. I noticed something funny.

When using this PAC-less TGT with a U2U request but without supplying an additional ticket, it was failing to decrypt the resulting ST:

The U2U ST should be encrypted with the session of within the provided TGT but as I didn't provide an additional ticket I assumed it was triyng to lookup the account based on the sname which I was setting to IDC1 the samaccountname of my now missing machine account. I had the idea to try decrypting this ST using the long term key of the domain controller that I was naming my machine account after (IDC1$):

It worked! It sucessfuly decrypted the ST, it just couldn't find the PAC because there wasn't one there. I tried the same thing using S4U2self and got the same result, the DC was looking for my IDC1 account, not finding it and then search for the same but adding a $ on the end, finding the domain controller account and encrypting the ticket using it's key instead.

At that time I still couldn't figure out why it wasn't adding the PAC, so I decided to try requesting the initial TGT with a PAC instead of without a PAC and surprisingly it worked! So apparently there was no need to request a TGT without a PAC, supplying a TGT with a PAC for an account that has the samaccountname of the DC minus the $ to a request for an S4U2self ticket, when the intial account no longer exists, results in the ST being encrypted using the key of the DC.

The sname of that resulting ST can be modified as per Alberto Solino's post here. So it can be used to authenticate against any service on the target box, even users protected from delegation, as Elad Shamir mentions in the Solving a Sensitive Problem section of Wagging the Dog.

The last thing to work out was how can we get a machine account in this state from a low privileged user, as until now I was manually modifying the machine account as an admin. Thankfully Kevin Robertson's amazing post on the Macine Account Quota helped massively. It explains that the creator of the machine account has control over various attributes including the samAccountName and ServicePrincipalName. Another problem I was running into was trying to change the samaccountname, as trying to change it to be the same as the DC minus the $, I was getting the following error:

As Kevin mentions in his post:

If you modify the samAccountName, DnsHostname, or msDS-AdditionalDnsHostName attributes, the SPN list will automatically update with the new values.

So the SPN it was trying to set was already an SPN belonging to the target DC. Ceri suggested removing the SPNs before changing the samaccountname, which worked.

Lastly, until now I was removing the machine account after requesting the TGT (which requires admin privileges), I had to test whether disabling it or renaming it worked too. Disabling it resulted in a S_PRINCIPAL_UNKNOWN error being returned by the DC when requesting the S4U2self but renaming it worked.

Finally all of the pieces were in place.

Checking If Exploitable

To exploit this requires 3 things, at least 1 DC not patched with either KB5008380 or KB5008602, any valid domain user account and a Machine Account Quota (MAQ) above 0.

To determine if a DC is patched is very easy. Using my additional /nopac switch to Rubeus' asktgt, request a TGT without a PAC, if the DC is vulnerable it'll look like the following:

Look at the size of the returned TGT. If the DC is not vulnerable the TGT will look as follows:

The size difference is immediately obvious. The next thing to check would be the MAQ:

By default it is 10 as above but can be changed, anything above 0 will do. Lastly we need to check the SeMachineAccountPrivilege which is granted to Authenticated Users by default:

If everything checks out, we can exploit this issue.

The Full Attack

The first step is to create a machine account we can use for the attack (The account I create is called TestSPN$). Kevin's Powermad works nicely for this:

After this, PowerView's Set-DomainObject can be used to clear the SPNs from the machine account:

Changing the machine account's samaccountname can be done using Powermad's Set-MachineAccountAttribute (Here I'm changing it to IDC1, because the DC's samaccountname is IDC1$):

Rubeus' asktgt can be leveraged to request a TGT for that newly created machine account (This is just a normal TGT for the machine we just created but using it's new samaccontname):

Set-MachineAccountAttribute can again be used to change the machine accounts samaccountname (either back to what it was or something else entirely, it doesn't matter):

With the machine account renamed, it is now possible to request an S4U2self ticket using the retrieved TGT and get an ST encrypted with the DC's key, at the same time we can rewrite the sname within the ticket to be the LDAP service:

Here, I've impersonated the Administrator user for the LDAP service on the DC. It's worth noting that this could be any user on any service on any system on the domain.

The ticket has been sucessfully injected into LSA as shown below:

Using that ticket it is now possible to DCSync:

The commands I run to do this are shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Create Machine Account
New-MachineAccount -MachineAccount TestSPN -Domain internal.zeroday.lab -DomainController idc1.internal.zeroday.lab -Verbose

# Clear SPNs
Set-DomainObject "CN=TestSPN,CN=Computers,DC=internal,DC=zeroday,DC=lab" -Clear 'serviceprincipalname' -Verbose

# Change Machine Account samaccountname
Set-MachineAccountAttribute -MachineAccount TestSPN -Value "IDC1" -Attribute samaccountname -Verbose

# Request TGT
.\Rubeus.exe asktgt /user:IDC1 /password:Password1 /domain:internal.zeroday.lab /dc:idc1.internal.zeroday.lab /nowrap

# Change Machine Account samaccountname
Set-MachineAccountAttribute -MachineAccount TestSPN -Value "TestSPN" -Attribute samaccountname -Verbose

# Request S4U2self
.\Rubeus.exe s4u /impersonateuser:Administrator /nowrap /dc:idc1.internal.zeroday.lab /self /altservice:LDAP/IDC1.internal.zeroday.lab /ptt /ticket:[TGT]

Mitigation / Detection

The best way to mitigate against this is to install the Microsoft patch (KB5008602), this patch fixes the issue with PAC confusion as well as fixes this issue with S4U2self created by the earlier KB5008380 patch.

Setting the Machine Account Quota to 0 is a quick and easy fix for stopping low privileged user from being able to create machine accounts, another related fix is to remove Authenticated Users from the SeMachineAccountPrivilege and adding Domain Admins or another group of allowed accounts.

There are several Events caused by various steps which would be useful for determining attempts to perform this attack. The credit for determining these Events should go entirely to Andrew Schwartz, I simply sent my logs to him after I performed the attack.

Machine Account Creation

Firstly, there is a 5156 event of an inbound connection to LDAP to create the machine account, For this event ID Andrew leveraged the research of “A Voyage to Uncovering Telemetry: Identifying RPC Telemetry for Detection Engineers” By: Jonathan Johnson:

Immediately followed by a 4673 event, which is the usage of the SeMachineAccountPrivilege:

As well as a 4741 event, describing the creation of the machine account:

And a 4724 event, regarding the password reset of the newly create machine account:

Clearing The SPNs

Next a 4742 event can be seen when the SPNs are removed from the machine account, this will show for the Service Principal Names field, as shown below:

Changing the SamAccountName

A 4742 event will also show when the SAM Account Name is changed, and the new name will be shown in the SAM Account Name field:

More interestingly, a 4781 event will show both the old account name and the new account name:

Get TGT

When retrieving the TGT, a 4768 event will show, interestingly the Account Name field will show the new name of the account, while the User ID field will show the old name:

Then the account name change happens again with the 2 events mentioned above.

S4U2self

Lastly, as Elad mentions in his Wagging the Dog post, event 4769 fires, this time, however, some discrepancy is shown between Account Name and Service Name, while the Service Name field has the proper machine account name, the Account Name is missing the trailing dollar ($):

Conclusion

With the November 9th updates, many changes were made to AD and I wouldn't be surprised if many other avenues existed using those issues but the one I use directly from a low privileged user to full domain takeover with a default configuration.

Ensuring all DC's are fully patched should be the main priority here as it will only take 1 unpatched DC for domain takeover to be possible.

On top of patching, proper AD hardening with decent monitoring will always minimise the impact of any compromise significantly.

More sAMAccountName Impersonation

By: 0xe7
11 December 2021 at 19:08

So in my excitement to put out the previous post I forgot something and since then I've thought of another attack path that may come in useful for some people.

For these examples I'm using the internal.user account in the internal.zeroday.lab domain, as shown below:

This is just a generic low privileged user.

Trusts

I did reply to my tweet afterwards, but I thought it'd be best to explain a little more that this works across trusts.

As mentioned in a previous post I did, creating machine accounts across trusts is not only possible but can be incredibly useful. This is another example of that.

A forest trust is configured between the internal.zeroday.lab and external.zeroday.lab forests:

A new machine account (named NewComputer) is created across this trust on the external.zeroday.lab domain:

The SPNs can be cleared from this newly created account:

It's best to get the distinguishedname of the machine account for changing the name:

Lastly, the name can be changed to the same as the domain controller minus the '$':

At this point the attack is exactly the same as the initial example I gave in the original post, ie. request a TGT for EDC1, rename machine account back, perform S4U2self.

User Account

Another example of exploitation involved user account control. 2 more prerequisites are required to perform the attack using a user account, The GenericAll privilege over the user account and access to the account credential. Any user account can be used to perform the attack.

There are several potential ways of obtaining the user account password when you have GenericAll over it, including tageted Kerberoasting from Will Schroeder, Shadow Credentials by Elad Shamir, just resetting the user password and probably more I'm forgetting right now. Point is, I'm not going to go into all of the potential ways you might do this, I'm just going to assume the password has been obtained.

So the user I'm running as (internal.user) has GenericAll over the target user (new.user):

Changing the samaccountname to that of the DC minus the '$' is also possible using PowerView:

The attack from this point is exactly the same as in the original post, I'm not going to duplicate all of that here, you can try it for yourself if you want. Interestingly on patched servers you can rename a user account this way with GenericAll over it, but the S4U2self part fails with KDC_ERR_TGT_REVOKED error due to the new Requestor PAC_INFO_BUFFER being included within the TGT's PAC.

EDIT: Charlie BROMBERG suggested GenericAll isn't actually required and this works with GenericWrite or even WriteProperty on sAMAccountName for changing the samaccountname, but it is important to remember that the ability to request a TGT for this account is required too, so the higher the privileges, the more likely you are to be able to do this.

Conclusion

It is very important that all domain controllers throughout the whole enterprise is patched against these issues due to the impact of exploitation and the ease with which it can be performed.

While more limited, it may still be possible in situations where a machine account cannot be created/controlled or renaming of machine account fails.

Defending the Three Headed Relay

By: 0xe7
9 May 2022 at 22:57

A joint blog written by Andrew Schwartz, Charlie Clark, and Jonny Johnson

Introduction

For the past couple of weeks it has become apparent that Kerberos Relaying has set off to be one of the hottest topics of discussion for the InfoSec community. Although this attack isn’t new and was discovered months ago by James Forshaw, it has recently taken off because a new tool called KrbRelayUp has come to surface that takes James’ work and automates that process for anyone wanting to exploit this activity. This tool however doesn’t only exploit James’ work, but also work from Elad Shamir around S4U2Self/S4U2Proxy, while using code from Rubeus by Will Schroeder. We as a group (Andrew, Charlie, and Jonny) found this interesting as we saw many detections coming out for “Kerberos Relay” that might not actually detect “Kerberos Relay” if the action was performed by itself, but more of post-exploitation actions — say in the S4U activity.

During this blog post we will take a look into Kerberos Relay, break out the different attack paths one could take, and talk about the different defensive opportunities tied to this activity and other activities leading up to Kerberos Relay or after.

Kerberos Relay Explained

Kerberos relaying was described in detail in James Forshaws blog post “Using Kerberos for Authentication Relay Attacks”. The primary focus of Kerberos relaying is to intercept an AP-REQ and relay it to the service specified within the service principal name (SPN) used to request the service ticket (ST). The biggest discovery within James’ research is that using certain protocols a victim client can be coerced to authenticate to an attacker using Kerberos while allowing an SPN to be specified that differs from the service that the client is connecting to. This means that the client will request a ST for an SPN of the attacker’s choosing, create an AP-REQ containing that ST and send it to the attacker. The attacker can then forward this AP-REQ to the target service, disregard the resulting AP-REP (unless the attacker needs to relay this back to the client for some reason) and at this point establish an authenticated session as the victim client.

While there are other potential ways Kerberos relaying can happen, (ie. like man-in-the-middle (MITM) attacks), the primary focus of this post will be on coercing a client to authenticate to the attacker as the method of receiving the AP-REQ. The process is essentially as follows:

Attacker coerces victim client auth with target service SPN -> client requests ST to SPN specified -> client sends AP-REQ to attacker -> attacker extracts AP-REQ sends to target service -> attacker establishes session as victim client

There are some caveat’s to this process. The first being protections enabled on the target service. As with NTLM relaying, if the target service has signing/sealing or channel binding enforced, relaying Kerberos authentication will not work. The second caveat is the protections supported by the client. With some target protocols, if the client indicates support for certain protections, the server will enable those protections, again making Kerberos relaying not possible without some other bug in the implementation.

Potential Attack Paths with Kerberos Relay

There are several potential attack paths that Kerberos relaying allows for. Many of these were documented by James in his initial blog post. As alluded to previously, there are 2 main considerations when discussing Kerberos relaying attack paths:

  1. The protocol used to trigger the authentication from the victim client
  2. The protocol used by the service the authentication is being relayed to

Trigger Protocol

As discussed, the main requirement for the trigger protocol is the ability for the attacker to specify an arbitrary SPN, or at least a partially attacker controlled SPN, when triggering the authentication. Protocols known to potentially have this requirement are:

  • IPSec and AuthIP
  • MSRPC
  • DCOM
  • HTTP
  • LLMNR
  • MDNS

Service Protocol

Depending on the protections enabled on the server, the following protocols are known to be target service protocols for Kerberos relaying:

  • LDAP/LDAPS
  • HTTP
  • SMB

Potentially many combinations of these protocols could be used as attack paths for Kerberos relaying. This presents many attack paths, for instance, relaying to an LDAP server could allow for modification of LDAP objects or relaying to an AD CS HTTP web enrolment endpoint could allow for requesting an authentication certificate.

Detecting Kerberos Relay

Before diving straight into detections, queries, and indicators of activity for these behaviors we think it is important to touch on what we are looking at for detection and why. It is fairly easy to take a tool that performs some behavior then immediately go look at the logs to see what telemetry exists. This isn’t a terrible approach, it just isn’t the only one and not the one we take.

We (Charlie, Andrew, and Jonny) like to approach this detection piece a little differently by breaking up a tool’s capability, understanding what it is trying to accomplish, understanding the technologies tied to an attack and their capabilities, and identifying what actions (if any) apply to other techniques. We then like to find the core behavior the attack is built on and identify what pieces of that action is or can be controlled by an attacker. This process helps us identify which behaviors are explicitly tied to the attack and which might relate to an action that was performed prior to the attack or after. One thing we don’t want to do is create detection explicitly tied to the tool, but to the attack. We are using the tool as a starting point of understanding the attack and the various variants an attacker may take to accomplish these actions.

That being said, every attack will have a pre, intra, and post action. These actions are extracted during the research process and help us scope what capabilities we are trying to detect. Let us explain.

In order for an attack to be run, an attacker must do something that gives them the ability to perform that action. This could be a number of things, let’s use the following as an example of pre-action activity:

  • Gain access to a domain user
  • Compromise/obtain a foothold on a box
  • Run a LDAP query for reconnaissance
  • Escalate to a local administrator/High IL

You then have the actual attack (intra-action):

  • Kerberoast
  • Dump LSASS
  • Access Token Impersonation

Finally, the attacker is going to do something with whatever output the attack gives them — being the post-action:

  • Logs on as user
  • Impersonates user

Here is a visual representation of this:

This allows us to apply a detection layering approach when creating detections for these behaviors because there is going to be something within the pre-action that we can relate to the intra-action, and similarly the intra-action to the post-action. Due to this we can change the diagram up a little bit:

As you can probably tell by now, every post-action leads into a pre-action. It restarts the attack flow. We see this below with Kerberos Relay. One potential post-action is to perform S4U2Self/S4U2Proxy. Kerberos Relay has now become a pre-action to this activity and a post-action could be that an attacker is using that ability to login, talk to the SCM to create a service and run a process as SYSTEM.

If we just run the attack and look directly at the logs it is easy to start making assumptions. So before we run the attack we can break out what we are looking for, then go look for it. This allows us to truly understand what layer we are applying a detection, which inherently will help us understand what level of coverage we have.

We can now apply this to Kerberos Relay in the next section.

Detection Queries

Some of the attacks within the pre/intra/post actions were applied due to how KrbRelayUp was exploiting this activity. The attacker doesn’t always have to take these exact paths and some of the specifics may change, for example — below we show a detection for the COM server initialization/TCP connection. An attacker could use a different protocol like HTTP/LDAP. Although we didn’t create queries for each one of these scenarios we wanted to share the different pre/intra/post-action detections someone could create.

Pre-Kerberos Relay Detections:

  • Initial domain user foothold (No detection added as there are so many options)
  • LDAP queries to identify potential SPNs available
  • Computer account added via LDAP (Using Microsoft Defender for Endpoint DeviceEvents)
1
2
3
4
5
DeviceEvents
| where ActionType containsLdapSearchand (InitiatingProcessParentFileName !has (“services.exe”) or InitiatingProcessAccountName !in (“local service”, “system”))
| extend SearchFilter= extractjson(“$.SearchFilter”, AdditionalFields)
| where SearchFilter containssAMAccountNameand SearchFilter contains “$”
| summarize count() by Timestamp, InitiatingProcessAccountName,InitiatingProcessParentFileName, InitiatingProcessFileName, SearchFilter, InitiatingProcessCommandLine, AdditionalFields, InitiatingProcessLogonId

Note: This query was created via MDE and will look for when a computer account is created via LDAP, for this attack this is totally optional. To perform this specific attack path, the attacker only requires the credentials of any computer object or a user object with an SPN. There are many other ways to potentially obtain one.

  • Computer Account added via Splunk and Window Security Event ID 4741:
1
index=windows sourcetype=Security EventCode=4741 AND SAM_Account_Name = “*$”

Going a step further would be to correlate the 4741 with Windows Security Event ID 4673. As Andrew wrote in his post the event details in 4673 contain the four (4) SPN’s that are also created when a computer account is created with certain attack tools (in their present state as of writing this post). Kevin Robertson first blogged about the 4 SPN’s being generated in his post, “MachineAccountQuota is USEFUL Sometimes: Exploiting One of Active Directory’s Oddest Settings.” Many publicly available Open Source Tools (OSTs) incorporate the same 4 SPNs into their tooling.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
index=windows (EventCode=4741 MSADChangedAttributes=*(*HOST/*) AND *(*RestrictedKrbHost/*) New_UAC_Value=0x80) OR (EventCode=4673 Privileges=SeMachineAccountPrivilege) 
| eventstats values(Process_Name),values(Privileges),values(EventCode) as EventCode by Logon_ID 
| search EventCode=4741
| rex field=_raw “(Message=(?<Message>[a-zA-z ].*))” 
| eval datetime=strftime(_time, “%m-%d-%Y %H:%M:%S.%Q”) 
| stats count values(datetime),values(Process_Name),values(Privileges),values(EventCode),values(MSADChangedAttributes),values(Message),values(Account_Domain),values(Security_ID),values(SAM_Account_Name),values(DNS_Host_Name) by Logon_ID 
| search count >=2 
| rename values(*) as * 
| eval Effecting_Account=mvindex(Security_ID,1) 
| eval New_Computer_Account_Name=mvindex(Security_ID,0) 
| table datetime,Account_Domain,Effecting_Account,Logon_ID,New_Computer_Account_Name,DNS_Host_Name,Message,MSADChangedAttributes,Process_Name,Privileges,EventCode

Intra-Kerberos Relay Detections:

  • DCOM Server connection with TCP connection to localhost (Using Splunk and Window Security Event ID 5156):
1
index=windows sourcetype=Security EventCode=5156 Direction=Inbound AND Source_Address=::1 AND Destination_Address=::1 AND Process_ID !=4 AND Protocol=6

Post-Kerberos Relay Detections:

  • RBCD Exploitation (Using Splunk and Window Security Event ID 5136/4768/4769)
1
2
3
4
5
6
7
8
index=windows sourcetype=”Security” ((EventCode=5136 AND “msDS-AllowedToActOnBehalfOfOtherIdentity”) AND (Type=”Value Added” OR Type=”Value Deleted”)) OR EventCode=4768 OR EventCode=4769 
| eval alt_type=mvindex(Type,2) 
| eval datetime=strftime(_time, “%m-%d-%Y %H:%M:%S.%Q”) 
| bucket _time span=11m
| stats dc(EventCode) as eventcodes,values(EventCode),values(datetime),values(LDAP_Display_Name),values(host),values(Account_Domain),values(Client_Address),values(Service_Name),values(Service_ID),values(Ticket_Options),values(Class),values(Ticket_Encryption_Type),values(alt_type) by _time 
| rename values(*) as *
| where eventcodes >=3
| table _time,datetime,host,Account_Domain,Client_Address,Service_Name,Service_ID,Ticket_Options,Ticket_Encryption_Type,Class,LDAP_Display_Name,alt_type,EventCode,eventcodes

It should be noted that this detection query has limitations given its use of bucket _time span. We employed the use of this time feature as there was not an easy way (i.e. by Logon ID) to correlate the three events. The only common variable we discovered between these three different events observed was time, specifically all within a 15 second window. While this query worked in our lab with our specific dataset, we would like to point out that by grouping the events by time in a bucket, events can possibly occur outside the span of the bucket as we don’t know WHEN the event will take place. As such the event could occur in the middle of the bucket or it could be on the “edge.” A thank you to Greg Rivas for helping create the above SPL query.

During the writing of this post the author of KrbRelayUp added support for Shadow Credentials, which performs slightly different post-actions than we have specified above. However; it is good to note that Shadow Credentials is still a post-action potential attack that can be leveraged.

Mitigations

  1. Limit MAQ attribute and/or restrict the SeMachineAccountPrivilege to a specific group rather than Authenticated Users
  2. Extended Protection for Authentication (EPA)/Protocol Signing/Sealing and Channel Binding
  3. Disabling mDNS/LLMNR
  4. Require authenticated IPsec/IKEv2
  5. Disabling Disable NTLM

A thank you to James Forshaw for vocalizing some of these mitigations when introducing this attack.

Conclusion

During this write-up we wanted to give a brief explanation of Kerberos Relay, how this can be exploited, and the various levels of detection/prevention that could be applied. Although we didn’t go over every pre/post-exploitation scenario an attacker could take, we wanted to highlight the importance of thinking about attacks from a pre/intra/post-action perspective. This helps us identify the scope of our detections, which will then allow us to identify at what depth we are applying the detection.

We hope this was helpful and a huge thank you to James Forshaw again for his previous work on this.

References

  • https://googleprojectzero.blogspot.com/2021/10/using-kerberos-for-authentication-relay.html
  • https://googleprojectzero.blogspot.com/2021/10/windows-exploitation-tricks-relaying.html
  • https://dirkjanm.io/relaying-kerberos-over-dns-with-krbrelayx-and-mitm6/
  • https://github.com/Dec0ne/KrbRelayUp
  • https://github.com/cube0x0/KrbRelay

External Trusts Are Evil

By: 0xe7
14 March 2023 at 16:28

This post is duel posted on the Semperis blog.

Another day, another “by design” but clearly unintended Windows AD feature. While playing with Kerberos tickets I discovered an issue that allowed me to authenticate to other domains within a forest across external non-transitive trusts. This means that there is in fact no such thing as a “non-transitive trust”, and the description as such when creating a trust is at best misleading, leaving systems administrators with a false sense of security. After reporting this issue to Microsoft, I received the following response:

Figure 1: MSRC Response

This post details the issue discovered but as Microsoft do not feel that it affects security and as a result are not planning on fixing this issue, there is no way to avoid what is being described within this post apart from not using external trusts at all.

Trusts and Transitivity

Will Schroeder published a great post on trusts back in 2017. If you are not too familiar with the different trusts and how they work, I highly recommend reading that first as I will not be going into as much detail on the various types. For this post, I will not be discussing intra-forest trusts (trusts within a single forest) other than as they apply to the specific attack path that I am going to discuss. I also will not be discussing selective authentication trusts, but I have never actually seen these in real environments. The remaining trusts are forest trusts and external trusts. Forest trusts are trusts between two forests; or more accurately a transitive trust between the root domains of two forests, which means any user from any domain within the trusted forest can authenticate to any domain within the trusting forest. External trusts, on the other hand, are trusts between two domains and are described as being “non-transitive”, which should mean that only users within the trusted domain can authenticate against only the trusting domain.

Figure 2: External Non-Transitive Trust

Other technical differences are present between forest and external trusts, but these will be discussed if and when required for this post.

Microsoft describes trust transitivity as follows:

Transitivity determines whether a trust can be extended outside of the two domains with which it was formed.

  • A transitive trust can be used to extend trust relationships with other domains.
  • A non-transitive trust can be used to deny trust relationships with other domains.

This description is very clear. For non-transitive trusts, only the two domains involved in the trust can authenticate to each other and not beyond. As I will demonstrate in this post, this is not the case.

Lab Setup

To properly demonstrate this issue, several multi-domain forests are required with external trusts between them. I have setup the lab as follows:

Figure 3: Lab Setup

This setup involves three (3) forests, two (2) of which have three (3) domains and the other containing two (2) domains. The domains semperis.lab and treetest.lab have a bidirectional non-transitive external trust:

Figure 4: External Trust 1

The domains grandchild1.child1.semperis.lab and semperisaz.lab also have a bidirectional non-transitive external trust:

Figure 5: External Trust 2

This should make it possible to demonstrate the implications and limitations of this issue.

How Kerberos Cross-Trust Authentication Works

The main thing that is required to authenticate to a service across a trust using Kerberos is a referral (or referral TGT). This is a ticket requested from your local domain controller (DC) for the foreign domain. This section assumes a basic understanding of how the normal Kerberos authentication flow works, for a detailed explanation of that check out Sean Metcalf’s Detecting Kerberoasting Activity post. It would also be advantageous to be reasonably familiar with Rubeus (as I will be using it to request tickets in the remaining sections). This section will focus on the semperis.lab forest which is sufficient to demonstrate how ticket requests across trusts are performed, the information here is, however, applicable to any “allowed” trust path.

The simplest example of cross-trust authentication is to authenticate to a service on a domain which has a direct trust with the local domain. The trust that the domain grandchild1.child1.semperis.lab has with child1.semperis.lab, shown in Figure 5 above, is an example of this. In this situation, the first step, after obtaining the initial TGT, in obtaining a service ticket for an account in grandchild1.child1.semperis.lab to a service in child1.semperis.lab is to obtain a referral for child1.semperis.lab:

Figure 6: Referral Request For child1.semperis.lab

As shown in Figure 6 above, the request was made to a DC (SGC1DC1.grandchild1.child1.semperis.lab) within the domain (grandchild1.child1.semperis.lab) local to the authenticating user (lowpriv). The request was made for the service krbtgt/child1.semperis.lab and the fact that the ServiceRealm (srealm) is the local domain (grandchild1.child1.semperis.lab) within the resulting ticket, shows that this ticket is a referral. The diagram below shows this:

Figure 7: Referral Request

This referral can now be used to request service tickets (STs) from the foreign domain:

Figure 8: Example Usage of Referral

Here an ST was requested from the foreign DC (SC1DC1.child1.semperis.lab) for the service host/SC1DC1.child1.semperis.lab using the referral retrieved previously.

Figure 9: Requesting ST Using Referral

To take this one step further, if a service on the forest root domain (semperis.lab) is required, a referral for this domain cannot be directly requested from the local (grandchild1.child1.semperis.lab) domain:

Figure 10: Referral Request For semperis.lab

As shown in Figure 10 above, a referral for krbtgt/semperis.lab was requested from the local DC sgc1dc1.grandchild1.child1.semperis.lab. However, the ticket returned by the DC was for the service krbtgt/child1.semperis.lab, meaning that this is a referral for the domain child1.semperis.lab, NOT semperis.lab.

Figure 11: Requesting Referral For semperis.lab

This can be explicitly shown by trying to use this ticket to request a ST for the semperis.lab domain:

Figure 12: Root Domain Service Ticket Request

As shown in Figure 12 above, this results in an AP_ERR_BAD_INTEGRITY error. This is because the referral ticket is encrypted with the trust key for the grandchild1.child1.semperis.lab --> child1.semperis.lab trust. The DCs in the root domain semperis.lab do not have knowledge of this key and so are not able to decrypt the ticket.

Figure 13: Requesting ST From semperis.lab

To request a service ticket for the root domain semperis.lab, first a referral for semperis.lab must be requested from a DC in the domain child1.semperis.lab using this referral:

Figure 14: Referral Request For semperis.lab

Here a request was made to the foreign DC (sc1dc1.child1.semperis.lab) using the referral for the domain child1.semperis.lab to request a further referral for the root domain semperis.lab. It should be noted here that in order to request this ticket, the /targetdomain argument is required. This is because by default Rubeus will use the domain within the ticket passed to it to fill in the domain in the TGS-REQ, in this case that would be grandchild1.child1.semperis.lab and would result in a ERR_WRONG_REALM error as the domain local to the DC is child1.semperis.lab. This will be very important in the next section.

Figure 15: Requesting Referral For semperis.lab

Lastly, this resulting referral for semperis.lab can be used to request STs for the semperis.lab domain:

Figure 16: Service Ticket Request For semperis.lab

This ST request from the user [email protected] made to the DC SDC1.semperis.lab for the SPN host/SDC1.semperis.lab is shown in the following diagram and was successful even though the two domains involved do not have a direct trust due to the trust path being allowed:

Figure 17: ST Request For host/sdc1.semperis.lab

This method of requesting referrals for trusting domains can be followed to request STs for any service within any domain for which the trust path is “allowed”.

Making The Non-Transitive Transitive

Now that we understand how authentication across trusts happens, let’s look at how it is possible to traverse external trusts to authenticate to domains that should be prohibited.

As shown in the Figure 3, the domains semperisaz.lab and grandchild1.child1.semperis.lab have a bidirectional external trust, this means that after retrieving a TGT for any account within the domain semperisaz.lab it is possible to request a referral for grandchild1.child1.semperis.lab:

Figure 18: Referral To granchild1.child1.semperis.lab

The diagram showing this request is below:

Figure 19: Request Referral For grandchild1.child1.semperis.lab

This referral can be used to request STs for services on the domain grandchild1.child1.semperis.lab, but if we try to obtain a referral to other domains within the same forest (for example child1.semperis.lab) we get an ERR_PATH_NOT_ACCEPTED error, which is expected:

Figure 20: Path Not Accepted Error

This is because the semperisaz.lab --> granchild1.child1.semperis.lab trust is non-transitive, so the path from semperisaz.lab to child1.semperis.lab is not allowed.

Figure 21: Requesting Referral For child1.semperis.lab

However, we can request a “local” TGT for grandchild1.child1.semperis.lab:

Figure 22: Requesting “local” TGT For grandchild1.child1.semperis.lab

I call this a “local” TGT because unlike the referral, which has a ServiceRealm (srealm) of semperisaz.lab, the ServiceRealm is grandchild1.child1.semperis.lab.

Figure 23: Requesting "local" TGT

Using this “local” TGT, a referral for child1.semperis.lab can now be requested:

Figure 24: Referral For child1.semperis.lab

This process can be seen in the following diagram:

Figure 25: "local" TGT To Request Referral For child1.semperis.lab

We now have a usable referral which can be used to request STs for child1.semperis.lab using any account from the semperisaz.lab domain and without making any changes to trusts or accounts within AD. This is demonstrated below:

Figure 26: Requesting ST For DC In child1.semperis.lab

In Figure 26, a ST was requested from the DC sc1dc1.child1.semperis.lab for the service host/sc1dc1.child1.semperis.lab as the user [email protected]:

Figure 27: Requesting ST For host/sc1dc1.child1.semperis.lab

Furthermore, this method can be used to hop around any domain within the same forest that grandchild1.child1.sermperis.lab exists in. We can demonstrate this by requesting a referral to the root domain semperis.lab:

Figure 28: Requesting Referral For semperis.lab

A referral was requested for semperis.lab from the DC sc1dc1.child1.semperis.lab as the user [email protected]:

Figure 29: Requesting Referral To semperis.lab

Fortunately for security, hopping across further trusts outside of the forest (external or forest trusts) is not possible using this method. As shown in Figure 3, the root domain semperis.lab has a bidirectional external trust with treetest.lab, this trust can be used to demonstrate that limitation:

Figure 30: Requesting Referral For treetest.lab

This at least stops an attacker using this method of trust hopping from hopping into another forest.

Figure 31: Requesting Referral For treetest.lab

This issue is clearly useful for attackers trying to elevate privileges within a forest from across a trust. Other than being able to query domain information from domains that systems administrators may think would be disallowed (and query more sensitive domains or potentially domains with weaker security), this also makes it possible to perform attacks such as Kerberoasting or NTLM authentication coercion on domains that may seem to be disallowed.

Hopping Further

While it is not possible to hop further using this specific method, I did write a post a while ago about the ability to create machine accounts across trusts and that this may open up new avenues of attack for attackers. This situation is another example of that.

Using the referral retrieved for semperis.lab, it is possible to request a ticket for the LDAP service on a DC in semperis.lab:

Figure 32: Request ST For LDAP

Here a ST for ldap/sdc1.semperis.lab was requested using the referral for semperis.lab as the user [email protected]:

Figure 33: Request ST For ldap/sdc1.semperis.lab

This ST can be injected and used to create a machine account directly in semperis.lab (given the configuration allows Authenticated Users, as is default):

Figure 34: Create Machine Account In semperis.lab

This causes the DC sdc1.semperis.lab to create the machine account TestComp within the semperis.lab domain:

Figure 35: Create Machine Account TestComp

This newly created TestComp machine account is a local account within the domain semperis.lab. Now that we have a machine account that exists within semperis.lab, we can retrieve a TGT for that account:

Figure 36: Machine Account TGT

Requesting the machine account TGT looks as follows:

Figure 37: Requesting TGT For TestComp

This machine account TGT is allowed to request a referral to the trusting domain treetest.lab:

Figure 38: Requesting Referral For treetest.lab

Here the machine account’s TGT was used to request a referral for the treetest.lab domain from the DC SDC1.semperis.lab:

Figure 39: Request Referral For treetest.lab

Lastly, the issue described in this post can again be used to gain access to the inaccessible dsptest.lab domain. First by requesting a “local” TGT for treetest.lab:

Figure 40: Requesting Local TGT For treetest.lab

The “local” TGT is requested using the referral for treetest.lab as the account [email protected] from the DC TDC1.treetest.lab:

Figure 41: Requesting "local" TGT For treetest.lab

Then using this “local” TGT to request a referral for dsptest.lab:

Figure 42: Requesting Referral For dsptest.lab

Here a referral was requested for the domain dsptest.lab from the DC TDC1.treetest.lab as the user [email protected]:

Figure 43: Requesting Referral For dsptest.lab

It should be clear that using the combination of these two methods would make it possible to hop very deep into AD enterprise infrastructures, using any low privileged account on a domain which has an external trust to any domain within a forest.

Detection

Unfortunately, the only way to prevent this attack path is to remove external trusts completely. If this is not possible, detection is possible. The relevant Windows events are 4769’s (A Kerberos service ticket was requested). The first indication is that a “local” TGT is requested from an account in a different forest:

Figure 44: 4769 For Local TGT

Here the Account Domain field is a domain that belongs to a different forest and the Service Name is krbtgt. This event is followed by another 4769 requesting a referral:

Figure 45: 4769 For Referral

Here the Account Domain is a domain within a different forest and the Service Name is another domain within the local forest.

It is worth noting that these two events can be on different DCs within the same domain, they do not have to reside on the same DC. At this point, any service tickets requested (4769’s) using this referral will have an Account Domain that contains a domain that should not be allowed to authenticate to the local domain.

Plans to consume these events and detect this attack are planned for an upcoming release of Directory Services Protector (DSP).

Conclusion

The biggest problem here is Microsoft’s description of non-transitive trusts being, at best, misleading to systems administrators. This problem is exaggerated by Microsoft’s refusal to accept that this affects security. As it stands, by creating an external “non-transitive” trust, companies must accept that any account within the trusted domain can authenticate against any domain within the whole forest where the trusting domain resides.

As demonstrated above, in the “Hopping Further” section of this post, this research again highlights the importance of disallowing Authenticated Users from being able to create machine accounts, as not doing so not only puts domains within the forest at a higher risk, but also puts any domains (and the whole forest within which they reside) which have an external trust with any domain within the forest at a higher risk. This is due to the ability to create machine account across trusts, even while using the technique used within this blog post to authenticate against domains which should be disallowed. More information on the machine account quota and how to prevent low privileged machine account creation can be found on Kevin Robertson’s excellent post “MachineAccountQuota is USEFUL Sometimes”.

If nothing else, I hope this blog post informs systems administrators of the real forest-wide risk involved in implementing external trusts. Afterall, how can systems administrators be expected to properly secure their environments while thinking authentication is not possible where it actually is.

Timeline

  • 2022/05/04 – MSRC case created.
  • 2022/05/12 – Case status changed to “Review/Repro”.
  • 2022/06/17 – Case status changed to “Develop” with email that stated "We confirmed the behavior you reported. We'll continue our investigation and determine how to address this issue."
  • 2022/06/17 – Case status changed to “Complete – Resolved”.
  • 2022/08/24 – Comment left on case to find out the status.
  • 2022/09/02 – A follow-up comment left on case to find out the status.
  • 2022/09/14 – A follow-up email sent to find out the status of the case.
  • 2022/09/29 – Email received explaining the issue was not determined to affect security.
  • 2023/03/14 – Public disclosure - Blog post released.

Acknowledgements

DES Is Useful... Sometimes

By: 0xe7
8 August 2023 at 22:39

Data Encryption Standard (DES) is a symmetric-key block cypher algorithm created in the 1970’s and was historically used within Active Directory (AD) environments to secure data. Most notably, DES was used as an encryption type for Kerberos tickets, although on modern environments it is disabled by default. This tweet by Steve Syfuhs mentions that DES is uninteresting and “off by default” but that got me thinking, what is involved in re-enabling it?

In this research, I’ll describe exactly how DES can be re-enabled and when it is enabled, how to use DES to takeover any AD account that isn’t krbtgt or a trust account.

Domain Controller Configuration

The main configuration required for DES to be enabled for Kerberos on a domain is at the domain controller’s level. The following GPO setting can be used to configure which encryption algorithms are supported by the DC’s, when it is applied to DC’s:

Computer Configuration\Windows Settings\Security Settings\Local Policies\Security Options\Network security: Configure encryption types allowed for Kerberos

Figure 1: Kerberos Encryption Types GPO Setting

Ultimately this sets the following registry key:

HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\parameters\SupportedEncryptionTypes

Figure 2: Kerberos Encryption Types Registry Setting

This is a bitfield where 2 represents DES-CBC-MD5. This means that the GPO setting does not need to be set for DES to be supported, this registry key only requires the bit that represent ‘2’ to be set on 1 domain controller on the domain.

The best way I’ve found to test this without the ability to query the registry for each DC is to request a TGT with a DES session key. I have modified Rubeus, which now includes the /suppenctype argument to asktgt, to make it easy to request TGT’s this way:

Figure 3: Requesting a TGT with a DES session key

Even though the TGT is encrypted with the krbtgt AES256 key, the session key is DES-CBC-MD5. It is important to note that this is possible to perform with any account without requiring any changes to the account. As shown below, there is no indication that the account supports DES in any way:

Figure 4: Account Supported Encryption Types and UAC

If the DC does not support DES-CBC-MD5, a KDC_ERR_ETYPE_NOTSUPP error is returned:

Figure 5: DES Unsupported

Cracking DES Tickets (Kerberoasting)

After understanding how DES could be enabled and how to tell if it was enabled, the next step was to understand exactly how to exploit this configuration.

The simplest example is when a service account is already configured for DES, this means has an SPN (service account) and has the useraccountcontrol (UAC) setting USE_DES_KEY_ONLY set:

Figure 6: Service account configured for DES

This makes it possible to request a service ticket (ST) for the service user des that is encrypted using DES:

Figure 7: Request DES encrypted service ticket

While the “KeyType” shown here is the session key type, which can be different to the encryption type of the ticket, the following wireshark output shows that the ticket is in fact encrypted using DES:

Figure 8: DES encrypted service ticket

Now the question was how can this be cracked? And more importantly, can the key be brute forced rather than cracking back to the plain text password? The reason this second question is more important is because if this is possible it would mean that any key can be recovered, even that of machine accounts.

To help figure out how to do this, I asked my friend Spiros Fraganastasis for help and he asked Sam Croley. Sam realised that if we knew the first block (8 bytes) of plain text, we could form a hash that would be crackable using hashcat’s mode 14000.

To form the hash, we need the initialization vector (IV), first block of cypher and first block of plain text. The IV is stored as the first 8 bytes of the cypher (after the checksum), the first block of cypher is the following 8 bytes after the IV. So, the only thing left to determine was the first block of plain text.

Dumping the decrypted enc-part for several tickets at first showed the same first block, but after more testing it was clear that this was changing a little:

Figure 9: Decrypted enc-part

Looking at this in an Asn.1 viewer it’s clear that the first 4 bytes are the header for the outer most tag, which contains the length of the data inside it (essentially the full length of the decrypted enc-part minus the length of the header (4 bytes)):

Figure 10: Application Asn.1 Tag

Similarly, the next 4 bytes is the header for a Sequence tag which contains the rest of the actual data and contains a length which is 4 bytes less than the previous length (length of the header less):

Figure 11: Sequence Asn.1 Tag

At first, I thought I could predict these values from having only the cypher, this is because the length of the plain text is always 24 bytes less than the full length of the cypher. The problem with this is after some testing, the full length of the plain text often differed from the full length of the actual data. This is because with DES the data is padded to ensure 8 byte blocks, so the plain text often had some junk padding at the end, which couldn’t be determined just from the cypher.

What I needed was a way to know with as close to 100% certainty what this first block is. After looking at the plain text again, I realised there are 3 sections which can vary in length:

Figure 12: Full enc-part Asn.1

These sections, from top to bottom, are the session key, client username (cname) and the PAC.

The session key type is determined, in large part, by the supported etypes supplied within the req-body of the request (controlled by the user requesting the ticket), the client username is determined by the TGT used to request the ticket. All that is left is the PAC, most of which is determined by the client user attributes within the AD database, the only exception is the server checksum, whose length is determined by the encryption type of the ticket (AES results in a 12 byte checksum, DES/RC4 results in a 16 byte checksum).

With all of this in mind, I realised it was possible to request a user-to-user (U2U) ticket which would match all of these requirements and result in a ticket that I could decrypt to get the first block of plain text. The session key as previously mentioned is determined by the supported etypes within the req-body of the request. The client user will be the user within the ticket granting ticket (TGT) which will be the same for the U2U request as for the service ticket request, meaning the cname along with most of the PAC will be the same (providing nothing changes for that user within AD between when the U2U ticket is requested and the DES encrypted ST is requested). And the U2U ticket encryption type (and ultimately the server checksum length within the PAC) is determined by the TGT session key, controllable when requesting the TGT.

After modifying Rubeus to return the first block of plain text when a ticket is decrypted within the DisplayTicket function, which is run when requesting a U2U ticket by default with Rubeus, we can see the plain text within the output:

Figure 13: Request U2U ticket to get plain text of first block

With knowledge of the plain text, it was now possible to form a crackable hash to brute force the DES key. This is done as follows:

CYPHER:(PLAINTEXT XOR IV)

So, imagine we have the following:

  • IV: D98C3338A9CB3626
  • CYPHER: 6C25DDEE294588FD
  • PLAINTEXT: 638203F8308203F4

We XOR the PLAINTEXT and IV, resulting in: BA0E30C0994935D2, and we end up with the following hash:

6C25DDEE294588FD:BA0E30C0994935D2

I’ve automated this into Rubeus’ describe command with the newly implemented /desplaintext argument, supplying the plain text we received from the U2U request along with the ST we requested earlier, we get back a crackable hash:

Figure 14: Rubeus describe returns crackable DES hash

Using this hash, the DES key used for encryption can be brute forced using the following hashcat command:

hashcat -a 3 -m 14000 A2CCBE7DA8A76607:94D29CF55C02153B -1 charsets/DES_full.charset --hex-charset ?1?1?1?1?1?1?1?1

Figure 15: Crack DES key

It’s worth noting 2 things here:

  1. For the DES key cracking in this post, I’ve put the first byte in manually to reduce the cracking time length, this is because I do not have access to a powerful cracking rig to demonstrate in the post.
  2. Due to DES having a parity bit at the end of each byte, there are 16 possible usable keys, the cracking will stop when it finds the first usable key, which may not be exactly the same as the key within the AD database but will work none-the-less.

This key can then be used to request a TGT as the service user:

Figure 16: Request TGT using DES

For the sake of completion, it should be clear that with write access to an account it would be possible to perform targeted DES Kerberoasting of the account (user or computer, due to brute forcing of the key) similar to Will Schroeder’s targeted Kerberoasting with the addition of modifying the accounts UAC bit USE_DES_KEY_ONLY to allow for retrieving the DES encrypted ST as well as authenticating using the DES key, of course this bit can be removed in between these actions to cause as little disruption as possible.

It is also worth noting that once the TGT has been retrieved, the USE_DES_KEY_ONLY UAC bit can be switched off:

Figure 17: USE_DES_KEY_ONLY disabled

Even with the USE_DES_KEY_ONLY bit off, the TGT retrieved using the DES key is still usable for its lifetime:

Figure 18: Use TGT retrieved using DES key

This means the USE_DES_KEY_ONLY bit can be on for a minimum amount of time for during a targeted account hijack to make risk of detection as low as possible.

Destroying DCs

As this method of Kerberoasting allows for the recovery of any account key due to a full address space brute forcing being possible, there are 3 types of accounts for which DES encrypted ST’s cannot be retrieved, even with the USE_DES_KEY_ONLY UAC bit set. These are the krbtgt account, trust accounts (INTERDOMAIN_TRUST_ACCOUNT) and DC accounts (SERVER_TRUST_ACCOUNT).

For science, I decided to see what could be done when write access to a DC is possible, partly because this is a semi-common situation where unprivileged users are joining the servers to the domain before a domain administrator (DA) promotes it to a DC, leaving the unprivileged user as the CREATOR OWNER.

I do not advise performing this attack on a live environment as the potential to cause disruption to the infrastructure is real, but it is, however, an interesting attack path.

So, in this example, the user one has GenericWrite over the DC DC2:

Figure 19: GenericWrite permission

While GenericWrite was chosen for ease of demonstration, this will also all work with CREATOR OWNER.

For this to be a useful attack path, it is assumed all the admin accounts are protected from delegation, as shown below:

Figure 20: Administrator marked as “Sensitive and cannot be delegated”

If this was not the case, it would be possible to use Resource-Based Constrained Delegation (RBCD) to escalate. It is also assumed that PKINIT is not configured, otherwise shadow credentials could be used to escalate.

Initially the DC2 accounts UAC value is usual for a DC:

Figure 21: DC2 UAC value

Enabling the USE_DES_KEY_ONLY UAC bit:

Figure 22: Enable DES for DC2

It can be demonstrated that a DES encrypted ST cannot be requested:

Figure 23: ETYPE_NOTSUPP error

So, to exploit this SERVER_TRUST_ACCOUNT (8192) needs to be changed to WORKSTATION_TRUST_ACCOUNT (4096), to do this the UAC field can be XOR’d with 12288:

Figure 24: Downgrade DC from SERVER_WORKSTATION_ACCOUNT to WORKSTATION_TRUST_ACCOUNT

Now that the DC2 account has been downgraded to a normal workstation account, it is possible to request a DES encrypted ST:

Figure 25: Request DES encrypted ST for DC account

Request a U2U ticket to get the valid plain text of the first block (as described previously):

Figure 26: Request U2U ticket to get plain text of first block

Use Rubeus’ describe, with the /desplaintext to get the crackable DES hash:

Figure 27: Rubeus describe returns crackable DES hash

As with the user hash in the previous example, this hash can be used to brute force the DES key for the DC2 account using the following hashcat command:

hashcat -a 3 -m 14000 31812F50B7AC7122:41299994A5D5ADB3 -1 charsets/DES_full.charset --hex-charset ?1?1?1?1?1?1?1?1

Figure 28: Brute forcing DES key

Using this DES key, a TGT can be requested for the DC2 account:

Figure 29: Request TGT for DC2 account

Now a TGT has been retrieved, DES needs to be disabled again:

Figure 30: Disable DES on the DC2 account

The DC2 account also needs to be escalated back to a SERVER_TRUST_ACCOUNT, unfortunately while this level of access grants the ability to downgrade from SERVER_TRUST_ACCOUNT to WORKSTATION_TRUST_ACCOUNT, it does not allow for the reverse:

Figure 31: Fail to escalate DC2 account

Escalation within the domain is required to escalate the DC2 account back to a SERVER_TRUST_ACCOUNT. This can be done by requesting a S4U2self ticket for DC2 as an administrative user (Administrator in this case):

Figure 32: S4U2self ticket for Administrator to DC2

This ticket can be used with impacket to DCsync the AES key for the Administrator from DC2:

Figure 33: DCsync Administrator AES key

This key can be used to request a TGT as the Administrator user:

Figure 34: Request Administrator TGT

Finally, the DC2 account can be escalated back to a SERVER_TRUST_ACCOUNT:

Figure 35: Escalate DC2 account

Again, I’d like to reiterate, I do not advise performing this attack path on a live environment. There is a LOT of room for real disruption.

DES In The Middle (TGT Session Roasting)

Anyone that was paying attention earlier and understood my previous research on AS-REQ ST’s may realise that there is another potential abuse here. In my previous post, in the section called “Proof of concept: RoastInTheMiddle” I demonstrate that it is possible to capture, modify and replay AS-REQ’s. In the section “Domain Controller Configuration” above, I showed that it is possible to request a TGT with a DES session key providing the DC supports DES and without changing the account in any way to support DES. This is done by only including the DES-CBC-MD5 etype within the req-body inside the AS-REQ.

This means if a valid AS-REQ with pre authentication can be captured, it could be modified to only include the DES-CBC-MD5 etype, replayed to a DC supporting DES which will result in a TGT with a DES session key being returned. All that is required to use this TGT is the session key. As the key used to encrypt the pre authentication data is unknown, it is not possible to decrypt the enc-part of the AS-REP to retrieve the session key and use the TGT immediately, it is possible however to request an ST that is encrypted with the TGT session key, using U2U. This is done by providing a TGT for an account for which credentials have been obtained and providing the TGT with a DES session key as the additional ticket. This will result in a ST encrypted with the DES session key but for the user of the account for which credentials have been obtained.

For example, User1’s credentials are known.

  1. Request a valid TGT for User1.
  2. Send U2U with User1’s TGT as both authentication and additional tickets to extract known plain text of first block.
  3. Man-in-the-Middle (MitM) is performed.
  4. AS-REQ for Computer1 is captured.
  5. AS-REQ modified to only include the DES-CBC-MD5 etype.
  6. Forward AS-REQ to a DC that supports DES.
  7. Extract TGT for Computer1 from AS-REP.
  8. Send U2U with User1’s TGT as the authentication ticket and Computer1’s TGT as the additional ticket to get an ST encrypted with Computer1’s TGT’s session key.
  9. Create a DES hash from U2U ST encrypted with Computer1’s TGT’s session key.
  10. Create KERB_CRED from Computer1’s TGT and known information, missing the session key.
  11. Crack the DES hash back to the TGT session key.
  12. Insert the TGT session key into the KERB_CRED.
  13. Use the TGT to authenticate as Computer1.

I modified my RoastInTheMiddle tool to perform steps 2-10 automatically:

Figure 36: RoastInTheMiddle session roasting

RoastInTheMiddle performs an ARP poison MitM of 192.168.74.13 and takes a TGT using the /tgt argument, this can be a valid TGT for any account. What it does next is shown in the following wireshark output:

Figure 37: RoastInTheMiddle wireshark output

So here, the first 2 (TGS-REQ/TGS-REP) is the initial U2U using the TGT passed in as the /tgt argument to retrieve the plaintext (displayed in the output with the line “Got usable known plain text:”).

The next 4 is a genuine authentication of the DSP computer account, normally Windows sends an AS-REQ without pre authentication first, this is to retrieve the correct salt for the key. These are passed along to the DC (192.168.74.11) as usual to avoid interrupting the authentication, but the AS-REQ that contains pre authentication is stored (this is shown in the output on the line that says “Got AS-REQ for user [email protected] to service krbtgt/DES.LAB”).

The last AS-REQ/AS-REP is the modified AS-REQ, containing only the DES-CBC-MD5 etype, being sent to a DC (192.168.74.12) and the TGT being sent back.

Lastly the U2U request is sent using the TGT passed in as the /tgt argument as the authentication ticket and the TGT retrieved in the previous step as the additional ticket is sent which results in the DC (192.168.74.12) sending back an ST encrypted with the session key of the DSP computer accounts TGT.

In the RoastInTheMiddle output above, the DES hash for the TGT session key is printed along with the KERB_CRED containing the DSP computer accounts TGT without the session key (making it currently unusable).

The next step is cracking the DES hash of the TGT session key, it is important however that this key is cracked within the lifetime of the TGT (by default 10 hours), otherwise the TGT will be unusable anyway, the following hashcat command was used for this:

hashcat -a 3 -m 14000 EF746FE49C358B28:DF4C43FC01762CA3 -1 charsets/DES_full.charset --hex-charset ?1?1?1?1?1?1?1?1

Figure 38: TGT session key recovered

Now the session key needs to be inserted into the KERB_CRED output by RoastInTheMiddle earlier, for this I added a kirbi command to Rubeus, this command can eventually be used to manipulate KERB_CRED’s further but for now it just supports modifying/inserting a session key with the /sessionkey and /sessionetype arguments:

Figure 39: Insert session key into KERB_CRED

Lastly, this TGT can now be used to authenticate as the DSP machine account and retrieve ST’s:

Figure 40: Use TGT to request ST

DES AS-REP Roasting

Some of you may be asking “what about AS-REP Roasting?” and after essentially writing this whole post, that was the same question that I asked myself. If “AS-REP Roasting” is new to you, check out Will’s post on the topic, as I won’t be going over all of the details here.

The first step was to see if a TGT could be requested without pre authentication using the DES key:

Figure 41: Request TGT without preauth using DES key

The last thing needed is to know the first block of plain text of the enc-part within the AS-REP, the decrypted enc-part is shown below, with the first block highlighted:

Figure 42: Decrypted AS-REP enc-part

The first block this time consists of the first 3 tag headers:

Figure 43: First 3 Asn.1 Tags

Let’s look first at the third tag here (CONTEXT SPECIFIC) which is the last 2 bytes of the plain text (A0 13):

Figure 44: Context Specific Tag

Here it’s clear that A0 is the tag number for CONTEXT SPECIFIC and 13 (or 19 in decimal) is the length. This tag contains only the session key, which will always be DES-CBC-MD5, so the length will always be the same. This means the last 2 bytes of plain text will always be A0 13.

The first 3 bytes of plain text (79 81 CF) is the APPLICATION tag:

Figure 45: Application Tag

Here CF is the length of 207 which is the whole enc-part of the AS-REQ. Lastly, there is the next 3 bytes (30 81 CC), which is the SEQUENCE tag:

Figure 46: Sequence Tag

Here the length (CC or 204) is the length in the APPLICATION tag (207) minus 3 (the length of the tag header). So, the only problem with being able to predict the plain text for the first block of the enc-part of an AS-REP is the ability to predict the length of the APPLICATION tag. To see how this may be possible, let’s look at the whole enc-part:

Figure 47: AS-REP enc-part

Looking at the Asn.1 above, it’s clear that the only elements that will vary in length is the srealm and realm within the sname, everything else should remain a fixed length. With this in mind, it should be relatively easy to calculate the correct length.

The domain name used in this example is “des.lab” which is 7 characters, this means the domain name takes up 14 bytes of the enc-part. The APPLICATION tag is 207 bytes, so:

207 – 14 = 193 bytes

To request a TGT without pre authentication, the domain name has to be known, so by doing the following, the proper length of the APPLICATION tag can be calculated:

193 + (length of domain name * 2)

Then to get the length of the SEQUENCE tag, just minus 3.

Using this method, I modified Rubeus’ asreproast command to support roasting DES enabled accounts when /aes isn’t passed:

Figure 48: AS-REP Roasting using LDAP

It is also possible to manually specify the account, rather than searching LDAP and requires including the /des switch:

Figure 49: AS-REP Roast without LDAP

It’s worth noting here that /format:hashcat is required because john the ripper doesn’t support cracking this type of hash. Using the following hashcat command, it is possible to crack this hash back to the account long term DES key:

hashcat -a 3 -m 14000 3DE9FBEA34F9851B:5B1E49C5BD8A76DF -1 charsets/DES_full.charset --hex-charset ?1?1?1?1?1?1?1?1

Figure 50: DES key cracked

This key can then be used to request a usable TGT:

Figure 51: Request TGT

Remediation/Detection

The best option to avoid these issues is to ensure that all DC’s have DES disabled in the relevant registry key:

HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\parameters\SupportedEncryptionTypes

The bit that represents ‘2’ has to be off. If this is the case, no DES attacks are possible on the domain.

Windows events can also be used to detect attacks that take advantage of DES encryption. The 4769 event can detect Kerberoast attacks that request DES ST’s:

Figure 52: 4769 requesting DES ticket

The 4768 event can be used to detect both the TGT session roasting MitM as well as the AS-REP roasting attacks:

Figure 53: 4768 requesting DES encryption

Conclusion

While DES is disabled by default in AD, it is still very easy to enable and when enabled puts the whole AD infrastructure at much higher risk. It should, therefore, be of utmost importance for sysadmins to ensure that DES is fully disabled on all DCs, as only a single DC supporting DES is all that is required to enable all of these attacks.

❌
❌