Skip to content

Commit 4c4d540

Browse files
committed
Warn about Open Redirect and Reply attacks
1 parent ab62b0d commit 4c4d540

File tree

5 files changed

+46
-0
lines changed

5 files changed

+46
-0
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,36 @@ your environment is not secure and will be exposed to attacks.
125125

126126
In production also we highly recommend to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment.
127127

128+
129+
### Avoiding Open Redirect attacks ###
130+
131+
Some implementations uses the RelayState parameter as a way to control the flow when SSO and SLO succeeded. So basically the
132+
user is redirected to the value of the RelayState.
133+
134+
If you are using Signature Validation on the HTTP-Redirect binding, you will have the RelayState value integrity covered, otherwise, and
135+
on HTTP-POST binding, you can't trust the RelayState so before
136+
executing the validation, you need to verify that its value belong
137+
a trusted and expected URL.
138+
139+
Read more about Open Redirect [CWE-601](https://cwe.mitre.org/data/definitions/601.html).
140+
141+
### Avoiding Reply attacks ###
142+
143+
A reply attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).
144+
145+
SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that
146+
make harder this kind of attacks, but they are still possible.
147+
148+
In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs alredy valdidated and processed. Those values only need
149+
to be stored the amount of time of the SAML Message life time, so
150+
we don't need to store all processed message/assertion Ids, but the most recent ones.
151+
152+
The OneLogin_Saml2_Auth class contains the [get_last_request_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L357), [get_last_message_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L364) and [get_last_assertion_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L371) methods to retrieve the IDs
153+
154+
Checking that the ID of the current Message/Assertion does not exists in the lis of the ones already processed will prevent reply
155+
attacks.
156+
157+
128158
Getting Started
129159
---------------
130160

demo-django/demo/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ def index(request):
8484
request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
8585
request.session['samlSessionIndex'] = auth.get_session_index()
8686
if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
87+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
88+
# the value of the req['post_data']['RelayState'] is a trusted URL.
8789
return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))
8890
elif auth.get_settings().is_debug_active():
8991
error_reason = auth.get_last_error_reason()
@@ -96,6 +98,8 @@ def index(request):
9698
errors = auth.get_errors()
9799
if len(errors) == 0:
98100
if url is not None:
101+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
102+
# the value of the url is a trusted URL
99103
return HttpResponseRedirect(url)
100104
else:
101105
success_slo = True

demo-flask/index.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ def index():
8383
session['samlSessionIndex'] = auth.get_session_index()
8484
self_url = OneLogin_Saml2_Utils.get_self_url(req)
8585
if 'RelayState' in request.form and self_url != request.form['RelayState']:
86+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
87+
# the value of the request.form['RelayState'] is a trusted URL.
8688
return redirect(auth.redirect_to(request.form['RelayState']))
8789
elif auth.get_settings().is_debug_active():
8890
error_reason = auth.get_last_error_reason()
@@ -95,6 +97,8 @@ def index():
9597
errors = auth.get_errors()
9698
if len(errors) == 0:
9799
if url is not None:
100+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
101+
# the value of the url is a trusted URL.
98102
return redirect(url)
99103
else:
100104
success_slo = True

demo-tornado/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def post(self):
4646
session['samlSessionIndex'] = auth.get_session_index()
4747
self_url = OneLogin_Saml2_Utils.get_self_url(req)
4848
if 'RelayState' in self.request.arguments and self_url != self.request.arguments['RelayState'][0].decode('utf-8'):
49+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
50+
# the value of the self.request.arguments['RelayState'][0] is a trusted URL.
4951
return self.redirect(self.request.arguments['RelayState'][0].decode('utf-8'))
5052
elif auth.get_settings().is_debug_active():
5153
error_reason = auth.get_last_error_reason()
@@ -104,6 +106,8 @@ def get(self):
104106
errors = auth.get_errors()
105107
if len(errors) == 0:
106108
if url is not None:
109+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
110+
# the value of the url is a trusted URL.
107111
return self.redirect(url)
108112
else:
109113
success_slo = True

demo_pyramid/demo_pyramid/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ def index(request):
7070
session['samlSessionIndex'] = auth.get_session_index()
7171
self_url = OneLogin_Saml2_Utils.get_self_url(req)
7272
if 'RelayState' in request.POST and self_url != request.POST['RelayState']:
73+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
74+
# the value of the request.POST['RelayState'] is a trusted URL.
7375
return HTTPFound(auth.redirect_to(request.POST['RelayState']))
7476
else:
7577
error_reason = auth.get_last_error_reason()
@@ -79,6 +81,8 @@ def index(request):
7981
errors = auth.get_errors()
8082
if len(errors) == 0:
8183
if url is not None:
84+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
85+
# the value of the url is a trusted URL.
8286
return HTTPFound(url)
8387
else:
8488
success_slo = True

0 commit comments

Comments
 (0)