Azure-Samples / ms-identity-python-flask-webapp-authentication

This sample demonstrates a Python Flask webapp that signs in users in your tenant using Azure Active Directory
MIT License
54 stars 18 forks source link

Users can manually set session IDs #4

Closed timovp closed 3 years ago

timovp commented 3 years ago

This issue is for a: (mark with an x)

- [x] bug report -> please search issues before submitting

when session is expired, I can copy another session=... value from an authentificated cookie which is not expired and from another used, paste it in my cookie and refresh, which then leads to a successful login.

Minimal steps to reproduce

implementation of this example with an app registration and other pre-requisites.

Expected/desired behavior

this should not work, or session authentification / authorization should not have been used as an exmaple. I would like to use flask and msal with users from the same tenant, same roles etc, without this being possible.

OS and Version?

azure app service (linux with gunicorn startup command) and locally with python flask run on windows machine. Happens in all kinds of browsers tried: firefox, edge, chrome

Versions

Mention any other details that might be useful


Thanks! We'll be in touch soon.

idg-sam commented 3 years ago

Hi @timovp Thank you for your interest in this sample. If I am understanding the scenario you are describing, this is expected behaviour and not a security flaw.

We are using a Flask-Session which stores session data on the server side. In order to synchronize your browser and its session data, Flask-Session sets a client-side cookie containing a sessionID which is linked to actual session data on the server side. That is to say, your browser needs to know the sessionID it is assigned so that the server can connect your browser's requests to the correct session data on the server.

Once a session expires, that session data is destroyed and is no longer accessible at all. While it may appear from your tests that this is re-activating a zombie session, it is important to note that this is not actually the case: Test this by logging in to different accounts using different sessions (e.g., different browsers or by also using private mode).

  1. Sign into an account, for example userA@example.com on browserA, and anther account, userB@example.com on browserB.
  2. Now make a note of the sessionID value for each browser(e.g., cookies for site 127.0.0.1:5000), and save them in a notepad as sidA and sidB.
  3. Then log out from browserA which contains details for userA. You'll note that you're not signed in is displayed.
  4. Paste sidA into browserB which previously showed details for userB . Notice that upon refreshing the page, instead of showing you either userA's or userB's details, it rightly shows the user as signed out since sidA was tied to userA who is no longer signed in.
  5. Now, try pasting sidB into either of the browsers. Note that since userB's session data is tied to sidB, and userB is logged in, either browser can show their details.

You may confirm this against your original scenario by waiting for a sufficient time before signing in between signing in to userA in browserA and userB in browserB in step 1, and instead of signing out in step 3, simply allowing sidA to expire and proceeding from there given sidB has not yet expired.

Your browser should keep these sessionIDs safe and doesn't share them with any other site. And even when the user of the browser conducts the tests outlined above, no new information is obtained, since the user obviously already has access to any signed-in accounts that are linked to from valid sessionIDs in their browsers.

Let me know how this goes for you!

Regards, idg-sam

timovp commented 3 years ago

Hi yes, I am able to reproduce this, and this makes sense, however, what worries me is that I can still 'hijack' the session is if it has not expired yet from server B side. I suppose that would be an inherent problem with session cookies?

timovp commented 3 years ago

And thank you for you elaborate explanation and the time to answer! @idg-sam Very much appriciated.

timovp commented 3 years ago

Another question, we noticed that the session is user-settable. Is this something we can easily disable?

idg-sam commented 3 years ago

Hi @timovp, Glad I could help out.

Hi yes, I am able to reproduce this, and this makes sense, however, what worries me is that I can still 'hijack' the session is if it has not expired yet from server B side. I suppose that would be an inherent problem with session cookies?

I would hesitate to call this a hijack: whoever has rightful access to a sessionID in the browser, already has full access to the full breadth of the data to which that sessionID links and entitles them. If your app users were to share their active sessionID, it would be no more data disclosed than, say, sharing their password, or ID Token or Access Token. This is true of any data that is in a browser, not just cookies.

Another question, we noticed that the session is user-settable. Is this something we can easily disable?

Can you rephrase the question? Maybe I am not interpreting your question correctly, but is it either of:

  1. Are you looking for a volatile user experience? e.g. non-session-based, ephemeral app data?
  2. Are you looking for a sessionID that can not be copy+pasted to another browser successfully?

Regards, @idg-sam

timovp commented 3 years ago

Hi @idg-sam I'm sorry for taking a while to respond. But thanks again for your elaborate responses.

I would hesitate to call this a hijack: whoever has rightful access to a sessionID in the browser, already has full access to the full breadth of the data to which that sessionID links and entitles them. If your app users were to share their active sessionID, it would be no more data disclosed than, say, sharing their password, or ID Token or Access Token. This is true of any data that is in a browser, not just cookies.

Thank you, for confirming this, this was also my understanding.

As for the clarification: - 2. this would be a great option to have. No, what I noticed is that when I capture the request that goes to login.microsoftonline /tenantid-url, it passes the "session=xx_sessionid_xx" cookie, and after authentication it, we get this same info returned.

However, I am able to change this session=... value to e.g. 'helloworld' when I capture the request and send it again, and then my session ID has become this value, and I am still able to pass the login-in wall. If I then am able to authenticate, the example seems to not check if this session_id was the same as before it send us to login.ms/tenant-url, it just looks for a successful login. Correct?

idg-sam commented 3 years ago

As for the clarification: - 2. this would be a great option to have.

There are ways to more-tightly couple a sessionID to a specific browser and machine, however I must caution (for the sake of other readers) that this would be a very niche use-case, and is normally not necessary. If a dev's use-case requires this, they might ensure that their web app checks and verifies that each sessionID is accessed only from a single IP address, or they might use another method like browser fingerprinting. This use-case is outside the scope of our samples (and outside the scope of OAuth2.0/OIDC) and would require independent research from you and your team.

No, what I noticed is that when I capture the request that goes to login.microsoftonline /tenantid-url, it passes the "session=xx_sessionid_xx" cookie, and after authentication it, we get this same info returned.

Session cookies are tied to specific domains. Any session cookie info tied to login.microsoftonline.com, or any other URL for that matter, is not associated with your web app's session info. In this case, the session you refer to contains info pertaining to one of Microsoft Azure's Identity Provider (IdP) domains and has no cross-over with session info for our app.

As per the OIDC protocol, the RP(relying party - this web app)'s communication with the IdP is not done based on session cookie data (as session data is siloed per domain), but it is composed of a series of GET requests and redirects. The parameter that keeps the IdP and the RP synchronized is the state parameter. It is generated by the RP, and passed by the user's browser to the IdP's authorize endpoint. When the IdP finally redirects the browser back to the RP's redirect endpoint, it passes this state param back to it as well. On the RP side, this state param is stored under a specific sessionID; it is how our web app knows that the sign-in is legitimately initiated by this sessionID in this RP itself. It is a security feature of the OAuth2.0/OIDC protocol. To confirm this, you may try copy+pasting the auth URL (where AzureAD IdP asks you to enter your user details/pick your account) to another browser and complete your sign in request there. You'll note that while Azure will sign you in due to entering correct credentials, the web app will (correctly) not honour this sign in since it did not originate from the same browser session.

I would suggest studying this official OIDC document for more information about the protocol: https://openid.net/specs/openid-connect-core-1_0.html

Regards, @idg-sam