scapy icon indicating copy to clipboard operation
scapy copied to clipboard

Kerberos TGT request with armoring fails with KDC_ERR_POLICY

Open amTeaq opened this issue 5 months ago • 12 comments

Brief description

Hello,

I'm encountering an issue when trying to request a Kerberos TGT using ticketer with the armor_with parameter set, in an environment where Kerberos armoring (FAST) is enabled. The TGT request fails systematically with KDC_ERR_POLICY.

I have reproduced the issue in production and in two separate lab environments, all configured with armoring enabled on the KDC side.

Scapy version

2.6.1.dev113

Python version

Python 3.11.11

Operating system

Ubuntu

Additional environment information

No response

How to reproduce

Hello,

I'm encountering an issue when trying to request a Kerberos TGT using ticketer with the armor_with parameter set, in an environment where Kerberos armoring (FAST) is enabled. The TGT request fails systematically with KDC_ERR_POLICY.

I have reproduced the issue in:

  • a production environment
  • two separate lab environments

Here is the output of the test:

>>> load_module("ticketer")

>>> t = Ticketer()

>>> t.request_tgt("[email protected]", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("38c772db85ab9e723ebfb4359b2390779b657aff4141c17ecfe47a6acc9d3abe")))

>>> t.show()
CCache tickets:
0. [email protected] -> krbtgt/[email protected]
   canonicalize+pre-authent+initial+renewable+forwardable
Start time         End time           Renew until        Auth time        
21/07/25 17:05:02  22/07/25 03:04:55  22/07/25 03:04:55  21/07/25 17:05:02

>>> t.request_tgt("[email protected]", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("73598779d8cff69c6ee215d57cba2df538706f18c847d8a4eb8de7a69e653a74")), armor_with=0)
ERROR: Received KRB_ERROR
###[ Kerberos ]###
  \root      \
   |###[ KRB_ERROR ]###
   |  pvno      = 0x5 <ASN1_INTEGER[5]>
   |  msgType   = 'KRB-ERROR' 0x1e <ASN1_INTEGER[30]>
   |  ctime     = None
   |  cusec     = None
   |  stime     = 2025-07-21 14:53:14 UTC <ASN1_GENERALIZED_TIME['20250721145314Z']>
   |  susec     = 0x7de5b <ASN1_INTEGER[515675]>
   |  errorCode = 'KDC_ERR_POLICY' 0xc <ASN1_INTEGER[12]>
   |  crealm    = None
   |  cname     = None
   |  realm     = <ASN1_GENERAL_STRING[b'TEST.FR']>
   |  \sname     \
   |   |###[ PrincipalName ]###
   |   |  nameType  = 'NT-SRV-INST' 0x2 <ASN1_INTEGER[2]>
   |   |  nameString= [<ASN1_GENERAL_STRING[b'krbtgt']>, <ASN1_GENERAL_STRING[b'test.fr']>]
   |  eText     = None
   |  eData     = None

The same operation works perfectly when not using armoring, so the issue seems specific to the FAST mechanism.

Let me know if I can provide further details or perform additional tests.

Thanks for your work on this project! 👍

Actual result

The TGT request should succeed and return a valid ticket when using armor_with=0, assuming the key is valid and armoring is supported by the KDC.

Expected result

The TGT request using armor_with=0 should succeed when Kerberos armoring is enabled on the domain controller, as long as the provided credentials and keys are valid. The expected behavior is for the KDC to return a valid TGT, not a KDC_ERR_POLICY.

Related resources

PS > whoami /claims

USER CLAIMS INFORMATION

Claim Name Claim ID Flags Type Values ==================== =========================== ===== ====== ========= "AuthenticationSilo" ad://ext/AuthenticationSilo String "T0_SILO"

Image

amTeaq avatar Jul 21 '25 15:07 amTeaq

@gpotter2

amTeaq avatar Jul 21 '25 15:07 amTeaq

Hi @amTeaq, I assume this was user error? If not, could you provide the full configuration of your Authentication policies and silos, in addition to proof that you correctly applied the GPOs to allow FAST on the DC?

gpotter2 avatar Jul 21 '25 17:07 gpotter2

Hi @gpotter2 , thanks for the follow-up!

At first, I actually thought it was a user error on my side. But after more testing, including in a production environment where Kerberos armoring is already in use and working the issue still persists. I consistently get a KDC_ERR_POLICY when using armor_with=0.

The configuration u ask for (it's another environnement) :

Image Image Image Image

Let me know if you'd like me to run more tests or provide additional logs.

The authentication silo policy is fully functional on the domain, the only part that fails is the TGT request with armoring through scapy, which still returns KDC_ERR_POLICY.

Proof :

>>> load_module("ticketer")
>>> t = Ticketer()

>>> t.request_tgt("[email protected]", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("ce07d44e373a690347da275aee399edd1721f61f512b921e1361471ba4eb2ba3")), ip="192.168.56.5")

>>> t.show()
CCache tickets:
0. [email protected] -> krbtgt/[email protected]
   canonicalize+pre-authent+initial+renewable+forwardable
Start time         End time           Renew until        Auth time
21/07/25 19:44:51  22/07/25 05:44:51  22/07/25 05:44:51  21/07/25 19:44:51


>>> t.request_tgt("[email protected]", password="*************", armor_with=0, ip="192.168.56.5")
ERROR: Received KRB_ERROR
###[ Kerberos ]###
  \root      \
   |###[ KRB_ERROR ]###
   |  pvno      = 0x5 <ASN1_INTEGER[5]>
   |  msgType   = 'KRB-ERROR' 0x1e <ASN1_INTEGER[30]>
   |  ctime     = None
   |  cusec     = None
   |  stime     = 2025-07-21 17:46:25 UTC <ASN1_GENERALIZED_TIME['20250721174625Z']>
   |  susec     = 0x2f8fa <ASN1_INTEGER[194810]>
   |  errorCode = 'KDC_ERR_POLICY' 0xc <ASN1_INTEGER[12]>
   |  crealm    = None
   |  cname     = None
   |  realm     = <ASN1_GENERAL_STRING[b'OLYMPIADE.FR']>
   |  \sname     \
   |   |###[ PrincipalName ]###
   |   |  nameType  = 'NT-SRV-INST' 0x2 <ASN1_INTEGER[2]>
   |   |  nameString= [<ASN1_GENERAL_STRING[b'krbtgt']>, <ASN1_GENERAL_STRING[b'olympiade.fr']>]
   |  eText     = None
   |  eData     = None

And ofc fail when trying to request tgt without armoring :

>>> t.request_tgt("[email protected]", password="*************", ip="192.168.56.5")
ERROR: Received KRB_ERROR
###[ KerberosTCPHeader ]###
  len       = 123
###[ Kerberos ]###
     \root      \
      |###[ KRB_ERROR ]###
      |  pvno      = 0x5 <ASN1_INTEGER[5]>
      |  msgType   = 'KRB-ERROR' 0x1e <ASN1_INTEGER[30]>
      |  ctime     = None
      |  cusec     = None
      |  stime     = 2025-07-21 17:57:23 UTC <ASN1_GENERALIZED_TIME['20250721175723Z']>
      |  susec     = 0x174a7 <ASN1_INTEGER[95399]>
      |  errorCode = 'KDC_ERR_POLICY' 0xc <ASN1_INTEGER[12]>
      |  crealm    = None
      |  cname     = None
      |  realm     = <ASN1_GENERAL_STRING[b'OLYMPIADE.FR']>
      |  \sname     \
      |   |###[ PrincipalName ]###
      |   |  nameType  = 'NT-SRV-INST' 0x2 <ASN1_INTEGER[2]>
      |   |  nameString= [<ASN1_GENERAL_STRING[b'krbtgt']>, <ASN1_GENERAL_STRING[b'olympiade.fr']>]
      |  eText     = None
      |  \eData     \
      |   |###[ KERB_ERROR_DATA ]###
      |   |  dataType  = 'KERB_ERR_TYPE_EXTENDED' 0x3 <ASN1_INTEGER[3]>
      |   |  \dataValue \
      |   |   |###[ KERB_EXT_ERROR ]###
      |   |   |  status    = STATUS_INVALID_WORKSTATION
      |   |   |  reserved  = 0x0
      |   |   |  flags     = 0x1

amTeaq avatar Jul 21 '25 17:07 amTeaq

gpresult on the DC:

Image

amTeaq avatar Jul 21 '25 18:07 amTeaq

It might be interesting to take a look at the event log for System/Security (or KDC) on the server side. https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/enable-kerberos-event-logging might also help. Could you find the error there?

gpotter2 avatar Jul 21 '25 18:07 gpotter2

I strongly suspect that Scapy isn't the issue here, but the debugging is interesting.

gpotter2 avatar Jul 21 '25 18:07 gpotter2

The event log when i ask TGT for the domain controller :

[-](https://github.com/secdev/scapy/issues/4801#) <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
[-](https://github.com/secdev/scapy/issues/4801#) <System>
  <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" /> 
  <EventID>4768</EventID> 
  <Version>2</Version> 
  <Level>0</Level> 
  <Task>14339</Task> 
  <Opcode>0</Opcode> 
  <Keywords>0x8020000000000000</Keywords> 
  <TimeCreated SystemTime="2025-07-21T19:07:47.4173926Z" /> 
  <EventRecordID>29380</EventRecordID> 
  <Correlation /> 
  <Execution ProcessID="596" ThreadID="1948" /> 
  <Channel>Security</Channel> 
  <Computer>AD01.olympiade.fr</Computer> 
  <Security /> 
  </System>
[-](https://github.com/secdev/scapy/issues/4801#) <EventData>
  <Data Name="TargetUserName">AD01$</Data> 
  <Data Name="TargetDomainName">OLYMPIADE.FR</Data> 
  <Data Name="TargetSid">S-1-5-21-2576495265-4123810678-669419419-1000</Data> 
  <Data Name="ServiceName">krbtgt</Data> 
  <Data Name="ServiceSid">S-1-5-21-2576495265-4123810678-669419419-502</Data> 
  <Data Name="TicketOptions">0x40810010</Data> 
  <Data Name="Status">0x0</Data> 
  <Data Name="TicketEncryptionType">0x12</Data> 
  <Data Name="PreAuthType">2</Data> 
  <Data Name="IpAddress">::ffff:192.168.1.22</Data> 
  <Data Name="IpPort">34608</Data> 
  <Data Name="CertIssuerName" /> 
  <Data Name="CertSerialNumber" /> 
  <Data Name="CertThumbprint" /> 
  <Data Name="ResponseTicket">DMCVf3raQoqPfRK8moh5eV5+Gax+qwg0WLBu5zFTetA=</Data> 
  <Data Name="AccountSupportedEncryptionTypes">0x1F (DES, RC4, AES128-SHA96, AES256-SHA96)</Data> 
  <Data Name="AccountAvailableKeys">AES-SHA1, RC4</Data> 
  <Data Name="ServiceSupportedEncryptionTypes">0x1F (DES, RC4, AES128-SHA96, AES256-SHA96)</Data> 
  <Data Name="ServiceAvailableKeys">AES-SHA1, RC4</Data> 
  <Data Name="DCSupportedEncryptionTypes">0x1F (DES, RC4, AES128-SHA96, AES256-SHA96)</Data> 
  <Data Name="DCAvailableKeys">AES-SHA1, RC4</Data> 
  <Data Name="ClientAdvertizedEncryptionTypes">AES256-CTS-HMAC-SHA1-96 AES128-CTS-HMAC-SHA1-96 RC4-HMAC-NT RC4-HMAC-NT-EXP DES-CBC-MD5</Data> 
  <Data Name="SessionKeyEncryptionType">0x12</Data> 
  <Data Name="PreAuthEncryptionType">0x12</Data> 
  </EventData>
  </Event>
Image

The event log when i ask TGT for T0_QCS with armoring :

Image

amTeaq avatar Jul 21 '25 19:07 amTeaq

I'm currently working on gathering more verbose/debug-level logs to better understand what's happening.

amTeaq avatar Jul 21 '25 19:07 amTeaq

GOT IT :

Image

EVENT ID 4820 :

A Kerberos Ticket-granting-ticket (TGT) was denied because the device does not meet the access control restrictions.

Account Information:
	Account Name:		T0_QCS
	Supplied Realm Name:	
	User ID:			OLYMPIADE\T0_QCS

Authentication Policy Information:
	Silo Name:		T0_SILO
	Policy Name:		T0_SILO
	TGT Lifetime:		120

Device Information:
	Device Name:		AD01$

Service Information:
	Service Name:		krbtgt/olympiade.fr
	Service ID:		S-1-5-21-2576495265-4123810678-669419419-0

Network Information:
	Client Address:		::ffff:192.168.1.22
	Client Port:		48090

Additional Information:
	Ticket Options:		0x878
	Result Code:		0xC
	Ticket Encryption Type:	0x27
	Pre-Authentication Type:	0

Certificate Information:
	Certificate Issuer Name:		
	Certificate Serial Number:	
	Certificate Thumbprint:		

Certificate information is only provided if a certificate was used for pre-authentication.

Pre-authentication types, ticket options, encryption types and result codes are defined in RFC 4120.

EVENT ID 4768 :

A Kerberos authentication ticket (TGT) was requested.

Account Information:
	Account Name:		T0_QCS
	Supplied Realm Name:	OLYMPIADE.FR
	User ID:			NULL SID
	MSDS-SupportedEncryptionTypes:	-
	Available Keys:	-

Service Information:
	Service Name:		krbtgt/olympiade.fr
	Service ID:		NULL SID
	MSDS-SupportedEncryptionTypes:	-
	Available Keys:	-

Domain Controller Information:
	MSDS-SupportedEncryptionTypes:	-
	Available Keys:	-

Network Information:
	Client Address:		::ffff:192.168.1.22
	Client Port:		48090
	Advertized Etypes:	-

Additional Information:
	Ticket Options:		0x40810010
	Result Code:		0xC
	Ticket Encryption Type:	0xFFFFFFFF
	Session Encryption Type:	0x2D
	Pre-Authentication Type:	-
	Pre-Authentication EncryptionType:	0x2D

Certificate Information:
	Certificate Issuer Name:		
	Certificate Serial Number:	
	Certificate Thumbprint:		

Ticket information
	Response ticket hash:		-

Certificate information is only provided if a certificate was used for pre-authentication.

Pre-authentication types, ticket options, encryption types and result codes are defined in RFC 4120.

amTeaq avatar Jul 21 '25 19:07 amTeaq

Hi,

To help you investigate the issue further, I’ve attached two .evtx files renamed as .png due to upload restrictions on GitHub. Please rename the files back to .evtx before opening them in the Event Viewer.

I) success_armored_MECM.evtx : This log shows a successful Kerberos authentication using armoring (FAST) from a legitimate domain-joined server (MECM$) to the domain controller (AD01$). This confirms that Kerberos armoring is working correctly in this environment.

II) scapy_attempts_failure.evtx : This log includes:

  1. a successful authentication of the MECM$ machine account (TGT).
  2. Two failed attempts to request a TGT using Scapy with the armor_with=0 option, where the user T0_QCS tries to authenticate using the TGT of MECM$ as armor. Both attempts result in KDC_ERR_POLICY, consistent with what was described in the issue.

The EVTX event :

Image Image

Scapy CMD :

>>> load_module("ticketer")
>>> t = Ticketer()
>>> t.request_tgt("[email protected]", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("4b08f4151fe3505c35afdf0f8f04f797a0626d8098bc6c6c9740a47b924f8946")), ip="192.168.56.5")
>>> t.show()
CCache tickets:
0. [email protected] -> krbtgt/[email protected]
   canonicalize+pre-authent+initial+renewable+forwardable
Start time         End time           Renew until        Auth time
22/07/25 23:43:05  23/07/25 09:43:05  23/07/25 09:43:05  22/07/25 23:43:05

>>> t.request_tgt("[email protected]", password="*************", armor_with=0, ip="192.168.56.5")
ERROR: Received KRB_ERROR
###[ Kerberos ]###
  \root      \
   |###[ KRB_ERROR ]###
   |  pvno      = 0x5 <ASN1_INTEGER[5]>
   |  msgType   = 'KRB-ERROR' 0x1e <ASN1_INTEGER[30]>
   |  ctime     = None
   |  cusec     = None
   |  stime     = 2025-07-22 21:43:11 UTC <ASN1_GENERALIZED_TIME['20250722214311Z']>
   |  susec     = 0x95e1f <ASN1_INTEGER[613919]>
   |  errorCode = 'KDC_ERR_POLICY' 0xc <ASN1_INTEGER[12]>
   |  crealm    = None
   |  cname     = None
   |  realm     = <ASN1_GENERAL_STRING[b'OLYMPIADE.FR']>
   |  \sname     \
   |   |###[ PrincipalName ]###
   |   |  nameType  = 'NT-SRV-INST' 0x2 <ASN1_INTEGER[2]>
   |   |  nameString= [<ASN1_GENERAL_STRING[b'krbtgt']>, <ASN1_GENERAL_STRING[b'olympiade.fr']>]
   |  eText     = None
   |  eData     = None

For context, here is a screenshot of the Authentication Policy Silo configuration showing that MECM$ is properly assigned to the silo. This confirms that the machine is authorized and configured to use Kerberos armoring in this setup :

Image

Let me know if you need additional logs, captures, or details.

Thanks again for your time !

amTeaq avatar Jul 22 '25 21:07 amTeaq

Sorry I haven't given an update on this: I haven't found the time to try and replicate. I'll just say that I re-tested armoring in my lab and it works fine. Most likely, the issue stands in the configuration somewhere (which seems to be confirmed by the fact that the error is POLICY).

I'll try and replicate the exact lab you did using the screenshots, but can't give an ETA.

Thanks again for the interest

gpotter2 avatar Sep 04 '25 12:09 gpotter2

I just noticed that you didn't show the full "User Sign On" section of the authentication Policy, so it's hard to guess what's going on.

gpotter2 avatar Sep 09 '25 17:09 gpotter2