0%

SSO安全简易综述

SSO安全简易综述

SSO即Single Sign On,单点登录,只是一个名称,而其下有多种实现方案,比较常见的实现方案为CAS和OAuth,然后基于OAuth又扩展出一个OpenID Connect(OIDC),最近读论文的时候看了些SSO安全,加上之前学过的一点内容,还是总归记录一下。

整篇文章的安全内容基本围绕OAuth,这个是目前研究比较多资料比较多的方向。

基本概念

基本概念只要搞清楚CAS,OAuth和OIDC是个啥就行
这块应该直接贴链接复制粘贴的。
具体过程不详述,多看链接

CAS 和 OAuth 的区别是什么?
OIDC 与 OAuth2.0 综述
理解OAuth 2.0
jAccount认证接入方法
标准协议认证模块

然后再贴几个常见缩写
Service Provider(SP)服务提供方,有时也叫Relying Party(RP)依赖方
Identity Provider(IdP)身份验证方
End User(EU)终端用户

CAS只进行认证,不进行授权。SP和IdP一般都由一个主体控制,比如学校的办事大厅之类的东西,在门户登录之后其他的子系统也可以一起登录。该过程只有认证,即你在门户完成了认证后,后续对子系统的访问由门户进行认证。

而OAuth则只负责授权,不进行认证环节。即常见的第三方登录功能,如github登录,微信登录等。OAuth场景下,你登录的SP大多分属于不同的主体,且IdP与SP之间也不会有什么关系。该过程是一个授权过程,通过将你在IdP处已有的账号的(部分)信息授权给SP,使得SP可以通过IdP提供的信息创建一个新的账号登录(或是登录之前已绑定过IdP的已有账号)。(但是放在国内,一般第三方授权创建新号之后还要强制再绑一个手机号,导致第三方登录并不方便。。。)

OIDC是在OAuth协议下的一种扩展,核心仍然使用OAuth进行授权,但是还多了认证功能,以及一大堆可选的插件。在OAuth的基础上多了ID Token,可以用于用户的认证。可以参看一下上面上交的OIDC手册的描述:

开放式认证系统连接OpenID Connect(OIDC)是基于OAuth2.0的身份认证标准协议,OIDC使用OAuth2的授权服务器来为第三方客户端提供用户的身份认证,并把对应的身份认证信息传递给客户端,且可以适用于各种类型的客户端(比如服务端应用,移动APP,JS应用)。

以及这篇文章,提到了Access token用于授权,而Id token用于认证。
Access Token vs Id Token

选择 OIDC 授权模式
以及这里提到了SPA(单页式应用)需要使用隐式模式授权。其实我感觉描述不是很对,感觉更应该像是纯前端应用,或者是APP之类的没有独立后端的应用。因为本身就不能安全的存储access token,所以就摆了,直接忽略code步骤拿access token

安全

oauth除了授权码模式和隐式模式(简化模式)外还有密码模式和客户端模式,后两种暴露的安全问题较少,这里只关注前两种的安全问题。

授权码模式

一般来说,会比较推荐使用授权码模式进行授权,该模式需要IdP在重定向时返回一次性且短期有效的code,放在url的hash中,然后接收方通过JavaScript读取hash中的code传递到SP,SP需要通过APPID和APPSecret去找IdP兑换access token,并使用access token去获取用户信息。
这个步骤中,access token对用户是不可见的。

开放重定向

最容易想到的问题是开放重定向,对于IdP的授权请求一般长下面这个样子(直接复制的上交开放文档的内容),以及这个示例请求中缺失了state参数,这会导致csrf攻击。

GET /oauth2/authorize?response_type=code&scope=openid&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
Host: jaccount.sjtu.edu.cn

用户授权后,重定向会长如下这样(但是这里又出现了state参数。。。)

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

一个很简单的想法就是,攻击者直接修改IdP授权请求的redirect_uri参数,即可将code任意重定向到攻击者可控平台,再自行构造重定向后的页面访问即可接管用户账户。

该攻击也有显而易见的防御措施,授权请求携带了client_id指明了其属于哪个应用,该应用在注册时设定好允许的重定向url,即可阻止攻击者的开放重定向。

XSS窃取

这个的攻击要求会更高一些,需要在SP上有一个xss漏洞,在IdP将code返回时拿到code并外带。并且由于code是一次性的,所以还需要保证重定向回到SP后,阻止前端将code发送到SP后端进行兑换。

以及常规情况下,SP的callback路径很难有存储型XSS,而IdP返回的重定向内容不太可控(虽然state其实是比较可控的,但是一般来说不太可能反射到页面上)。所以一般的攻击思路是在主站上拿到一个xss后注入iframe,诱骗用户在iframe处进行第三方授权,然后通过拦截重定向结果(比如往页面中注入超长cookie直接塞爆中间件,或者对ajax函数进行hook之类的操作)的方式阻止后端去兑换token并读取iframe url外带。

CSRF攻击

CSRF攻击是诱骗用户将自己SP的账号与攻击者的IdP绑定,这里涉及到SP账户和IdP账户两个主体,即使你在SP处使用了第三方登录,SP最终还是会通过IdP的信息给你创建一个SP的号。反过来,如果你先创建一个SP的号,可以再进行第三方登录使得该SP账户与IdP账户绑定。

具体攻击步骤为,攻击者先进行授权码模式的认证,当验证进行到返回code这一步时,拦截该请求并诱导受害者访问该链接,此时返回的code代表的是攻击者的IdP账户,而受害者在访问该连接后SP会使用code得到攻击者IdP的信息,使得受害者SP绑定上攻击者IdP账户,导致攻击者可以通过IdP登录受害者SP。

防御方式也就是之前提到的state参数,这个参数可以认为是csrf token,即要求SP在发起授权请求时携带该参数,并在IdP返回code时,将state原封不动的返回。攻击者无法获取用户的state参数,并且用户如果没有发起第三方登录验证的话,也不会存在state参数,即可做到对于csrf攻击的防御。

不过,就看的一些论文的调查上,似乎许多实现对于state的校验并不严格。有没有一种可能是允许无state,然后用户本身只要不发起授权流程,state就是空就可以直接置空绕过呢。

可以参看如下链接
Oauth2.0 里面的 state 参数是干什么的?

reference信息泄露

这个的话在实际应用场景中感觉不多,但是论文就喜欢研究这个。
如果不设置refer policy,或者用的是某些古早浏览器的话,同一个页面如果加载了其他网站的资源,会携带完整的url作为header中的refer字段,而code如果是通过request parameter形式传播的话,就会被包含在完整url中。

但是感觉上是不太会有这种问题的,如果code通过parameter传递,那code应该直接抵达后端,后端验证完之后直接应该重定向到登陆完成页面?或者中间有一个中转页面,但是设计上也不应该加载其他外部资源导致code泄露。并且就算需要加载外部资源,也无法被攻击者控制,感觉危害性不是很够。(虽然论文很喜欢说网络嗅探)

如果code通过hash传递,callback页面也应该只引入js然后让js传递到后端,也不应该会引入其他可能泄露的资源(感觉上,没有写过,云的),所以暂且不太理解这个攻击的实际意义

简化模式

简化模式相较于授权码模式会比较直白,省略了从code兑换token的步骤,在重定向返回时直接返回access token。多适用于无独立后端的应用。如单页式应用或者一些纯前端app之类的。
给出的理由是纯前端应用无法安全的存储密钥,所以直接摆了。暂且不知道这里提到的密钥是什么,权且认为是app secret。

这里似乎在实现上存在一定的分歧,通过access token获取用户信息是只需要app id的,但是如果想刷新access token的时间,根据authing的文档,是需要app secret的,这也是authing文档中指出简化模式下不支持刷新token持续时间的原因。但是上交文档中却没有这个限制(以及上交文档中token和code都是通过query传递的,暂且认为不太专业)

token归属不明

这个算是一个比较经典的漏洞类型,在使用简化模式进行授权时,token是直接暴露在外的,所以用户是可以手动控制token值的。
假设存在一个SP example.com和一个攻击者可控的恶意SP attacker.com,当受害者在attacker.com上完成了第三方认证时,攻击者就可以拿到对应用户IdP身份的access token,再把这个token直接用到example.com上去授权,example用这个token一样可以拿到用户的信息,验证即可通过,也就完成了对用户账户的接管。
该攻击需要在简化模式下才能使用,是因为简化模式才允许攻击者直接指定access token,授权码模式下access token是独立存放在后端的。

修复方案也很明了,上文提到过

通过access token获取用户信息是只需要app id的

所以IdP方需要负责记录发出的access token与app id的对应关系,只有当app id正确的情况下才能验证通过。

感觉上,其实和授权码模式下的CSRF攻击有点类似。在不考虑state的情况下,似乎也可以用恶意应用去获取用户code然后去SP上尝试登录。不过,code在兑换时本身就需要提供app id和secret,肯定也是做了相关绑定的。

奇怪的论文研究

An Investigation of Identity-Account Inconsistency in Single Sign-On

An Investigation of Identity-Account Inconsistency in Single Sign-On

提出了一个玄学场景,之,当你使用IdP注册了一个号(邮箱),然后你的这个邮箱因为某种原因丢失了/注销了/改名了,然后被攻击者重新注册,攻击者就可以通过这个邮箱接管你之前通过IdP认证的SP账户。

乍一看有一种我拿到了你的密保邮箱之后就可以用你的密保邮箱接管你的账号的荒诞感。但是仔细阅读之后还是觉得很有道理的。
其实只需要看论文里的一张图就知道讲的是什么。

image-20241210162656680

这里主要讨论后三种情况,

  1. bob用bob@example.com通过IdP注册了SP账户,但是把邮箱改成了alice@example.com(号不变,因为sub不变),能否登录原来账户。
  2. alice使用了alice@example.com这个邮箱注册了账户,但是没有绑定IdP(即sub为空),后来该邮箱被攻击者获取,攻击者能否通过该邮箱接管alice的SP庄户。
  3. bob注册且绑定IdP后,该邮箱被注销并被攻击者重新注册(分配了新的sub),攻击者能否用该相同邮箱登录bob SP账户。

感觉根据逻辑来说,情况1应该是允许登录,情况2我个人认为应该也可以登录?看SP本身的考量,而情况3如果登录成功则属于漏洞。

这个问题牵扯到的核心是,SP应该使用什么来唯一确定IdP的用户,因为邮箱是不允许重复的,所以如果使用邮箱作为索引,就会导致攻击的产生。然而,根据Oauth规范,access token换来的用户信息中应包含sub(subject)项,作为用户ID的唯一标识。当然也会携带email项。

所以解决思路很明了,只以sub为用户唯一索引即可。
这么讨论一下,是不是感觉他说的还挺有道理的。

DISTINCT: Identity Theft using In-Browser Communications in Dual-Window Single Sign-On

DISTINCT: Identity Theft using In-Browser Communications in Dual-Window Single Sign-On
CCS传奇文章,写挺好的

讨论了一个没有标准化的OAuth流程,双窗口OAuth。(不知道现在是不是也有标准了,但是实际上这些漏洞的成因和OAuth流程关系不大,更像是常规web漏洞的利用)
传统OAuth流程依赖于重定向,对于SPA单页式应用,重定向是不(那么)可用的,所以也有许多的SP是通过内嵌iframe,或是pop up弹出式窗口与IdP进行授权,完成后通过postMessage之类的浏览器内部通信方式进行传输。

所以整篇文章讨论的其实是postMessage误配置或是使用window.location传递数据时可能存在的问题。
关键内容也就是论文里的一张图

image-20241210162736439

攻击者通过iframe把SP的认证页面嵌入到自己的页面中,然后进行授权
第一种情况是postMessage未设置目标域名,直接获取到数据
第二种情况是SP不校验回调url,向攻击者发送数据
第三种情况是攻击者可以向location中写入数据造成xss(这个我感觉有点凑数),需要注意的是,跨域情况下,是可以写入跨域iframe的location的,但是不能读取。

提出了一种全新的漏洞存在方式,还是挺有感觉的。

O Single Sign-Off, Where Art Thou? An Empirical Analysis of Single Sign-On Account Hijacking and Session Management on the Web

O Single Sign-Off, Where Art Thou? An Empirical Analysis of Single Sign-On Account Hijacking and Session Management on the Web
usenix传奇文章,提出了单点登录的反向情况,单点登出的问题。讨论的是当IdP账户因为某种原因被接管的情况下,用户在重获IdP账户所有权后,如何取消之前的所有授权。

这个想法也很明确,当SP能够使用token获取到用户信息时,SP就会自己创建对应的账户,并以登录的形式下发cookie给用户,之后并没有其他的理由没事去check一下token是否被吊销。只有当cookie过期之后,攻击者才会失去对用户账户的控制。

(不过应该也有常见需要一直维护token的有效性的吧,不然refresh token是干什么的呢)

解决方案的话,好像目前为止标准上都没有特别确定的?(没有专门翻文档,云的)感觉就是强制每隔一段时间去check一下token有效性?不过IdP会不会因为这样有比较大的负载压力?