oauth0 oauth2
When working with developers on authentication and authorization, I find that the nonce
and state
parameters are two of the more difficult parts of the OAuth 2.0 and OpenID Connect specifications to understand. It’s clear from the specs that these parameters are for security, but what specific attacks do these parameters help to prevent? Well, these two parameters work in tandem to thwart replay attacks and cross-site request forgery (CSRF). Let’s take a look at a few examples of replay attacks against an OAuth or OpenID Connect client, and then go over some mitigation techniques using a nonce
and a state
.
与开发人员一起进行身份验证和授权时,我发现nonce
和state
参数是OAuth 2.0和OpenID Connect规范中比较难理解的两个部分。 从规格中可以明显看出,这些参数是为了安全起见,但是这些参数可帮助防止哪些特定的攻击? 好吧,这两个参数协同工作可阻止重播攻击和跨站点请求伪造(CSRF)。 让我们看一些针对OAuth或OpenID Connect客户端的重放攻击示例,然后再讨论一些使用nonce
和state
缓解技术。
This article assumes that OAuth’s Authorization Code Flow is being used, and that the reader is familiar with OAuth 2.0 or OpenID Connect (OIDC). It’s worth pointing out that OAuth’s Implicit Flow is deprecated due to a number of well-known attacks, not all of which have sufficient mitigation strategies. Also, the techniques described herein have applications outside of authorization and authentication. For example, a nonce
and state
can be used to harden the security of a password-reset process.
本文假定正在使用OAuth的授权代码流 ,并且读者熟悉OAuth 2.0或OpenID Connect(OIDC)。 值得指出的是,由于许多众所周知的攻击 ,OAuth的“隐式流”已被弃用,并非所有攻击都具有足够的缓解策略。 而且,本文描述的技术具有授权和认证之外的应用。 例如,可以使用nonce
和state
来加强密码重置过程的安全性。
Lastly, a few terms:
最后,几个术语:
- Resource Owner: The entity, probably a human or service account, that is using your Client application and owns some data, like photos or messages. In this article, this is generally referred to as the user. 资源所有者:使用客户端应用程序并拥有一些数据(例如照片或消息)的实体,可能是个人帐户或服务帐户。 在本文中,这通常称为用户。
- User Agent: The user’s browser, mobile application, or other software that’s sent to the Authorization Server to receive an authorization code. For example, Chrome or Firefox. 用户代理:用户的浏览器,移动应用程序或其他发送到授权服务器以接收授权代码的软件。 例如,Chrome或Firefox。
- Client: The application that needs the Resource Owner’s authorization. For instance, a web application that you are developing. 客户端:需要资源所有者授权的应用程序。 例如,您正在开发的Web应用程序。
- Authorization Server: Where the user is sent to establish identity or obtain authorization. For example, Auth0, Okta, Microsoft B2C, or Google. 授权服务器:发送用户以建立身份或获取授权的位置。 例如,Auth0,Okta,Microsoft B2C或Google。
简要回顾代码流 (A Brief Refresher on the Code Flow)
With the Code Flow, the User Agent is redirected from the Client to the Authorization Server with a bunch of query parameters in the URL.
使用代码流,用户代理通过URL中的一堆查询参数从客户端重定向到授权服务器。
client_id
: A unique identifier for the Client application.client_id
:客户端应用程序的唯一标识符。redirect_uri
: A URI to which the User Agent will be redirected after an authorization attempt.redirect_uri
:授权尝试后,用户代理将重定向到的URI。scope
: What level of access is being requested. With OIDC this must include “openid,” but may include other scopes like “profile,”scope
:正在请求什么级别的访问权限。 对于OIDC,该字段必须包含“ openid”,但可能包含“ scope”之类的其他范围“email,” “address,” and “phone.” With OAuth this is some access level defined by an application, such as “read-messages” or “manage-repositories.”
“电子邮件”,“地址”和“电话”。 使用OAuth,这是应用程序定义的某种访问级别,例如“读取消息”或“管理存储库”。
response_type
: This is hard-coded to “code” in the Code Flow.response_type
:这在代码流中被硬编码为“编码”。response_mode
: The mechanism to use to respond to the Client. This is generally “form_post” (recommended), but it could be another mechanism like “query” or “web_message” (not recommended).response_mode
:用于响应客户端的机制。 通常,这是“ form_post”(推荐),但也可以是“ query”或“ web_message”之类的另一种机制(不推荐)。
nonce
and state
parameters can also be included in the request, both of which are described in detail below.
nonce
和state
参数也可以包含在请求中,下面将详细介绍这两个参数。
Altogether, an authorization request might look like this (newlines added for clarity):
总共,授权请求可能如下所示(为清楚起见添加了换行符):
https://auth.my-auth-server.com?
client_id=my-client
&redirect_uri=https://my-site.com/auth/callback/
&scope=openid
&response_type=code
&response_mode=form_post
&state=some-state
&nonce=some-nonce
At the Authorization Server, some sort of user interaction may be required. The user may need to log in or accept grants (“Ben’s Awesome App wants to read your email address, name, and avatar. Do you accept?”). Once all that mumbo-jumbo is complete, the Authorization Server sends the User Agent back to the redirect_uri
with a few parameters:
在授权服务器上,可能需要某种类型的用户交互。 用户可能需要登录或接受授权(“ Ben的Awesome应用希望读取您的电子邮件地址,名称和头像。您接受吗?”)。 一旦完成所有mumbo-jumbo,授权服务器就会使用以下几个参数将用户代理发送回redirect_uri
:
code
: An authorization code that can be exchanged for one or more tokens, e.g. anaccess_token
,id_token
, andrefresh_token
.code
:可以交换一个或多个令牌的授权代码,例如access_token
,id_token
和refresh_token
。state
: The unmodifiedstate
parameter as it was originally sent to the Authorization Server.state
:原始state
参数,最初发送给授权服务器的状态。
Note that the code
and state
parameters are exposed to the User Agent, either in the request body, as query parameters, or in a URL fragment depending on the response_mode
.
请注意, code
和state
参数作为请求参数在请求正文中或作为response_mode
参数在URL片段中公开给用户代理,具体取决于response_mode
。
Back at the Client, the code
is then exchanged using a server-to-server request. Importantly, the User Agent is not part of this request, so the code exchange cannot be intercepted by the Resource Owner or a malicious actor between the User Agent and the Authorization Server.
回到客户端,然后使用服务器到服务器的请求交换code
。 重要的是,用户代理不是此请求的一部分,因此资源所有者或恶意行为者无法在用户代理与授权服务器之间拦截代码交换。
Here’s an ASCII diagram of this process, taken from the Section 4.1 of the OAuth 2.0 Specification.
这是此过程的ASCII图,摘自OAuth 2.0规范的4.1节 。
+----------+
| 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)
授权码重播 (Authorization Code Replay)
Now that the prerequisites are out of the way, let’s talk about code replay attacks. A nefarious actor between the User Agent and the Authorization Server may intercept the authorization response: part “C — Authorization Code” in the above diagram. That bad actor can then manually post the code to the Client’s redirect_uri
, triggering a code exchange, and then gain access to the Client on behalf of the Resource Owner.
既然先决条件已经解决,让我们来谈谈代码重播攻击。 用户代理和授权服务器之间的恶意行为者可能拦截授权响应:上图中的“ C —授权代码”部分。 然后,不良行为者可以手动将代码发布到客户端的redirect_uri
,触发代码交换,然后代表资源所有者获得对客户端的访问权限。
Here are some ways the code might be intercepted.
有一些方法可以拦截代码。
- Legitimate users on a corporate network that monitors HTTPS traffic using a proxy server and “trusted” certificates on domain workstations. In this scenario, corporate admins have access to clear-text network traffic between the Client and the Authorization Server, even if both use TLS certificates. 在公司网络上使用代理服务器和域工作站上的“受信任”证书监视HTTPS流量的合法用户。 在这种情况下,即使公司管理员都使用TLS证书,也可以访问客户端和授权服务器之间的明文网络流量。
A coworker sits down at a legitimate user’s workstation and clicks the browser’s back button until they reach the Client’s
redirect_uri
. They then resubmit the Authorization Server’s form post and steal a code.一位同事坐在合法用户的工作站上,然后单击浏览器的后退按钮,直到他们到达客户端的
redirect_uri
为止。 然后,他们重新提交授权服务器的表单并窃取代码。- A bad actor with access to the Authorization Server — like a Microsoft or Auth0 developer — intercepts an authorization request or pulls an authorization code directly out of a database. 像Microsoft或Auth0开发人员那样,具有访问授权服务器权限的坏角色,将拦截授权请求或直接从数据库中提取授权代码。
Someone steals a legitimate user’s mobile device. The legitimate user obtains authorization from a desktop. The thief takes advantage of the mobile device’s browser synchronization to steal a code by using the browser’s history and navigating back to the Client’s
redirect_uri
.有人窃取了合法用户的移动设备。 合法用户从桌面获得授权。 小偷利用移动设备的浏览器同步功能,通过使用浏览器的历史记录并导航回客户端的
redirect_uri
来窃取代码。
The list goes on and on. The important bit is that the authorization code flows through the User Agent, so it is susceptible to interception. It is generally not possible for a developer to prevent this interception.
清单不胜枚举。 重要的一点是授权代码流经用户代理,因此很容易被拦截。 开发人员通常无法阻止这种拦截。
跨站请求伪造 (Cross-Site Request Forgery)
Another code interception attack involves cross-site request forgery (CSRF), but it’s a bit different than the run-of-the-mill. In this scenario, an attacker creates an account, authenticates with the Authorization Server, and gets a legitimate authorization code for use with the Client. The attacker then creates a malicious page on a site which they control. Behind the scenes, that malicious page replays the attacker’s authorization code.
另一种代码拦截攻击涉及跨站点请求伪造(CSRF),但与常规攻击有所不同。 在这种情况下,攻击者将创建一个帐户,并通过授权服务器进行身份验证,并获取供客户端使用的合法授权代码。 然后,攻击者在他们控制的站点上创建恶意页面。 在后台,该恶意页面会重放攻击者的授权代码。
The attacker distributes a link to their malicious page, causing an unknowing victim to establish authorization against the Client on behalf of the attacker. Later, the legitimate user uses the Client application and saves information. Unbeknownst to the legitimate user, that information is saved to the attacker’s account.
攻击者将链接分发到其恶意页面,从而导致不知情的受害者代表攻击者针对客户端建立授权。 后来,合法用户使用客户端应用程序并保存信息。 合法用户不知道,该信息已保存到攻击者的帐户中。
重播和CSRF缓解攻击 (Replay and CSRF Attack Mitigation)
So authorization codes can be intercepted and, as developers, it’s out of our control. But two techniques can be used to combat replay and CSRF attacks. The first is to bind a state
to the User Agent to ensure that an authorization request and response both happen in the same User Agent. The second is to use a nonce
— a “number used once” — to ensure that a code is used once and only once.
因此,授权代码可以被截获,并且作为开发人员,这是我们无法控制的。 但是可以使用两种技术来抵抗重放和CSRF攻击。 第一种是将state
绑定到用户代理,以确保授权请求和响应都在同一用户代理中发生。 第二种是使用nonce
(即“一次使用的数字”)以确保代码仅使用一次。
将用户代理绑定到状态 (Tying the User Agent to a State)
In the Client application, before redirecting the User Agent to the Authorization Server, a token is generated and stored in the User Agent in a state
object. (The state
object may contain other information, for example, a session identifier or a URI in the Client application to which the user should be redirected after obtaining authorization, but that’s outside of the scope of this article.) This token is sometimes referred to as a “CSRF token,” sometimes as a “proof key,” and sometimes a “code verifier,” depending on the context wherein this technique is employed.
在客户端应用程序中,在将用户代理重定向到授权服务器之前,将生成令牌并将其存储在用户代理中的state
对象中。 ( state
对象可能包含其他信息,例如,客户端应用程序中的会话标识符或URI,在获得授权后用户应将用户重定向到该URL,但这不在本文的讨论范围之内。)有时有时会引用此令牌取决于使用此技术的上下文,它有时称为“ CSRF令牌”,有时称为“证明密钥”,有时称为“代码验证器”。
The PKCE Specification provides recommendations on how to generate such a token. In a nutshell, it recommends a 43- to 128-character string consisting of the characters [a-zA-Z0-9-._~]
, generated using a high-entropy, cryptographically secure pseudorandom number generator.
PKCE规范提供了有关如何生成此类令牌的建议。 简而言之,建议使用43至128个字符的字符串,其中包含由[a-zA-Z0-9-._~]
字符组成的字符串,这些字符串是使用高熵, 密码安全的伪随机数生成器生成的 。
The state
object might look like this (the proofKey
is truncated for brevity).
state
对象可能看起来像这样(为简洁起见, proofKey
被截断了)。
{
"afterAuth": "https://www.my-site.com/admin/",
"proofKey": "Es.wszQ-wqc_N9fR7ppwjqOaD~jADC..."
}
That state
object is then base-64-URL encoded and stored in a cookie. (This encoding is only used to ensure that the cookie does contain reserved characters, like commas, semicolons, and equal signs. It’s in no way used for security purposes.) The cookie is set with the Secure
and HttpOnly
flags, with a Path
set to the Client’s redirect_uri
, and with SameSite=none
. That last part might be counter-intuitive, but the cookie needs to be read when the Authorization Server — a different site — posts an access code to the Client server.
然后,该state
对象经过base-64-URL编码,并存储在cookie中。 (此编码仅用于确保cookie确实包含保留字符,例如逗号,分号和等号。绝不用于安全目的。)cookie使用Secure
和HttpOnly
标志设置,并设置Path
到客户端的redirect_uri
,并使用SameSite=none
。 最后一部分可能是违反直觉的,但是当授权服务器(另一个站点)向客户端服务器发布访问代码时,需要读取cookie。
Lastly, the encoded state
object is hashed. The hash of the state object is transmitted to the Authorization Server in the state
query parameter. If an authorization response is intercepted, the interceptor only has the hash of the state
. The attacker cannot recreate the original state
object in their browser manually. As part of the authorization response, the Client retrieves the state
from the User Agent, hashes it, and verifies that the hash matches the state
parameter that’s sent in the authorization response. If the hash matches, the code is exchanged; if not, the response is considered malicious and appropriate action is taken. “Appropriate action” depends on the security needs of the application, but it may be something along the lines of temporarily deactivating the user’s account and notifying the user via email.
最后,对编码state
对象进行哈希处理。 状态对象的哈希将通过state
查询参数传输到授权服务器。 如果拦截了授权响应,则拦截器仅具有state
的哈希值。 攻击者无法在其浏览器中手动重新创建原始state
对象。 作为授权响应的一部分,客户端从用户代理检索state
,对其进行哈希处理,并验证哈希是否与授权响应中发送的state
参数匹配。 如果哈希匹配,则交换代码。 如果不是,则认为该响应是恶意的,并已采取适当的措施。 “适当的操作”取决于应用程序的安全需求,但这可能类似于暂时停用用户帐户并通过电子邮件通知用户的行为。
As an added security check, as part of the authorization response it’s recommended to deny responses that do not come from the Authorization Server. This helps to stop CSRF attacks, and can be accomplished using the (misspelled) referer
header. OWASP has guidelines on implementing this check.
作为附加的安全检查,建议将拒绝来自授权服务器的响应作为授权响应的一部分。 这有助于阻止CSRF攻击,并且可以使用(拼写错误) referer
标头来完成。 OWASP具有实施此检查的指南 。
The state
is effectively used as a password, and it helps to harden the Client against CSRF attacks. It also helps to prevent some types of code replay, like races where a malicious user intercepts an access code and races to exchange it before a legitimate user. However, since the state
is stored in the User Agent and transmitted in the headers of the authorization response, it is still subject to interception by a man-in-the-middle (MITM). That’s where the nonce
comes in.
state
可以有效地用作密码,它有助于使客户端免受CSRF攻击。 它还有助于防止某些类型的代码重播,例如在恶意用户拦截访问代码并在合法用户之前进行竞争以交换访问代码的竞赛。 但是,由于state
存储在用户代理中并在授权响应的标头中传输,因此状态仍然会受到中间人(MITM)的拦截。 那就是nonce
出现的地方。
确保不重复使用授权码 (Ensuring an Authorization Code is not Reused)
The OpenID Connect Spec recommends but does not require that the Authorization Server disallows reusing authorization codes. “If possible, verify that the Authorization Code has not been previously used.” And, unfortunately, some of the major identity providers out there skip this part of the spec (here’s looking at you, Microsoft B2C). A nonce
parameter can be used on the Client side to enforce one-time code usage. This helps to conform to the OAuth spec on the Client side. Specifically, the spec states that “the client must not use the authorization code more than once.”
OpenID Connect规范 建议但不要求授权服务器禁止重用授权代码。 “如果可能,请验证以前未使用授权码。” 而且,不幸的是,一些主要的身份提供者跳过了规范的这一部分(这里是Microsoft B2C)。 可以在客户端使用nonce
参数来强制使用一次性代码。 这有助于符合客户端的OAuth规范。 具体来说,该规范指出“客户端不得多次使用授权代码。”
Here’s how the nonce
works. Just like the state
, before sending the User Agent to the Authorization Server, a nonce
is generated. Another 128-character high-entropy string is sufficient. This nonce
is then stored by the Client but not bound to the User Agent. For example, it can be stored in a database, in Client memory, or on disk, but not in a cookie or any sort of User Agent storage. A timestamp is stored alongside the nonce
. Then the nonce
is sent to the Authorization Server in clear text.
这是nonce
工作原理。 就像state
,在将用户代理发送到授权服务器之前,会生成一个nonce
。 另一个128个字符的高熵字符串就足够了。 然后,该nonce
由客户端存储,但未绑定到用户代理。 例如,它可以存储在数据库,客户端内存或磁盘中,但不能存储在cookie或任何类型的用户代理存储中。 时间戳记与nonce
一起存储。 然后, nonce
以明文形式发送到授权服务器。
After the authorization code is exchanged for an id_token
, the Client decodes the id_token
and verifies its signature, issuer, and audience. Then it verifies that the nonce
claim in the id_token
's payload matches a nonce
in the Client’s persistent storage. It also uses the aforementioned timestamp to verify that the nonce
is not expired. A nonce
should only be valid for a small window of time, like the amount of time it would take a user to sign in and possibly reset their password. The OAuth spec recommends that authorization codes expire in no more than 10 minutes, so that’s a reasonable amount of time for the nonce
expiration.
在将授权代码交换为id_token
,客户端将对id_token
解码并验证其签名,发行者和受众。 然后,它会验证nonce
的要求id_token
的有效负载的匹配nonce
在客户端的持久化存储。 它还使用上述时间戳验证nonce
未过期。 nonce
应仅在很小的时间范围内有效,例如用户登录并可能需要重置密码所需的时间。 OAuth规范建议授权代码在不超过10分钟的时间内失效,因此这是nonce
到期的合理时间。
If those two checks are met, the
nonce
is deleted and the user is allowed to interact with the Client application.如果同时满足这两个检查,则将
nonce
删除,并允许用户与客户端应用程序进行交互。If the
nonce
is expired, then the user is forced to log in again. This can happen if, for example, the user takes a long time to log in (e.g. requests a password reset and then takes a while to access their email, or navigates to the Authorization Server and leaves the log-in screen open throughout their lunch break). With OIDC, a user is forced to re-authenticate by supplying theprompt=login
parameter (section 3.1.2.1).如果
nonce
已过期,则将强制用户再次登录。 例如,如果用户花费很长时间登录(例如,请求重设密码然后花一些时间来访问其电子邮件,或者导航到授权服务器并在整个午餐时间都保持登录屏幕处于打开状态,则可能发生这种情况)打破)。 使用OIDC时,用户必须通过提供quickprompt=login
参数( 第3.1.2.1节 )来重新进行身份验证。If the
nonce
does not exists — that is, it’s already been used — then the interaction is treated as malicious and appropriate action is taken (e.g. account deactivation and notification).如果
nonce
不存在(也就是说,它已经被使用过),则该交互被视为恶意,并采取了适当的措施(例如,停用帐户和通知)。
With the nonce
checks in place, even if a bad actor intercepts an authorization code and obtains a clear-text state
, the Client enforces one-time-use codes. This holds true even if the Authorization Server doesn’t do its part and allows code reuse.
进行nonce
检查后,即使不良行为者拦截了授权代码并获得了明文state
,客户端也将强制执行一次性使用代码。 即使Authorization Server不做自己的工作并允许代码重用,这也适用。
Lastly, while OIDC uses a nonce
, OAuth does not. That said, a nonce
can still be used by simply concatenating the nonce
to the hashed state
parameter.
最后,虽然OIDC使用nonce
,但OAuth不使用。 就是说,通过将nonce
值简单地连接到哈希state
参数,仍然可以使用nonce
。
最后的想法 (Final Thoughts)
Security is performed in layers, and using a nonce
and state
adds two more layers of protection. Adding these checks can additionally help to secure a Client application in the event of a data breach on the Authorization Server end. The take-home is that the overall security of a hardened authentication and authorization integration depends not only on the Authorization Server, but also on the Client. It’s symbiotic.
安全性是分层执行的,使用nonce
和state
可增加两层保护。 添加这些检查还可以帮助在授权服务器端发生数据泄露的情况下保护客户端应用程序的安全。 总而言之,强化的身份验证和授权集成的总体安全性不仅取决于授权服务器,而且还取决于客户端。 这是共生的。
If you enjoyed this article, you might also enjoy my “Secure Access Token Storage with Single-Page Applications” series. It describes some other common OAuth attacks, and provides details on securely storing access tokens for use with SPAs.
如果您喜欢本文,则可能还喜欢我的“ 具有单页应用程序的安全访问令牌存储 ”系列。 它描述了其他一些常见的OAuth攻击,并提供了有关安全存储访问令牌以供SPA使用的详细信息。
As always, leave a note or question in the comments section below and I’ll try to respond quickly. And thank you for reading my article!
与往常一样,在下面的评论部分中留下笔记或问题,我将尽快做出回应。 并感谢您阅读我的文章!
需要开发人员吗? (Need a Developer?)
Drop me a line and let me know how I can help you out. We’re a small development team that handles security, web development, DevOps, and all sorts of other cool stuff.
请给我打个电话 ,让我知道我可以如何帮助您。 我们是一个小型的开发团队,负责安全性,Web开发,DevOps和其他各种有趣的事情。
翻译自: https://medium.com/@benjamin.botto/oauth-replay-attack-mitigation-18655a62fe53
oauth0 oauth2