IBM Security Verify

 View Only
  • 1.  Preventing OAuth refresh token replay attacks

    Posted Tue May 31, 2022 05:34 PM
    Per the OAuth 2.0 Security Best Current Practice document, refresh tokens should be invalidated if the authorization server detects a replay of a refresh token in the chain.

    For example, considering a PKCE flow (it really wouldn't matter if it were a true authorization grant flow, except it would be less likely to happen since the client credentials would need to be compromised in that flow):
    1. Client obtains AT (access token) and RT (refresh token)
    2. Client refreshes and obtains new AT2 and RT2
    3. RT2 is compromised by an attacker
    4. The attacker utilizes RT2 to obtain AT3 and RT3
    5. The original client tries to utilize RT2 (not knowing RT2 was compromised and already used)
    At the juncture of step 5, because a previous refresh was replayed, based on the oauth security best practices, we would like to invalidate all refresh tokens in this chain.  Is this possible?  I was under the impression that once the RT was used and a new AT and RT were generated, the previous RT was removed from the HVDB.  Also note we are using the hashed tokens option, so I am not sure if that further complicates things.

    The only thing I can foreseeably think here is when the RT is refreshed, to store the previous RT in the database by adding it to the oauth:saved:claim in the stsuu in the post mapping rule.  Then with each new RT, continue to add all previous values and the latest RT used to this claim.  Then if we ever see an invalid refresh token used (I am not sure if the pre-mapping rule would even fire on one, but considering that it does) I could somehow search for that RT value in those saved claims and then remove the associated RT.  This seems pretty convoluted and I am making a lot of assumptions here.

    So, my questions:
    • Has anyone ever had to deal with revoking previous oauth refresh tokens if a previous one in the chain is used hence trigger a potential replay attack?
    • Is there some feature of ISVA that could deal with this easily?
    • If there is no current future, is this something perhaps IBM would entertain as an RFE/idea?  Would it be possible?
    • Is there any way anyone can conceive to do this in mapping rules?

    This is the exact text we found from the specification when this ask was brought to our attention:

       *  *Refresh token rotation:* the authorization server issues a new
          refresh token with every access token refresh response.  The
          previous refresh token is invalidated but information about the
          relationship is retained by the authorization server.  If a
          refresh token is compromised and subsequently used by both the
          attacker and the legitimate client, one of them will present an
          invalidated refresh token, which will inform the authorization
          server of the breach.  The authorization server cannot determine
          which party submitted the invalid refresh token, but it will
          revoke the active refresh token.  This stops the attack at the
          cost of forcing the legitimate client to obtain a fresh
          authorization grant.
    Thanks for your input!

    Matt Jenkins

  • 2.  RE: Preventing OAuth refresh token replay attacks

    Posted Wed June 01, 2022 04:01 AM
    Edited by Shane Weeden Wed June 01, 2022 04:01 AM

    I personally believe this is pretty much impossible to implement effectively, let alone efficiently. A better approach is to use device-bound proofs on RT flows, such as DPOP. This would mean a lift-and-shift approach of using a stolen RT would not work. I'd also argue that aside from a brute force attack, how did the RT get compromised? If that was easily possible, then perhaps there are bigger problems in play? 

    The DPOP standard is still evolving, and whilst not currently natively supported in ISVA, I have implemented a prototype via some creative additions to mapping rules and have a sample client that runs in a well-known API test tool. It might be best to reach out to your customer advocate, and suggest they arrange a time with me to take you through that sample if you want to be an early adopter. Otherwise, I have a fair idea of your capabilities and believe you might know how to do this as well.... either way, at least consider the merits of DPOP over trying to implement that SBP. If you cannot do this because the clients are out of your control and incapable of DPOP, then everything you've suggested about using mapping rules to store [hashes of] previously presented RT's is the only viable approach. I believe you should still be able to get to a pre-token mapping rule even if the RT is unknown to ISVA - that is how we support "custom implementations of ATs and RTs such as JWTs".

    Shane Weeden

  • 3.  RE: Preventing OAuth refresh token replay attacks

    Posted Wed June 01, 2022 09:10 AM
    Shane, thanks very much for the reply and mentioning device bound proofs.  We already implemented RFC8705 mTLS certificate bound access tokens.  I even implemented this for PKCE flows, although that wasn't part of the spec, I store the x509 fingerprint of the attended user into the JWT or into the database (if using opaque tokens, we support both but steer towards JWT).  However, we do not check the mTLS during refresh token exchange, but if I did, that would eliminate the issue I believe, at least where mTLS was involved.  I don't think, however, I am associating the fingerprint with the refresh token.

    Further, however, I was thinking this out for PKCE after reading your message and meditating on it some.  PKCE flows are attended flows, so we should always know the subject.  If we just record with the refresh token it was issued from an attended flow, then when we go to exchange it, we just check that the subject matches the current authenticated user.  If they don't match, then something is amiss and we do not allow the exchange.  This doesn't protect the access tokens, which DPoP would protect (when we don't have mTLS authentication involved), but this solution would protect the refresh tokens for attended flows.  For confidential unattended flows, the client id+secret must be present for the refresh token exchange from my understanding, so they would be protected by the API client credentials in that case.

    Any thoughts on just protecting the refresh tokens for these attended flows?  Is there any way to tell the refresh token was generated for a PKCE attended flow today, or will I likely have to associate some attribute during the pre-mapping rule with it?

    This doesn't exactly give the same functionality as what was asked, to remove all refresh tokens if there is a compromise, but it would prevent a replay attack, which is the goal of revoking them in the first place.  Aside, this would provide a better user experience, as if their token was compromised, and they went to refresh their token in the future, their flow would not be broken because someone compromised their token (which the attacker could then do nothing with).

    Thanks Shane!

    PS:  For others interested, here were two great points of reading for DPoP that Shane mentioned.

    Matt Jenkins

  • 4.  RE: Preventing OAuth refresh token replay attacks

    Posted Wed June 01, 2022 10:26 AM
    For the most part, associate attributes such as DPoP keys or other MTLS/x509 info with the *grant* rather than a specific token. I think you probably meant this anyway. By this I mean use the state_id of the grant as an index, not the individual ATs or RTs that are issued through the lifetime of that grant (unless of course there is a specific use case for doing so).

    While on terminology, OAuth grants (which include refresh tokens) are issued to a client, not a user. So what you are trying to ensure is that the same
    client to whom the grant was issued is subsequently presenting the RT during a refresh operation at the /token endpoint.

    In the case of confidential clients (those that use MTLS or a client secret), this whole topic should be largely irrelevant as you already surmised. Technologies like DPoP add value on refresh token flows when you have public clients - those which do not have a unique client authentication credential. DPoP also has a place in resource requests using an access token where client credentials are not provided (as does MTLS), but that's out of scope for this question.

    Such public clients are typically either native mobile apps or SPAs (single page web apps), and while these should also use PKCE, PKCE itself isn't the catalyst for device-bound proofs on refresh token flows - public clients are. I don't quite follow what you mean by "attended flows", but you most certainly can tell if a client is confident or public. When implementing a scheme like DPoP what you really care about is that the same proof of possession key is presented during a refresh flow (or use of an access token on a protected resource) as was presenting during the initial establishment of the grant (typically exchanging an initial authorization code for RT+AT+id_token, etc).

    I advocate for a slight variation of the last DPoP draft I read during refresh token flows - which includes an rt_hash in DPoP jwt to bind the RT value to the DPoP included in the request. Either way just make sure you are comfortable that your scheme provides some kind of guarantee that the presenter of a refresh token in a public client context is the actual client to which it was issued. Schemes like DPoP can help do this.

    Shane Weeden

  • 5.  RE: Preventing OAuth refresh token replay attacks

    Posted Wed June 15, 2022 05:51 PM
    I am coming back to this, and I have a solution now, but I do have a question with regards to it (see bottom paragraph for question).

    I'll start by saying a refresh token attack using a refresh token generated from a PKCE flow is somewhat the fault of the PKCE spec and/or ISVA.  The reason for this, if you are using a true authorization code grant flow (without PKCE), you cannot exchange the refresh token without authenticating the client (via client ID or mTLS).  However, this breaks for PKCE, because the API client is not authenticated.

    With PKCE, the API client has no secret and the token_endpoint_auth_method is none, and hence the API client does not require authentication.  This creates the ability of someone to compromise the refresh token that was generated for a PKCE flow and reuse it without providing any authentication to the authorization server.  However, because the token was generated for a PKCE flow, the token should be bound to the subject user that initially generated the refresh (and obviously access) token rather than ISVA just blindly exchanging the refresh token.

    Now, I can see a counter argument to this.  One could argue that the user does not need to be logged into the IAM system where the authorization server resides to refresh their token.  For example, say the user's client pulls down a single page application, get a token from ISVA, then the user goes on their merry way of using the SPA for several hours while not interacting with the ISVA infrastructure, except, to refresh their token.  One could argue perhaps that the client SHOULD be allowed to refresh the token without requiring the user to have a valid session with the authorization server.  So perhaps that is why there is somewhat a risk here both in the PKCE spec and hence in ISVA.

    I suspect this counter argument I mention is why the PKCE spec works the way it does, because if you didn't care about this risk, you'd simply deny refresh tokens (which ISVA v10 can correctly do now by simply not registering the PKCE client with the refresh_token grant type) for PKCE clients and tell the developers their app needs to just get a new access token when it is expired, because after all, the user needs to be logged in anyway otherwise we can't protect the refresh tokens like we want to.  However, when I tried to explain this to our developers, they said their code required the use of refresh tokens, hence why I am in this pickle.  My guess is that down the road, someone will complain about what I've implemented below, and I'll have to put a switch that can be set as an attribute on the client registrations to override this restriction, hence allowing the refresh token refresh without a valid user session present putting the functionality back to how it is today.  What fun!

    Here is my solution (note we use a lot of mTLS here implemented to RFC8705 spec, so I have this at hand as well for extra protection):
    1. In post mapping rule, when an access token request for an authorization code grant is created, and there is a code_verifier present in the oauth body parameters, I associate an attribute to say something isPkce=true to the token's state id

    2. Note for mTLS I also associate the cert fingerprint for all tokens' state id (hence every token we record this where mTLS authentication is used by an unattended API client or attended user)

    3. In the pre mapping rule, for a request to refresh the token, I look to see if that isPkce association is present for the token's state id and if that association value is set to true.  If it does exist and it is set to true, we know this token was generated during a PKCE flow when it was initially requested, so we go into further restriction logic, otherwise we just get out of this logic block I just added and continue normally.

    4. In this new restriction logic, I grab the current user id (stsuu.getPrincipalName()), the user ID stored on the token (using token.getUsername()), the mTLS cert fingerprint used for the current call (if present), and the mTLS cert fingerprint stored for the original access token request (if present)

    5. I then compare all this, and if the username and/or mTLS fingerprint (if both original OR current mTLS fingerprints are present) are different, I call throwSTSCustomUserMessageException and throw 400 with an invalid_grant so no access token is returned.
    Now, here's the thing.  All this works great.  However, because the refresh tokens can only be used a single time, immediately after the refresh token is replayed without the correct subject or mTLS fingerprint, the refresh token is removed by the ISVA logic and hence no longer valid during a legitimate refresh.  For example, if the refresh token was compromised or somehow the flow was broken, such as if the user was not logged in when refreshing the token.

    Is there any way to stop the refresh token from being removed so if it is compromised, it would still be valid for the original user?  Or is this not possible?  Honestly the API developers wanted it to be removed as per the spec I mentioned previously in this thread.  However, I think this is a headache on the original user, since with what I have implemented, the token is worthless to someone that compromises it now.  So if I could prevent it from being removed, I think it would be best for end user experience.

    Thanks for any input!

    Matt Jenkins

  • 6.  RE: Preventing OAuth refresh token replay attacks

    Posted Wed June 15, 2022 10:41 PM

    Not sure if it will help or not, but have you turned on the "Enable multiple refresh tokens for fault tolerance" checkbox on the OAuth definition? This should keep the n-1 RT valid and might assist in your use case.

    Something else you said caught my attention - about state_id. It might just be the words you used, however keep in mind the state_id is unique pre-grant, which means all access tokens and refresh tokens issued under the lifetime of the same initial azncode have the same state_id - think of it like an index established once at the time of the initial user authorization event. In your point (2) above, it sounds like you set this association for every "token", including on RT flows, whereas it need only be done once when the azncode is exchanged.

    I do also question the legitimacy of the "attack". Is it practical, or theoretical in nature?

    Shane Weeden

  • 7.  RE: Preventing OAuth refresh token replay attacks

    Posted Thu June 16, 2022 11:43 AM
    Shane, good point about "Enable multiple refresh tokens for fault tolerance".  I currently do not have it enabled, but I suppose that would be a solution.

    I see your point on state_id, and yes I only associate the data during an access token authorization code, at least for PKCE.  The mTLS fingerprint I was associating for every access token but I am getting ready to change that as I had the epiphany last night right before I fell asleep.  What happens if you associate an attribute that is already there, does it just get updated with the new data?  I've never intentionally tried that.

    As far as the attack, it is practical.  If an access and refresh token are issued during a PKCE authorization code grant, then the refresh token is used by someone else that is not logged in, the person that compromised the token can exchange that refresh token for a new refresh and access token indefinitely.

    Matt Jenkins