Day18 - 【概念篇】OAuth flows: PKCE

本系列文之後也会置於个人网站


                                                 +-------------------+
                                                 |   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 ---------|               | |
       +--------+                                | +---------------+ |
                                                 +-------------------+
 
                     Figure 2: Abstract Protocol Flow

PKCE模式

说穿了PKCE是基於Code flow的安全强化版。在整个过程前後添加了两个动作--产生code_verifiercode_challenge,并在最後透过code_challenge验证code_verifier。其目的有很大程度是为了建立前端通讯与後端通讯的关联。

原先风险

那麽先来看看原本发生了什麽问题。

首先,已经知道Code Flow的整个流程是:

  1. 资源拥有者登入验证身份并授权。
  2. 资源拥有者代理(通常爲浏览器)从授权服务器取得一个临时特殊密码--code
  3. 资源拥有者代理将特殊密码code转交给授权的客户端。
  4. 客户端使用code,像授权服务器换取access_token

可以看到code可能透过网路传递了多次,资源拥有者代理与授权服务器之间、资源拥有者代理与客户端之间、客户端与授权服务器之间。传递多次同时意味这泄漏的风险提高,也就有可能有恶意中间层取得存取权杖。

就算不是截取到code,了解攻击手法的,同样有可能不小心就猜测到code,进行攻击。说真的这种情况还真的很难防范,防不胜防。爲了降低被攻击的机会,最好在添加一些秘密,使攻击难度提升。当然配合使用 Client Credentials Flow 或许是一个办法,毕竟按照设计client_secret只会由客户端拥有,并只在客户端与授权服务器之间流通。但这只证明了客户端是已经被认可的客户端,尚未证明资源拥有者授权的客户端与使用code换取存取权杖的爲同一个。

这就像是任何人都可以声称自己是被授权的「那个人」。

难道授权服务器要等个400多年吗?还是随便一个人说自己是「那个人」也就认可了呢?怎麽想都不太对吧!

    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
    | 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 |          |
    | +----------------------------+ |                   +----------+
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+

             Figure 1: Authorization Code Interception Attack

   A number of pre-conditions need to hold for this attack to work:

   1. The attacker manages to register a malicious application on the
      client device and registers a custom URI scheme that is also used
      by another application.  The operating systems must allow a custom
      URI scheme to be registered by multiple applications.

   2. The OAuth 2.0 authorization code grant is used.

   3. The attacker has access to the OAuth 2.0 [RFC6749] "client_id" and
      "client_secret" (if provisioned).  All OAuth 2.0 native app
      client-instances use the same "client_id".  Secrets provisioned in
      client binary applications cannot be considered confidential.

   4. Either one of the following condition is met:

      4a. The attacker (via the installed application) is able to
          observe only the responses from the authorization endpoint.
          When "code_challenge_method" value is "plain", only this
          attack is mitigated.

解决办法

那麽如何做才能够在前端通讯与後端通讯建立更爲明确的关联了?客户端必须有某种方式能攻证明授权的是自己。

  1. 在概念上客户端告诉浏览器证明自己的方式,并让浏览器将这个讯息告诉授权服务器。
  2. 接着浏览器取得特殊密码code。并与授权服务器约定好,在未来又一个人会带着这个code,并使用一个能证明自己就是「那个人」的方法来找你。
  3. 然後浏览器将code交给客户端。
  4. 客户端带着能够证明自己的方式与code访问授权服务器。
  5. 授权服务器验证了客户端爲资源拥有者代理告诉的是同一个,并将存取权杖交给授权服务器。

这边我不打算详细说明客户端如何证明身份。从概念上来说,就是有一个难题,这个难题的答案只有自己知道。要从难题推出答案很难,但是从难题证明答案正确很容易。
换个比喻就是:

  1. 客户端将一把锁交给了浏览器代理
  2. 浏览器代理又将这把锁交给了授权服务器
  3. 接着客户端将锁的钥匙交给授权服务器
  4. 授权服务器使用钥匙尝试打开锁,如果成功就证明客户端是「那个人」

而这里使用到的难题就是单向杂凑函数(One-Way Hash Function)。如同其名,这个函数功能要透过一个方向运算非常简单,但是反过来却非常困难。

实际上产生过程在OAuth 2.0 Playground已经看过了:

产生出的code_challenge和已知的演算法(单向杂凑函数,这里是SHA256)组成难题。只要能够提出正确的code_verifier就能够证明客户端(出题人)。

透过Keycloak和OAuth.Tools实战

这次同样已Code模式爲基础,透过PKCE来强化安全性。

同样登入。但在登入时,将code_challege与演算法组成的难题告诉授权服务器。

最後在後端通讯时,将答案告诉授权服务器:

如果验证失败就不会通过授权,并且code可能也已经被泄漏,也不再可用。如果都通过,就能够正常取得存取权杖。

产生code_verifitycode_challenge

要产生并没有那麽困难,code_verifity只是乱数字串而已。然後透过一定演算法就可以得到code_challenge

验证code_verifitycode_challenge

同样的验证也就没有那麽困难。授权服务器先後得到:

  1. code_challenge和验证使用的方法
  2. code_verifity

现在姑且叫第一个叫做c1,第二个叫做v,验证使用的方叫做m。透过mv可以得到c2,只要c1c2一致就是通过验证。下面可以用Python程序码简单验证一下:

from base64 import urlsafe_b64encode
from hashlib import sha256

def gen_challege(vertify_str):
    return urlsafe_b64encode(sha256(vertify_str).digest())


####################

c1 = b'24OdicZTLu8T9kV3Pf1ZaPr8iJAGwaQJ0dvTQy5SSf0'
m = gen_challege

####################

v = b'xgoALuaqJPR4bK2wgNUSEBwKrxy6ljufTU7k4DRw7SA7NcqjLLqJXX4bI0091bbK'

####################

c2 = m(v)
c2 = c2.decode().rstrip('=').encode()
c1 == c2  # True

参考资料


<<:  Day19-JavaScript(JS)与TypeScript(TS)的函式(Function) Part1

>>:  NNI如何搬到云端上玩?

[Day6] 渗透测试证照 - OSCP 小分享

前言 前面几篇写了一些有趣没什麽人讨论的攻击手法,中场休息偷懒一下 之前在PTT上看到有人讨论OSC...

Day18

传值与传址,但C++是一个特别的语言比C语言更复杂真要细分可分成3类(传值,传址,传参考),同时指标...

第32天~MS-SQL开始

这个得上一篇-https://ithelp.ithome.com.tw/articles/10283...

#3 The V8 Engine

在谈论V8引擎时,我们得先了解什麽是 JavaScript 引擎。 JavaScript Engin...

django新手村4 -----templates

再来说说templates 先修改在noob1 中的setting.py,找到TEMPLATES,修...