saml and replay attack vulnerability
See original GitHub issueI am new to SAML, so please excuse my ignorance or anything that I am misinformed about.
I have setup a SAML SP in my Django Project that talks to an Okta Chicklet (IDP) that I also setup. My Django App has two url endpints that we will call for brevity “login” and “sso”. Login is responsible for preparing the authentication assertion that redirects the user to the configured idp’s login form. Sso is responsible for verifying the idp’s success response and logging the user into my web application.
All pretty clear and simple so far, right?
I’ve been successful in making a working version that logs me in, but there is one big issue that is bothering me. If I take the POST data for the sso endpoint that comes from the IDP, and “replay” it on another computer my SP thinks that the data is valid and logs the user into my web app. While the “replay attack” only works for a couple minutes, it still seems like a big enough security hole that I would like to fix it.
Most of my inspiration and code came from the Okta SP Example found here. I’ve included some parts of my pysaml2 implementation that I thought might be useful in understanding exactly what I am doing. If you need more, please just let me know and I can hopefully supply it.
Any help or advise on how to stop the replay attack, or why it isn’t a viable attack in the wild, would be deeply appreciated. Thanks!
# Saml2Client config and setup
saml_settings = {
'entityid': 'https://www.foobar.com/saml/sso/',
'xmlsec_binary': '/usr/bin/xmlsec1',
'metadata': {
'remote': [{
'url': 'https://okta.com/path/to/the/idp/config/metadata.xml,
'cert': None,
}],
},
'service': {
'sp': {
'endpoints': {
'assertion_consumer_service': [
('https://www.foobar.com/saml/sso/', BINDING_HTTP_REDIRECT),
('https://www.foobar.com/saml/sso/', BINDING_HTTP_POST),
],
},
'allow_unsolicited': True,
'authn_requests_signed': False,
'want_assertions_signed': True,
},
},
}
sp_config = Saml2Config()
sp_config.load(saml_settings)
sp_config.allow_unknown_attributes = True
saml_client = Saml2Client(config=sp_config)
# SP auth assertion generation
req_id, info = saml_client.prepare_for_authenticate()
redirect_url = None
for key, value in info['headers']:
if key is 'Location':
redirect_url = value
response = redirect(redirect_url)
# IDP response validation
saml_response = request.POST.get('SAMLResponse')
authn_response = saml_client.parse_authn_request_response(saml_response, BINDING_HTTP_POST)
authn_response.get_identity()
user_info = authn_response.get_subject()
Issue Analytics
- State:
- Created 7 years ago
- Comments:13 (9 by maintainers)
I think this should be considered as a vulnerability of a SAML Service Provider implementation that uses pysaml2 , and not a vulnerability of the library itself. I have no objections on documenting this more clearly, but we don’c currently have any high level documentation on " how to build a production grade SAML SP with pysaml2" where this could fit
The library (
pysaml2
) cannot satisfy the requirement you’d like, as it would have to make a choice on the persistence mechanism that would be used (a certain database, a file on disk, etc) to store the requests.What the library can do, is allow the user (the SP) to pass that information (as an argument), and have the library do the checks where needed. This offloads the responsibility of long-term storage (and across multiple processes/threads) to the user. The user can choose what storage mechanism to use and with what architecture and topology.
The two systems -the user and the library- should communicate this information through a well established data structure, where the user is the writer and the library is the reader.
This is currently implemented; though I believe the logic is a bit scattered. The name of the variable that represents that structure is
outstanding_queries
. The structure is a mapping (a dictionary) between session-IDs andRequest
objects (normally, originating from the SP). When a response is received and ifallow_unsolicited
is set toFalse
, the structure is checked for the session-ID (the entity-id) and if a matchingRequest
object is not found the response is considered to be unsolicited.