IdentityPython / djangosaml2

Django SAML2 Service Provider based on pySAML2
Apache License 2.0
259 stars 143 forks source link

SameSite Cookie and Unsolicited response error due to lost session ID #203

Closed LVMuser closed 4 years ago

LVMuser commented 4 years ago

saml2 complains that it cannot find the outstanding query which was saved to cache by djangoSaml2. Is there a configuration option I am missing? Below are relevant log messages:


DEBUG /usr/local/lib/python3.5/dist-packages/djangosaml2/views.py:250 views: Saving the session_id in the OutstandingQueries cache: id-ZEV53y7Cx2PoA3Pfe
...
/ERROR /usr/local/lib/python3.5/dist-packages/saml2/response.py:540 response: Unsolicited response id-ZEV53y7Cx2PoA3Pfe not found in {}

I altered saml2 error message to print the self.outstanding_queries being searched which turns up empty.

Am I missing some configuration, or is this a bug?

IDP is Microsoft azure I have no access to however is returning a successful login before this error.

peppelinux commented 4 years ago

Hi, Please do not use py35 if possible, at least py36. Try to configure allow_unsolicited_response in pysaml2 configuration and then let them know

LVMuser commented 4 years ago

Looking into the logs more I believe djangoSaml2 is posting two requests, i assume the OutstandingQuery is removed from the cache once the first response is recieved and its the second response with the ID which has since been cleared from the OutstandingQueries cache which causes the error. Could this be an issue caused by DjangoSaml2 sending two requests rather than just the one?

DEBUG /usr/local/lib/python3.5/dist-packages/djangosaml2/views.py:99 views: Login process started
DEBUG /usr/local/lib/python3.5/dist-packages/saml2/mdstore.py:623 mdstore: service => {'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': [{'location': 'https://login.microsoftonline.com/[redacted]/saml2', '__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&SingleSignOnService', 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'}], 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': [{'location': 'https://login.microsoftonline.com/[redacted]/saml2', '__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&SingleSignOnService', 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'}]}
DEBUG /usr/local/lib/python3.5/dist-packages/djangosaml2/views.py:164 views: Trying binding urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect for IDP https://sts.windows.net/[redacted]/
DEBUG /usr/local/lib/python3.5/dist-packages/saml2/mdstore.py:1075 mdstore: service(https://sts.windows.net/[redacted]/, idpsso_descriptor, single_sign_on_service, None)
DEBUG /usr/local/lib/python3.5/dist-packages/saml2/mdstore.py:623 mdstore: service => {'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': [{'location': 'https://login.microsoftonline.com/[redacted]/saml2', '__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&SingleSignOnService', 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'}], 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': [{'location': 'https://login.microsoftonline.com/[redacted]/saml2', '__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&SingleSignOnService', 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'}]}
WARNING /usr/local/lib/python3.5/dist-packages/saml2/client_base.py:192 client_base: The SAML service provider accepts unsigned SAML Responses and Assertions. This configuration is insecure.
DEBUG /usr/local/lib/python3.5/dist-packages/djangosaml2/views.py:187 views: Redirecting user to the IdP via urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect binding.
DEBUG /usr/local/lib/python3.5/dist-packages/saml2/mdstore.py:1075 mdstore: service(https://sts.windows.net/[redacted]/, idpsso_descriptor, single_sign_on_service, urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect)
DEBUG /usr/local/lib/python3.5/dist-packages/saml2/mdstore.py:623 mdstore: service => [{'location': 'https://login.microsoftonline.com/[redacted]/saml2', '__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&SingleSignOnService', 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'}]
INFO /usr/local/lib/python3.5/dist-packages/saml2/client.py:111 client: destination to provider: https://login.microsoftonline.com/[redacted]/saml2
INFO /usr/local/lib/python3.5/dist-packages/saml2/entity.py:486 entity: REQUEST: <samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://[redacted].net/saml2/acs/" Destination="https://login.microsoftonline.com/[redacted]/saml2" ID="id-ZEV53y7Cx2PoA3Pfe" IssueInstant="2020-07-21T11:04:29Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://[redacted].net/saml2/metadata/</saml:Issuer></samlp:AuthnRequest>
INFO /usr/local/lib/python3.5/dist-packages/saml2/client.py:120 client: AuthNReq: <samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://[redacted].net/saml2/acs/" Destination="https://login.microsoftonline.com/[redacted]/saml2" ID="id-ZEV53y7Cx2PoA3Pfe" IssueInstant="2020-07-21T11:04:29Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://[redacted].net/saml2/metadata/</saml:Issuer></samlp:AuthnRequest>
INFO /usr/local/lib/python3.5/dist-packages/saml2/entity.py:226 entity: HTTP REDIRECT
DEBUG /usr/local/lib/python3.5/dist-packages/djangosaml2/views.py:250 views: Saving the session_id in the OutstandingQueries cache: id-ZEV53y7Cx2PoA3Pfe
WARNING /usr/local/lib/python3.5/dist-packages/saml2/client_base.py:192 client_base: The SAML service provider accepts unsigned SAML Responses and Assertions. This configuration is insecure.

DEBUG /usr/local/lib/python3.5/dist-packages/saml2/response.py:325 response: xmlstr: <samlp:Response ID="_d4561ed6-9bf6-4c11-af1c-5e3841fc792a" Version="2.0" IssueInstant="2020-07-21T11:04:30.116Z" Destination="https://[redacted].net/saml2/acs/" InResponseTo="id-ZEV53y7Cx2PoA3Pfe"  ...</samlp:Response>
DEBUG /usr/local/lib/python3.5/dist-packages/saml2/response.py:325 response: xmlstr: <samlp:Response ID="_d4561ed6-9bf6-4c11-af1c-5e3841fc792a" Version="2.0" IssueInstant="2020-07-21T11:04:30.116Z" Destination="https://[redacted].net/saml2/acs/" InResponseTo="id-ZEV53y7Cx2PoA3Pfe" ...</samlp:Response>

DEBUG /usr/local/lib/python3.5/dist-packages/saml2/response.py:291 response: response: <samlp:Response xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Destination="https://[redacted].net/saml2/acs/" ID="_d4561ed6-9bf6-4c11-af1c-5e3841fc792a" InResponseTo="id-ZEV53y7Cx2PoA3Pfe" ...</samlp:Response>
ERROR /usr/local/lib/python3.5/dist-packages/saml2/response.py:540 response: Unsolicited response id-ZEV53y7Cx2PoA3Pfe not found in {}

Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/saml2/entity.py", line 1143, in _parse_response
    response = response.loads(xmlstr, False, origxml=xmlstr)
  File "/usr/local/lib/python3.5/dist-packages/saml2/response.py", line 520, in loads
    self._loads(xmldata, decode, origxml)
  File "/usr/local/lib/python3.5/dist-packages/saml2/response.py", line 340, in _loads
    **args)
  File "/usr/local/lib/python3.5/dist-packages/saml2/sigver.py", line 1650, in correctly_signed_response
    raise SignatureError('Signature missing for response')
saml2.sigver.SignatureError: Signature missing for response
ERROR /usr/local/lib/python3.5/dist-packages/saml2/client_base.py:718 client_base: XML parse error: Unsolicited response: id-ZEV53y7Cx2PoA3Pfe
ERROR /usr/local/lib/python3.5/dist-packages/djangosaml2/views.py:319 views: Received SAMLResponse when no request has been made.
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/saml2/entity.py", line 1143, in _parse_response
    response = response.loads(xmlstr, False, origxml=xmlstr)
  File "/usr/local/lib/python3.5/dist-packages/saml2/response.py", line 520, in loads
    self._loads(xmldata, decode, origxml)
  File "/usr/local/lib/python3.5/dist-packages/saml2/response.py", line 340, in _loads
    **args)
  File "/usr/local/lib/python3.5/dist-packages/saml2/sigver.py", line 1650, in correctly_signed_response
    raise SignatureError('Signature missing for response')
saml2.sigver.SignatureError: Signature missing for response
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/djangosaml2/views.py", line 296, in post
    response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries)
  File "/usr/local/lib/python3.5/dist-packages/saml2/client_base.py", line 711, in parse_authn_request_response
    binding, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/saml2/entity.py", line 1153, in _parse_response
    response = response.loads(xmlstr, False, origxml=xmlstr)
  File "/usr/local/lib/python3.5/dist-packages/saml2/response.py", line 542, in loads
    "Unsolicited response: %s" % self.in_response_to)
saml2.response.UnsolicitedResponse: Unsolicited response: id-ZEV53y7Cx2PoA3Pfe
ERROR /usr/local/lib/python3.5/dist-packages/saml2/response.py:540 response: Unsolicited response id-ZEV53y7Cx2PoA3Pfe not found in {}
LVMuser commented 4 years ago

Hi, Please do not use py35 if possible, at least py36. Try to configure allow_unsolicited_response in pysaml2 configuration and then let them know

Will upgrade py and try, thank you

peppelinux commented 4 years ago

I see a single authnRequest there, you can even audit for that using samltracer, on firefox or its equivalent for chrome. I read that warning about unsigned response, add authn_response_signed as follows https://github.com/peppelinux/Django-Identity/blob/master/djangosaml2_sp/djangosaml2_sp/djangosaml2_sp/sp_pysaml2_satosa.py#L74

Put saml2.client_base in debug (in settings LOGGING). Just play with it a bit more, then you'll be able to understand what's happening.

rotherfuchs commented 4 years ago

I had the same issue with Python 3.7.6.

DEBUG Trying binding urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect for IDP https://sso.****.***/idp/shibboleth
DEBUG Redirecting user to the IdP via urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect binding.
DEBUG Saving the session_id in the OutstandingQueries cache
[pid: 19882|app: 0|req: 23/27] 10.222.90.79 () {58 vars in 1566 bytes} [Wed Jul 22 13:02:24 2020] GET /saml2/login/ => generated 39 bytes in 30 msecs (HTTP/1.1 302) 7 headers in 830 bytes (2 switches on core 0)
DEBUG Login process started
DEBUG Trying binding urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect for IDP https://sso.****.***/idp/shibboleth
DEBUG Redirecting user to the IdP via urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect binding.
DEBUG Saving the session_id in the OutstandingQueries cache
[pid: 19882|app: 0|req: 24/28] 10.222.90.79 () {56 vars in 1542 bytes} [Wed Jul 22 13:02:25 2020] GET /saml2/login/ => generated 39 bytes in 18 msecs (HTTP/1.1 302) 7 headers in 830 bytes (2 switches on core 0)
Unsolicited response id-TEqwj2KhDl88czDDq
NoneType: None
Unsolicited response
XML parse error: Unsolicited response: id-TEqwj2KhDl88czDDq
ERROR Received SAMLResponse when no request has been made.
Traceback (most recent call last):
  File "/opt/nld/var/pyenv/lib/python3.7/site-packages/djangosaml2/views.py", line 296, in post
    response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries)
  File "/opt/nld/var/pyenv/lib/python3.7/site-packages/saml2/client_base.py", line 711, in parse_authn_request_response
    binding, **kwargs)
  File "/opt/nld/var/pyenv/lib/python3.7/site-packages/saml2/entity.py", line 1143, in _parse_response
    response = response.loads(xmlstr, False, origxml=xmlstr)
  File "/opt/nld/var/pyenv/lib/python3.7/site-packages/saml2/response.py", line 542, in loads
    "Unsolicited response: %s" % self.in_response_to)
saml2.response.UnsolicitedResponse: Unsolicited response: id-TEqwj2KhDl88czDDq

Setting allow_unsolicited = True in the SAML_CONFIG solved it.

peppelinux commented 4 years ago

Not for me, @rotherfuchs are you dealing with ADFS? I'd investigate in the cache system you're using and in the way these information would be extracted from there.

put a pdb there and follow the stream if you can do that in a debugging environment

Rjevski commented 4 years ago

The problem is most likely the "SameSite" and HttpOnly cookie restrictions. Basically if you're using the HTTP POST binding, the POST is initiated via JavaScript on the IdP-supplied page which will not include the Django session cookie, thus the outstanding queries being empty because there is no session. You can check that by doing the flow while having the browser's developer tools "network" tab open and then checking the cookies on the /saml2/acs POST request. You should see your session cookie in there for the "outstanding sessions" logic to work but you most likely won't.

Potential solutions:

1) Set SameSite=None (and the Secure flag since it's required in that case) on the Django session cookie. This might have detrimental security-related side-effects so I wouldn't recommend.

2) Amending the library to use a different, separate session (with SameSite=None) only for the SAML outstanding queries cache, and keep the default Django session untouched (and secure; with default SameSite settings).

3) Allowing unsolicited responses in your SAML config. This might have security implications (which I'm trying to determine at the moment as I am facing the exact same issue).

peppelinux commented 4 years ago

I close this issue and invite all of you to continue it here: https://github.com/knaperek/djangosaml2/issues/146#issuecomment-666007053

Thanks everyone for the clarifications

peppelinux commented 4 years ago

Implemented SameSite workaround in v0.30.0