WICG/dbsc

Start session can result in security bugs during implementation.

Closed this issue · 17 comments

The start session doesn’t make proof of possession of the private key, that can result in security bugs for anonymous sessions (without security artifact), or for session where authentication tokens appear later.

Assume a case, I’m a software developer that tries to implement this. I have a scenario where I have cookies and do not have authentication (anonymous case), or I have a case where authentication in the system appears later.

Parameter “authorization” is optional.

I start a session without “authorization” and produce “auth_cookie” which doesn’t have authorization inside, only a public key. Assume “auth_cookie” has embedded the thumbprint of the public key and the expiration. All my additional cookies will be bound to this “auth_cookie” by thumbprint key, e.g. I’ll copy the thumbprint of from the “auth_cookie” into each workload cookie. As long as the server sees a valid “auth_cookie” and validates that thumbprint of all cookies matches to this “auth_cookie”, we can assume that those cookies are valid. This implementation looks logical, but it has a gap that is not visible at first glance.

If there is no auth artifact in the start session request, I can produce “auth_cookie” as many times as I need.
The attacker steals the public key, and the workload cookies. Whenever attacker needs to use the workload cookies, they can repeat the “start session” request with the public key and get back fresh and ready-to-use “auth_cookie”, and repeat full request with old workload cookies, and newly generated “auth_cookie”.

The attack will also work if the authentication appears later after the binding and the auth information is stored in workload cookies.

We can fix it in multiple ways:

  1. Describe this attack in the document and ask to avoid binding to the public key thumbprint but use some unpredictably high entropy random identifier for binding workload cookies.
  2. Add proof of possession of the private key to “start session”, to avoid complexity, future bugs and people who not read documentation (all software developers)

For clarification, with (2) you mean the client signs something with the newly created TPM private key when contacting the registration endpoint?

Yes.

Thank you, I will add that.

this something should be "challenge".

I updated the registration to be signed with the challenge here: fe0e224

Let me know if you think this works.

Looks good, but it has a minor gap, the flow diagram in "High level overview" doesn't show TPM interaction for the signing operation during the starting session. The flow diagram has the key creation and the signature during the refresh, but doesn't have signature during "start session".

Other then that looks good for me.

The high level overview doesn't have either the headers or the signature, it's because I didn't create it myself but it was contributed before the recent changes. I'll try to figure out what program was used to create it or make another one.

Looked one more time, and understood that "nonce" in start session can be expired, so we need take into account this case and issue the nonce challenge. And repeat the request.

For both the registration and the refresh request the server can always return
HTTP/1.1 401
Sec-Session-Challenge: session_identifier=,challenge=

To ask for a new sign with the key.

Does this solve the issue? I can make it more clear if so.

yes, it just to make it clear, that on start session we should expect 401 with a new challenge.

Updated with an example here: e834267

Also line 132-137 i think the payload format is not correct
{
"aud", "URL of this request"
"jti", "nonce"
"iat", "timestamp"
"key", "public key";
}

It should be:
{
"aud": "URL of this request",
"jti": "nonce",
"iat": "timestamp",
"key": "public key"
}

We also need to put optional "authorization" parameter.

{
"aud": "URL of this request",
"jti": "nonce",
"iat": "timestamp",
"key": "public key",
"authorization": "<authentication_code>", // optional
}

Updated here: c5fdf70

Where does the browser get the auth code?

I think I made a mistake to put 2 problems in one comment:

  1. Format of JSON in payload is not correct.
  2. "authorization" parameter.

In the current version format still incorrect, as key and value are comma separated, but should be colon separated:
"key", "public key" -> "key": "public key"

"authorization/authentication" parameter
First, in your JS-version the parameter is called "authorization": "", but in payload it is called "authentication". IMHO, we should be consistent.

Secondly, you right that browser will not be able to get auth code in case of redirect. In JS case, authcode will be there and JS will parse it and pass it. However, I'm not sure that the story is clear when there is an auth scheme based on redirects. We need to discuss separately how auth and binding will play together in redirect model. We don't want separate binding and authentication.

I think Sec-Session-Registration header should also have "authorization/authentication" parameter, then the browser will be able to embed it into startSession JWT.

However, this scheme is a little bit redundant for a redirect-based auth. We need an extra roundtrip to initiate the binding, but we can save this roundtrip, if we will remember before auth that a web app wants to start session when request will go back on redirect URI. We can made up a headers for this, that will initiate binding before the session.

Updated based on feedback here: d23e52d

Maybe if you think it works now we can close this issue and open a new issue with the roundtrip improvement suggestion?

Closing this, as the primary concern was addressed. Opening a separate comment about redundancy.