Kerberos TGT request with armoring fails with KDC_ERR_POLICY
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"
@gpotter2
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?
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) :
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
gpresult on the DC:
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?
I strongly suspect that Scapy isn't the issue here, but the debugging is interesting.
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>
The event log when i ask TGT for T0_QCS with armoring :
I'm currently working on gathering more verbose/debug-level logs to better understand what's happening.
GOT IT :
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.
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:
- a successful authentication of the MECM$ machine account (TGT).
- 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 :
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 :
Let me know if you need additional logs, captures, or details.
Thanks again for your time !
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
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.