kerberos 定义
- Kerberos 是一种网络认证协议,其设计目标是通过密钥系统为客户机、服务器应用程序提供强大的认证服务。
- Kerberos 作为一种可信任的第三方认证服务,是通过传统的密码技术(如:共享密钥)执行认证服务的。
- Kerberos 也能达到单点登录的效果,即当 Client 通过了 Kerberos server 的认证后,便可以访问多个 Real Server。
kerberos 几个重要组件
- KDC 又称 Kerberos 服务器:负责管理发放 Ticket 和记录授权的中心服务器,包含两部分:AS 认证服务器(Authentication Server),TGS 服务授权服务器 (Ticket Granting Server)
- Client 客户端:需要使用 kerbores 服务的客户端
- AppServer 服务端:提供具体服务的服务端
- Client 在 Kerberos 注册用户名 c, 密码 Kc;Kerberos 服务器将用户的用户名及密码存放在它的数据库中,因此密码 Kc 只有 Client 和 Kerberos 才拥有。
- AppServer 在 Kerberos 注册用户名 s, 密码 Ks;同理密码 Ks 只有 AppServer 和 Kerberos 才拥有。
- TGS 在 Kerberos 注册用户名 tgs, 密码 Ktgs;同理密码 Ktgs 只有 TGS 和 Kerberos 才拥有。
kerberos 中几个重要概念
- 用户 principal 的命名:类似 elis/admin@EXAMPLE.COM,形式是用户名/角色/realm 域。
- 服务 principal 的命名类似 ftp/station1.example.com@EXAMPLE.COM,形式是服务名/地址(提供者)/realm 域;
- principal(认证的主体即简称“用户名”):每添加一个服务或者用户就要添加一条 principal,每个 principal 都有一个密码。用户 principal 的密码用户自己记住,服务的 principal 密码服务自己记录在硬盘上(keytab 文件中);
- realm(Kerberos 的域):有点像编程语言中的 namespace。在编程语言中,变量名只有在某个 ”namespace” 里才有意义。同样的,一个 principal 只有在某个 realm 下才有意义。所以 realm 可以看成是 principal 的一个 ” 容器 ” 或者 ” 空间 ”。相对应的,principal 的命名规则是 ”what_name_you_like@realm”。
在 kerberos, 大家都约定成俗用大写来命名 realm, 比如 ”EXAMPLE.COM”
- Kerberos 的进程:有 krb5kdc(主进程,也就是 KDC)、kadmind(用于远程管理 principal 数据库)、kpropd(slave 同步数据用)。配置文件在 /etc/krb5.conf 和 /var/kerberos/krb5kdc/ 下,数据库在 /var/kerberos/krb5kdc/princical;
- Kerberos 只提供一种功能——在网络上安全的完成用户的身份验证。它并不提供授权功能或者审计功能。
kerberos 认证交互过程
kerberos 认证交互过程可以简要概括为以下图片中的三次通信
- 每次通信,消息包含两部分,一部分可解码,一部分不可解码;
- 服务端不会直接与 KDC 通信
- KDC 保存所有机器的账户名和密码
- KDC 本身具有一个密码
第一次通信
client–>AS(Authentication Service) 认证服务
- 想要访问 http 服务,首先要向 KDC 表名自己的身份,通过未加密的信息发送至 KDC 获取 Ticket Granting Ticket (TGT)。
- 信息包含(你的用户名 /ID、你的 IP 地址、TGT 的有效时间);
- Authentication Server 收到你的请求后,会去数据库中验证,你是否存在。注意!仅仅是验证是否存在,不会验证对错。
- 如果存在,Authentication Server 会产生一个随机的 Session key(可以是一个 64 位的字符串),这个 key 用于你和 Ticket Granting Server (TGS) 之间通信。Authentication Server 会发送两部分信息给你,一部分信息为 TGT,通过 KDC 自己的密码进行加密,包含(你的 name/ID、TGS 的 name/ID、时间戳、你的 IP 地址、TGT 的生命周期、TGS session key),另外一部分通过你的密码进行加密,包含的信息(TGS 的 name/ID、时间戳、生命周期、TGS session key)
- 如果不存在会报出:client not found in Kerberos databases while getting initial 这样的错误,意思就是 KDC 的数据库中没有找到你这用户。
- 使用你的密码解密第二部分信息,获取到 TGS session key。如果密码不正确,无法解密,则认证失败。第一部分信息 TGT,你是无法解密的,但需要暂时缓存起来。
第二次通信
client–>TGS(Ticket Granting Service) 票据授予服务器
如果第一步认证成功了,说明此时你已经拥有了一个无法解密的 TGT 和一个通过自己密码解密第二部分信息获取到的 TGS Session Key。
- 请求信息包含三部分:1. 通过 TGS Session Key 加密的认证器部分(你的 name/ID、时间戳)2. 明文传输部分:(请求的 Http 服务名(就是请求信息)、HTTP Service 的 Ticket 生命周期)3.TGT 部分
- Ticket Granting Server 收到信息后,首先检查数据库中是否包含有你请求的 Http 服务名。
- 如果无,直接返回错误信息。
- 如果存在,则通过 KDC 的密码解密 TGT,这个时候。我们就能获取到 TGS Session key。然后,通过 TGS Session key 去解密你传输的第一部分认证器,获取到你的用户名和时间戳。
- TGS 再次进行验证:
- 对比 TGT 中的用户名与认证器中的用户名
- 比较时间戳(认证器中的时间戳和系统的时间戳),不能超过一定范围(通常 30 分钟),时间戳用于防止重放攻击,所以所有的机器都必需使用安全的类似 NTP 的机制把时间同步好。
- 检查是否过期
- 检查 IP 地址是否一致
- 检查认证器是否已在 TGS 缓存中(避免应答攻击)
- 验证通过后 TGS 会发送两部分信息给你:1. 通过 Http 服务的密码进行加密的信息(ST):(你的 name/ID、Http 服务 name/ID、你的 IP 地址、时间戳、ST 的生命周期、Http Service Session Key)和 通过 TGS Session Key 加密的信息(Http 服务 name/ID、时间戳、ST 的生命周期、Http Service Session Key)
- 你收到信息后,通过 TGS Session Key 解密,获取到了 Http Service Session Key,但是你无法解密 ST。
第三次通信
client–>Http 服务
前面两步成功后,以后每次获取 Http 服务,在 Ticket 没有过期,或者无更新的情况下,都可直接进行这一步。省略前面两个步骤。
- 向 Http Server 发送请求信息,包含两部分:1. 通过 Http Service Session Key,加密部分 (你的 name/ID、时间戳),2.ST
- Http 服务端通过自己的密码解密 ST(KDC 是用 Http 服务的密码加密的),这样就能够获取到 Http Service Session Key,解密第一部分。
- 服务端解密 ST 后,进行检查
- 对比 ST 中的用户名(KDC 给的)与认证器中的用户名
- 比较时间戳(认证器中的时间戳和系统的时间戳),不能超过一定范围
- 检查是否过期
- 检查 IP 地址是否一致
- 检查认证器是否已在 HTTP 服务端的缓存中(避免应答攻击)
- Http Server 应答信息:通过 Http Service Session Key 加密的信息(Http 服务 name/ID、时间戳)
- 你通过缓存的 Http Service Session Key 解密这部分信息,然后验证是否是你想要的服务器发送给你的信息。完成你的服务器的验证。
至此,整个过程全部完成。
kerberos 疑问
- 上面有个数据包是 KDC 经 Client 转发给 Server 的,为什么不直接发给 Server?
- 因为 Server 可能给多个 Client 提供服务,这样 Server 需要维护一个 Client 和会话密钥的对应表,这对 Server 是一个负担。
- 为什么要发两份关于 Client 的信息给 Server?
- 通过这两份数据的对比,Server 就能判断出是不是对的 Client 在访问服务。
- Client 是如何判断自己在访问对的 Server 呢?
- 因为 Client 给 Server 的一个数据包是用 Server 的 master key 来加密的所以只有对的 Server 才能解密。
- 为什么要用会话密钥
- 通信方的 master key 是长期有效的,如果在网络上传输,一旦被截取,理论上来说只要有足够的时间是可以破解的。
- 所以我们才用临时的会话密钥来通信,一段时间后会话密钥会过期,同时时间戳也防止了,恶意用户重复使用同一个数据包。
- 为什么要用时间戳?
- 如果 Client 向 Server 传送的数据包被其他的 Client 截取,然后自己拿来向 Server 请求服务这,这样就会出问题
- Server 收到请求后将从解密后的数据包中获得的时间戳和当前时间对比,一旦超过一定范围将直接拒绝请求
kerberos 自身的安全工作如何做?
- kerberos 的安全是建立在主机都是安全的而网络不是安全的假定之上的。所以 kerberos 的安全其实就在于把主机的安全做好;
- 尤其是 KDC 那台机器的安全。出于安全的考虑,跑 KDC 的机器上不能再跑别的服务,如果 KDC 被攻陷了,那么所有的密码就全部泄露了;
- 如果只是服务的机器被攻陷了,那么更改服务的 principal 的密码即可;
- 如果是用户的机器被攻陷了,那么在 ticket 超时(一般是数小时的时间里)之前,用户都是不安全的,攻击者还有可能尝试反向用户的密码;
- Kerberos 依赖其它的服务来存放用户信息(登录 shell、UID、GID 啥的),因此需要注意到这些信息依然是很容易遭受攻击并且泄露的;
- 由于任何人都可以向 KDC 请求任何用户的 TGT(使用用户密码加密的 session key),那么攻击者就有可能请求一个这样的包下来尝试解密,他们有充足的时间离线去做这个工作,一旦解开了,他们也就拿到了用户的密码。简单密码几乎一解就开,所以不能设置简单密码,也不能在字典里。另外还可以打开 Kerberos 的预验证机制来防御这种攻击,预验证机制就是在 KDC 收到用户请求 TGT 的请求之后,要求用户先发一个用自己密码加密的时间戳过来给 KDC,KDC 如果确实可以用自己存储的用户密码解密,才发 TGT 给用户,这样攻击者在没有用户密码的时候就拿不到可用于反向的包含用户密码的 TGT 包了。在 MIT kerberos 中在配置文件中 default_principal_flags = + preauth 可以打开这个机制。但这个机制也并不是无懈可击的,攻击者依然可以通过嗅探的方式在正常用户请求 TGT 时拿到上述的那个包(这个难度显然就高了一些);
- 还有一个问题是攻击者可能伪造一个 KDC,然后用一个伪造的实际不存在的用户向这个 KDC 请求验证,通过后他就得到了一个用户登录系统的 shell。这种攻击需要在客户端防御,需要客户端主动去验证一下 KDC 是否是正确的 KDC。具体来说就是客户端在得到 TGT 后进一步要求 KDC 给一个本物理机的 principal(也就是一个用物理机密钥加密的串),然后尝试用物理机存储的密码去解密,由于伪造的 KDC 没有物理机(host/hostname)的 principal 密码,所以它无法给出这个包,也就被客户端认定为是伪造的 KDC,认证失败。这个机制需要在客户端开启(认证服务器端么),默认是关闭的,在 krb5.conf 里[appdefaults]章节里 pam 的部分中设置 validate=true 来开启;
Kerberos 可以在不同的 realm 之间建立信任关系
这样用户在一个 realm 中登录后就可以享用多个 realm 中的服务。简单的建立互信的方法是在 2 个域名都建立一条共享密码的名为 krbtgt 的 principal。更好的建立互信的方法是多个 realm 都建立一条信任到自己的父 realm,这样在子 realm 之间也能建立起信任;
Kerberos 还能向 SSL 帮助网络流量套上一层加密
比如说 telnet 这种明文协议就可能能用上。但是跟 SSL 或者 SSH 比起来安全性仍然略低,因为它的 session key 是用对称加密传输的(如果 TGT 被盗取了,流量的加密也就失效了),这在理论上增加了被嗅探破解的可能。而 SSH 和 SSL 协商 session key 采用的 DH 算法是理论安全的(被称为 PFS 即 Perfect forward secrecy,详见 http://en.wikipedia.org/wiki/…_forward_secrecy)。还有一个问题是 Kerberos 采用的一些加密算法是已经不安全的了(如 CRC32 和 DES),而另一些加密算法(AES)并没有被其他的 kerberos 实现广泛使用,所以会有一些兼容性的麻烦,选择加密算法时要避开这些算法;
如果将 princical 密码设置为 random
那么每次将其 dump 到本地 keytab 中时,都会 random 新的密码并 dump 到本地(可用于更新密码);