Skip to content

Commit c088449

Browse files
authored
Merge pull request #107 from francoisfreitag/unsolicited_response
Make replay attacks result in 403
2 parents b0ed9ea + 28956fe commit c088449

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changes
22
=======
33

4+
UNRELEASED
5+
----------
6+
- A 403 (permission denied) is now raised if a SAMLResponse is replayed, instead of 500.
7+
48
0.16.11 (2017-12-25)
59
----------
610
- Dropped compatibility for Python < 2.7 and Django < 1.8.

djangosaml2/tests/__init__.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,6 @@ def test_assertion_consumer_service(self):
217217
metadata_file='remote_metadata_one_idp.xml',
218218
)
219219

220-
self.init_cookies()
221-
222220
# session_id should start with a letter since it is a NCName
223221
session_id = "a0123456789abcdef0123456789abcdef"
224222
came_from = '/another-view/'
@@ -259,6 +257,37 @@ def test_assertion_consumer_service(self):
259257
self.assertEqual(url.path, settings.LOGIN_REDIRECT_URL)
260258
self.assertEqual(force_text(new_user.id), self.client.session[SESSION_KEY])
261259

260+
def test_assertion_consumer_service_no_session(self):
261+
settings.SAML_CONFIG = conf.create_conf(
262+
sp_host='sp.example.com',
263+
idp_hosts=['idp.example.com'],
264+
metadata_file='remote_metadata_one_idp.xml',
265+
)
266+
267+
# session_id should start with a letter since it is a NCName
268+
session_id = "a0123456789abcdef0123456789abcdef"
269+
came_from = '/another-view/'
270+
self.add_outstanding_query(session_id, came_from)
271+
272+
# Authentication is confirmed.
273+
saml_response = auth_response(session_id, 'student')
274+
response = self.client.post(reverse('saml2_acs'), {
275+
'SAMLResponse': self.b64_for_post(saml_response),
276+
'RelayState': came_from,
277+
})
278+
self.assertEqual(response.status_code, 302)
279+
location = response['Location']
280+
url = urlparse(location)
281+
self.assertEqual(url.path, came_from)
282+
283+
# Session should no longer be in outstanding queries.
284+
saml_response = auth_response(session_id, 'student')
285+
response = self.client.post(reverse('saml2_acs'), {
286+
'SAMLResponse': self.b64_for_post(saml_response),
287+
'RelayState': came_from,
288+
})
289+
self.assertEqual(response.status_code, 403)
290+
262291
def test_missing_param_to_assertion_consumer_service_request(self):
263292
# Send request without SAML2Response parameter
264293
response = self.client.post(reverse('saml2_acs'))

djangosaml2/views.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@
4545
from saml2.ident import code, decode
4646
from saml2.sigver import MissingKey
4747
from saml2.s_utils import UnsupportedBinding
48-
from saml2.response import StatusError, StatusAuthnFailed, SignatureError, StatusRequestDenied
48+
from saml2.response import (
49+
StatusError, StatusAuthnFailed, SignatureError, StatusRequestDenied,
50+
UnsolicitedResponse,
51+
)
4952
from saml2.validate import ResponseLifetimeExceed, ToEarly
5053
from saml2.xmldsig import SIG_RSA_SHA1, SIG_RSA_SHA256 # support for SHA1 is required by spec
5154

@@ -287,6 +290,9 @@ def assertion_consumer_service(request,
287290
except MissingKey:
288291
logger.exception("SAML Identity Provider is not configured correctly: certificate key is missing!")
289292
return fail_acs_response(request)
293+
except UnsolicitedResponse:
294+
logger.exception("Received SAMLResponse when no request has been made.")
295+
return fail_acs_response(request)
290296

291297
if response is None:
292298
logger.warning("Invalid SAML Assertion received (unknown error).")

0 commit comments

Comments
 (0)