PKCE - OAuth2 的安全措施

Table of Contents

OAuth2 是现代 Web 应用的通用登录标准,使用 OAuth2 可以将用户系统从业务系统中抽离出来,实现单点登录(SSO)。如果你还不熟悉的话,建议你去看看:

随着 Web 应用的普及,OAuth2 的使用范围也越来越广泛。在某些场景中, OAuth2 会显得不那么安全,这里介绍一个 OAuth2 在使用中的设计漏洞,和官方提出的修正措施。

1 在浏览器上打开应用

现代的操作系统,都允许应用注册一个 url,当用户在浏览器上访问这个url时,就打开这个应用。比如,在我的 windows 上,想打开 vscode,只需要访问下面的地址:

vscode://file/c:/Windows/System32/Drivers/etc/hosts

能打开 vscode 是因为 vscode 在安装时像系统注册了 “vscode://” 这个 URI,当访问这个 URI,系统会从注册表中查找对应的应用,再打开它。

2 OAuth2 流程 (Authorization Code)

当应用程序想要使用 OAuth2 时,如果不想带上一个浏览器在程序包里面,就可以使用系统自带的浏览器。跟 Web 的 OAuth2 不同的是,redirect_uri 设置成了 APP 向系统注册的 URI,这个 URI 可以直接打开 APP。流程如下图所示:

dev_auth.png

3 OAuth2 流程的缺陷

这个流程有个问题,那就是其它的 APP 也可以注册自己的 URI,它注册的 URI 跟正常的 APP 的 URI 一样的话,那么系统可能就会打开这个新注册的 APP,这个新注册的 APP 就拿到了 code,进而拿到 access_token。

+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| End Device (e.g., Smartphone)  |
|                                |
| +-------------+   +----------+ | (6) Access Token  +----------+
| |Legitimate   |   | Malicious|<--------------------|          |
| |OAuth 2.0 App|   | App      |-------------------->|          |
| +-------------+   +----------+ | (5) Authorization |          |
|        |    ^          ^       |        Grant      |          |
|        |     \         |       |                   |          |
|        |      \   (4)  |       |                   |          |
|    (1) |       \  Authz|       |                   |          |
|   Authz|        \ Code |       |                   |  Authz   |
| Request|         \     |       |                   |  Server  |
|        |          \    |       |                   |          |
|        |           \   |       |                   |          |
|        v            \  |       |                   |          |
| +----------------------------+ |                   |          |
| |                            | | (3) Authz Code    |          |
| |     Operating System/      |<--------------------|          |
| |         Browser            |-------------------->|          |
| |                            | | (2) Authz Request |          |
| +----------------------------+ |                   +----------+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+

4 PKCE 流程

PKCE (Proof Key for Code Exchange by OAuth Public Clients) 是官方提出的修正方案,专门针对上面提到的问题。他在 (1) 请求中加入参数 "code_challange",他是对随机数的哈希结果。在请求 token 地址的时候,再将随机数发送过去,这样 token 地址对应的服务就可以计算这个随机数的哈希,与 (1) 请求的 code_challange 对比,如果不一样就拒绝请求。

                                          +-------------------+
                                          |   Authz Server    |
+--------+                                | +---------------+ |
|        |--(A)- Authorization Request ---->|               | |
|        |       + t(code_verifier), t_m  | | Authorization | |
|        |                                | |    Endpoint   | |
|        |<-(B)---- Authorization Code -----|               | |
|        |                                | +---------------+ |
| Client |                                |                   |
|        |                                | +---------------+ |
|        |--(C)-- Access Token Request ---->|               | |
|        |          + code_verifier       | |    Token      | |
|        |                                | |   Endpoint    | |
|        |<-(D)------ Access Token ---------|               | |
+--------+                                | +---------------+ |
                                          +-------------------+

上图中, "t(code_verifier)" 就是这个哈希值;"code_verifier" 是随机数; t_m 指的是 "code_challenge_method" 参数,指的是哈希算法,如 "S256"。

具体细节可以查看 rfc7636

5 总结

OAuth2 的流程在 redirect_uri 不受控的情况下会有较严重的安全问题,PKCE 使用一个哈希值校验的方式解决了这个问题。目前大多数 OAuth2,或者 OIDC 服务都开启了 PKCE,如果需要用到第三方登录的话,可以试试看,到底谁没开。

其实这个方案还有一点点问题,那就是 auth request (A)虽然是 https 的,但本地 APP 可以从浏览器历史记录访问到 t(code_verifier),而这个哈希值可以用采用表匹配,运气好的话就能匹配到。本地应用里面也有一些反骨的,他们用语言自带的伪随机数(C的 rand(),python 的 random)当做生成 code_verifier,导致每次随机数都是一样的,也是会造成缺陷。但这些缺陷都是使用的问题,PKCE 流程本身是没问题的,这些细节在 rfc 里都谢了。不注意看文档的开发者,又不愿意用现有方案非要自己实现的,只能自求多福了。


By .